OpenReceipt · v3 · July 2026 · normative

The specification.

RFC-style. Normative sections in §2–§8, informative examples and a reference implementation in the appendix. v3 is the current envelope: client-signed before egress, optionally gateway-countersigned. Legacy v1/v2 receipts continue to verify.

§1 Introduction

OpenReceipt is a vendor-neutral specification for proving — cryptographically — that personally identifiable information (PII) was tokenised before a request left the device that produced it. From v3 the receipt is signed by the client — the desktop app, browser extension, or in-browser engine that actually performed the tokenisation — with a locally generated ECDSA P-256 key, before any byte egresses. A gateway in the request path verifies the client signature and adds an additive countersignature witnessing the relay facts it alone can see (§6). Verifiers check receipts offline: v3 envelopes are self-contained (embedded attester public key, §8.2) and never require contacting the producer at runtime.

Version notice (July 2026): Three envelope versions verify. v3 (current — string v: "v3", alg: "ecdsa-p256-sha256", client-signed claims envelope, RFC 8785 JCS) is the target for all new integrations. v2 (legacy gateway-signed flat envelope, integer v: 2, alg: "ES256", JCS) and v1 (legacy, alg: "ES512", signed_fields + PHP ksort canonicalisation) are deprecated for new producers but MUST continue to verify indefinitely (audit-retention requirement). Verifiers MUST dispatch on the envelope's v and alg claims. The transition is gap-free: a conformant v3 verifier accepts all three versions. See §4.0 for the legacy shapes and §8 for canonicalisation rules.

The normative wire format below is OpenReceipt v3. References to RFC 8785 (JCS), RFC 6979 (deterministic ECDSA), RFC 7515 (JWS), RFC 7517 (JWK), and RFC 7518 (JWA) are normative.

§2 Terminology

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119 and RFC 8174.

  • Producer / attester — the client system that performed the tokenisation and signs the OpenReceipt envelope before egress.
  • Gateway — an intermediary that relays the tokenised request upstream; it verifies the client signature and MAY countersign (§6).
  • Verifier — system that validates signatures, schema, chain discipline, and (for v3) the attestation claims.
  • Chain — append-only sequence of receipts under a common chain_id.
  • Detector — algorithm that classifies a span of input as PII.
  • Tier — the trust level a receipt earned from its actual request path: gateway-countersigned, beacon-anchored, or local-only (§6).
  • Anchoring — recording a receipt's canonical hash in the public transparency log, directly (countersignature) or via a content-free beacon.

§3 Envelope

The envelope is the outer JSON object containing the receipt's metadata, claims, and signature. It MUST be valid JSON per RFC 8259. The v3 top-level keys are: v, alg, kid, iss, sub, iat, nbf (optional), exp (optional), jti, chain, claims, sig. All attestation content lives inside claims; a v3 receipt is marked by v: "v3" together with the presence of claims["v3-attestation"] (§4.2).

§4 Signed payload

Normative. Producers MUST respect every clause in this section.

The signed payload is the JSON-canonicalised serialisation of the envelope. Producers MUST emit exactly the fields enumerated in §4.1, canonicalised per §8 (RFC 8785 JCS). The sig field is computed over the canonical bytes of the envelope without the sig claim, then attached at the top level for embedded transport.

MUST: For v3 the v field MUST be the string "v3" and alg MUST be "ecdsa-p256-sha256". A verifier MUST dispatch on v/alg before signature verification: envelopes with integer v: 2 / alg: "ES256" or alg: "ES512" are verified under the legacy rules of §4.0.

§4.0 Legacy v1/v2 envelopes (deprecated, verify forever)

The legacy v2 envelope is a gateway-signed flat object (integer v: 2, alg: "ES256", JCS canonicalisation, JOSE raw r||s signature) carrying iss/kid/jti/iat, provider/model, upstream request/response hashes, token counts, and a chain object. The legacy v1 envelope uses alg: "ES512", an explicit signed_fields list, and a non-JOSE PHP ksort + JSON_UNESCAPED_SLASHES canonicalisation (§8.1). New producers MUST NOT emit v1 and SHOULD NOT emit v2 for new integrations. Verifiers MUST continue to accept both for archived audit retention. Legacy deprecation was signalled via the response header Warning: 299 - "Deprecated; use X-CloakAPI-Receipt-v2 header" alongside X-CloakAPI-Receipt.

