DEOS Receipt Log — Merkle Mountain Range v1
Status: v1.0 — published 2026-05-02
Spec URI: https://projectmaya.deoscomputing.io/merkle/log/v1
Maintainer: DEOS Computing (did:web:deos.computing)
Reference impl: Rust crate + CLI in DEOS Computing's working repo (access-controlled during v1 alpha).
The receipt log is an append-only, tamper-evident Merkle Mountain Range keyed by canonical receipt bytes. Each appended receipt becomes a leaf; whenever the right edge of the structure has two adjacent same-height nodes they are merged into a parent. The root of the log is the bagged hash of the resulting peaks, mixed with the leaf count.
This document is normative for v1.
1. Why MMR
A flat Merkle tree requires the size to be known up front, or pads to the next power of two and rewrites the structure when it grows. An MMR appends in amortized O(log n) work per leaf, produces O(log n) inclusion proofs, and never rewrites past nodes — a property that maps cleanly onto persistent CAS.
Receipts can therefore be added one at a time, with each append producing a new root that is committed (and eventually anchored externally in M5).
2. Hash domain separation
All hashes are BLAKE3-256. Every hash computation uses one of four domain tags to prevent cross-context collisions:
| Tag | Used for |
|---|---|
b"deos-mmr-leaf-v1:" |
Hashing leaf data (canonical receipt bytes) |
b"deos-mmr-node-v1:" |
Hashing two children into a parent internal node |
b"deos-mmr-bag-v1:" |
Bagging two peaks |
b"deos-mmr-root-v1:" |
Final root: tag || leaf_count_be || bagged_peaks |
leaf_hash = BLAKE3(LEAF_TAG || leaf_data)
parent_hash = BLAKE3(NODE_TAG || left_child || right_child)
bag(L, R) = BLAKE3(BAG_TAG || L || R)
root = BLAKE3(ROOT_TAG || u64_be(leaf_count) || bagged_peaks)
3. Node positions
Nodes are stored in append order. Position numbering is 1-based in the spec; the reference impl uses 0-based indices internally.
The 1-based MMR position of leaf i (0-based leaf index) is:
leaf_position(i) = 2*i + 1 - popcount(i)
Examples (1-based positions): leaf 0 → 1, leaf 1 → 2, leaf 2 → 4, leaf 3 → 5, leaf 4 → 8, leaf 7 → 12, leaf 8 → 16.
4. Append algorithm
append(leaf_data):
h = leaf_hash(leaf_data)
push h
merges = trailing_zeros(leaf_count + 1) # number of merges to perform
height = 0
while height < merges:
sibling_pos = current_pos - (2^(height+1) - 1) # 1-based
parent = node_hash(nodes[sibling_pos], nodes[current_pos])
push parent
height += 1
leaf_count += 1
return (leaf_count - 1, compute_root())
Append is deterministic: given the same sequence of leaves, the resulting nodes vector and root are byte-identical across implementations.
5. Root computation
compute_root():
if leaf_count == 0:
return ZERO_HASH
peaks = peaks_for(leaf_count) # left-to-right, highest first
acc = nodes[peaks.last().pos]
for peak in peaks reversed (skipping last):
acc = bag(nodes[peak.pos], acc) # right-to-left fold
return BLAKE3(ROOT_TAG || u64_be(leaf_count) || acc)
peaks_for(L) returns one peak per set bit in L. The leftmost peak corresponds
to the highest bit and the largest perfect-tree subtree.
6. Inclusion proofs
A proof for leaf i in a log of leaf count L consists of:
{
"leaf_index": <u64>,
"leaf_count": <u64>,
"siblings": ["<hex>", ...], // h hashes, one per height up to the leaf's peak
"bag_peaks_left": ["<hex>", ...], // peaks left of the leaf's peak (left-to-right)
"bag_peaks_right": ["<hex>", ...] // peaks right of the leaf's peak (left-to-right)
}
6.1 Generating
Walk from leaf_position(i) upward to the peak that contains it. At each
height h, bit (i >> h) & 1 decides whether the leaf is a left or right
descendant; the sibling at the same height is at:
- left child:
current_pos + (2^(h+1) - 1) - right child:
current_pos - (2^(h+1) - 1)
Push that sibling's hash into siblings. Move current_pos to the parent
position.
The other peaks (everything except the one we walked to) become
bag_peaks_left and bag_peaks_right.
6.2 Verifying (standalone)
The verifier needs only the leaf bytes, the proof, and the published root — no log state.
verify(leaf_data, proof, root):
h = leaf_hash(leaf_data)
for level, sibling in enumerate(proof.siblings):
bit = (proof.leaf_index >> level) & 1
h = node_hash(h, sibling) if bit == 0 else node_hash(sibling, h)
all_peaks = proof.bag_peaks_left + [h] + proof.bag_peaks_right
if len(all_peaks) != popcount(proof.leaf_count): reject
acc = all_peaks[-1]
for peak in all_peaks[:-1] reversed:
acc = bag(peak, acc)
return BLAKE3(ROOT_TAG || u64_be(proof.leaf_count) || acc) == root
7. Snapshot format (on-disk persistence)
The log persists across restarts as a single binary blob:
offset size field
------ ---- -----
0 8 magic = b"DMMR0001"
8 8 leaf_count (u64, big-endian)
16 8 node_count (u64, big-endian)
24 32*N nodes (concatenated 32-byte hashes)
24+32N 32 integrity (BLAKE3 over the preceding bytes)
On read: verify magic; read counts; read node hashes; verify integrity checksum. Mismatch is a fatal error — the log MUST NOT be loaded silently under corruption.
The reference impl writes via <path>.tmp + atomic rename to avoid torn
writes.
8. CLI
# Initialize an empty log on disk.
receipt-canon log init <log.bin>
# Append a receipt (canonical bytes are the leaf payload).
# Prints {"leaf_index","leaf_count","root"} as JSON.
receipt-canon log append <log.bin> <receipt.json>
# Print {"leaf_count","root"} for the current log.
receipt-canon log root <log.bin>
# Emit a JSON inclusion proof for the given leaf index.
receipt-canon log prove <log.bin> <leaf_index>
# Standalone verification: succeeds with exit 0, fails with exit 1.
receipt-canon log verify <receipt.json> <proof.json> <root-hex>
9. Integration with the substrate
The reference MMR is std-only and standalone. A substrate integrates it by:
- Serving
GET /attestation/v1/log/<root>andGET /attestation/v1/receipt/<id>from the attestation API (/attestation/api/v1). - Persisting the log under content-addressed storage. Each appended receipt is blob-stored and indexed by receipt id so attestation requests can re-fetch the original receipt and produce a fresh inclusion proof.
- Anchoring the root to OpenTimestamps every N receipts (see
/anchor/v1).
The on-disk snapshot format from §7 is the same format DEOS Computing's reference substrate uses internally; the blob lives in the persistent CAS partition.
10. Performance (informational)
Reference-impl numbers on macOS arm64 (M-series), release build:
- 10,000 appends: < 50 ms total
- 1 inclusion proof + verify: < 50 µs
- Snapshot of 10,000-leaf log: ~624 KB on disk
11. Beyond v1
- Frozen-prefix segmentation — fixed 4,096-node segments; frozen segments are written once, only the open segment is touched on each append. Delivers a 111× speedup at 1M leaves vs single-file snapshots, ~3000× at 30M. The reference impl ships this as v1.2; substrates using it MUST advertise segment format in their snapshot header.
- Consistency proofs between two roots —
prove_consistency(old_size)produces an absorption path for every MMR_N peak up to its containing MMR_M peak. Verifier reconstructsroot_Nfrom witnessed old peaks, walks each absorption path, then reconstructsroot_M. Substrate exposes this viaGET /attestation/v1/consistency-proof?from={N}. - Log sharding — per-tenant logs vs one global log. Design decision deferred to operator policy.
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).