A Trust Receipt is a signed, offline-verifiable record that a specific action was authorized — by whom, under what policy, with what outcome. Anyone with the signer’s public key can verify one with no account, no API, no network. This page specifies it precisely enough to implement a verifier in any language.
anchor is optional; @version, payload, and signature are required.
{
"@version": "EP-RECEIPT-v1",
"payload": { ... the signed claim ... },
"signature": { "algorithm": "ed25519", "value": "<base64url>" },
"anchor": { // OPTIONAL
"leaf_hash": "<hex sha-256>",
"merkle_proof": [ { "hash": "<hex>", "position": "left|right" } ],
"merkle_root": "<hex>"
}
}The payload is application-defined; the signature covers it whole. EP’s convention:
{
"receipt_id": "ep_...",
"issued_at": "2026-06-04T00:00:00Z",
"claim": {
"action": "payment.release",
"outcome": "allow | allow_with_signoff | deny",
"approver": "operator:<named human>",
"context": { "amount": 50000, "destination": "acct_9f12", "currency": "USD" }
}
}The exact bytes that get signed. Recursive, depth-first key sort at every level — byte-identical on signer and verifier for any nesting depth. A shallow sort is not sufficient; nested keys must be ordered too.
object -> "{" + keys.sort().map(k => json(k) ":" canon(v[k])).join(",") + "}"
array -> "[" + elements.map(canon).join(",") + "]"
scalar -> JSON encoding (UTF-8; non-ASCII NOT escaped)Algorithm Ed25519, over canonicalize(payload) as UTF-8 bytes. The public key is the base64url of its SPKI DER encoding; signature.value is the base64url of the 64-byte signature.
leaf_hash is a hex SHA-256. Each proof step folds the running hash with a sibling: sorted([a, b]) then SHA-256(lo ‖ hi) (hex); position: "left" means the sibling is on the left. The reconstructed value must equal merkle_root. Proof length is bounded (≤ 20).
@version ∈ {EP-RECEIPT-v1}, else invalid.signature.value over canonicalize(payload) with the signer’s key.anchor is present, reconstruct the root from leaf_hash + merkle_proof; it must equal merkle_root.Interop is tested: a receipt signed on the JS side verifies under the Python implementation, and vice versa.