DEOS Receipt Format — v1 Specification
Status: v1.0 — published 2026-05-02
Spec URI: https://projectmaya.deoscomputing.io/receipts/format/v1
Maintainer: DEOS Computing (did:web:deos.computing)
Schema identifier: deos_receipt_v1
Canonicalization: RFC 8785 (JSON Canonicalization Scheme, JCS)
Hash: BLAKE3-256
Default signature: Ed25519
A receipt is a signed, tamper-evident assertion that a specific agent took a specific action on behalf of a specific principal under a specific capability, with these specific inputs and outputs. Receipts are the substrate's load-bearing surface — every other layer either produces or verifies them.
This document is normative for v1. Implementations MUST conform exactly to interoperate.
1. Goals
A v1 receipt must:
- Be canonically serializable so that any compliant implementation produces byte-identical output for the same input.
- Be content-addressable — the receipt's
idis the BLAKE3 hash of its canonical bytes. - Bind a chain of accountability: substrate → agent → principal → capability → action → output.
- Link to replay: contain enough information to fetch a full replay bundle and verify byte-for-byte equivalence.
- Be time-anchorable: be includable in a Merkle log whose root is anchored to a public timestamp service (M5).
- Be independently verifiable: a third party with only the spec and the receipt can verify everything except replay (which additionally requires fetching the bundle).
A v1 receipt explicitly does not carry:
- The full inputs/outputs (hashes only; payloads live in CAS or external storage).
- Selective-disclosure proofs over its own contents (deferred to v1.1).
- Settlement state for payment receipts (the receipt is the authorization; settlement evidence is a separate receipt linked by reference).
2. Schema
A receipt is a JSON object with the following top-level fields:
{
"v": 1,
"schema": "deos_receipt_v1",
"id": "<32-byte BLAKE3 hex of canonical receipt minus the id and signatures fields>",
"issued_at": <unix nanos, integer>,
"issuer": "<DID of the substrate node that issued the receipt>",
"subject": "<DID of the agent that performed the action>",
"principal": "<DID of the human/org on whose behalf the agent acted>",
"capability": {
"id": "<32-byte BLAKE3 hex of root capability>",
"chain": [ /* UCAN tokens, root-first, see §5 */ ],
"scope": { /* resolved scope after applying caveats; see §5.3 */ }
},
"action": {
"type": "<see §6>",
"code_hash": "<BLAKE3 hex of the executable code that ran>",
"inputs_hash": "<BLAKE3 hex of canonical-serialized inputs>",
"outputs_hash": "<BLAKE3 hex of canonical-serialized outputs>",
"metadata": { /* action-type-specific; see §6 */ }
},
"replay": {
"bundle_uri": "<absolute URI of the replay bundle, optional>",
"bundle_hash": "<BLAKE3 hex of the replay bundle, optional>",
"deterministic": true
},
"log": {
"tree_id": "<identifier of the Merkle log this receipt is appended to>",
"leaf_index": <integer, 0-based>,
"root": "<BLAKE3 hex of the log root at the time of inclusion, optional>"
},
"anchors": [
{ "service": "opentimestamps", "proof": "<base64>" }
],
"signatures": [
{
"kid": "<DID URL with key fragment, e.g. did:deos:abc#sig-1>",
"alg": "ed25519",
"sig": "<base64-url-nopad of 64-byte Ed25519 signature>"
}
]
}
2.1 Required vs optional fields
| Field | Required | Notes |
|---|---|---|
v |
yes | MUST equal 1 for this spec. |
schema |
yes | MUST equal "deos_receipt_v1". |
id |
yes | Computed (§4); not signed itself. |
issued_at |
yes | Unix nanoseconds; MUST be monotonic per issuer. |
issuer |
yes | DID. |
subject |
yes | DID of the acting agent. |
principal |
yes | DID of the entity ultimately accountable. May equal subject for autonomous agents acting on their own behalf. |
capability.id |
yes | Hash of the root capability. |
capability.chain |
yes | Non-empty UCAN chain, root-first. |
capability.scope |
yes | Resolved scope after caveat evaluation. |
action.type |
yes | See §6. |
action.code_hash |
yes | BLAKE3 of executable; for non-executable actions, MUST be the all-zero hash. |
action.inputs_hash |
yes | BLAKE3 of canonical inputs; if no inputs, all-zero hash. |
action.outputs_hash |
yes | BLAKE3 of canonical outputs; if no outputs, all-zero hash. |
action.metadata |
yes | Object; MAY be empty {}. |
replay.deterministic |
yes | Boolean. v1 status: producers MAY claim true but the substrate-managed replay-bundle pipeline is not in v1; treat as informational. |
replay.bundle_uri |
optional | Producer-managed in v1 (substrate does not host bundles). MUST be a valid URI if present. v2 will introduce substrate-managed CAS-backed bundle storage. |
replay.bundle_hash |
optional | BLAKE3 hex of the bundle pointed to by bundle_uri. MUST be present if bundle_uri is present. |
log.tree_id |
yes | |
log.leaf_index |
yes | |
log.root |
optional | Snapshot at time of inclusion. Verifier may also fetch via attestation API. |
anchors |
optional | Array, MAY be empty. |
signatures |
yes | Non-empty. At least one signature MUST be by the issuer. |
2.2 Strict typing
- All hashes are BLAKE3-256 encoded as lowercase hex (no
0xprefix), exactly 64 characters. - All signatures are encoded as base64-url-nopad (RFC 4648 §5, no
=padding). - All timestamps are integer Unix nanoseconds in UTC.
- All DIDs follow W3C DID Core syntax.
- Numeric fields MUST be JSON integers (no decimals, no exponents). JCS is enforced.
3. Action types (v1)
The action.type field is a registered string. v1 reserves these values:
type |
Meaning | Required metadata keys |
|---|---|---|
tool_call |
An MCP-style tool invocation | tool_name, server_did |
payment_intent |
An authorization to settle funds | from, to, amount, currency |
payment_settlement |
Evidence of completed settlement | intent_receipt_id, rail, external_ref |
identity_claim |
An attestation about an identity | claim_type, evidence_hash |
message_send |
An outbound message | to, transport, content_hash |
code_execution |
Generic deterministic code run | program_did, args_hash, exit_code |
policy_evaluation |
A policy decision (allow/deny) | policy_id, decision, reason |
Implementations MAY emit receipts with an unregistered action.type, but MUST prefix it with x- (e.g. x-vendor.thing). Verifiers SHOULD pass through unknown x- types without rejecting.
4. Canonicalization & receipt id
4.1 Canonical form
Receipts are canonicalized with RFC 8785 (JCS), with one deliberate divergence on key ordering:
- UTF-8 encoding.
- Object keys sorted lexicographically by Unicode codepoint value (equivalently, by UTF-8 byte order, since UTF-8 is codepoint-order-preserving). NOTE: this differs from RFC 8785 §3.2.3 which specifies UTF-16 code-unit order. The two orderings agree for keys composed of BMP codepoints (U+0000..U+FFFF) but disagree for keys containing supplementary characters (U+10000 and above, e.g. emoji or supplementary CJK). v1 receipts MUST use codepoint order. Implementations using a default
Array.sort()in JavaScript (which uses UTF-16 code-unit order) MUST replace it with a codepoint-aware comparator. - No whitespace outside string values.
- Numbers in canonical form (integers as integers, no trailing zeros, no exponents).
- String escapes per RFC 8785 §3.2.2.2.
4.2 Computing id
The id is computed over the receipt with the id and signatures fields removed:
canonical_receipt_for_id = JCS(receipt minus { id, signatures })
id = lowercase_hex(BLAKE3-256(canonical_receipt_for_id))
The id is therefore stable across re-signing and is determined solely by content.
4.3 Signing
Each signature signs the canonical receipt with id present but with the signatures array removed:
canonical_receipt_for_sig = JCS(receipt minus { signatures })
sig_payload = "deos-receipt-v1:" || canonical_receipt_for_sig (concatenation, no separator beyond the colon)
sig.sig = base64url_nopad(Ed25519_sign(private_key, sig_payload))
The "deos-receipt-v1:" prefix is a domain-separation tag. Verifiers MUST include it.
The bare-string concatenation is unambiguous because every registered tag in /registry/v1 §6 is colon-terminated and prefix-free, and canonical JSON bodies never begin with :. See /registry/v1 §6.1 for the formal invariant and the CI test that enforces it. v2 may switch to length-prefix framing for explicit safety; v1 relies on the registry-managed invariant.
4.4 Verification
To verify a receipt:
- Recompute
idper §4.2 and check it matches theidfield. - For each signature in
signatures:- Resolve
kidto a public key via the DID document. - Construct
sig_payloadper §4.3. - Verify
sigoversig_payloadwith the public key.
- Resolve
- At least one signature with
kidbelonging to theissuerMUST verify. - Verify the capability chain (§5.4).
- If a log inclusion proof is being checked (M2), verify it against the published root.
- If an anchor proof is present, verify it (M5).
- If replay verification is requested, fetch the bundle and re-execute (M3).
5. Capability chain
5.1 Format
capability.chain is a non-empty array of UCAN-shaped JSON tokens, root-first.
The full normative format is specified in /ucan/tokens/v1.
Briefly, each token has these fields:
iss(issuer DID, REQUIRED)aud(audience DID — next link in the chain, REQUIRED)att(attenuations: capabilities being granted, REQUIRED non-empty array)exp(expiry, unix seconds, REQUIRED)nbf(not-before, optional)nnc(nonce, optional)prf(parent token hashes, optional)s(inline signature object:{alg, kid, sig}, REQUIRED in v1)
Tokens are NOT JWT-encoded; they are inline canonical JSON within the
receipt. Wire-compatibility-via-translation with UCAN v1.0 is described
in /ucan/tokens/v1 §7.
5.2 Chain validity
The chain validation rules are normative in
/ucan/tokens/v1 §4.
Summary:
- Per-token signature verifies;
s.kidbelongs toiss. - Chain continuity:
chain[0].iss == receipt.principal,chain[i].aud == chain[i+1].iss,chain[last].aud == receipt.subject. - Every token's
[nbf, exp]window coversreceipt.issued_at. - Each child attenuation narrows-or-equal to a parent attenuation.
- The composed leaf scope satisfies the receipt's action (v1: shape-level,
plus
payment_intent.metadata.amount <= maxfor spend caveats). - (M7) No token in the chain appears in the revocation log.
5.3 Resolved scope
capability.scope is the intersection of all attenuations in the chain, evaluated against any caveats (M6). It is an opaque object whose schema depends on the action type. Verifiers MUST recompute scope from the chain and check that it matches capability.scope.
5.4 Capability id
capability.id is BLAKE3(JCS(capability.chain[0])) — the root token's content hash. It is the stable identifier for "this line of authority."
6. Action metadata schemas
For each registered action.type, the metadata object SHOULD conform to:
6.1 tool_call
{
"tool_name": "<string>",
"server_did": "<DID of the MCP server>",
"request_id": "<optional, opaque>"
}
6.2 payment_intent
{
"from": "<DID of payer>",
"to": "<DID or other identifier of payee>",
"amount": "<decimal string, e.g. \"42.50\">",
"currency": "<ISO 4217 or chain-asset, e.g. \"USD\", \"USDC.base\">",
"rail_hint": "<optional, e.g. \"fednow\", \"ach\", \"erc20\">"
}
6.3 payment_settlement
{
"intent_receipt_id": "<receipt id of the original payment_intent>",
"rail": "<the actual settlement rail used>",
"external_ref": "<rail-specific reference, e.g. tx hash or ACH trace>",
"settled_at_ns": <unix nanos>
}
6.4 identity_claim
{
"claim_type": "<string, e.g. \"email_verified\", \"over_18\">",
"claim_subject": "<DID of the entity the claim is about>",
"evidence_hash": "<BLAKE3 hex of evidence; evidence stored separately>"
}
6.5 message_send
{
"to": "<DID or address of recipient>",
"transport": "<e.g. \"smtp\", \"signal\", \"deos-msg\">",
"content_hash": "<BLAKE3 hex of canonical message content>",
"thread_id": "<optional, opaque>"
}
6.6 code_execution
{
"program_did": "<DID of the program>",
"args_hash": "<BLAKE3 hex>",
"exit_code": <integer>,
"duration_ns": <integer, optional>
}
6.7 policy_evaluation
{
"policy_id": "<BLAKE3 hex of canonical policy>",
"decision": "<\"allow\" | \"deny\" | \"approve_required\">",
"reason": "<short string>",
"matched_rule_id": "<optional>"
}
7. Versioning
vMUST be1for this spec. Future revisions bumpv.- New
action.typevalues do not bumpv(additive). - Breaking changes to the canonicalization, signing, hash algorithm, or required fields bump
v. - Verifiers MUST reject receipts with unknown
vvalues rather than guess.
8. Test vectors
Test vectors covering the canonical-encoding and signature-verification surface ship with the reference implementation. v1 covers:
01-minimal-tool-call.json— minimum-viable receipt, single signature, no anchors.02-payment-intent-three-deep-chain.json— three-deep capability delegation.03-message-send-multi-sig.json— multi-signer (issuer + subject).04-policy-evaluation-with-anchor.json— includes an OTS anchor proof.05-tampered-rejected.json— same receipt as01with one byte mutated; expected: verification fails.
Each vector includes:
receipt.json— the receiptprivate_keys.json— keys used (test only)expected_id.txt— expected BLAKE3 of canonical bytesverify.txt—okor expected error code
Independent implementations are expected to round-trip these vectors before claiming v1 conformance. Vectors are available from DEOS Computing on request during the v1 alpha.
9. Wire transport
Receipts travel over:
- HTTP as
application/vnd.deos.receipt+json; version=1. - CAS (kernel-internal) as canonical bytes, content-addressed by
id. - MCP transport as a JSON object embedded in the standard MCP envelope.
Receipts SHOULD be transported as canonical bytes whenever possible to avoid re-canonicalization at verification time. Implementations MAY emit pretty-printed JSON for human inspection but MUST canonicalize before hashing or signing.
10. Security considerations
10.1 Replay (network) attacks
The receipt's issued_at and the capability chain's nbf/exp bound the validity window. The Merkle log (M2) provides ordering. The OTS anchor (M5) provides time-of-existence-by-T against a public timeline.
10.2 Key compromise
If an issuer's signing key is compromised, all receipts signed after the compromise are suspect. Mitigation: short-lived keys (rotated frequently), key revocation log (M7), and audit log of kid usage.
10.3 Replay bundle availability
A receipt without an accessible bundle is verifiable as to signatures and chain, but not as to byte-for-byte execution equivalence. Verifiers MUST treat "bundle unavailable" distinctly from "bundle present and mismatched."
10.4 Capability scope leakage
capability.scope exposes resolved permissions. For privacy-sensitive actions, scope SHOULD be expressed as a hash commitment with selective-disclosure proofs (deferred to v1.1).
10.5 Hash algorithm agility
v1 hardcodes BLAKE3-256. v2 will introduce algorithm negotiation if BLAKE3 is broken or superseded. Implementations SHOULD treat the hash algorithm as a v-bound choice, not a runtime parameter.
11. Open questions deferred to v1.1
- Selective-disclosure ZKPs over receipt fields.
- Confidential receipts (encrypted body, signed envelope).
- Threshold signatures for issuers.
- Receipt batching (one signature over many receipts).
- DAG receipts (this receipt depends on these prior receipts).
12. Maintainer
DEOS Computing — design questions, conformance reports, and proposed additions to github.com/DEOS-Computing.
License: CC BY 4.0 (text), Apache-2.0 (reference code).