signed-receipts/v1
An A2A extension. It adds a did:web key-trust convention for AgentCard signing (A2A §8.4) and a signed receipt object an agent can attach to a task outcome. This page is the specification's canonical address. It is published by Council of AI (CSOAI Ltd) under the Apache-2.0 licence.
Files
- SPEC.md: the specification, draft 0.2 (the text on this page)
- interceptor.py: reference implementation: signs and verifies receipts (corrected 28 Sep 2026)
- test_interceptor.py: its regression suite (python3 test_interceptor.py)
- conformance/: golden vectors and a one-command runner: implement it in 5 minutes
SPEC.md is pinned byte for byte in our source mirror at councilof-ai-source @ 96bf3a07. The mirror's interceptor.py is the version before 28 September 2026, which returned VALID when it could not resolve the signing key; the copy here returns UNVERIFIABLE_KEY instead. Earlier references to the GitHub repository CSOAI-ORG/a2a-signed-receipts do not resolve at present; this page replaces them. The extension URI itself is https://councilof.ai/a2a/extensions/signed-receipts/v1.
Implementing it?
The conformance kit has 17 Ed25519 cases, 4 ML-DSA-65 interop cases and a runner: node run.mjs my-results.json. A PASS means your results match the vectors. It is not a certification.
Third-party use
The independent conformance suite @fractalai/pqc-agent-receipts-conformance (npm 0.3.1) includes a profile, a2a-receipt-ml-dsa-65, that reuses the CSOAI signed-receipts/v1 receipt object with an ML-DSA-65 signature.
Specification text
Reproduced from SPEC.md without changes.
A2A Extension: signed-receipts/v1 (draft 0.2, 2026-08-20)
URI: https://councilof.ai/a2a/extensions/signed-receipts/v1
Status: draft — for the A2A two-tier extension path (experimental → official)
Author: CSOAI Ltd (Council of AI) — independent measurement body, UK 16939677
License: Apache-2.0
0. Changes in draft 0.2 (2026-08-20)
- Full RFC 8785 (JCS) canonicalisation in
interceptor.py: UTF-16 key sort, ES6 number serialisation (1e+30,1e-27, no-0,2^53safe-integer domain), short-form escapes (\b \t \n \f \r),\uXXXXfor other control chars, and surrogate pairs for astral chars (ECMAScriptJSON.stringifysemantics, matching the IETF JCS interop suite). Verified byte-identical to the reference implementation on 300+ fuzz cases. - Exact public-key matching in DID verification (v0.1 did a substring match,
which a crafted DID doc could false-positive); supports
publicKeyHex,publicKeyMultibase(base58btc/base16/base64url), andpublicKeyJwk. - Revocation support per §5 append-only rotation:
"revoked": trueon a verification method rejects otherwise-valid signatures. - Reference implementation + regression suite:
python3 test_interceptor.py(12 checks: roundtrip, tamper x2, identity, substring-guard, wrong-key, revoked, multibase x2, RFC 8785 vector, domain guard, determinism).
1. Problem
A2A v1.0 §8.4 standardises the envelope for AgentCard signing (JWS/RFC 7515 over RFC 8785 canonical JSON) but deliberately leaves the trust root unspecified, and message-level attestation of what an agent actually did is wholly unstandardised. Two agents can interoperate, but neither can hand a third party durable evidence of the interaction's outcome.
2. What this extension adds
- A key-trust convention for §8.4: the JWS
kidis a DID URL underdid:web:(e.g.did:web:example.org#keys-1). Verifiers resolve the DID document athttps://<host>/.well-known/did.jsonand match the verification method. No new registry, no new PKI — HTTPS + a JSON file the host already controls. - A signed receipt object an agent MAY attach to any Task completion
(
Task.metadata["signed-receipts/v1"]) or return from a dedicated skill:
{
"schema": "a2a.signed-receipt/0.1",
"issuer": "did:web:example.org",
"subject_card": "https://example.org/.well-known/agent-card.json",
"task_id": "…",
"claims": [{ "type": "measurement", "detail": "…", "evidence_sha256": "…" }],
"issued_at": "2026-08-19T09:00:00Z",
"content_id": "sha256 of canonical JSON minus signature",
"signature": { "alg": "Ed25519", "kid": "did:web:councilof.ai#…", "sig": "hex" }
}
Canonicalisation: RFC 8785 (same as §8.4). Verification is offline: recompute
content_id, resolve kid → DID doc → public key, check Ed25519.
- Register (normative): a receipt is evidence of what was claimed and when by the issuer — it is not a certification, endorsement, or conformity mark, and MUST NOT be presented as one.
3. AgentCard declaration
{ "capabilities": { "extensions": [ {
"uri": "https://councilof.ai/a2a/extensions/signed-receipts/v1",
"required": false,
"params": { "issuer": "did:web:councilof.ai" }
} ] } }
4. Reference implementation
interceptor.py — an ADK-style client/server interceptor (~180 lines) that attaches a
signed receipt to task completion and verifies inbound ones. Framework-agnostic core;
only cryptography required. Canonicalisation is a self-contained RFC 8785 (JCS)
implementation — no third-party dependency, deterministic across interpreters.
5. Security considerations
- did:web inherits HTTPS/DNS trust — rotation via DID doc updates; old kids stay
resolvable with
"revoked": truemarkers rather than deletion (append-only). - Receipts embed the public key AND the kid: offline integrity always verifiable; identity verifiable whenever the DID doc is reachable (cache it).
- Never sign secrets or raw user content — claims carry hashes, not payloads.
6. Relationship to prior art
Sigstore signs artifacts, not evals; SPIFFE binds workloads, not measurements; §8.4 signs cards, not outcomes. This extension completes the third leg: signed evidence of agent behaviour, portable across all three.
Questions and corrections
To report an error in the specification or the reference code, email nicholas@csoai.org. Dated corrections go to our corrections ledger.