§4.1 — v3 receipt object (JCS / ecdsa-p256-sha256, client-signed)
{
  "v":    "v3",
  "alg":  "ecdsa-p256-sha256",
  "kid":  "device-key-01JZQ…",
  "iss":  "https://api.cloakapi.io",
  "sub":  "req_01JZQ4M7Q8E2YK7X9V3B6N1T0F",
  "iat":  1778089598,
  "jti":  "<UUID v7>",
  "chain": {
    "chain_id":  "<UUID v7, per device/session>",
    "seq":       0,
    "prev_hash": null
  },
  "claims": {
    "v3-attestation": {
      "tier":                   "beacon-anchored",
      "no_raw_pii_egress":      true,
      "self_check":             { "fired": true, "extra_tokenised": 0 },
      "tokenised_body_hash":    "<sha256 hex of the exact bytes that egressed>",
      "detector_categories":    ["EMAIL", "CREDIT_CARD"],
      "engine_ruleset_version": "v1.3.0",
      "engine_binary_hash":     "<sha256 hex of the engine that ran>",
      "genuine_engine":         true,
      "privacy_tier":           "balanced",
      "token_binding_hmac":     "<hmac-sha256 hex, token↔PII binding commitment>",
      "attester_public_key":    "04<X><Y>  (SEC1 uncompressed P-256, 130 hex chars)",
      "client_destination":     "chatgpt.com",
      "anchoring": {
        "inclusion_state":       "anchored",
        "transparency_log_leaf": "<hex leaf hash>"
      }
    }
  },
  "sig": "<raw r||s, base64url, 86 chars, no padding>"
}

Envelope field semantics:

  • v — envelope version. String "v3" for the current wire format.
  • alg — fixed string "ecdsa-p256-sha256" for v3. ML-DSA-65 is reserved for a post-quantum successor; dual-signing is permitted during migration windows.
  • kid — identifier of the signing key. For client-signed receipts this names the device/extension key; resolution is via the embedded attester_public_key (§8.2) or the issuer JWKS.
  • iss — issuer base URL. MUST NOT embed PII.
  • sub — opaque request or session identifier. MUST NOT embed PII.
  • iat — issuance time (Unix seconds, UTC). nbf/exp are optional; chain-linking supersedes expiry for historical audit.
  • jti — unique receipt identifier (UUID v7, timestamp-prefixed).
  • chain — chain-linking object (§5).
  • claims — structured claims object: v3-attestation (§4.2), optional routing_decision / countersignature (§6), registered v1.1-* sub-tags and vendor x-* extensions (§4.3).
  • sig — ECDSA P-256 signature: raw r || s (64 bytes), base64url without padding, computed over the JCS canonical bytes of the envelope with sig removed. DER encoding is rejected. Deterministic nonces per RFC 6979 are RECOMMENDED for byte-stable fixtures.
MUST NOT: Producers MUST NOT include raw PII in any receipt field — no plaintext prompts, no completions, no detected values. Only hashes, counts, category identifiers, and HMAC commitments. detector_categories carries category names only; token_binding_hmac commits to the token↔PII bindings without revealing them. A verifier MUST NOT be able to recover original PII from any field (§7).

§4.2 The v3-attestation claim

