Skip to content

Commit accbc13

Browse files
committed
Align subtrees with updated specs
1 parent 98f61d9 commit accbc13

25 files changed

Lines changed: 211 additions & 129 deletions

cmd/proofgen/main.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -744,18 +744,42 @@ func invalidSubtreeConsistencyProof(size1, size2 uint64, root1, root2 []byte, pr
744744
}
745745

746746
func staticSubtreeConsistencyProbes(dir string) error {
747-
for _, p := range staticConsistencyProbes() {
748-
sp := subtreeConsistencyProbe{
749-
Start: 0,
750-
End: p.Size1,
751-
Size: p.Size2,
752-
Root1: p.Root1,
753-
Root2: p.Root2,
754-
Proof: p.Proof,
755-
Desc: p.Desc,
756-
WantError: p.WantError,
757-
}
758-
if err := writeSubtreeConsistencyProbe(dir, sp); err != nil {
747+
root1 := []byte("don't care 1")
748+
root2 := []byte("don't care 2")
749+
proof1 := [][]byte{}
750+
proof2 := [][]byte{sha256EmptyTreeHash}
751+
752+
for _, p := range []subtreeConsistencyProbe{
753+
{0, 0, 0, root1, root2, proof1, "sizes are equal (zero) but roots are not", true},
754+
{0, 1, 1, root1, root2, proof1, "sizes are equal (one) but roots are not", true},
755+
{0, 0, 1, root1, root2, proof1, "size1 is zero and does not equal size2", true},
756+
// Sizes that are always consistent.
757+
{0, 1, 1, root2, root2, proof1, "sizes are equal (one) and proof is empty", false},
758+
// Empty subtree
759+
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty sizes are equal (zero) subtree root valid proof is empty", false},
760+
{0, 0, 0, sha256EmptyTreeHash, root1, proof1, "subtree is empty sizes are equal (zero) subtree root valid tree root random proof is empty", false},
761+
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty sizes are equal (zero) roots valid but proof is not empty", true},
762+
{0, 0, 0, root1, root1, proof1, "subtree is empty sizes are equal (zero) roots match but not valid", true},
763+
{1, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty sizes are equal (one) subtree root valid proof is empty", false},
764+
{1, 1, 1, sha256EmptyTreeHash, root1, proof1, "subtree is empty sizes are equal (one) subtree root valid tree root random proof is empty", false},
765+
{1, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty sizes are equal (one) roots valid but proof is not empty", true},
766+
{1, 1, 1, root1, root1, proof1, "subtree is empty sizes are equal (one) roots match but not valid", true},
767+
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty subtree root valid proof is empty", false},
768+
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof1, "subtree is empty subtree root valid tree root random proof is empty", false},
769+
{1, 1, 2, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "subtree is empty roots valid but proof is not empty", true},
770+
{1, 1, 2, root1, root1, proof1, "subtree is empty roots match but not valid", true},
771+
// Time travel to the past.
772+
{0, 1, 0, root1, root2, proof1, "size1 is greater than size2", true},
773+
{0, 2, 1, root1, root2, proof1, "size1 is greater than size2 again", true},
774+
// Empty proof.
775+
{0, 1, 2, root1, root2, proof1, "sizes do not watch and proof is empty", true},
776+
// Roots don't match.
777+
{0, 1, 1, sha256EmptyTreeHash, root2, proof1, "roots do not not match and sizes are one", true},
778+
// Sizes match but the proof is not empty.
779+
{0, 0, 0, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are zero", true},
780+
{0, 1, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "sizes match but proof is not empty and sizes are one", true},
781+
} {
782+
if err := writeSubtreeConsistencyProbe(dir, p); err != nil {
759783
return err
760784
}
761785
}

proof/proof.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ func subtreeConsistency(start, end, size uint64) (Nodes, error) {
113113
if start == 0 && end == size {
114114
return Nodes{IDs: []compact.NodeID{}}, nil
115115
}
116+
if start == end {
117+
return Nodes{IDs: []compact.NodeID{}}, nil
118+
}
116119

117120
// If end == size, prove inclusion of [start, end) into the tree.
118121
if end == size {
@@ -286,11 +289,11 @@ type Subtree struct {
286289
// - There are no "extra" entries covered past end, but there may be covered entries prior to start.
287290
// - The number of entries covered before start is always less than half the size of the first returned subtree.
288291
func FindSubtrees(start, end uint64) ([]Subtree, error) {
289-
if start >= end {
290-
return nil, fmt.Errorf("start %d must be strictly less than end %d", start, end)
292+
if start > end {
293+
return nil, fmt.Errorf("start %d must be less than or equal to end %d", start, end)
291294
}
292-
if end-start == 1 || isSubtreeValid(start, end) == nil {
293-
return []Subtree{{Start: start, End: end}}, nil
295+
if end-start <= 1 {
296+
return []Subtree{{Start: start, End: end}, {Start: end, End: end}}, nil
294297
}
295298
last := end - 1
296299
// Find where start and last's tree paths diverge.
@@ -313,12 +316,15 @@ func FindSubtrees(start, end uint64) ([]Subtree, error) {
313316
// - no extra node to the left of the subtree
314317
// - potentially extra nodes to the right of the subtree
315318
func isSubtreeValid(start, end uint64) error {
316-
if start >= end {
317-
return fmt.Errorf("start %d must be strictly less than end %d", start, end)
319+
if start > end {
320+
return fmt.Errorf("start %d must be less than or equal to end %d", start, end)
318321
}
319322
if start == 0 {
320323
return nil
321324
}
325+
if start == end {
326+
return nil
327+
}
322328

323329
l := end - start
324330

proof/proof_test.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,6 @@ func TestSubtreeConsistency(t *testing.T) {
457457
wantErr bool
458458
}{
459459
// Errors.
460-
{start: 0, end: 0, size: 0, wantErr: true}, // start = end = 0
461-
{start: 1, end: 1, size: 1, wantErr: true}, // start = end
462460
{start: 2, end: 1, size: 0, wantErr: true}, // start > end
463461
{start: 0, end: 5, size: 0, wantErr: true}, // end > size
464462
{start: 0, end: 9, size: 8, wantErr: true}, // end > size
@@ -467,6 +465,7 @@ func TestSubtreeConsistency(t *testing.T) {
467465

468466
// Small trees.
469467
// start = 0
468+
{start: 0, end: 0, size: 0, want: Nodes{IDs: []compact.NodeID{}}}, // start = end = 0
470469
{start: 0, end: 1, size: 2, want: nodes(id(0, 1))}, // b
471470
{start: 0, end: 1, size: 4, want: nodes(id(0, 1), id(1, 1))}, // b bb
472471
{start: 0, end: 1, size: 6, want: rehash(2, 3, id(0, 1), id(1, 1), id(1, 2))}, // b bb cc
@@ -485,12 +484,14 @@ func TestSubtreeConsistency(t *testing.T) {
485484
{start: 0, end: 7, size: 8, want: nodes(
486485
id(0, 6), id(0, 7), id(1, 2), id(2, 0))}, // g h cc aaa
487486
// start > 0
487+
{start: 1, end: 1, size: 1, want: Nodes{IDs: []compact.NodeID{}}}, // start = end
488488
{start: 1, end: 2, size: 3, want: rehash(1, 2, id(0, 0), id(0, 2))}, // a c
489489
{start: 1, end: 2, size: 5, want: rehash(2, 3, id(0, 0), id(1, 1), id(0, 4))}, // a bb e
490490
{start: 2, end: 4, size: 5, want: rehash(1, 2, id(1, 0), id(0, 4))}, // aa e
491491
{start: 1, end: 2, size: 7, want: rehash(2, 4, id(0, 0), id(1, 1), id(0, 6), id(1, 2))}, // a bb g cc
492492
{start: 2, end: 4, size: 10, want: rehash(2, 3, id(1, 0), id(2, 1), id(1, 4))}, // aa bbb ee
493493
{start: 4, end: 6, size: 10, want: rehash(2, 3, id(1, 3), id(2, 0), id(1, 4))}, // dd aaa ee
494+
{start: 4, end: 4, size: 10, want: Nodes{IDs: []compact.NodeID{}}}, // start = end
494495
{start: 4, end: 7, size: 11, want: rehash(4, 6, // ccc=hash(ee,k)
495496
id(0, 6), id(0, 7), id(1, 2), id(2, 0), id(0, 10), id(1, 4))}, // g h cc aaa k ee
496497
{start: 4, end: 8, size: 11, want: rehash(1, 3, // ccc=hash(ee,k)
@@ -599,16 +600,16 @@ func TestSubtreeConsistency(t *testing.T) {
599600

600601
func TestInclusionSucceedsUpToTreeSize(t *testing.T) {
601602
const maxSize = uint64(555)
602-
for ts := uint64(1); ts <= maxSize; ts++ {
603-
for i := ts; i < ts; i++ {
603+
for ts := range maxSize + 1 {
604+
for i := range ts {
604605
if _, err := Inclusion(i, ts); err != nil {
605606
t.Errorf("Inclusion(ts:%d, i:%d) = %v", ts, i, err)
606607
}
607608
}
608609
}
609610
}
610611

611-
func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
612+
func TestSubtreeInclusionSucceedsUpToTreeSize(t *testing.T) {
612613
const maxSize = uint64(555)
613614
for sbe := uint64(1); sbe <= maxSize; sbe++ {
614615
for sbs := range sbe {
@@ -637,9 +638,9 @@ func TestConsistencySucceedsUpToTreeSize(t *testing.T) {
637638

638639
func TestSubtreeConsistencySucceedsUpToTreeSize(t *testing.T) {
639640
const maxSize = uint64(100)
640-
for s := uint64(1); s <= maxSize; s++ {
641-
for sbe := uint64(1); sbe <= s; sbe++ {
642-
for sbs := range sbe {
641+
for s := range maxSize + 1 {
642+
for sbe := range s + 1 {
643+
for sbs := range sbe + 1 {
643644
if err := isSubtreeValid(sbs, sbe); err != nil {
644645
continue
645646
}
@@ -829,16 +830,16 @@ func TestFindSubtrees(t *testing.T) {
829830
}{
830831
// Already-valid subtrees are returned as-is.
831832
// Single entry subtrees:
832-
{start: 0, end: 1, want: []Subtree{{Start: 0, End: 1}}},
833-
{start: 3, end: 4, want: []Subtree{{Start: 3, End: 4}}},
833+
{start: 0, end: 1, want: []Subtree{{Start: 0, End: 1}, {Start: 1, End: 1}}},
834+
{start: 3, end: 4, want: []Subtree{{Start: 3, End: 4}, {Start: 4, End: 4}}},
834835
// Perfectly aligned subtrees:
835-
{start: 4, end: 6, want: []Subtree{{Start: 4, End: 6}}},
836-
{start: 16, end: 32, want: []Subtree{{Start: 16, End: 32}}},
836+
{start: 4, end: 6, want: []Subtree{{Start: 4, End: 5}, {Start: 5, End: 6}}},
837+
{start: 16, end: 32, want: []Subtree{{Start: 16, End: 24}, {Start: 24, End: 32}}},
837838
// Non-perfect trees are split into two:
838839
{start: 5, end: 13, want: []Subtree{{Start: 4, End: 8}, {Start: 8, End: 13}}},
839840
{start: 7, end: 9, want: []Subtree{{Start: 7, End: 8}, {Start: 8, End: 9}}},
840841
// Invalid inputs:
841-
{start: 5, end: 5, wantErr: true},
842+
{start: 5, end: 5, want: []Subtree{{Start: 5, End: 5}, {Start: 5, End: 5}}},
842843
{start: 6, end: 5, wantErr: true},
843844
} {
844845
t.Run(fmt.Sprintf("%d:%d", tc.start, tc.end), func(t *testing.T) {

proof/verify.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,22 @@ func VerifyConsistency(hasher merkle.LogHasher, size1, size2 uint64, proof [][]b
107107
// VerifySubtreeConsistency checks that the passed-in subtree consistency proof
108108
// is valid between the passed in subtree indices and parent tree size, with
109109
// respect to the corresponding subtree root node hash. It Requires:
110-
// - 0 <= start < end <= size.
110+
// - 0 <= start <= end <= size.
111111
// - start to be a multiple of the smallest power of two greater than or equal to
112112
// (end - start)
113113
func VerifySubtreeConsistency(hasher merkle.LogHasher, start, end, size uint64, proof [][]byte, subRoot, parentRoot []byte) error {
114+
if start == end {
115+
if end > size {
116+
return fmt.Errorf("size (%d) < end (%d)", size, end)
117+
}
118+
if len(proof) > 0 {
119+
return errors.New("start=end, but proof is not empty")
120+
}
121+
if !bytes.Equal(subRoot, hasher.EmptyRoot()) {
122+
return errors.New("start=end, but subRoot is not empty root")
123+
}
124+
return nil
125+
}
114126
hash2, err := RootFromSubtreeConsistencyProof(hasher, start, end, size, proof, subRoot)
115127
if err != nil {
116128
return err
@@ -144,7 +156,7 @@ func RootFromConsistencyProof(hasher merkle.LogHasher, size1, size2 uint64, proo
144156
// a consistency proof.
145157
//
146158
// It requires:
147-
// - 0 <= start < end <= size.
159+
// - 0 <= start <= end <= size.
148160
// - start to be a multiple of the smallest power of two greater than or equal to
149161
// (end - start)
150162
//
@@ -156,6 +168,14 @@ func RootFromSubtreeConsistencyProof(hasher merkle.LogHasher, start, end, size u
156168
return nil, fmt.Errorf("subtree invalid: %v", err)
157169
case size < end:
158170
return nil, fmt.Errorf("size (%d) < end (%d)", size, end)
171+
case start == end && size == 0:
172+
if len(proof) > 0 {
173+
return nil, errors.New("start=end, but proof is not empty")
174+
}
175+
if !bytes.Equal(subRoot, hasher.EmptyRoot()) {
176+
return nil, errors.New("start=end, but subRoot is not empty root")
177+
}
178+
return hasher.EmptyRoot(), nil
159179
case start == 0 && size == end:
160180
if len(proof) > 0 {
161181
return nil, errors.New("start=0 and end=size, but proof is not empty")

testdata/subtreeconsistency/additional/roots-do-not-match-and-sizes-are-zero.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

testdata/subtreeconsistency/additional/size1-is-zero-and-size2-is-not-zero.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

testdata/subtreeconsistency/additional/sizes-are-equal-(one)-and-proof-is-empty.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

testdata/subtreeconsistency/additional/sizes-are-equal-(one)-but-roots-are-not.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

testdata/subtreeconsistency/additional/sizes-are-equal-(zero)-but-roots-are-not.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

testdata/subtreeconsistency/additional/sizes-are-equal-zero-and-proof-is-empty.json renamed to testdata/subtreeconsistency/additional/subtree-is-empty-roots-match-but-not-valid.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"start": 0,
3-
"end": 0,
4-
"size": 0,
2+
"start": 1,
3+
"end": 1,
4+
"size": 2,
55
"root1": "ZG9uJ3QgY2FyZSAx",
66
"root2": "ZG9uJ3QgY2FyZSAx",
77
"proof": [],
8-
"desc": "sizes are equal (zero) and proof is empty",
8+
"desc": "subtree is empty roots match but not valid",
99
"wantErr": true
1010
}

0 commit comments

Comments
 (0)