Protocols

Agent-commerce standards are young and some details are still unpublished. This page says exactly what ProveTheBuy checks for each one, and marks every place where it follows an assumption rather than a published rule.

RFC 9421

HTTP Message Signatures are the base layer for Web Bot Auth, Visa TAP and Mastercard Agent Pay.

  • Parses Signature-Input and Signature (structured fields), builds the signature base from the covered components (@authority, @path, @method, @target-uri, content-digest and any header).
  • Algorithms: ed25519, rsa-pss-sha512, ecdsa-p256-sha256, and ps256. When alg is missing it is inferred from the key.
  • Passes the RFC's own Ed25519 test vector, Appendix B.2.6. The test is in packages/verify/test/verify.test.mjs.

Web Bot Auth

  • Purpose tags agent-browser-auth (looking) and agent-payer-auth (paying).
  • Required parameters: created, expires, keyid, nonce, tag. The window expires - created may be at most 480 seconds (configurable), with 60 seconds of clock skew.
  • The nonce is stored until the signature expires; a second use is blocked as a replay. The check runs after the signature check, so an attacker can't burn a real agent's nonce with a forged request.

Visa Trusted Agent Protocol

  • Keys come from https://mcp.visa.com/.well-known/jwks (KNOWN_DIRECTORIES.visa).
  • Consumer recognition object and payment container object in the body are checked: each must carry the header signature's nonce and key id, and its own signature must verify.
  • Assumption: the body objects are signed over their RFC 8785 canonical JSON with the signature field removed. The spec doesn't say this yet; it's asked in visa/trusted-agent-protocol#23. If the answer differs, only src/bodyObjects.ts changes.
  • ProveTheBuy never decrypts the payment container. It checks the binding and stores a hash.

Mastercard Agent Pay and Verifiable Intent

Verifiable Intent is modelled as three layers:

Layer Signed by Holds
L1 Card issuer SD-JWT naming the user's key (cnf.jwk)
L2 User SD-JWT with the constraints (amount, merchants, items), the mode (autonomous or attended), and the agent's key
L3 Agent JWT with transaction_id set to the order hash

Each layer carries prev, the SHA-256 of the layer before it, so they can't be mixed between purchases. ProveTheBuy checks every signature, every link, the expiry, that L3's transaction_id equals the hash of the checkout actually presented, and that the order sits inside L2's constraints.

Status: preview. The layer layout follows the published Verifiable Intent drafts and its reference implementation; field names may change before Mastercard finalises it.

AP2

  • Cart mandate: the merchant's signature (checked against your own merchantJwk) and the user's signature over the same cart.
  • Intent mandate: a user-signed intent without a fixed cart. Orders above limits.intent_mandate_max_order step up, because nobody saw the final cart.
  • Open question: AP2 doesn't yet say how a merchant should trust the user's key. ProveTheBuy reports this as a mandate_user_key_trust note rather than pretending to know.

Stripe shared payment tokens and ACP

The token's allowance is checked against the order: max_amount, currency, merchant_id, expires_at. Only the limits; the token itself is redeemed by Stripe as usual.

Test vectors

test-vectors/vectors.json holds signed requests with fixed keys and a fixed clock, each with its expected decision and failing check. They are generated by npm run vectors and checked on every npm test. Ports to other languages should pass all of them.