Open Specification · ADR · Version 1.0

AI Decision Receipt — Technical Specification

A vendor-neutral, cryptographically verifiable record of a single decision made by an AI agent or automated system. Anyone holding a receipt — auditor, regulator, insurer, counterparty — can verify it independently, with no access to the issuing system, the model, the prompt, or the output.

Spec version: 1.0· Signature: Ed25519· Hash: SHA-256· Encoding: Canonical JSON (RFC 8785)· License: CC BY 4.0
Open Verification Portal → Discovery document

1. Overview

An AI Decision Receipt (ADR) is a JSON document that records a single decision made by an AI agent and binds it to a verifiable identity through a digital signature. Each receipt is also linked to the previous one through a cryptographic hash chain, producing a tamper-evident ledger of every action the agent has taken.

A receipt deliberately does not contain the prompt, the model output, training data, or any other confidential content — only fingerprints (SHA-256 hashes) of the inputs and outputs, plus the metadata an auditor or regulator needs: who acted, when, under what authority, with what risk classification, and whether a human reviewed.

The format is platform-neutral. Any party can issue, verify or relay receipts; a single private key is the only credential required to sign, and the corresponding public key is the only credential required to verify.

2. Receipt structure

A receipt is a flat JSON object. The body fields describe the decision; two attached members — receipt_hash and signature — bind the body cryptographically. The body is everything except receipt_hash and signature; the body alone is the input to the hash.

{
  "version":       "1.0",
  "id":            "STR-7F3A21C9D4",
  "type":          "decision_receipt",
  "sequence":      42,
  "agent": {
    "id":          "agent_32b8ef2c52cef5f8972999e2",
    "name":        "FinanceBot"
  },
  "model": {
    "provider":    "openai",
    "name":        "gpt-4o",
    "version":     "2026.4"
  },
  "decision": {
    "type":        "loan_rejection",
    "input_hash":  "sha256:6718c8a836...abbcf768",
    "output_hash": "sha256:8a3466a5e7...8882c42a",
    "risk_level":  "high",
    "human_review": true,
    "permissions": ["credit.decide"],
    "policies":    ["eu-ai-act-high-risk", "internal-credit-v3"]
  },
  "metadata":      { "source": "website-hero" },
  "timestamp":     "2026-06-17T10:00:00.000Z",
  "previous_hash": "sha256:c1f2...e07b",

  "receipt_hash":  "sha256:9d4c0a...f5b8",
  "signature": {
    "algorithm":   "ed25519",
    "public_key":  "MCowBQYDK2VwAyEA0Cv4wWal2IfaOEfBVRFLz8f/Fy55AVuFTFay9E/7eKE=",
    "value":       "NolYh3rDECrCcdYs6glRX1XqgEt9DfyGpaqd6R8Tzx3mIZOVlUlA6KEMCcZk5emxqLMu896EB4QP9Tk9ZJ3iCw=="
  }
}

3. Field reference

3.1 Mandatory body fields

FieldTypeDescription
versionstringSpec version, e.g. 1.0.
idstringGlobally unique receipt identifier. Reference implementation: STR- + 10 uppercase hex chars.
typestringAlways decision_receipt for ADR v1.0.
sequenceintegerMonotonically increasing position in the issuer's ledger. The genesis receipt is sequence 0.
timestampstringISO 8601 UTC timestamp with millisecond precision.
agent.idstringStable identifier of the issuing agent.
decision.typestringShort categorical label (e.g. loan_rejection, fund_transfer).
decision.risk_levelenumOne of low · medium · high · critical.
previous_hashstringThe full receipt_hash string of the immediately preceding receipt in this issuer's ledger (e.g. sha256:…). For the genesis receipt this is 64 hex zeros, without prefix.

3.2 Attached cryptographic fields

