Skip to content

Commit d14cc61

Browse files
committed
better isSubtreeValid
1 parent 4098e0c commit d14cc61

5 files changed

Lines changed: 58 additions & 23 deletions

File tree

proof/proof.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,12 @@ func Inclusion(index, size uint64) (Nodes, error) {
5959
// - start to be a multiple of the smallest power of two greater than or equal to
6060
// (end - start)
6161
func SubtreeInclusion(index, start, end uint64) (Nodes, error) {
62-
if start >= end {
63-
return Nodes{}, fmt.Errorf("start %d greater than or equal to end %d", start, end)
62+
if err := isSubtreeValid(start, end); err != nil {
63+
return Nodes{}, fmt.Errorf("subtree invalid: %v", err)
6464
}
6565
if index < start || index >= end {
6666
return Nodes{}, fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
6767
}
68-
if err := isSubtreeValid(start, end); err != nil {
69-
return Nodes{}, fmt.Errorf("subtree invalid: %v", err)
70-
}
7168

7269
// Shift the subtree to the left, such that it starts at 0.
7370
p := nodes(index-start, 0, end-start).skipFirst()
@@ -229,20 +226,27 @@ func reverse(ids []compact.NodeID) {
229226
// - no extra node to the left of the subtree
230227
// - potentially extra nodes to the right of the subtree
231228
func isSubtreeValid(start, end uint64) error {
232-
l := end - start
229+
if start >= end {
230+
return fmt.Errorf("start %d must be strictly less than end %d", start, end)
231+
}
233232
if start == 0 {
234233
return nil
235-
} else if l > uint64(1)<<63 {
236-
// special-case large subtree to avoid panic
237-
return fmt.Errorf("start %d must be 0 when subtree length %d > 1<<63. ", start, l)
238234
}
239-
if bc := bitCeil(l); start%bc != 0 {
235+
236+
l := end - start
237+
238+
// special-case large subtree to avoid panic
239+
if l > uint64(1)<<63 {
240+
return fmt.Errorf("start %d must be 0 when subtree length %d > 1<<63", start, l)
241+
}
242+
if bc := bitCeil(l); start&(bc-1) != 0 {
240243
return fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
241244
}
245+
242246
return nil
243247
}
244248

245-
// bitCeil returns the smallest power of 2 larger than n.
249+
// bitCeil returns the smallest power of 2 larger than or equal to n.
246250
// MUST NOT be used with n larger than uint64(1)<<63.
247251
func bitCeil(n uint64) uint64 {
248252
if n <= 1 {

proof/proof_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
425425
const maxSize = uint64(555)
426426
for sbe := uint64(1); sbe <= maxSize; sbe++ {
427427
for sbs := range sbe {
428-
if err := isSubtreeValid(sbs, sbe); err != nil {
428+
if err := IsSubtreeValid(sbs, sbe); err != nil {
429429
continue
430430
}
431431
for i := sbs; i < sbe; i++ {

proof/verify.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,12 @@ func VerifyInclusion(hasher merkle.LogHasher, index, size uint64, leafHash []byt
5959
// - start to be a multiple of the smallest power of two greater than or equal to
6060
// (end - start)
6161
func VerifySubtreeInclusion(hasher merkle.LogHasher, index, start, end uint64, leafHash []byte, proof [][]byte, root []byte) error {
62-
if start >= end {
63-
return fmt.Errorf("start %d greater than or equal to end %d", start, end)
62+
if err := IsSubtreeValid(start, end); err != nil {
63+
return fmt.Errorf("subtree invalid: %v", err)
6464
}
6565
if index < start || index >= end {
6666
return fmt.Errorf("index %d out of bounds for subtree [%d, %d)", index, start, end)
6767
}
68-
if err := isSubtreeValid(start, end); err != nil {
69-
return fmt.Errorf("subtree invalid: %v", err)
70-
}
7168
calcRoot, err := RootFromInclusionProof(hasher, index-start, end-start, leafHash, proof)
7269
if err != nil {
7370
return err

testonly/tree.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
package testonly
1616

1717
import (
18+
"fmt"
19+
"math/bits"
20+
1821
"github.com/transparency-dev/merkle"
1922
"github.com/transparency-dev/merkle/compact"
2023
"github.com/transparency-dev/merkle/proof"
@@ -143,3 +146,38 @@ func (t *Tree) getNodes(ids []compact.NodeID) [][]byte {
143146
}
144147
return hashes
145148
}
149+
150+
// isSubtreeValid returns whether a subtree covers a valid range.
151+
// A subtree is valid if there exist a parent tree node to:
152+
// - all the subtree nodes
153+
// - no extra node to the left of the subtree
154+
// - potentially extra nodes to the right of the subtree
155+
func isSubtreeValid(start, end uint64) error {
156+
if start >= end {
157+
return fmt.Errorf("start %d must be strictly less than end %d", start, end)
158+
}
159+
if start == 0 {
160+
return nil
161+
}
162+
163+
l := end - start
164+
165+
// special-case large subtree to avoid panic
166+
if l > uint64(1)<<63 {
167+
return fmt.Errorf("start %d must be 0 when subtree length %d > 1<<63", start, l)
168+
}
169+
if bc := bitCeil(l); start&(bc-1) != 0 {
170+
return fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
171+
}
172+
173+
return nil
174+
}
175+
176+
// bitCeil returns the smallest power of 2 larger than or equal to n.
177+
// MUST NOT be used with n larger than uint64(1)<<63.
178+
func bitCeil(n uint64) uint64 {
179+
if n <= 1 {
180+
return 1
181+
}
182+
return uint64(1) << bits.Len64(n-1)
183+
}

testonly/tree_fuzz_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package testonly
33
import (
44
"bytes"
55
"math"
6-
"math/bits"
76
"testing"
87

98
"github.com/google/go-cmp/cmp"
@@ -86,8 +85,7 @@ func FuzzSubtreeInclusionProofAndVerify(f *testing.F) {
8685
if end >= math.MaxUint16 {
8786
return
8887
}
89-
t.Logf("index=%d, start=%d, end=%d", index, start, end)
90-
if start >= end {
88+
if err := isSubtreeValid(start, end); err != nil {
9189
return
9290
}
9391
if index < start {
@@ -96,9 +94,7 @@ func FuzzSubtreeInclusionProofAndVerify(f *testing.F) {
9694
if index >= end {
9795
return
9896
}
99-
if bc := uint64(1) << bits.Len64(end-start-1); start%bc != 0 {
100-
return
101-
}
97+
t.Logf("index=%d, start=%d, end=%d", index, start, end)
10298
tree := newTree(genEntries(end))
10399
p, err := tree.SubtreeInclusionProof(index, start, end)
104100
t.Logf("proof=%v", p)

0 commit comments

Comments
 (0)