Skip to content

AP2 v2 conformance (Node/TS) — SD-JWT/KB-SD-JWT mandate chain wire format #39

Description

@dzuluaga

Part of the trust-level roadmap (#16). Prompted by direct feedback from the AP2 team (Yanhe Chen, Google, 2026-06-02): our mandates are AP2 v1-shaped (ap2.PaymentMandate / 0.1-mock), the amount is not cryptographically signed, and deviceAuth/issuerAuth are unverified. The last two are already tracked (#13 does the key-signing swap, #14 the issuer/device trust anchor). This issue is the remaining gap: emitting and verifying the AP2 v2 wire format itself.

Reference: AP2 Python SDK — SD-JWT / KB-JWT with dSD-JWT delegation chains (draft-gco-oauth-delegate-sd-jwt-00).

Scope (Node/TS, attestomcp-gate):

  • Closed PaymentMandate as a terminal KB-SD-JWT: vct: "mandate.payment.1", transaction_id (= sha256 of the checkout JWT), payee, payment_instrument, iat/exp, aud/nonce challenge binding.
  • payment_amount in ISO 4217 integer minor units (27999 = $279.99) — convert at the mandate boundary; CeremonyOrder uses float dollars today.
  • Chain wire format: ~~-joined hops, typ: kb+sd-jwt+kb (open, has cnf) vs kb+sd-jwt (closed), sd_hash / issuer_jwt_hash binding to the preceding hop. Candidate base: @sd-jwt/core (OpenWallet Foundation sd-jwt-js); the chain walk is a thin layer on top.
  • Open mandate + constraints (OpenPaymentMandate: AmountRange, AllowedPayees, allowed instruments) — this is AP2 v2's carrier for the human-not-present leg (Human-Not-Present delegation — ap2.IntentMandate (presence: live → delegated · v0.2) #12).
  • Receipts: ES256-signed PaymentReceipt, reference = sha256(closed leaf JWT).
  • Codegen TS types from the AP2 JSON schemas (same source their pydantic generated/ comes from).
  • Bypass tests: tampered amount, forged hop key, wrong aud/nonce, broken sd_hash → all refused. Keep the deterministic re-derivation gates (Security invariant 2) as defense in depth.

Out of scope: issuer/device COSE signature verification on the mdoc rails (#14); the underlying key-signing swap (#13); Python cross-SDK conformance (companion issue).

Sequencing: builds on #13 (real keys); #12 consumes the open-mandate constraints; the companion Python-interop issue is the acceptance harness.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

Priority

None yet

Projects

Status
Ready

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions