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-InputandSignature(structured fields), builds the signature base from the covered components (@authority,@path,@method,@target-uri,content-digestand any header). - Algorithms:
ed25519,rsa-pss-sha512,ecdsa-p256-sha256, andps256. Whenalgis 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) andagent-payer-auth(paying). - Required parameters:
created,expires,keyid,nonce,tag. The windowexpires - createdmay 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
signaturefield removed. The spec doesn't say this yet; it's asked in visa/trusted-agent-protocol#23. If the answer differs, onlysrc/bodyObjects.tschanges. - 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_orderstep 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_trustnote 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.