The claims["v3-attestation"] object is the client-provable attestation set. Required fields: tier, no_raw_pii_egress, tokenised_body_hash, detector_categories, engine_ruleset_version, engine_binary_hash, genuine_engine, privacy_tier, token_binding_hmac, anchoring. Optional fields: self_check, attester_public_key, client_destination, compliance_pack.

  • tier — the trust tier the receipt actually earned: gateway-countersigned, beacon-anchored, or local-only (§6). Producers MUST NOT label a tier the request path did not earn.
  • no_raw_pii_egress — egress-guard verdict: no raw PII left the device.
  • self_check — second-pass detector result: { fired, extra_tokenised }.
  • tokenised_body_hash — SHA-256 (hex) of the exact tokenised bytes that egressed.
  • detector_categories — detector categories that fired locally (e.g. "EMAIL") — never the values.
  • engine_ruleset_version / engine_binary_hash — the detector ruleset version and the hash of the engine binary/WASM that ran, for reproducibility.
  • genuine_engine — true iff engine_binary_hash matched the published signed-engine allowlist.
  • privacy_tier — the tokenisation policy tier applied.
  • token_binding_hmac — HMAC commitment binding tokens to their PII (proves reversibility-only-by-key without revealing PII).
  • attester_public_key — the signer's public key, SEC1 uncompressed P-256 point hex (04||X||Y, 130 chars). Makes the receipt self-contained for fully offline verification (§8.2).
  • client_destination — the egress target the client itself contacted (client-asserted; distinct from gateway-witnessed routing, §6).
  • compliance_pack{ id, definition_hash, origin }; covers built-in and customer-imported packs.
  • anchoring{ inclusion_state: countersigned|anchored|pending, beacon_nonce?, receipt_hash?, transparency_log_leaf?, inclusion_proof? }.

The full field-level schema and registry entry (v3-attestation) live in the extension registry.

§4.3 Extension fields

Fields outside the core envelope MUST use one of two forms: vendor-specific claims prefixed x-<vendor>-<key> (no registration required), or registered versioned sub-tags nested under claims (e.g. v1.1-file-ext, v1.1-streaming, v1.1-router, v1.1-local-ai, v1.1-web-search, v3-attestation). The authoritative index is the extension registry. Extensions MUST NOT be required for core verification.

MAY: Verifiers MAY surface x-* fields to users, but MUST ignore unknown extension fields when computing the verification verdict.

§5 Chain discipline

Chains are scoped by chain_id. Each new receipt sets seq to the previous seq + 1 and sets prev_hash to the SHA-256 (hex) of the prior receipt's canonical bytes including its sig field. For the first receipt in a chain, prev_hash MUST be null. When a request crosses a trust boundary (client → gateway), a boundary MAY start a child chain carrying chain_parent referencing the parent receipt's jti.

MUST: A verifier MUST reject any receipt whose seq is not exactly 1 greater than the prior receipt in the same chain, any prev_hash mismatch, and any fork (two receipts with the same chain_id and seq).

§6 Trust tiers & gateway countersignature

A v3 receipt earns exactly one of three tiers, recorded in claims["v3-attestation"].tier:

  • gateway-countersigned — the request went through a gateway, which verified the client signature, countersigned the relay facts it witnessed (provider, model, upstream byte hashes, token counts), and anchored the receipt hash. Strongest tier.
  • beacon-anchored — off-gateway (e.g. a browser extension egressing directly to a third-party site): the receipt is client-signed locally, then a content-free beacon records the receipt hash plus token counts in the public transparency log. The log witnesses existence, never content.
  • local-only — fully offline: client-signed into a local per-device chain; anchoring is pending and drains on reconnect.

The gateway countersignature is additive: the client envelope is returned untouched, and the gateway attaches a separately signed witness object. Its receipt_hash is the SHA-256 of the JCS canonical bytes of the full signed client envelope (including sig) — the same value that is anchored in the transparency log.

§6.1 — gateway countersignature object (v3-countersign)
{
  "v":            "v3-countersign",
  "kid":          "cloakapi-gateway-receipt-2026-q3-es256",
  "receipt_hash": "<sha256 hex of the canonical client envelope, incl sig>",
  "timestamp":    "2026-07-09T12:00:00.000Z",
  "hash_upstream_response": "<sha256 hex>",          // optional
  "request_id":   "req_…", "provider": "…", "model": "…",
  "input_tokens": 412,     "output_tokens": 188,       // optional witness claims
  "signature":     "<gateway ECDSA P-256 signature>",
  "signed_fields": ["v","kid","receipt_hash","timestamp", "…"]
}
MUST: A gateway MUST verify the client envelope's signature before countersigning. Gateway-witnessed relay facts (provider, model, upstream hashes) live in the countersignature and claims.routing_decision — a client MUST NOT self-attest them (§7).

§7 Integrity principle — client-provable claims only

The v3 attestation records only what the signing client can cryptographically or architecturally prove. A property that depends on a third party's unverifiable internal behaviour is never attested: notably, a "no-training" claim is excluded — a client can prove it set a provider's no-train flag, not that the provider honoured it. Likewise, a blind client cannot witness which provider a gateway relayed to; those facts appear only under the gateway's own countersignature (§6).

