The four-signature trail
Every OID4AC charge produces four signed artefacts. Each is signed by a different party, covers a different scope, and binds to the others by hash. Reconstructing a charge from these four signatures is deterministic; missing any one signature collapses the chain.
Signature 1: Signed Offer (merchant -> agent)
The merchant publishes Offer JSON-LD at a stable URL (typically /products/{sku}). The body is signed with an RFC 9421 HTTP message signature over @method, @target-uri, @authority, content-type, and content-digest. The signature
commits the merchant to the SKU, the price (amount_minor), and
the currency at a specific URL within a 300-second window.
| Field | Detail |
|---|---|
| Signer | Merchant (private key registered in their JWKS) |
| Spec | RFC 9421 HTTP message signature |
| Algorithm | ed25519 default, ecdsa-p256-sha256 fallback |
| Commits to | SKU, price, currency, offer URL, content digest |
| TTL | expires - created in (0s, 300s] |
Signature 2: JWT-AT (AS -> agent)
The agent posts a PAR request to the AS
citing the offer digest. After consent at the Wallet Portal, the AS returns a
JWT Access Token with typ=at+jwt, signed with the AS's Ed25519
signing key, and bound to the agent's DPoP key via cnf.jkt. The
token commits the AS to: this agent acted on behalf of this principal, with
this mandate, for this audience, for the next 300 seconds.
| Field | Detail |
|---|---|
| Signer | Authorization Server (Ed25519 signing key) |
| Spec | RFC 9068 JWT-AT claim set |
| Algorithm | EdDSA |
| Commits to | sub (principal), aud (merchant), client_id (agent), cnf.jkt, mandate_id |
| TTL | 300 s |
Signature 3: SD-JWT VC mandate (AS -> wallet -> merchant)
The AS issues an SD-JWT Verifiable Credential at consent time. The mandate carries the principal's spend cap, currency, merchant allowlist, and validity window in disclosable form. The wallet stores it; the agent retrieves it; the merchant verifies it against the AS JWKS at charge time. Selective disclosure lets the agent reveal only the fields the merchant needs.
| Field | Detail |
|---|---|
| Signer | Authorization Server (Ed25519 signing key) |
| Spec | draft-ietf-oauth-sd-jwt-vc |
| Algorithm | EdDSA |
| Commits to | principal_id, spend_cap_minor, currency, merchant_allowlist, not_before, not_after |
| TTL | Up to the mandate's not_after claim (typical: 24 h to 30 d) |
Signature 4: KB-JWT presentation (agent -> merchant)
At charge time the agent mints a Key Binding JWT signed by the same DPoP
keypair whose thumbprint is in the JWT-AT's cnf.jkt claim. The
KB-JWT binds this presentation to this merchant audience and
a single-use nonce of the form sha256(merchant_nonce || offer_digest). Replay across merchants
or against a different offer is impossible.
| Field | Detail |
|---|---|
| Signer | Agent (DPoP keypair) |
| Spec | draft-ietf-oauth-sd-jwt-vc §4.3 (Key Binding JWT) |
| Algorithm | EdDSA or ES256 |
| Commits to | aud (merchant origin), nonce (one-shot), iat |
| TTL | 60-second window |
The chain
Dispute reconstruction
Given the four signatures plus the audit chain entry, a dispute resolver can independently prove every causal link:
- Was the price what the merchant published? Verify signature 1 against the merchant's JWKS at the time of the dispute (JWKS history is retained for this lookup).
- Did the agent have authorisation? Verify signature 2 against the AS JWKS for that
kid. - Did the principal consent to this kind of spend? Verify signature 3 (the mandate VC) and check the
spend_cap_minorvs the charge amount. - Was this charge a fresh act, not a replay? Verify signature 4's
nonceagainst the merchant's nonce log and the offer digest. - Did this happen at the time the merchant claims? Verify the audit-chain entry's
seqandprev_hashagainst the hourly-signed chain head.
A documented dispute-evidence procedure describes how to assemble the four signatures and the audit-chain entry into a single verifiable evidence pack.