Skip to main content

The Open Tabs Standard: Passkey-Rooted Delegated Signers for Non-Custodial Payment Channels on Solana
draft-sander-open-tabs-passkey-00

Document Type Active Internet-Draft (individual)
Author Nicholas Sander
Last updated 2026-06-19
RFC stream (None)
Intended RFC status (None)
Formats
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-sander-open-tabs-passkey-00
Network Working Group                                          N. Sander
Internet-Draft                                               Independent
Intended status: Informational                              20 June 2026
Expires: 22 December 2026

   The Open Tabs Standard: Passkey-Rooted Delegated Signers for Non-
                  Custodial Payment Channels on Solana
                   draft-sander-open-tabs-passkey-00

Abstract

   This document specifies the passkey-rooted delegated-signer
   authorization model of the Open Tabs Standard (OTS), a non-custodial
   payment-channel scheme on Solana.  It uses Solana's native secp256r1
   signature verification precompile ([SIMD-0075]) to authorize voucher
   signers via WebAuthn passkeys ([WEBAUTHN]).

   The model is interoperable with the Solana Session Intent
   ([draft-solana-session-00]), with which it shares the channel and
   voucher primitives.  For a passkey-authorized signer it defines a
   distinct signatureType value, an exact signed message format, an
   exact Solana verification program, and a binding between the
   delegated signer and the channel's PDA derivation.

   A reference implementation is live on Solana mainnet.  The dexter-
   vault program (account Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc)
   verifies secp256r1 signatures over a fixed canonical message via the
   SIMD-0075 precompile, records the active session key on the vault
   account, and exposes a read-only prove_passkey instruction for off-
   chain liveness attestation.  A live reference vault (account
   7FE9VUeabi3sF8wUABV7F3eyvEi1ekDbER9k5JBYrWAi) demonstrates the full
   lifecycle on Solana mainnet.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

Sander                  Expires 22 December 2026                [Page 1]
Internet-Draft              Open Tabs Passkey                  June 2026

   This Internet-Draft will expire on 22 December 2026.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
     1.1.  Background  . . . . . . . . . . . . . . . . . . . . . . .   3
     1.2.  Why passkey-rooted delegation . . . . . . . . . . . . . .   3
     1.3.  Terminology . . . . . . . . . . . . . . . . . . . . . . .   4
   2.  Extension Identification  . . . . . . . . . . . . . . . . . .   4
   3.  Channel Open with Passkey Authority . . . . . . . . . . . . .   5
     3.1.  authorizedSigner field  . . . . . . . . . . . . . . . . .   5
     3.2.  Vault account binding . . . . . . . . . . . . . . . . . .   5
     3.3.  Vault address derivation  . . . . . . . . . . . . . . . .   5
   4.  Register Session Key Message Format . . . . . . . . . . . . .   6
     4.1.  Wire format . . . . . . . . . . . . . . . . . . . . . . .   6
     4.2.  Passkey signing ceremony  . . . . . . . . . . . . . . . .   8
     4.3.  SIMD-0075 signed payload  . . . . . . . . . . . . . . . .   8
     4.4.  Application-layer challenge binding (informative) . . . .   9
   5.  On-Chain Verification Program . . . . . . . . . . . . . . . .   9
     5.1.  Required instructions . . . . . . . . . . . . . . . . . .   9
     5.2.  Revocation message format . . . . . . . . . . . . . . . .  10
     5.3.  prove_passkey for off-chain liveness  . . . . . . . . . .  11
     5.4.  SIMD-0075 precompile  . . . . . . . . . . . . . . . . . .  12
   6.  Voucher Format Compatibility  . . . . . . . . . . . . . . . .  12
   7.  Off-Chain Verifier Flow . . . . . . . . . . . . . . . . . . .  13
     7.1.  Voucher verification sequence . . . . . . . . . . . . . .  13
     7.2.  Liveness for application authentication . . . . . . . . .  13
   8.  Security Considerations . . . . . . . . . . . . . . . . . . .  14
     8.1.  The passkey is not a hardware wallet  . . . . . . . . . .  14
     8.2.  Replay protection . . . . . . . . . . . . . . . . . . . .  14
     8.3.  Session key compromise  . . . . . . . . . . . . . . . . .  15
     8.4.  Program upgrade authority . . . . . . . . . . . . . . . .  15
     8.5.  clientDataJSON parsing  . . . . . . . . . . . . . . . . .  15
     8.6.  Origin pinning (relying-party responsibility) . . . . . .  15
   9.  Backward Compatibility  . . . . . . . . . . . . . . . . . . .  15
   10. Implementation Status . . . . . . . . . . . . . . . . . . . .  16
     10.1.  Reference implementation on Solana mainnet . . . . . . .  16

