Agent action receipt verification

A receipt says what an agent was recorded as doing at the boundary that governed it. Verifying one is not a signature check. Six things can go wrong, each measured on a running implementation, and each turning the record into something worse than no record.

What the check has to do first

HASH-INPUT = JCS(receipt without chain.hash and without sig.value)
MESSAGE    = b"NOA-Receipt-v0.1-sig:" ++ sha256(HASH-INPUT)
signature  = Ed25519(MESSAGE)
chain.hash = "sha256:" ++ hex(sha256(HASH-INPUT))

Canonical form is RFC 8785: keys sorted by UTF-16 code units, integers only, no Unicode normalisation. Two implementations that sort differently produce different bytes for the same object — and then one of them is wrong about who signed what.

Six ways a verifier can lie to you

#What goes wrongWhat a buyer sees
1It re-serialises the parsed object before hashingA receipt with an extra space verifies. The values checked are not the bytes the signer signed.
2An unauthenticated checkpoint is treated as evidenceAn honest chain is reported as tampered because of a file anyone can hand over.
3A chain that grew past an old checkpoint is called a conflictEvery normal chain that keeps running eventually "fails".
4Only the first failure is reportedAn operator fixes one thing and the next check finds the other, forever.
5It stays silent when no identity manifest was supplied"Valid" is read as "this agent did it", when all that was established is "some trusted key signed".
6It stays silent when no checkpoint was suppliedAn unanswered question looks like a clean one.

The first three are the same defect in different clothes: the verifier accuses an object that did not fail, or names a cause it did not observe. For a tool whose product is credible accusation, that is not a bug in a feature — it is a failure of the thing being sold.

What to ask any verifier before you rely on it

Check one yourself

1. open https://attest.rta.lat/verify-offline and save the page
2. paste the receipt and the issuer public key
3. turn off the network — the check still runs

The corpus is public and CC0: 12 vectors, including five receipts that must be rejected and one positive control. A Python and a browser implementation agree on all twelve: receipt-vectors.json.

About this implementation

attest implements the IETF SCITT profile for agent action receipts (draft-noa-scitt-ai-agent-receipt), verifies offline, and publishes its vectors. It does not claim that an agent was correct, safe or wise — only that this record was issued by that key and has not changed since. Free verifier, paid audit. Documentation · Pricing.