Detector evidence is privacy-preserving by construction: detector_categories names categories only, and HMAC-SHA-256 fingerprints (keyed per tenant/session) let auditors confirm that specific tokens were substituted while keeping both the original PII and the substituted values secret across producers.

§8 Canonicalisation

OpenReceipt v3 (and legacy v2) use RFC 8785 JSON Canonicalization Scheme (JCS) verbatim:

  • Object keys are sorted lexicographically by their UTF-16 code-unit sequence (RFC 8785 §3.2.3).
  • Strings are emitted as raw UTF-8 with the minimal JSON escapes (\", \\, \b, \t, \n, \f, \r, \uXXXX for U+0000..U+001F).
  • Numbers are serialised per ECMAScript Number-to-String (ES2017 §7.1.12.1).
  • No insignificant whitespace; literals are true, false, null.

Test vectors (verifier MUST pass all):

§8.1 — JCS test vectors
{"a":1,"Z":2}                 → {"Z":2,"a":1}      // UTF-16 ordering
{"outer":{"z":1,"a":2}}       → {"outer":{"a":2,"z":1}}
{"arr":[3,1,2]}               → {"arr":[3,1,2]}    // arrays preserve order
{"s":"tab\there\nbreak"}      → {"s":"tab\there\nbreak"}
{"s":"Norge — æøå"}           → {"s":"Norge — æøå"}  // raw UTF-8, not \uXXXX
{"s":"a\"b\\c"}               → {"s":"a\"b\\c"}
{"x":true}                    → {"x":true}
{"a":false,"b":null}          → {"a":false,"b":null}
{}                            → {}                  // empty object canonicalises to itself

The reference verifier at /verifier/ ships a JCS implementation in JavaScript; reference implementations in other languages are published under /source/.

§8.1 v1 canonicalisation (deprecated)

v1 envelopes serialise the subset of fields named in signed_fields via PHP json_encode(ksort($fields), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) with SHA-512 digest. v1 verifiers MUST continue to apply this rule for legacy receipts; v2 and v3 receipts MUST use JCS only.

§8.2 Key discovery & self-contained verification

v3 (self-contained): a v3 receipt MAY embed the signer's public key as claims["v3-attestation"].attester_public_key (SEC1 uncompressed P-256 point, 04||X||Y, 130 hex chars — exactly the browser's crypto.subtle.exportKey('raw', …) output). Verification then requires no network round-trip: canonicalise the envelope minus sig (§8), SHA-256, verify ECDSA P-256 against the embedded key. The key is inside the signed envelope, so it is tamper-evident; its authenticity is separately cross-checkable against the transparency log or an out-of-band device pairing.

Gateway keys (JWKS): gateways publish current and retired countersigning keys at {iss}/.well-known/openreceipt-pubkeys.jwks. The CloakAPI canonical issuer publishes three active entries during the coexistence window:

§8.2 — JWKS keys for issuer https://api.cloakapi.io
{
  "keys": [
    { "kid": "gw-eu-west-...",                       "alg": "ES256", "kty": "EC", "crv": "P-256", "use": "sig" },  // chain-linked sidecar
    { "kid": "cloakapi-gateway-receipt-2026-05",     "alg": "ES512", "kty": "EC", "crv": "P-256", "use": "sig" },  // v1 deprecated header
    { "kid": "cloakapi-gateway-receipt-2026-q3-es256","alg": "ES256","kty": "EC", "crv": "P-256", "use": "sig" }   // v2 canonical header
  ]
}

Appendix A — JSON Schema

The normative JSON Schema is published under CC0 at signedreceipts.org/source/spec.

Appendix B — Test vectors

Known-good and known-bad test vectors covering schema validity, signature verification, chain discipline, and malicious mutations. Required for conformance — see /conformance/.

Appendix C — Reference implementation

Apache-2.0 reference implementations in Rust and TypeScript. The Rust crate ships a FIPS-validated path via aws-lc-rs (CMVP #4759).

← §3 Envelope §5 Chain discipline → last edited 2026-07-09 · OpenReceipt v3