Sander                  Expires 22 December 2026                [Page 2]
Internet-Draft              Open Tabs Passkey                  June 2026

     10.2.  Canonical client implementation  . . . . . . . . . . . .  17
     10.3.  Conformance tests  . . . . . . . . . . . . . . . . . . .  17
   11. Reference Implementation  . . . . . . . . . . . . . . . . . .  18
     11.1.  Verification that the reference implementation matches the
            deployed program . . . . . . . . . . . . . . . . . . . .  18
   12. Test Vectors  . . . . . . . . . . . . . . . . . . . . . . . .  19
   13. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  20
   14. References  . . . . . . . . . . . . . . . . . . . . . . . . .  20
     14.1.  Normative References . . . . . . . . . . . . . . . . . .  20
     14.2.  Informative References . . . . . . . . . . . . . . . . .  21
   Appendix A.  Acknowledgments  . . . . . . . . . . . . . . . . . .  21
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  21

1.  Introduction

1.1.  Background

   This document specifies the secp256r1-passkey delegated-signer
   authorization model of the Open Tabs Standard (OTS).  OTS is a non-
   custodial payment-channel scheme whose on-chain channel primitive and
   off-chain voucher format are shared with, and interoperable with, the
   Solana Session Intent ([draft-solana-session-00]).  For a passkey-
   rooted signer, this document defines four things: a distinct
   signatureType value, an exact signed message format, the exact Solana
   verification program used on-chain, and the binding between the
   delegated signer and the channel PDA derivation.

1.2.  Why passkey-rooted delegation

   A passkey ([WEBAUTHN]) holds a secp256r1 keypair on a user's device,
   authorized by a biometric or PIN gesture.  Solana's SIMD-0075
   precompile verifies these signatures natively.  Together they permit
   a delegation model in which no service operator holds a key that can
   move user funds: the user holds the credential, the credential
   authorizes a session key within bounds the on-chain program enforces,
   and the session key (not the credential) signs vouchers.

   The authority boundary under this extension is the verifying program,
   not the credential.  A compromised passkey authorizes at most what
   the program permits (a fixed cap, a specific counterparty, an expiry
   window), and raising those bounds requires a fresh credential
   ceremony.  Implementations that treat the passkey as the sole
   security boundary do not conform to this specification.

Sander                  Expires 22 December 2026                [Page 3]
Internet-Draft              Open Tabs Passkey                  June 2026

1.3.  Terminology

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in
   [RFC2119] and [RFC8174].

   *Passkey.* A WebAuthn credential as defined by [WEBAUTHN], whose
   public key is a secp256r1 (NIST P-256) point.

   *Compressed pubkey form.* The 33-byte SEC1 compressed encoding
   ([SEC1]) of an elliptic curve point: a 1-byte prefix (0x02 or 0x03)
   followed by the 32-byte X coordinate.

   *Vault account.* A program-owned account that records the passkey's
   33-byte compressed public key, the active session key (if any), and
   the active scope parameters.  The vault account is the on-chain
   anchor of the authority relationship.

   *Authority program.* The Solana program that owns the vault account
   and enforces the on-chain rules.  This document uses dexter-vault as
   the reference implementation but does not require any specific
   program identity for conforming implementations.

   *Session key.* An Ed25519 keypair the client holds, authorized by the
   passkey to sign vouchers within scope.  The session key's public key
   is the authorizedSigner recorded in the session intent's channel
   state.

   *Scope.* The on-chain parameters that bound what the session key may
   authorize: spending cap (max_amount), allowed counterparty
   (allowed_counterparty), expiry timestamp (expires_at).

2.  Extension Identification

   Implementations of this extension MUST set the voucher's
   signatureType field to:

   passkey-p256-session-v1

   This value is distinct from the base specification's ed25519 value
   and MUST NOT be used by implementations that have not implemented
   this extension's verification flow.

   The trailing -v1 permits future revisions of this extension to use a
   distinct signatureType value if the wire format changes incompatibly.

Sander                  Expires 22 December 2026                [Page 4]
Internet-Draft              Open Tabs Passkey                  June 2026

3.  Channel Open with Passkey Authority

3.1.  authorizedSigner field

   Per the base specification, the channel state's authorizedSigner
   field records the public key that signs vouchers.  Under this
   extension, authorizedSigner is the Ed25519 public key of the session
   key, NOT the passkey itself.  The passkey authorizes the session key;
   the session key signs vouchers.  The verifier checks each voucher's
   signature against the session key.

   This indirection is intentional.  The session key signs many vouchers
   per second without user interaction; the passkey performs a single
   biometric gesture per session.

3.2.  Vault account binding

   The channel's PDA derivation, as defined by the base specification,
   binds the channel to authorizedSigner.  Under this extension,
   authorizedSigner is the session key.  The session key, in turn, is
   bound to the passkey via the vault account.

   A conforming implementation MUST ensure that:

   1.  The vault account is initialized before any channel open
       transaction that uses an authorizedSigner derived from a passkey
       session.

   2.  The session key recorded in the vault account's active session
       field matches the authorizedSigner field in the channel state at
       open time.

   3.  The vault account's owner is the conforming authority program.

   A verifier MUST NOT accept a voucher unless all three conditions hold
   for the channel and vault at the time of verification.

