Verifiable Intent: Design Rationale¶
This document explains the key design decisions in the Verifiable Intent (VI) specification. It is informative, not normative.
1. Why SD-JWT?¶
VI requires a credential format that supports selective disclosure (each party in a transaction sees only the claims they need) while remaining compact enough for payment flows. Four candidate formats were evaluated:
| Format | Selective Disclosure | Compactness | Standards Status | Ecosystem Fit |
|---|---|---|---|---|
| SD-JWT | Native (salted hash commitments) | Compact (base64url) | IETF RFC 9901 | JSON-native, JWT ecosystem |
| W3C VC + JWT | Requires VC-specific profiles | Moderate | W3C Recommendation | Broad but no native SD |
| W3C VC + JSON-LD | Via BBS+ signatures (complex) | Verbose | W3C Recommendation | Academic/government focus |
| AnonCreds / ZKP | Native (zero-knowledge proofs) | Variable | Hyperledger | Limited payments adoption |
| mDL (ISO 18013-5) | Per data element (within nameSpaces) |
CBOR (binary) | ISO standard | Mobile identity, not payments |
Decision: SD-JWT (IETF RFC 9901, formerly draft-ietf-oauth-selective-disclosure-jwt).
Rationale:
-
Native selective disclosure. Each claim is individually disclosable via salted hash commitments. No additional cryptographic machinery (BBS+, ZKP circuits) required. This matches VI's core need: the merchant sees the checkout mandate but not the payment details; the network sees the payment mandate but not the checkout.
-
IETF standardization track. SD-JWT is on the IETF OAuth working group standards track, giving it institutional legitimacy for payments infrastructure. W3C VCs are a recommendation but lack a single canonical selective disclosure mechanism.
-
Compact format. Base64url-encoded JSON with
~-delimited disclosures fits in HTTP headers, query parameters, and payment protocol message fields where binary formats (CBOR) or verbose formats (JSON-LD) would not. -
JSON ecosystem alignment. Payment APIs (Google AP2, Mastercard ACP, EMVCo SRC) are JSON-based. SD-JWT integrates without format translation.
2. Relationship to OpenID4VP¶
OpenID for Verifiable Presentations (OpenID4VP) defines a protocol for requesting and presenting verifiable credentials. VI credentials can be transported via OpenID4VP, but VI itself is a credential format specification, not a presentation protocol.
Key distinction: OpenID4VP answers "how do I request and deliver a credential?" VI answers "what is inside the credential and how do I verify the delegation chain?"
A future integration guide may define VI credential presentation via OpenID4VP
vp_token parameters. The SD-JWT format choice makes this integration
straightforward since OpenID4VP already supports SD-JWT as a credential format.
3. Relationship to FIDO2/WebAuthn¶
FIDO2 and WebAuthn provide strong user authentication using public key cryptography, typically with the P-256 curve that VI also uses for ES256 signatures. However, VI credentials are not WebAuthn credentials, and the relationship is one of complementary deployment patterns rather than direct technical integration.
Key distinctions:
- FIDO2/WebAuthn authenticates the user to a relying party ("this person is who they claim to be") using origin-scoped credentials.
- VI authorizes a specific transaction ("this person approved this purchase with these constraints") using payment-scoped credentials.
Deployment complementarity:
In a production deployment, the user's VI signing key (bound in L1 cnf.jwk)
may be stored in the same secure hardware (device Secure Enclave, HSM) that
stores WebAuthn credentials. User authorization flows might combine both:
- WebAuthn assertion authenticates the user to the wallet application
- VI L2 signing authorizes the specific transaction with the user's key
These are separate cryptographic operations using separate keys: - WebAuthn key: Origin-scoped, used for authentication assertions - VI key: Payment network-scoped, used for signing VI credentials
The VI signing operation would typically be gated by biometric authentication (Face ID, Touch ID, Windows Hello) using the same local authentication mechanisms that WebAuthn uses, but the VI credential itself is not a WebAuthn credential.
Hardware alignment:
The choice of ES256 (ECDSA over P-256) as VI's signing algorithm benefits from the same hardware ecosystem that supports WebAuthn: - Mobile device Secure Enclaves (Apple, Android) support P-256 - Hardware security modules (HSMs) support P-256 - Cloud KMS services support P-256
This hardware availability was a factor in the ES256 choice, though the primary motivation was broader HSM compatibility for payment infrastructure deployments (see §5).
4. Relationship to SCA/PSD2¶
Strong Customer Authentication (SCA) under PSD2 requires that electronic payments include authentication elements linked to a specific amount and payee (dynamic linking). VI's constraint model provides a cryptographic implementation of dynamic linking:
- Amount binding: The
payment.amountconstraint cryptographically limits the transaction amount to a min/max range. The L3aamountmust satisfy this constraint for verification to succeed. - Payee binding: The
payment.allowed_payeeconstraint specifies allowed merchants. Only transactions to listed payees pass verification. - Checkout-payment binding: The
checkout_hashandconditional_transaction_idmechanisms bind the payment authorization to a specific set of goods, going beyond PSD2's amount-and-payee requirement.
VI does not replace SCA — it provides the cryptographic evidence that a compliant SCA implementation can produce and verify.
5. Signing Algorithm: ES256¶
Decision: ES256 (ECDSA over NIST P-256) as the required signing algorithm.
Rationale:
-
HSM and Secure Enclave compatibility. P-256 is supported by all major hardware security modules (AWS CloudHSM, Azure Dedicated HSM, Google Cloud HSM) and mobile secure enclaves (Apple Secure Enclave, Android Keystore). This is essential for production key management.
-
Compact signatures. ES256 produces 64-byte signatures (two 32-byte integers), keeping SD-JWT tokens compact.
-
FIDO2/WebAuthn alignment. P-256 is the mandatory-to-implement curve in WebAuthn, enabling the same secure hardware to store both authentication credentials (WebAuthn) and authorization credentials (VI), though they remain separate keys with separate purposes.
-
Payment ecosystem precedent. Google's Agent Payments Protocol (AP2) examples use ES256. EMVCo tokenization specifications reference P-256 for device binding.
Future: EdDSA (Ed25519) is planned as an optional algorithm in a future version. Ed25519 offers deterministic signatures and simpler implementation, but has less HSM support today.
6. Delegation Model: Layered cnf Claims¶
VI uses the cnf (confirmation) claim from RFC 7800 to build a delegation
chain across SD-JWT layers. This is the specification's core novel
contribution.
How it works:
- Layer 1 (Issuer SD-JWT):
cnf.jwkbinds the user's public key. The issuer asserts "this key belongs to this user." - Layer 2 (User KB-SD-JWT): In Autonomous mode, each mandate includes
cnf.kidandcnf.jwkbinding the agent's public key. The user asserts "this agent key may act within these constraints." - Layer 3 (Agent KB-SD-JWT): The
kidin the header identifies the delegated agent key, which verifiers resolve from L2cnf.jwk. The agent asserts "I am the authorized agent, and here are the final transaction details." L3 contains nocnfclaim — the delegation chain terminates here, preventing further sub-delegation.
Why cnf claims instead of custom fields:
- Standard mechanism. RFC 7800 is the established JWT method for proof of possession. Reusing it avoids inventing new verification semantics.
- Presence/absence encodes mode. In Immediate mode, L2 mandates have no
cnf— the user signs final values directly. In Autonomous mode, L2 mandates includecnf.jwk— the user delegates to an agent. The presence or absence ofcnfin mandates is the technical distinction between "user-final" and "agent-delegated." - Verifier simplicity. Verifiers follow the same
cnf→ key extraction → signature verification pattern at each layer, regardless of mode.
Relationship to RFC 7800: In standard RFC 7800 usage,
cnf.jwknames the key that proves possession of the current JWT. VI applies a profile-specific extension of this semantics for multi-party delegation:cnf.jwkin a mandate names the key authorized to create the next layer. While the verification mechanics (extract key fromcnf, verify signature) follow the same pattern, VI's use is a profile-level application that broadens the semantic scope from "holder of this token" to "authorized delegate for this mandate."
7. Two Execution Modes¶
Decision: VI defines two modes (Immediate and Autonomous) rather than a single general-purpose flow.
Rationale:
- Immediate mode (2-layer) covers the common case where the user is present and confirms final values directly. No agent delegation, no constraints, no L3. This is the simpler flow and should be the default for most transactions.
- Autonomous mode (3-layer) covers the case where an AI agent acts on the user's behalf without real-time confirmation. The additional layer (L3) and constraint system add complexity but provide the cryptographic authorization evidence needed when the end-user is not in the loop.
Collapsing these into a single mode would either over-complicate simple transactions (requiring empty constraint sets and unnecessary L3 for end-user- present flows) or under-protect autonomous transactions (allowing unconstrained agent actions).
8. Why Constraints Instead of Top-Level Recurrence?¶
Earlier VI drafts included recurrence as a top-level field on payment
mandates, treating subscription setup as distinct from constraint-based
delegation. V0.1 replaces this with two constraint types: payment.recurrence
(merchant-managed subscriptions) and payment.agent_recurrence (agent-managed
recurring purchases).
Rationale:
-
Unified enforcement model. All agent authority bounds — whether per-transaction limits, merchant allowlists, or recurring purchase terms — are constraints. This gives verifiers a single validation framework rather than separate logic for "recurrence fields vs. constraints."
-
Clearer mode distinction. The presence of constraints signals Autonomous mode delegation. Top-level recurrence fields obscured this — a mandate with only
recurrencebut no other constraints looked like Immediate mode data, not a delegated authority grant. -
Multi-transaction support.
payment.agent_recurrenceexplicitly authorizes the agent to create multiple L3 pairs over time (the multi-transaction mandate pair model), paired withpayment.budgetto cap cumulative spend. This is a natural extension of the constraint model; top-level fields provided no clear path for this. -
Subscription setup clarity.
payment.recurrencemakes it explicit that the transaction is a subscription setup (one L3, merchant charges thereafter). The constraint validates setup parameters against user intent.
9. Why One-L3-Per-Pair in v0.1 Base Model?¶
V0.1 defines one-L3-per-mandate-pair as the base transaction model, with
payment.agent_recurrence as an explicit extension that authorizes multiple L3
fulfillments.
Rationale:
-
Simpler security model. A one-to-one mapping between mandate pairs and transactions (in the base case) eliminates replay and over-spend attack classes. Payment networks enforce this through stateful tracking (see security-model.md §4.2), and the invariant is straightforward to audit.
-
Establishes enforcement infrastructure. The stateful tracking that payment networks build for one-L3-per-pair (transaction logs keyed by L2
sd_hashand mandate pair index) is the same infrastructure that multi-transaction mandate pairs build on. Starting with the simpler invariant lets implementations validate their tracking before adding bounded multiplicity. -
Subscription setup covers immediate need. The
payment.recurrenceconstraint lets an agent establish a subscription with a single L3 pair. Subsequent recurring charges are merchant-initiated on normal payment rails (card-on-file, network tokens). This covers the most common recurring payment pattern. -
Explicit multi-transaction opt-in. The
payment.agent_recurrenceconstraint explicitly signals "this mandate pair authorizes multiple L3 fulfillments" with clear bounds (max_occurrences,payment.budgetcumulative cap, date range). This makes the multi-transaction authorization an intentional, verifiable choice rather than an implicit behavior.
Transaction scope models in v0.1:
- Single-transaction (base): One L3 pair per mandate pair
- Subscription setup:
payment.recurrenceconstraint; one L3 pair starts merchant-managed recurring charges - Multi-transaction:
payment.agent_recurrenceconstraint; multiple L3 pairs within bounds (occurrence cap, budget cap, date range)
References¶
- RFC 9901 — Selective Disclosure for JSON Web Tokens (SD-JWT)
- RFC 7800 — Proof-of-Possession Key Semantics for JWTs
- OpenID4VP — OpenID for Verifiable Presentations
- WebAuthn Level 2 — Web Authentication API
- PSD2 RTS on SCA — EU delegated regulation on Strong Customer Authentication