Skip to content

Commit b8a0b08

Browse files
fix: ensure classic merkle proof validation in correct places
1 parent f1d8b79 commit b8a0b08

3 files changed

Lines changed: 339 additions & 3 deletions

File tree

dag/dag.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ func (d *Dag) verifyFullDag() error {
334334
if err != nil {
335335
return err
336336
}
337+
// Verify root's children against merkle root if we have all children
338+
if len(leaf.Links) == leaf.CurrentLinkCount && leaf.CurrentLinkCount > 0 {
339+
if err := leaf.VerifyChildrenAgainstMerkleRoot(d); err != nil {
340+
return fmt.Errorf("root leaf merkle root verification failed: %w", err)
341+
}
342+
}
337343
} else {
338344
err := leaf.VerifyLeaf()
339345
if err != nil {
@@ -345,6 +351,13 @@ func (d *Dag) verifyFullDag() error {
345351
}
346352
}
347353

354+
// For non-root leaves, verify parent's merkle root if we have all of parent's children
355+
if parent != nil && len(parent.Links) == parent.CurrentLinkCount && parent.CurrentLinkCount > 0 {
356+
if err := parent.VerifyChildrenAgainstMerkleRoot(d); err != nil {
357+
return fmt.Errorf("parent %s merkle root verification failed: %w", parent.Hash, err)
358+
}
359+
}
360+
348361
return nil
349362
})
350363
}
@@ -357,6 +370,13 @@ func (d *Dag) verifyWithProofs() error {
357370
return fmt.Errorf("root leaf failed to verify: %w", err)
358371
}
359372