3.3.  Vault address derivation

   The vault account address is a Solana Program-Derived Address (PDA)
   derived from operator-defined identity bytes, NOT directly from the
   passkey public key.  The reference implementation uses the following
   seed schema, which conforming implementations SHOULD adopt:

   seeds      = [b"vault", identity_claim[0..16]]
   program_id = authority_program

Sander                  Expires 22 December 2026                [Page 5]
Internet-Draft              Open Tabs Passkey                  June 2026

   Where identity_claim is a 32-byte opaque value supplied at vault
   initialization.  The authority program does not interpret these
   bytes; they are operator-defined.  The reference implementation
   writes a Supabase UUID into the first 16 bytes and zeros the
   remaining 16.  Other operators MAY use any 16-byte identifier
   suitable to their identity substrate.

   The vault account stores the passkey's 33-byte compressed pubkey in
   its state.  The PDA address does not depend on the passkey.  This
   separation permits passkey rotation (replacing the stored pubkey with
   a fresh one) without changing the vault address.  The existing
   balance, custody binding, and identity claim remain stable across
   credential rotation.

   Conforming implementations that use a different seed schema MUST
   document it in implementation notes so verifiers and clients can
   independently recompute the address.

4.  Register Session Key Message Format

4.1.  Wire format

   To authorize a session key, the client constructs a fixed 180-byte
   registration message, signs it with the passkey via WebAuthn, and
   submits the signature and message to the authority program.

   The registration message is laid out as follows.  All multi-byte
   integers are little-endian.

Sander                  Expires 22 December 2026                [Page 6]
Internet-Draft              Open Tabs Passkey                  June 2026

   +======+========+======================+===========================+
   |Offset| Length | Field                | Encoding                  |
   +======+========+======================+===========================+
   |0     | 32     | domain               | ASCII                     |
   |      |        |                      | "OTS_SESSION_REGISTER_V1" |
   |      |        |                      | (23 bytes) padded with 9  |
   |      |        |                      | zero bytes                |
   +------+--------+----------------------+---------------------------+
   |32    | 32     | program_id           | Authority program ID (raw |
   |      |        |                      | Solana address bytes)     |
   +------+--------+----------------------+---------------------------+
   |64    | 32     | vault_pda            | Vault PDA address (raw)   |
   +------+--------+----------------------+---------------------------+
   |96    | 32     | session_pubkey       | Session key Ed25519       |
   |      |        |                      | public key (raw)          |
   +------+--------+----------------------+---------------------------+
   |128   | 8      | max_amount           | u64 LE; cumulative        |
   |      |        |                      | spending cap in token     |
   |      |        |                      | base units; MUST be > 0   |
   +------+--------+----------------------+---------------------------+
   |136   | 8      | expires_at           | i64 LE; Unix timestamp in |
   |      |        |                      | seconds; MUST be strictly |
   |      |        |                      | greater than current on-  |
   |      |        |                      | chain time                |
   +------+--------+----------------------+---------------------------+
   |144   | 32     | allowed_counterparty | Recipient public key      |
   |      |        |                      | (raw); zero MAY be        |
   |      |        |                      | permitted only if         |
   |      |        |                      | implementation supports   |
   |      |        |                      | an unbounded counterparty |
   |      |        |                      | (the reference            |
   |      |        |                      | implementation does not)  |
   +------+--------+----------------------+---------------------------+
   |176   | 4      | nonce                | u32 LE; per-session value |
   |      |        |                      | chosen by the client      |
   +------+--------+----------------------+---------------------------+

                                 Table 1

   Total: 180 bytes.

   The domain field's hex representation is:

   4f54535f53455353494f4e5f52454749535445525f5631000000000000000000

Sander                  Expires 22 December 2026                [Page 7]
Internet-Draft              Open Tabs Passkey                  June 2026

   (That is the ASCII bytes of OTS_SESSION_REGISTER_V1 followed by 9 NUL
   bytes.)  The 23-byte label distinguishes this message from other
   operations the same passkey might sign.  Conforming implementations
   MUST use this exact 32-byte sequence.

   The combination of program_id, vault_pda, session_pubkey, expires_at,
   and nonce makes the message uniquely identifying across all program
   deployments, all vaults, all sessions, and all time.