FieldTypeDescription
receipt_hashstringsha256: + lowercase hex SHA-256 of canonical-JSON(body). The body is the receipt minus receipt_hash and signature.
signature.algorithmstringAlways ed25519 for ADR v1.0.
signature.public_keystringBase64 of the SPKI-DER encoding of the Ed25519 public key (44 bytes raw → 60 base64 characters). Same key MUST be published in the issuer's discovery document.
signature.valuestringBase64 of the Ed25519 signature over the UTF-8 bytes of the receipt_hash string (88 chars).

3.3 Optional body fields

FieldTypeDescription
agent.namestringHuman-readable agent name.
model.providerstringModel vendor (e.g. openai, anthropic).
model.namestringModel family / name.
model.versionstringModel version string.
decision.human_reviewbooleanTrue if a human approved or reviewed the decision.
decision.permissionsstring[]Permission scopes the agent was operating under.
decision.policiesstring[]Policy identifiers the decision was bound by.
decision.input_hashstringsha256: hash of the input. Privacy-preserving: the input itself is never stored.
decision.output_hashstringsha256: hash of the output.
metadataobjectFree-form key-value map for issuer-specific context (no PII; opaque to verification).

4. Canonical encoding

Receipts MUST be serialised using RFC 8785 JSON Canonicalization Scheme (JCS) before hashing or signing. JCS guarantees a single byte-for-byte encoding for any JSON value, so any party recomputing the hash gets the same digest as the issuer.

Key rules from RFC 8785 that implementers must observe:

  • Object keys are sorted lexicographically by UTF-16 code units.
  • No whitespace between tokens.
  • Strings are escaped per RFC 8259 with the minimal escape set; non-ASCII characters are emitted literally as UTF-8.
  • Numbers use the shortest round-trip representation defined by RFC 8785 §3.2.2.3.

5. Hashing & receipt ID

Let body be the receipt object with the two attached members receipt_hash and signature removed. Let B be the JCS-encoded UTF-8 bytes of body. Then:

receipt_hash = "sha256:" + lowercase_hex(SHA-256(B))

The receipt identifier id is independent of receipt_hash and is assigned by the issuer. It SHOULD be stable, unique within the issuer, and human-friendly (the reference implementation uses STR- + 10 uppercase hex chars derived from a random 5-byte source).

6. Ed25519 signing

Receipts are signed with Ed25519 (RFC 8032). The signer's input is the UTF-8 bytes of the receipt_hash string — including the sha256: prefix and the lowercase hex digest. This keeps the signed value identical to what an offline verifier can paste and re-check.

message   = UTF-8 bytes of receipt_hash  // e.g. "sha256:9d4c0a...f5b8"
signature = Ed25519-sign(sk, message)
signature.value = base64(signature)      // 88 chars including padding

The Ed25519 public key is encoded in SubjectPublicKeyInfo (SPKI) DER form, base64-wrapped (44 raw bytes → 60 base64 characters) — the form produced by Node.js crypto.createPublicKey(…).export({ type: 'spki', format: 'der' }) and accepted by OpenSSL. Verifiers reconstruct the message bytes from the receipt and call Ed25519-verify(pk, message, base64-decode(signature.value)). The expected public key is published in the issuer's discovery document (see §9).

7. Hash chain

Each receipt links to the previous one through previous_hash, which MUST equal the receipt_hash of the immediately preceding receipt in the issuing agent's ledger. The first receipt (sequence 0) uses the fixed genesis value:

previous_hash = "0000000000000000000000000000000000000000000000000000000000000000"

Note: for the genesis receipt, previous_hash is 64 hex zeros without the sha256: prefix. For every subsequent receipt, previous_hash carries the prefix because it copies the predecessor's receipt_hash verbatim.

This produces an append-only ledger: removing or reordering any receipt breaks the chain, and rewriting any receipt invalidates every subsequent previous_hash. Anyone with the full chain can detect tampering by walking the receipts in sequence order and recomputing each link.

