signed-receipts/v1 › conformance

Implement signed-receipts/v1 in 5 minutes

Golden vectors and a one-command runner for the A2A receipt format. Everything here is static, served from councilof.ai, Apache-2.0. No GitHub account, no sign-up, no key.

PASS means your verifier's result for a case matches the expected result in vectors.json. It is not a certification, an endorsement or a conformity mark, and nobody here issues one.

The steps

  1. Read the receipt object and the verification rule in the specification (SPEC.md, draft 0.2). A receipt is JSON; its bytes are RFC 8785 (JCS); content_id is the SHA-256 of the receipt minus content_id and signature; the Ed25519 signature covers the receipt minus signature.
  2. Download vectors.json. Each case gives a receipt, a did_documents map standing in for DID resolution (a DID missing from the map cannot be resolved), and the expected result.
  3. Run your verifier over every case, resolving DIDs only through that map, and write what it returns:
    { "implementation": "my-verifier 0.1",
      "results": { "valid-publickeyhex": "VALID", "unresolvable-key": "UNVERIFIABLE_KEY", … } }
  4. Check it. One line, Node 20 or later, no dependencies:
    curl -sO https://councilof.ai/spec/signed-receipts/v1/conformance/run.mjs && node run.mjs my-results.json
    Or with Python and the cryptography package:
    curl -sO https://councilof.ai/spec/signed-receipts/v1/conformance/run.py && python3 run.py my-results.json
    Each prints PASS or FAIL per case and exits 0 only when every core case matches. Both read the published vectors.json by default, so no other file is needed; add --vectors <path> to use a local copy.

The three results

ResultWhen
VALIDcontent_id recomputes; the Ed25519 signature verifies under signature.signer_public_key; and the DID in signature.kid resolves to a document that lists exactly that key, not revoked.
INVALIDMalformed; content_id mismatch; bad signature; or the DID document resolved and does not list the key, or marks it "revoked": true. Integrity is checked first, so a tampered receipt is INVALID even when its key is also unresolvable.
UNVERIFIABLE_KEYIntegrity holds against the key the receipt carries, but the DID could not be resolved (no resolver, unsupported method, network failure). Whose key it is, is unknown. Never report this as VALID.

The third result exists because of a defect of ours. Until 28 September 2026 our reference verifier returned VALID when it could not resolve the signing key, so a receipt signed with anyone's key passed. IETF SCITT architecture issue #462 cites it as the failure case. The reference interceptor.py now returns UNVERIFIABLE_KEY there, and the vectors hold every implementation, ours included, to it.

What the vectors cover

17 core Ed25519 cases: valid (key as publicKeyHex, publicKeyMultibase and publicKeyJwk); an RFC 8785 edge case (UTF-16 key order, non-ASCII text, 0.5, 1e+30); tampered payload, with and without a recomputed content_id; tampered signature; different-key forgery; three unresolvable-key cases; tampered and unresolvable; a key that only matches as a substring; a revoked key; two wrong canonicalisations (insertion key order, and ", "/": " separators); and a missing signature.

4 interop cases for the a2a-receipt-ml-dsa-65 profile, ML-DSA-65 (FIPS 204) over the same receipt object. The receipts, keys and signatures are copied unmodified from @fractalai/pqc-agent-receipts-conformance 0.3.1 (FractalAI, Apache-2.0); the expected results map that suite's own four checks onto the three codes. They are optional: a candidate that leaves them out gets SKIP, not FAIL. The runners here have no ML-DSA-65, so those cases were re-run with that package's own verifier (all four pass).

Not covered: time validity (draft 0.2 defines issued_at only, no validity window, so there are no expiry cases), and issuer binding (draft 0.2 does not require issuer to equal the DID in kid). One erratum in draft 0.2: its change notes say astral characters are escaped as surrogate pairs. RFC 8785 emits them as UTF-8, the reference now does too (corrected 28 September 2026), and case valid-jcs-edge tests it.

Files

Tell us you implemented it

If your implementation matches the vectors, email nicholas@csoai.org with its name, version and a link to a run. Nobody is added to any list of implementers without their written consent, and a listing will say only what the run shows.