4.2.  Passkey signing ceremony

   The client signs the registration message using the passkey via the
   WebAuthn assertion ceremony:

   1.  The relying party (the authority program's client SDK) constructs
       the 180-byte registration message above.

   2.  The client computes challenge = sha256(registration_message) and
       passes it as the WebAuthn challenge parameter for
       navigator.credentials.get().

   3.  The user authorizes the gesture (biometric, PIN, or hardware-key
       touch).

   4.  The browser returns the WebAuthn assertion, containing
       authenticatorData, clientDataJSON, and the signature.

   5.  The client submits all three plus the registration arguments to
       the authority program's register_session_key instruction,
       preceded by a SIMD-0075 secp256r1_verify instruction in the same
       transaction covering the WebAuthn signed payload.

4.3.  SIMD-0075 signed payload

   Per the WebAuthn specification, the authenticator signs:

   signed_payload = authenticator_data || sha256(client_data_json)

   This is the payload the client submits to the SIMD-0075 precompile
   along with the 33-byte compressed pubkey and the signature.

   The authority program verifies the precompile result by:

   1.  Reading the previous instruction from the instructions sysvar.

   2.  Asserting that the previous instruction's program ID is
       Secp256r1SigVerify1111111111111111111111111.

Sander                  Expires 22 December 2026                [Page 8]
Internet-Draft              Open Tabs Passkey                  June 2026

   3.  Parsing the precompile's instruction data (offset structure
       defined by [SIMD-0075]) to extract the verified message bytes and
       pubkey bytes.

   4.  Asserting the pubkey bytes equal the 33-byte passkey_pubkey
       stored on the vault.

   5.  Asserting the message bytes equal authenticator_data ||
       sha256(client_data_json) computed from the instruction arguments.

   The authority program then parses client_data_json to extract the
   challenge field.  The value MUST be base64url-encoded per [RFC4648]
   Section 5 without padding.  The program base64url-decodes it and
   asserts:

   decoded_challenge == sha256(registration_message)

   If any check fails, the program MUST reject the transaction.

   The clientDataJSON parser MAY be a minimal-footprint scanner that
   locates the "challenge":"<value>" field; the WebAuthn specification
   guarantees the challenge is base64url-encoded and therefore contains
   only [A-Za-z0-9_-] characters, so the parser does not need to handle
   JSON-escape sequences inside the value.

4.4.  Application-layer challenge binding (informative)

   Implementations that wish to bind the registration to an application
   layer challenge (for example, to satisfy a single-sign-on flow that
   issues a server-side nonce, as discussed in [TIP-1053] for a
   different substrate) MAY encode such a challenge into identity_claim
   or allowed_counterparty.  The protocol does not interpret these
   fields beyond what is specified in Section 5; an off-chain verifier
   that needs challenge-binding can recover and check it from the same
   wire bytes the program signed.

   This extension does not include a separate witness field in v1.  A
   future revision MAY add one if implementer demand emerges.

5.  On-Chain Verification Program

5.1.  Required instructions

   A conforming authority program MUST implement at minimum the
   following instructions.  Instruction names are guidance; conforming
   implementations MAY use different names provided the semantics are
   preserved.

Sander                  Expires 22 December 2026                [Page 9]
Internet-Draft              Open Tabs Passkey                  June 2026

     +======================+=======================================+
     | Instruction          | Purpose                               |
     +======================+=======================================+
     | initialize_vault     | Initialize a vault PDA, recording the |
     |                      | passkey's 33-byte compressed pubkey,  |
     |                      | the bound session authority key, the  |
     |                      | operator's identity claim, and any    |
     |                      | withdrawal cooling-off parameters.    |
     +----------------------+---------------------------------------+
     | register_session_key | Authorize a session key with scope,   |
     |                      | gated by a passkey signature over the |
     |                      | 180-byte registration message defined |
     |                      | in Section 4.                         |
     +----------------------+---------------------------------------+
     | revoke_session_key   | Revoke the active session key, gated  |
     |                      | by a passkey signature over a         |
     |                      | 128-byte revocation message           |
     |                      | (Section 5.2).                        |
     +----------------------+---------------------------------------+
     | prove_passkey        | Verify a passkey signature over an    |
     |                      | arbitrary 32-byte challenge for off-  |
     |                      | chain liveness attestation.  Read-    |
     |                      | only; mutates no state.               |
     +----------------------+---------------------------------------+

                                 Table 2

   The reference implementation provides additional instructions for
   withdrawal flow, custody binding, and authority rotation.  Those are
   out of scope for this extension specification but documented in
   Section 8.

5.2.  Revocation message format

   To revoke the active session key, the client signs a 128-byte
   revocation message:

Sander                  Expires 22 December 2026               [Page 10]
Internet-Draft              Open Tabs Passkey                  June 2026

   +========+========+================+===============================+
   | Offset | Length | Field          | Encoding                      |
   +========+========+================+===============================+
   | 0      | 32     | domain         | ASCII "OTS_SESSION_REVOKE_V1" |
   |        |        |                | (21 bytes) padded with 11     |
   |        |        |                | zero bytes                    |
   +--------+--------+----------------+-------------------------------+
   | 32     | 32     | program_id     | Authority program ID          |
   +--------+--------+----------------+-------------------------------+
   | 64     | 32     | vault_pda      | Vault PDA                     |
   +--------+--------+----------------+-------------------------------+
   | 96     | 32     | session_pubkey | The session pubkey currently  |
   |        |        |                | recorded on the vault         |
   +--------+--------+----------------+-------------------------------+

                                 Table 3

   Total: 128 bytes.

   The domain field's hex representation is:

   4f54535f53455353494f4e5f5245564f4b455f56310000000000000000000000

   The revocation domain separator is distinct from the registration
   domain separator so a registration signature cannot be reinterpreted
   as a revocation or vice versa.

   The signing ceremony and verification flow are identical to the
   registration flow (Sections 4.2 and 4.3) except for the message bytes
   themselves.

