did-btcr2-js

ADR 036: Adopt the Zero-Hash SMT Model per algorithms.html

Status: Accepted

Date: 2026-06-16

Branch / PR: feat/scenario-orchestrator

References: ADR 017, ADR 035, ADR 016

Context

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:

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

Decision

Implement the zero-hash SMT of algorithms.html in the did:btcr2 aggregate-beacon layer, replacing the collapsing model for protocol use.

Assumptions flagged for the spec owner

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:

  1. 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.
  2. Build algorithm. We derived a top-down zero-hash build (empty subtree of height 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.

Consequences

Positive

Negative

Explicitly accepted / escalated

Validation

References