Skip to content

fix(dig-node): recompute the merkle root in the shared anchor-admit gate so admitted/pushed capsules are data-complete (#2246 #2240) - #197

Draft
MichaelTaylor3d wants to merge 1 commit into
mainfrom
fix/anchor-admit-merkle-recompute
Draft

fix(dig-node): recompute the merkle root in the shared anchor-admit gate so admitted/pushed capsules are data-complete (#2246 #2240)#197
MichaelTaylor3d wants to merge 1 commit into
mainfrom
fix/anchor-admit-merkle-recompute

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Closes #2246, Closes #2240 (super-repo DIG-Network/dig_ecosystem#2246 / #2240 — one deduped family; both are the same ChainAnchoredModuleVerifier code path).

Problem: the capsule-admit trust gate (ChainAnchoredModuleVerifier::rejection_reason, module_anchor.rs) today only byte-compares the module's committed CurrentRoot section against the chain-anchored root — it never proves that committed root is the merkle root of the DATA actually present. A header-matching-but-incomplete/tampered .dig is therefore admitted → cached → served → DHT-announced, turning an honest node into an authoritative source of corrupt content. Same gate backs both reshare-admit (#2246) and cache.pushCapsule land (#2240).

Fix: add a fail-closed rule to the shared verifier — recompute MerkleTree::from_leaves(decode_merkle_leaves(MerkleNodes)).root() from the parsed DataView and reject (NotAnchored) if it ≠ the committed CurrentRoot (or MerkleNodes is absent/undecodable). digstore recompute primitives are already available (no cross-repo cascade).

RED-first regression test (small in-memory fixture — committed root == chain root but MerkleNodes recompute to a different/absent root → must flip Anchored→NotAnchored) + a push-path twin. §5.1 backwards-compat: verified against golden .dig fixtures (the canonical read path already requires MerkleNodes). Version fix: patch 0.100.1→0.100.2.

Draft = loop salvage anchor; implementation in progress.


Generated by Claude Code

…t MerkleNodes digests (#2246 #2240)

The capsule-admit trust gate (ChainAnchoredModuleVerifier::rejection_reason)
recomputed the merkle root from the capsule's own MerkleNodes section and
compared it to the committed CurrentRoot. That is hollow: rule 4 already forces
CurrentRoot == chain_root, a single untagged leaf's root IS that leaf, and
decode_merkle_leaves accepts arbitrary bytes -- so MerkleNodes=[chain_root] with
an empty/garbage ChunkPool passed for free, admitting a contentless capsule that
was then cached and DHT-announced (phantom-holder / holder-set poisoning).

Bind the SERVED content instead: recompute each resource leaf as
resource_leaf(concat(ChunkPool ciphertexts)) per KeyTable entry, sort by
static_key (the producer's order), fold, and require the root == committed root
(== chain_root). Also require the recomputed leaves equal decode_merkle_leaves
(MerkleNodes) so served inclusion proofs stay consistent with content. MerkleNodes
is no longer trusted for the admit decision. Fail-closed on absent/undecodable
KeyTable/ChunkPool or any out-of-range chunk index; an empty store folds to
sha256([]). Covers both reshare-admit and cache.pushCapsule land (shared gate;
full ChunkPool is materialized before the gate on both paths). No digstore change.

0.100.2->0.100.3.

Closes #2246, Closes #2240.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d force-pushed the fix/anchor-admit-merkle-recompute branch from 0797b99 to e8b1ba5 Compare August 7, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants