Status: Accepted
Date: 2026-06-16
Branch / PR: feat/scenario-orchestrator
References: ADR 017, ADR 035, ADR 016
ADR 017 adopted an Optimized (collapsing / path-compressing) Sparse Merkle Tree as the aggregate-beacon primitive. While building live test vectors that shadow danubetech’s reference examples, we found the did:btcr2 specification describes the SMT two different and incompatible ways:
appendix/optimized-smt.html): a collapsing SMT: an empty sibling is skipped and a single-child node passes its child’s hash up unchanged. The collapsed bitmap is read LSB-first (leaf at bit 0).algorithms.html#smt-proof-verification): a zero-hash SMT: every one of the 256 levels is hashed, and an empty sibling contributes a precomputed cachedZero value. Read MSB-first (i = 255 - n).The spec owner confirms algorithms.html is the source of truth and the appendix is outdated. These are not two views of one tree: a single-leaf tree collapses to the leaf hash under the appendix model but hashes up 256 levels under the zero-hash model, producing different roots. We verified three mutually incompatible root constructions in play: our implementation (a depth-byte-padding collapse), danubetech’s driver (pure-skip collapse), and the spec’s algorithms.html (zero-hash). The leaf formula (hash(hash(nonce) || hash(update))) and index (hash(did)) agree across all three; only the empty-sibling handling (and hence the root) differs.
The authoritative verification pseudocode (verbatim):
cachedZero = []; z = 0; for i in 0..=255 { z = hash(z‖z); cachedZero[i] = z }
candidate = hash(hash(proof.nonce) + proof.updateId); index = hash(did)
for n in 0..=255 { i = 255 - n
sib = collapsed[i]==1 ? cachedZero[n] : hashes.pop_front()
candidate = index[i]==1 ? hash(sib‖candidate) : hash(candidate‖sib) }
return candidate == proof.id
Implement the zero-hash SMT of algorithms.html in the did:btcr2 aggregate-beacon layer, replacing the collapsing model for protocol use.
smt/src/zero-hash.ts: CACHED_ZERO (the empty-subtree table), zeroHashRoot, generateZeroHashProof, and verifyZeroHash, the last a line-for-line implementation of the authoritative verification pseudocode.btcr2-tree.ts (BTCR2MerkleTree) builds the zero-hash root and emits zero-hash proofs; btcr2-proof.ts serializes/deserializes the wire format and verifies via verifyZeroHash. The public API (BTCR2MerkleTree, serializeProof/deserializeProof/verifySerializedProof, SerializedSMTProof) is unchanged, so the resolver and aggregation layers are untouched.collapsed is the empty-sibling bitmap, MSB-first (bit i = 255 = leaf level), a full 32-byte value.OptimizedSMT / SMTProof (collapsing) remain in the package as generic, self-contained utilities but are decoupled from the protocol (candidate for the dead-code sweep). This supersedes ADR 017 for aggregate-beacon use.algorithms.html gives the verification but not the construction, and two details are underspecified. We resolved each defensibly and isolated it so a future spec clarification is a one-line change:
cachedZero seed. The spec writes z = 0 with no byte width. We seed z with 32 zero bytes (the project’s NULL_HASH convention). Only CACHED_ZERO’s seed changes if the spec pins a different value.h = cachedZero[h], split bit 256 - h) and proved it consistent with the authoritative verifier by (a) round-trip over random trees and (b) re-verifying generated proofs with an independent re-implementation of the algorithms.html pseudocode.Positive
Negative
da1c26e2… / 799b3ecd…). The to-be-anchored OP_RETURN payload changes; beacon addresses are unchanged (derived from the cohort key), so no re-funding is required, only re-anchoring the new root. No production data exists; only test vectors regenerate.OptimizedSMT as a generic utility). Flagged for cleanup.Explicitly accepted / escalated
algorithms.html (zero-hash) vs appendix (collapsing) conflict is a spec-internal inconsistency, and danubetech’s driver implements the appendix model, so our spec-conformant SMT vectors will not resolve in danubetech’s current driver, and vice versa. This is to be raised with the spec owner; we conform to the stated source of truth rather than to any implementation.@did-btcr2/smt: 211 tests pass (zero-hash round-trip + format + negative cases; generic OptimizedSMT/SMTProof unchanged).@did-btcr2/method: 247 tests pass; all 16 scenarios resolve through the real resolver using the zero-hash verifySerializedProof.algorithms.html pseudocode.packages/smt/src/zero-hash.ts: CACHED_ZERO, zeroHashRoot, generateZeroHashProof, verifyZeroHash.packages/smt/src/btcr2-tree.ts, btcr2-proof.ts: the BTCR2 layer.algorithms.html#smt-proof-verification: the authoritative algorithm.appendix/optimized-smt.html: the outdated collapsing description.