SignedReceipt Specification v1
Normative specification for the SignedReceipt signed privacy-attestation envelope. JSON Canonical Form (RFC 8785), ECDSA P-256, chain linking, public-key discovery.
1. Overview
An SignedReceipt is a tamper-evident JSON document that cryptographically proves three things: (a) what data was processed on the issuing device before egress; (b) what processing occurred (tokenization level, detector firings, hashes); and (c) that the document has not been altered. Receipts are chained so that the full history of a session can be verified without any central authority.
This specification is normative. Implementations that deviate from it are non-conformant regardless of what label they carry. The conformance test suite at signedreceipt.org/conformance is the executable form of this specification.
2. Envelope format
Receipts are UTF-8 JSON with LF line endings and no BOM. Post-canonicalization key order:
| Field | Type | Required | Description |
|---|---|---|---|
v | string | yes | Version tag. "v1" at launch. |
alg | string | yes | Signature algorithm. "ecdsa-p256-sha256" at v1. |
kid | string | yes | Key identifier. Resolved via /.well-known/signedreceipt-pubkey.pem. |
iss | string (URL) | yes | Issuer base URL. MUST NOT embed PII. |
sub | string | yes | Opaque subject identifier. MUST NOT embed PII. |
iat | integer | yes | Issued-at Unix epoch seconds. |
jti | string (ULID) | yes | Unique receipt identifier. |
chain | object | yes | Chain-linking metadata. See §4. |
claims | object | yes | Structured claims. See §3. |
sig | string (base64url) | yes | Signature over canonicalized receipt with sig removed. |
3. Canonicalization (RFC 8785 JCS)
Before signing and before verification, the receipt MUST be serialized to JSON Canonical
Form per RFC 8785. The sig field is removed before hashing. Any implementation
that produces non-canonical output is non-conformant. The conformance fixture corpus ships
byte-stable expected_canonical.bin files against which implementations are tested.
4. Signing algorithm
v1: ECDSA over P-256 with SHA-256 per FIPS 186-5. Deterministic nonces per
RFC 6979 to produce byte-stable signatures given the same canonical bytes and key. Signature
encoding: raw r || s (64 bytes), base64url, no padding. DER encoding is rejected.
v2 reservation: ML-DSA-65 (Dilithium) is reserved under
"mldsa-65-sha3-256". Dual-signing ("ecdsa-p256-sha256+mldsa-65-sha3-256")
is permitted during migration windows.
Keys MUST be generated on a FIPS-validated module for FIPS-certified deployments.
5. Chain linking
Each receipt's chain object carries:
| Field | Description |
|---|---|
prev_hash | SHA-256 hex of previous receipt canonical bytes including sig. null for the first receipt. |
chain_id | ULID unique to the session/device chain. Immutable. |
seq | Monotonic integer starting at 0. MUST increment by exactly 1 per receipt. |
A chain is valid iff every prev_hash matches its predecessor, seq
is gapless, and every signature verifies. Forked chains (two receipts with the same
seq and chain_id) MUST be rejected.
6. Public-key discovery
Issuers publish current and retired keys at
<iss>/.well-known/signedreceipt-pubkey.pem — PEM-encoded
SubjectPublicKeyInfo blocks, one per key, each preceded by an armoured comment carrying
kid, iat, and optionally retired. Transport: HTTPS only.
Verifiers MAY pin a local trust bundle via --trust-bundle <path>.