5.3.  prove_passkey for off-chain liveness

   The prove_passkey instruction verifies that the holder of the passkey
   can produce a fresh signature over an application challenge.  It does
   not mutate state.  Verifiers MAY use prove_passkey via
   simulateTransaction RPC calls to confirm liveness without consuming
   compute units or paying fees.

   The signed operation message is:

   op_msg = b"siwx_login" || challenge

   Where challenge is a 32-byte value supplied by the off-chain verifier
   (for example, a server-issued login nonce).  The literal siwx_login
   is the domain prefix for this operation; conforming implementations
   MUST use this exact ASCII string.

Sander                  Expires 22 December 2026               [Page 11]
Internet-Draft              Open Tabs Passkey                  June 2026

   This instruction is the canonical mechanism for application-level
   authentication in flows that do not register a new session key.

5.4.  SIMD-0075 precompile

   All passkey signature verification under this extension MUST flow
   through the Secp256r1SigVerify1111111111111111111111111 precompile
   ([SIMD-0075]).  Implementations MUST NOT verify secp256r1 signatures
   through alternative means (for example, in-program ECDSA
   verification).  The precompile's verification semantics are normative
   for this extension.

6.  Voucher Format Compatibility

   This extension does NOT modify the voucher format defined in the base
   specification ([draft-solana-session-00]).  The 48-byte fixed Borsh
   layout (channelId, cumulativeAmount, expiresAt) remains canonical,
   and signature remains a base58-encoded Ed25519 signature.

   A voucher signed under this extension is signed with the session key
   (Ed25519), not the passkey.  The signatureType field distinguishes
   channels that use this extension (passkey-p256-session-v1) from
   channels using the default Ed25519 authority (ed25519).

   A seller verifying a voucher under this extension MUST:

   1.  Parse the voucher and signature as defined in the base
       specification.

   2.  Verify the Ed25519 signature against the session key recorded as
       the channel's authorizedSigner.

   3.  Independently verify, via on-chain account read, that:

       *  The session key recorded on the vault's active session field
          equals the voucher's signer pubkey.

       *  The active session's expires_at is strictly greater than the
          current Unix time.

       *  The active session's max_amount is greater than or equal to
          the voucher's cumulativeAmount.

       *  The active session's allowed_counterparty equals the seller's
          receiving address for this channel.

Sander                  Expires 22 December 2026               [Page 12]
Internet-Draft              Open Tabs Passkey                  June 2026

   The third check is what enforces the security boundary.  The session-
   key signature is necessary but not sufficient; the on-chain scope is
   authoritative.

7.  Off-Chain Verifier Flow

7.1.  Voucher verification sequence

   A seller (verifier) receives a voucher from a buyer (payer).  The
   verifier's sequence is:

   1.  Parse the HTTP request and extract the voucher object.

   2.  Confirm signatureType == "passkey-p256-session-v1".  If not, this
       extension does not apply; fall back to the base specification or
       reject.

   3.  Verify the Ed25519 signature on the voucher against the signer
       field.

   4.  Resolve the channel state from the on-chain channel PDA.  Read
       authorizedSigner and assert it equals the voucher's signer.

   5.  Resolve the vault state by deserializing the vault account (the
       account layout is defined by the authority program).  Confirm
       that the vault's active session matches the constraints listed in
       Section 6.

   6.  If all checks pass, accept the voucher.

   Verifiers SHOULD cache the vault state for short intervals (on the
   order of a few seconds) to amortize RPC reads across many vouchers
   from the same channel, evicting the cache entry when the active
   session's expiry approaches.

7.2.  Liveness for application authentication

   For flows that authenticate a user to an application without opening
   a payment channel (for example, single sign-on or session
   bootstrapping), the verifier issues a 32-byte random challenge, the
   client signs b"siwx_login" || challenge with the passkey via
   WebAuthn, and the verifier confirms by submitting a prove_passkey
   instruction to the authority program via simulateTransaction.  The
   transaction is never broadcast; the simulation result attests
   liveness.

   This permits "passkey once, web2 session thereafter" UX without
   coupling authentication to payment-channel state.

Sander                  Expires 22 December 2026               [Page 13]
Internet-Draft              Open Tabs Passkey                  June 2026

8.  Security Considerations