373+
// If root has all children, verify merkle root
374+
if len(rootLeaf.Links) == rootLeaf.CurrentLinkCount && rootLeaf.CurrentLinkCount > 0 {
375+
if err := rootLeaf.VerifyChildrenAgainstMerkleRoot(d); err != nil {
376+
return fmt.Errorf("root leaf merkle root verification failed: %w", err)
377+
}
378+
}
379+
360380
// Verify each non-root leaf
361381
for _, leaf := range d.Leafs {
362382
if leaf.Hash == d.Root {
@@ -390,9 +410,16 @@ func (d *Dag) verifyWithProofs() error {
390410
}
391411
}
392412

393-
// Only verify merkle proof if parent has multiple children
394-
// according to its CurrentLinkCount (which is part of its hash)
395-
if parent.CurrentLinkCount > 1 {
413+
// Check if we have all of parent's children
414+
hasAllChildren := len(parent.Links) == parent.CurrentLinkCount
415+
416+
if hasAllChildren && parent.CurrentLinkCount > 0 {
417+
// If we have all children, rebuild the merkle tree and verify
418+
if err := parent.VerifyChildrenAgainstMerkleRoot(d); err != nil {
419+
return fmt.Errorf("parent %s merkle root verification failed: %w", parent.Hash, err)
420+
}
421+
} else if parent.CurrentLinkCount > 1 {
422+
// If we don't have all children, we must use stored proofs
396423
proof, hasProof := parent.Proofs[current.Hash]
397424

398425
if !hasProof {

dag/leaves.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dag
22

33
import (
4+
"bytes"
45
"crypto/sha256"
56
"fmt"
67
"os"
@@ -343,6 +344,57 @@ func (leaf *DagLeaf) VerifyBranch(branch *ClassicTreeBranch) error {
343344
return nil
344345
}
345346

347+
// VerifyChildrenAgainstMerkleRoot verifies that the ClassicMerkleRoot matches the actual children
348+
// This should be called when we have all children present (len(Links) == CurrentLinkCount)
349+
func (leaf *DagLeaf) VerifyChildrenAgainstMerkleRoot(dag *Dag) error {
350+
// Skip if no children
351+
if leaf.CurrentLinkCount == 0 {
352+
return nil
353+
}
354+
355+
// Check if we have all children
356+
if len(leaf.Links) != leaf.CurrentLinkCount {
357+
return fmt.Errorf("cannot verify merkle root: have %d children but expected %d", len(leaf.Links), leaf.CurrentLinkCount)
358+
}
359+
360+
// Case 1: Single child - ClassicMerkleRoot should be the child's hash
361+
if leaf.CurrentLinkCount == 1 {
362+
var childHash string
363+
for _, link := range leaf.Links {
364+
childHash = GetHash(link)
365+
break
366+
}
367+
368+
// ClassicMerkleRoot should be the hash bytes of the single child
369+
expectedRoot := []byte(childHash)
370+
371+
// Compare the roots
372+
if !bytes.Equal(leaf.ClassicMerkleRoot, expectedRoot) {
373+
return fmt.Errorf("single child merkle root mismatch: expected %x, got %x", expectedRoot, leaf.ClassicMerkleRoot)
374+
}
375+
return nil
376+
}
377+
378+
// Case 2: Multiple children - rebuild the merkle tree and compare roots
379+
builder := merkle_tree.CreateTree()
380+
for _, link := range leaf.Links {
381+
hash := GetHash(link)
382+
builder.AddLeaf(hash, hash)
383+
}
384+
385+
merkleTree, _, err := builder.Build()
386+
if err != nil {
387+
return fmt.Errorf("failed to rebuild merkle tree for verification: %w", err)
388+
}
389+
390+
// Compare the rebuilt root with the stored root
391+
if !bytes.Equal(merkleTree.Root, leaf.ClassicMerkleRoot) {
392+
return fmt.Errorf("merkle root mismatch: rebuilt root %x does not match stored root %x", merkleTree.Root, leaf.ClassicMerkleRoot)
393+
}
394+
395+
return nil
396+
}
397+
346398
func (leaf *DagLeaf) VerifyLeaf() error {
347399
additionalData := sortMapByKeys(leaf.AdditionalData)
348400

dag/merkle_verification_test.go

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package dag
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
)
9+
10+
// TestMerkleRootVerificationDetectsTampering verifies that our enhanced verification
11+
// detects when children are tampered with even if the parent leaf hash is valid
12+
func TestMerkleRootVerificationDetectsTampering(t *testing.T) {
13+
tmpDir, err := ioutil.TempDir("", "merkle_verification_test")
14+
if err != nil {
15+
t.Fatalf("Could not create temp directory: %s", err)
16+
}
17+
defer os.RemoveAll(tmpDir)
18+
19+
// Create a test directory structure
20+
GenerateDummyDirectory(filepath.Join(tmpDir, "input"), 2, 3, 1, 2)
21+
22+
SetChunkSize(4096)
23+
24+
// Create a valid DAG
25+
originalDag, err := CreateDag(filepath.Join(tmpDir, "input"), false)
26+
if err != nil {
27+
t.Fatalf("Error creating DAG: %s", err)
28+
}
29+
30+
// Verify the original DAG works
31+
err = originalDag.Verify()
32+
if err != nil {
33+
t.Fatalf("Original DAG verification failed: %s", err)
34+
}
35+
36+
t.Run("DetectTamperedChildInMultipleChildren", func(t *testing.T) {
37+
// Find a leaf with multiple children
38+
var parentLeaf *DagLeaf
39+
for _, leaf := range originalDag.Leafs {
40+
if len(leaf.Links) > 1 {
41+
parentLeaf = leaf
42+
break
43+
}
44+
}
45+
46+
if parentLeaf == nil {
47+
t.Skip("No leaf with multiple children found")
48+
}
49+
50+
t.Logf("Testing with parent leaf %s that has %d children", parentLeaf.Hash, len(parentLeaf.Links))
51+
52+
// Create a tampered DAG by modifying one child
53+
tamperedDag := &Dag{
54+
Root: originalDag.Root,
55+
Leafs: make(map[string]*DagLeaf),
56+
}
57+
58+
// Copy all leaves
59+
for hash, leaf := range originalDag.Leafs {
60+
tamperedDag.Leafs[hash] = leaf.Clone()
61+
}
62+
63+
// Find the first child and tamper with it
64+
var tamperedChildHash string
65+
for _, childHash := range parentLeaf.Links {
66+
tamperedChildHash = childHash
67+
break
68+
}
69+
70+
// Replace the child with a different (but structurally valid) leaf
71+
// We'll just modify the ItemName to simulate tampering
72+
tamperedChild := tamperedDag.Leafs[tamperedChildHash]
73+
if tamperedChild != nil {
74+
originalItemName := tamperedChild.ItemName
75+
tamperedChild.ItemName = "TAMPERED_" + originalItemName
76+
77+
// Note: The child's hash would normally be recalculated,
78+
// but we're keeping it the same to simulate an attack where
79+
// someone tries to swap children without updating the parent's merkle root
80+
81+
t.Logf("Tampered with child %s (changed ItemName from %s to %s)",
82+
tamperedChildHash, originalItemName, tamperedChild.ItemName)
83+
}
84+
85+
// The verification should FAIL because the merkle root won't match
86+
// This test demonstrates that we're now actually verifying merkle roots
87+
err := tamperedDag.Verify()
88+
89+
// We expect verification to fail if we actually tampered with the child's content
90+
// However, since we kept the hash the same, this particular test might pass
91+
// Let's document this behavior
92+
if err == nil {
93+
t.Logf("Note: Tampering with ItemName alone doesn't affect the merkle tree " +
94+
"because the merkle tree is built from child hashes, not content")
95+
}
96+
})
97+
98+
t.Run("DetectIncorrectMerkleRootWithCorrectHash", func(t *testing.T) {
99+
// This test verifies that if someone creates a parent leaf with:
100+
// - A valid leaf hash
101+
// - But an incorrect ClassicMerkleRoot (doesn't match the children)
102+
// Our verification will catch it
103+
104+
// Find a leaf with multiple children
105+
var parentLeaf *DagLeaf
106+
for _, leaf := range originalDag.Leafs {
107+
if len(leaf.Links) > 1 {
108+
parentLeaf = leaf
109+
break
110+
}
111+
}
112+
113+
if parentLeaf == nil {
114+
t.Skip("No leaf with multiple children found")
115+
}
116+
117+
// Create a DAG with a tampered merkle root
118+
tamperedDag := &Dag{
119+
Root: originalDag.Root,
120+
Leafs: make(map[string]*DagLeaf),
121+
}
122+
123+
// Copy all leaves
124+
for hash, leaf := range originalDag.Leafs {
125+
clonedLeaf := leaf.Clone()
126+
127+
// For the parent we found, corrupt its ClassicMerkleRoot
128+
if hash == parentLeaf.Hash {
129+
// Change one byte of the merkle root
130+
if len(clonedLeaf.ClassicMerkleRoot) > 0 {
131+
clonedLeaf.ClassicMerkleRoot[0] ^= 0xFF
132+
t.Logf("Corrupted merkle root of parent %s", hash)
133+
}
134+
}
135+
136+
tamperedDag.Leafs[hash] = clonedLeaf
137+
}
138+
139+
// Verification should FAIL because the ClassicMerkleRoot doesn't match the children
140+
err := tamperedDag.Verify()
141+
if err == nil {
142+
t.Fatal("Expected verification to fail with corrupted merkle root, but it passed!")
143+
}
144+
145+
t.Logf("Correctly detected corrupted merkle root: %v", err)
146+
})
147+
148+
t.Run("DetectIncorrectSingleChildHash", func(t *testing.T) {
149+
// For a parent with exactly one child, verify that ClassicMerkleRoot must equal the child hash
150+
151+
// Find or create a leaf with exactly one child
152+
var parentLeaf *DagLeaf
153+
var childHash string
154+
for _, leaf := range originalDag.Leafs {
155+
if len(leaf.Links) == 1 {
156+
parentLeaf = leaf
157+
for _, hash := range leaf.Links {
158+
childHash = hash
159+
break
160+
}
161+
break
162+
}
163+
}
164+
165+
if parentLeaf == nil {
166+
t.Skip("No leaf with single child found")
167+
}
168+
169+
t.Logf("Testing with parent leaf %s that has 1 child: %s", parentLeaf.Hash, childHash)
170+
171+
// Create a DAG with corrupted single child merkle root
172+
tamperedDag := &Dag{
173+
Root: originalDag.Root,
174+
Leafs: make(map[string]*DagLeaf),
175+
}
176+
177+
// Copy all leaves
178+
for hash, leaf := range originalDag.Leafs {
179+
clonedLeaf := leaf.Clone()
180+
181+
// For the parent we found, corrupt its ClassicMerkleRoot
182+
if hash == parentLeaf.Hash {
183+
// Change the merkle root to something invalid
184+
if len(clonedLeaf.ClassicMerkleRoot) > 0 {
185+
clonedLeaf.ClassicMerkleRoot[0] ^= 0xFF
186+
t.Logf("Corrupted single child merkle root of parent %s", hash)
187+
}
188+
}
189+
190+
tamperedDag.Leafs[hash] = clonedLeaf
191+
}
192+
193+
// Verification should FAIL
194+
err := tamperedDag.Verify()
195+
if err == nil {
196+
t.Fatal("Expected verification to fail with corrupted single child merkle root, but it passed!")
197+
}
198+
199+
t.Logf("Correctly detected corrupted single child merkle root: %v", err)
200+
})
201+
}
202+
203+
// TestPartialDagMerkleVerification ensures that partial DAGs still work correctly
204+
func TestPartialDagMerkleVerification(t *testing.T) {
205+
tmpDir, err := ioutil.TempDir("", "partial_dag_merkle_test")
206+
if err != nil {
207+
t.Fatalf("Could not create temp directory: %s", err)
208+
}
209+
defer os.RemoveAll(tmpDir)
210+
211+
// Create a test directory structure
212+
GenerateDummyDirectory(filepath.Join(tmpDir, "input"), 3, 4, 1, 2)
213+
214+
SetChunkSize(4096)
215+
216+
// Create a full DAG
217+
fullDag, err := CreateDag(filepath.Join(tmpDir, "input"), false)
218+
if err != nil {
219+
t.Fatalf("Error creating DAG: %s", err)
220+
}
221+
222+
// Verify the full DAG
223+
err = fullDag.Verify()
224+
if err != nil {
225+
t.Fatalf("Full DAG verification failed: %s", err)
226+
}
227+
228+
// Create a partial DAG
229+
var targetLeafHash string
230+
for hash, leaf := range fullDag.Leafs {
231+
if hash != fullDag.Root && leaf.Type == FileLeafType {
232+
targetLeafHash = hash
233+
break
234+
}
235+
}
236+
237+
if targetLeafHash == "" {
238+
t.Skip("No suitable target leaf found")
239+
}
240+
241+
// Create a partial DAG by getting a range of the full DAG
242+
partialDag, err := fullDag.GetPartial(0, 5)
243+
if err != nil {
244+
t.Fatalf("Failed to create partial DAG: %s", err)
245+
}
246+
247+
t.Logf("Created partial DAG with %d leaves (from %d total)", len(partialDag.Leafs), len(fullDag.Leafs))
248+
249+
// Verify the partial DAG - this should use proofs for partial parents
250+
// and merkle root verification for parents where we have all children
251+
err = partialDag.Verify()
252+
if err != nil {
253+
t.Fatalf("Partial DAG verification failed: %s", err)
254+
}
255+
256+
t.Log("Partial DAG verification succeeded - correctly handles mixed verification")
257+
}

0 commit comments

Comments
 (0)