Multi-agent deployments MAY keep a single ledger across agents (the reference implementation does); per-agent verification is then a projection of that ledger.

8. Verification flow

To verify a single receipt R:

  1. Parse R as JSON. Require the body fields plus the attached receipt_hash and signature.
  2. Compute body = R with receipt_hash and signature removed.
  3. Compute B = JCS(body) and check that "sha256:" + hex(SHA-256(B)) == R.receipt_hash — this is the Integrity check.
  4. Read the public key from R.signature.public_key (and confirm it matches the issuer's discovery document — see §9).
  5. Verify Ed25519 signature over the UTF-8 bytes of R.receipt_hash — this is the Signed check.
  6. (Optional) Fetch the previous receipt by R.previous_hash from the issuer's ledger and re-verify it; recurse to the genesis receipt — this is the Chain check.

A receipt is Valid iff Integrity and Signed both pass. Chain verification is an additional guarantee about the receipt's position in history and is recommended for audits and incident investigations.

A reference verifier is available at /verify (web) and via POST /api/v1/verify (HTTP — no auth required; see API docs).

9. Key discovery

Every issuer MUST publish a discovery document at:

https://<issuer>/.well-known/signatrust.json

The document contains the active public verification key (public_key, base64-encoded SPKI-DER — same encoding as signature.public_key on receipts), the supported spec_version, the signature and hash algorithms, and the URLs of the public verification endpoints. A receipt's embedded signature.public_key SHOULD be cross-checked against this discovery document before relying on it.

The reference document is available at /.well-known/signatrust.json.

10. Error codes

Verifiers SHOULD return structured errors so callers can react programmatically.

CodeMeaning
invalid_jsonThe input is not parseable JSON.
missing_fieldA mandatory field is absent from the body.
hash_mismatchRecomputed receipt_hash does not match the receipt.
signature_invalidEd25519 verification failed against the issuer's public key.
unknown_issuerNo discovery document or public key resolvable for the issuer.
chain_brokenA prev_hash does not match the predecessor receipt.
not_foundReceipt ID is unknown to the queried node (lookup-by-ID only).

11. Privacy model

ADR is designed so that proving a decision happened and is intact never requires disclosing the decision's content. This is a structural property of the format, not a policy promise.

11.1 Content stays out

A receipt carries only the SHA-256 fingerprints of the input and output (decision.input_hash, decision.output_hash). The prompt, the model output, documents, and any other confidential payload are hashed by the issuer and never appear in the receipt. An issuer MAY run the signing node entirely inside its own perimeter (on-premise / air-gapped); only signatures and hashes are recorded.

11.2 Data minimization

No field requires personal data. metadata is free-form and issuer-controlled and SHOULD NOT contain personal data or secrets, since it is part of the signed, shareable body. Identifiers such as applicant or customer IDs SHOULD be referenced indirectly (e.g. hashed or tokenized) when a receipt may be shared outside the issuer.

11.3 Verification reveals nothing

A third party verifies a receipt using only the receipt JSON and the issuer's public key. Verification establishes authenticity, integrity and chain position; it discloses no content. This lets an auditor, regulator, insurer or counterparty confirm a decision exists and was not altered without ever seeing what the decision was about.

11.4 Selective disclosure

When an auditor legitimately needs the content, the issuer can disclose the raw input/output out-of-band. The auditor recomputes sha256 and matches it against input_hash / output_hash in the (independently signature-verified) receipt — cryptographically binding the disclosed content to the sealed decision, with no trust in the issuer's word.

11.5 Hash pre-image caveat

SHA-256 is a fingerprint, not encryption. If an input has low entropy (e.g. a short enumerable value), its hash can be brute-forced. Where the confidentiality of the input itself matters, issuers SHOULD hash a high-entropy representation (e.g. prefix a per-receipt random salt and record the salted construction in metadata) rather than the bare value.

12. Threat model

ADR makes a deliberately narrow security claim: a valid receipt proves that a specific decision was recorded by a specific key, at a stated position in an append-only chain, and has not been altered since. It does not claim the decision was correct, wise, or compliant beyond what the receipt records.

ThreatDefence
Alter a stored receipt's bodyThe Integrity check recomputes receipt_hash from canonical JSON; any change flips the hash and breaks the Ed25519 signature.
Forge a receipt without the keyEd25519 (RFC 8032) signature over the receipt_hash string. Without the private key a forgery is computationally infeasible; verifiers check against the public key published in the issuer's discovery document.
Reorder, delete or insert receiptsEach receipt embeds previous_hash. Removing, reordering or inserting a receipt breaks the Chain link at that point.
Backdate / rewrite historyRewriting any receipt invalidates every subsequent previous_hash. Timestamps are issuer-asserted; for independent time proof, anchor a periodic head hash to an external timestamp authority or public ledger (out of scope for v1.0).
Signing-key compromiseKeys SHOULD be stored encrypted at rest (AES-256-GCM) or in a KMS/HSM and rotated. Historic receipts embed the key they were signed with and stay verifiable after rotation; updating the discovery document revokes trust in a compromised key for new receipts.
Swap the public key (MITM on discovery)The discovery document MUST be served over TLS; verifiers SHOULD pin or cross-check the key. A receipt's embedded signature.public_key is only trusted once it matches the discovery key.
Replay an old receiptEach receipt has a unique id and monotonic sequence; a replay is byte-identical and asserts nothing new.
Infer content from a hashSee §11.5 — salt low-entropy inputs.

Out of scope: correctness/authorization of the underlying decision, availability and rate-abuse of an issuing node (an implementation concern), and proof of wall-clock time without an external anchor.

13. Reference implementation

Signatrust operates an open reference implementation of ADR v1.0 — the same node that serves this page. It issues, chains, signs, verifies and publishes discovery, using only language built-ins (no native dependencies).

  • Primitives: Ed25519 (RFC 8032) signatures, SHA-256 (FIPS 180-4) hashing, RFC 8785 (JCS) canonicalization — all via the Node.js standard crypto module.
  • Issue: POST /api/v1/receipts (authenticated). Fetch: GET /api/v1/receipts/{id}. Verify (stored): GET /api/v1/receipts/{id}/verify. Verify (any JSON, no auth): POST /api/v1/verify. Whole chain: GET /api/v1/verify/ledger. Discovery: GET /.well-known/signatrust.json.
  • SDKs: JavaScript/TypeScript and Python (standard library only) — issue a receipt in ~3 lines. See /docs.
  • Live examples: real, verifiable receipts for concrete decisions are published at /use-cases.

Independent verification needs only the receipt and the issuer's public key (from discovery). The complete algorithm:

body = receipt without { receipt_hash, signature }
assert receipt.receipt_hash == "sha256:" + hex( SHA-256( JCS(body) ) )      // Integrity
assert Ed25519_verify(
         publicKey = receipt.signature.public_key,                          // cross-check vs discovery
         message   = utf8_bytes(receipt.receipt_hash),
         signature = base64_decode(receipt.signature.value) )               // Signed
// optional: fetch predecessor by previous_hash and recurse to genesis       // Chain

The receipt specification is published under CC BY 4.0; the reference implementation is Apache-2.0. Anyone may issue and verify ADR receipts without permission.

14. References

  • RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA) — defines Ed25519.
  • RFC 8785 — JSON Canonicalization Scheme (JCS).
  • RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format.
  • RFC 4648 — Base16, Base32 and Base64 Data Encodings.
  • FIPS 180-4 — Secure Hash Standard (SHA-256).
  • Signatrust Compliance & Governance Framework v1.0 — /framework.
  • Reference verifier (web) — /verify.
  • Reference API — /docs.

© 2026 Signatrust. This specification is published under CC BY 4.0 — free to share and adapt with attribution.