8.1.  The passkey is not a hardware wallet

   Phone-resident passkeys, particularly those synced through device
   manufacturer keychains, may store and operate key material in ways
   that do not match the security guarantees of dedicated hardware
   wallets.  Implementations of this extension MUST NOT treat the
   passkey as the sole security boundary for the funds the vault
   controls.

   The security boundary is the authority program.  The passkey
   authorizes within bounds the program enforces.  Compromise of the
   passkey limits an attacker to actions permitted by the active
   session's scope.  Raising the scope requires a fresh credential
   ceremony, which an attacker can perform only if they retain the
   passkey at the time of the new ceremony.  The user can close that
   window by re-enrolling on a fresh credential.

   Hardware-grade authenticators (for example, FIDO2 security keys with
   non-exportable keys) MAY be used as passkeys under this extension
   with no protocol change.  The signature scheme is the same; only the
   storage of the credential differs.

8.2.  Replay protection

   Within a single vault, the program enforces at most one active
   session at a time.  A registration whose message specifies a session
   whose expiry has not yet passed is rejected if any unexpired session
   already exists.  An expired session is silently overwritten.

   The 180-byte registration message embeds program_id, vault_pda,
   session_pubkey, expires_at, and nonce.  A signature over this message
   cannot be replayed against a different program, vault, session, or
   moment in time.  The nonce field is operator-controlled and not
   enforced for monotonicity by the program; clients SHOULD generate it
   fresh per session to prevent confusing collisions across their own
   sessions.

   Voucher replay is bounded by the base specification's monotonic
   cumulative-amount semantics: a voucher with cumulative amount N is
   useless once a voucher with cumulative amount M > N has been accepted
   for the same channel.

Sander                  Expires 22 December 2026               [Page 14]
Internet-Draft              Open Tabs Passkey                  June 2026

8.3.  Session key compromise

   A compromised session key authorizes spending up to the current
   session's cap, against the specific counterparty, before the specific
   expiry.  Implementations SHOULD set conservative caps and short
   expiries by default.  Implementations MUST provide a
   revoke_session_key path callable by the passkey holder without
   requiring the compromised session key to participate.

8.4.  Program upgrade authority

   If the authority program is upgradeable, the program's upgrade
   authority is itself a trust assumption beyond the passkey.  A
   malicious or compromised upgrade authority could deploy a modified
   program that bypasses signature verification or relaxes scope
   enforcement.  Implementations SHOULD document the upgrade authority
   and its governance.  Reference implementations are encouraged to burn
   the upgrade authority once the program is considered stable.

8.5.  clientDataJSON parsing

   The reference implementation parses clientDataJSON with a minimal-
   footprint scanner that does not handle JSON-escape sequences.  This
   is sound because the WebAuthn specification guarantees the challenge
   field's value is base64url-encoded and therefore contains only [A-Za-
   z0-9_-] characters.  Conforming implementations that use full JSON
   parsing MUST handle escape sequences safely; implementations that
   scan MUST verify the assumption holds for the operating environment.
   A non-conforming relying party that emits non-base64url challenges
   would otherwise mis-parse.

8.6.  Origin pinning (relying-party responsibility)

   The authority program verifies the WebAuthn challenge binding but
   does NOT inspect the origin field of clientDataJSON.  Origin pinning
   is the relying party's responsibility and is enforced by the
   browser's WebAuthn API based on the registered credential's relying
   party ID.  Verifiers operating outside a browser context (server-side
   verification, native applications) MUST themselves verify that
   clientDataJSON.origin matches an expected value.

9.  Backward Compatibility

   This extension is additive to the base specification.  Channels
   opened under the default Ed25519 authority continue to operate
   without modification.

Sander                  Expires 22 December 2026               [Page 15]
Internet-Draft              Open Tabs Passkey                  June 2026

   A channel opened under this extension is distinguished by its
   signatureType value.  Verifiers that do not implement this extension
   will reject vouchers with signatureType == "passkey-p256-session-v1",
   which is the correct fail-safe.

   The 180-byte registration message format and 128-byte revocation
   message format defined in this document are version v1.  Future
   revisions of this extension that change either layout MUST use a
   distinct signatureType value (for example, passkey-p256-session-v2)
   and a distinct domain separator (for example,
   OTS_SESSION_REGISTER_V2).

10.  Implementation Status

   This section records the status of known implementations of the
   protocol defined by this specification at the time of publication,
   per the guidance of [RFC7942].  Inclusion in this section does not
   imply endorsement by the IETF.  The information is provided to assist
   readers in evaluating maturity and interoperability.

10.1.  Reference implementation on Solana mainnet

   A reference implementation is live on Solana mainnet.

   *  *Authority program*: Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc

   *  *Reference vault account*:
      7FE9VUeabi3sF8wUABV7F3eyvEi1ekDbER9k5JBYrWAi

   *  *Implementer*: independent

   *  *License*: open source (see source repository)

   *  *Coverage*: All four required instructions defined in Section 5.1
      (initialize_vault, register_session_key, revoke_session_key,
      prove_passkey) plus the additional instructions enumerated in
      Section 11.

   *  *Maturity*: deployed on mainnet; byte-parity between the deployed
      program and its source build is reproducible via the procedure in
      the Reference Implementation section.

   *  *Interop*: an end-to-end voucher settlement transaction on Solana
      mainnet, exercising the full register-session, voucher-sign, and
      on-chain-settle path through the reference implementation, is
      recorded at the mainnet transaction signature listed in the
      Reference Implementation section.

Sander                  Expires 22 December 2026               [Page 16]
Internet-Draft              Open Tabs Passkey                  June 2026

10.2.  Canonical client implementation

   An off-chain TypeScript implementation of the byte-precise message
   encoders, instruction builders, account decoders, and secp256r1
   precompile helpers required by this specification is published as the
   @dexterai/vault npm package.

   *  *Distribution*: npm registry, package @dexterai/vault

   *  *Version*: 0.11.0

   *  *Source repository*: https://github.com/Dexter-DAO/dexter-vault-
      sdk

   *  *Coverage*: the 180-byte registration message (Section 4.1), the
      128-byte revocation message (Section 5.2), the siwx_login
      operation message (Section 5.3), the on-chain instructions
      relevant to this extension exposed by the reference authority
      program, the secp256r1 precompile instruction builder, and the
      WebAuthn authenticatorData || sha256(clientDataJSON) precompile-
      message assembly.

   *  *Maturity*: in production use; tested against the live reference
      implementation.

10.3.  Conformance tests

   The byte-parity test suite at tests/byte-parity.test.ts in the
   @dexterai/vault source repository is the canonical conformance test
   for off-chain implementations of this specification.  The suite locks
   each domain separator, each instruction discriminator, and the
   serialized bytes of the 180-byte registration message, the 128-byte
   revocation message, and the 44-byte voucher payload against snapshot
   fixtures.

   A conforming off-chain implementation MUST produce the same bytes as
   this suite for the same inputs.

   The conformance test file in @dexterai/vault version 0.11.0 has SHA-
   256:

   f7fc48da373069eb209ac3366046cbc5540c17450ce76ca7870aa2ee9adeb8d0

   This hash is a point-in-time snapshot of the suite at the named
   @dexterai/vault version; it changes when the suite changes, so
   implementers re-run sha256sum tests/byte-parity.test.ts against the
   version they target.

Sander                  Expires 22 December 2026               [Page 17]
Internet-Draft              Open Tabs Passkey                  June 2026

   Subsequent revisions of the suite that change snapshots without
   incrementing this document's signatureType value would constitute a
   specification break and SHOULD be rejected by implementers.

11.  Reference Implementation

   The dexter-vault Solana program is the reference implementation of
   this extension.  Source:

   *  Authority program: https://github.com/Dexter-DAO/dexter-vault

   *  Client SDK: https://github.com/Dexter-DAO/dexter-x402-sdk

   *  MCP server: https://github.com/Dexter-DAO/dexter-mcp

   Deployed instances on Solana mainnet:

   *  Authority program ID: Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc

   *  Reference vault account:
      7FE9VUeabi3sF8wUABV7F3eyvEi1ekDbER9k5JBYrWAi

   An end-to-end voucher settlement transaction on Solana mainnet,
   exercising the full register-session, voucher-sign, and on-chain-
   settle path through the reference implementation, is recorded at the
   following mainnet transaction signature:

3hqDvf3b8ZwZNGmtZAQXsdh5ejGwuuUrNt7Rv2PfuSs1aqKJdC3cxviKgVcd8tawtGB1F79XYjwcx2Re2nSb8oqR

   The reference implementation includes additional instructions beyond
   the minimum required by this extension: set_swig (one-time binding of
   a Solana smart wallet to the vault), request_withdrawal /
   finalize_withdrawal (gated buyer withdrawal flow), force_release
   (stuck-voucher recovery), rotate_passkey (credential rotation),
   rotate_dexter_authority (operator key rotation), and settle_voucher
   (operator-initiated session settlement).  These instructions are part
   of the Open Tabs Standard described in the reference repository but
   are out of scope for the session-intent extension specified here.

11.1.  Verification that the reference implementation matches the
       deployed program

   The deployed mainnet program at
   Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc can be verified bit-for-
   bit against the source-on-disk build.  Because the reference
   implementation evolves, this document specifies the reproducible
   procedure by which an implementer confirms byte parity at any
   version, rather than pinning a section hash that would rot at the

Sander                  Expires 22 December 2026               [Page 18]
Internet-Draft              Open Tabs Passkey                  June 2026

   next deployment, per [RFC7942].

   The procedure compares the SHA-256 of each loaded ELF section (.text,
   .rodata, .data.rel.ro) of the on-chain bytecode (obtained via solana
   program dump) against the local Anchor build (target/deploy/
   dexter_vault.so).  When all three loaded sections match, any file-
   level size difference between the deployed and local .so is in non-
   loaded metadata (.dynamic, .dynsym, .dynstr, ELF padding) and does
   not affect execution semantics.  Section offsets shift on recompile
   and are read per build via readelf -S.

   Reproducibility of the verification:

   solana program dump Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc \
     deployed.so --url https://api.mainnet-beta.solana.com
   cd /path/to/dexter-vault
   anchor build
   python3 -c "
   import hashlib
   sections = [('.text', 0x120, 0x38608),
               ('.rodata', 0x38728, 0x4068),
               ('.data.rel.ro', 0x3c790, 0x1808)]
   for label, path in [('deployed', 'deployed.so'),
                       ('local',    'target/deploy/dexter_vault.so')]:
       buf = open(path, 'rb').read()
       for name, off, sz in sections:
           h = hashlib.sha256(buf[off:off+sz]).hexdigest()
           print(f'{label:8s} {name:14s} {h}')
   "

   The section offsets in the script above (0x120, 0x38728, 0x3c790 and
   the corresponding sizes) are themselves a function of the build and
   shift when the program is recompiled; re-read them from the section
   header table of the current .so (for example with readelf -S) before
   running the hash comparison.  Implementers re-verify against the
   current deployment; the reference implementation evolves, so these
   values are a point-in-time snapshot per [RFC7942].

   Implementers building their own reference are encouraged to perform
   the same comparison whenever they deploy a new program version, to
   make the spec's normative claims auditable on-chain.

12.  Test Vectors

   The following test vector exercises the 180-byte registration message
   serialization with placeholder values.  The placeholder bytes are
   chosen for visual distinguishability and do not correspond to a real
   on-chain deployment.

Sander                  Expires 22 December 2026               [Page 19]
Internet-Draft              Open Tabs Passkey                  June 2026

   Inputs:

   *  program_id: 32 bytes of 0xff

   *  vault_pda: 32 bytes of 0xee

   *  session_pubkey: 32 bytes of 0x11

   *  max_amount: 1_000_000 (decimal) = 0x40420f0000000000 (LE u64)

   *  expires_at: 1735000000 (decimal) = 0xc0ff696700000000 (LE i64)

   *  allowed_counterparty: 32 bytes of 0x22

   *  nonce: 1 = 0x01000000 (LE u32)

   Serialized registration message (180 bytes, hex):

   4f54535f53455353494f4e5f52454749535445525f5631000000000000000000
   ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
   eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
   1111111111111111111111111111111111111111111111111111111111111111
   40420f0000000000
   c0ff696700000000
   2222222222222222222222222222222222222222222222222222222222222222
   01000000

   SHA-256 of the serialized message:

   acaf34c904b60f1e3dccd30a9543eab7325e06982582d5852c3405beb620e6ad

   This SHA-256 value is what the client passes as the WebAuthn
   challenge parameter.  The WebAuthn assertion's
   clientDataJSON.challenge field, base64url-decoded, MUST equal this
   value.

13.  IANA Considerations

   This document defines a new value (passkey-p256-session-v1) for the
   signatureType field of the Solana Session Intent voucher payload
   ([draft-solana-session-00]).  The base specification's namespace
   governs registration of this value; this document does not request
   independent IANA action.

14.  References

14.1.  Normative References

Sander                  Expires 22 December 2026               [Page 20]
Internet-Draft              Open Tabs Passkey                  June 2026

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

   [RFC4648]  Josefsson, S., "The Base16, Base32, and Base64 Data
              Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
              <https://www.rfc-editor.org/info/rfc4648>.

   [WEBAUTHN] "Web Authentication, Level 3", n.d.,
              <https://www.w3.org/TR/webauthn-3/>.

   [SIMD-0075]
              "SIMD-0075: Precompile for secp256r1 sigverify", n.d.,
              <https://github.com/solana-foundation/solana-improvement-
              documents/blob/main/proposals/0075-precompile-for-
              secp256r1-sigverify.md>.

   [draft-solana-session-00]
              "Solana Session Intent for HTTP Payment Authentication",
              n.d., <https://github.com/tempoxyz/mpp-specs/pull/201>.

14.2.  Informative References

   [RFC7942]  Sheffer, Y. and A. Farrel, "Improving Awareness of Running
              Code: The Implementation Status Section", BCP 205,
              RFC 7942, DOI 10.17487/RFC7942, July 2016,
              <https://www.rfc-editor.org/info/rfc7942>.

   [SEC1]     "SEC 1: Elliptic Curve Cryptography", n.d.,
              <https://www.secg.org/sec1-v2.pdf>.

   [TIP-1053] "TIP-1053: Witnesses in Key Authorizations", n.d.,
              <https://tips.sh/1053>.

Appendix A.  Acknowledgments

   This extension depends on the SIMD-0075 secp256r1 verification
   precompile and is compatible with the extension surface defined in
   draft-solana-session-00.  Normative references are listed at the
   front of this document.

Author's Address

Sander                  Expires 22 December 2026               [Page 21]
Internet-Draft              Open Tabs Passkey                  June 2026

   Nicholas Sander
   Independent
   Email: branch@dexter.cash

Sander                  Expires 22 December 2026               [Page 22]