Skip to content

Commit 141d627

Browse files
committed
testonly + reftests
tests: better comments, remove prints
1 parent f371b77 commit 141d627

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

testonly/reference_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,67 @@ func refConsistencyProof(entries [][]byte, size2, size1 uint64, hasher merkle.Lo
102102
refRootHash(entries[:split], hasher))
103103
}
104104

105+
// refSubtreeConsistencyProof returns the subtree consistency proof for the
106+
// subtree [start, end) in a Merkle tree with the given entries and size.
107+
// This is a reference implementation for cross-checking.
108+
func refSubtreeConsistencyProof(entries [][]byte, size, start, end uint64, hasher merkle.LogHasher, haveRoot1 bool) [][]byte {
109+
if start >= end {
110+
return nil
111+
}
112+
if end == 0 || end > size {
113+
return nil
114+
}
115+
// Consistency proof between a tree and itself is empty.
116+
if start == 0 && end == size {
117+
// Record the hash of this subtree if it's not the root for which the proof
118+
// was originally requested (which happens when [start, end) is a full subtree).
119+
if !haveRoot1 {
120+
return [][]byte{refRootHash(entries[:size], hasher)}
121+
}
122+
return nil
123+
}
124+
125+
// At this point: end < size.
126+
split := downToPowerOfTwo(size)
127+
switch {
128+
// The subtree is on the left of split. Prove that the subtree is consistent
129+
// with the subtree on the left of split, and record the root of the right
130+
// subtree.
131+
case end <= split:
132+
return append(
133+
refSubtreeConsistencyProof(entries[:split], split, start, end, hasher, haveRoot1),
134+
refRootHash(entries[split:], hasher))
135+
// The subtree is on the right of split. Prove that the subtree is consistent
136+
// with the subtree on the right of split, and record the root of the left
137+
// subtree.
138+
case split <= start:
139+
return append(
140+
refSubtreeConsistencyProof(entries[split:], size-split, start-split, end-split, hasher, haveRoot1),
141+
refRootHash(entries[:split], hasher))
142+
// Otherwise, split is between start and end.
143+
// This means that start is 0.
144+
// Prove that the subtree is consistent with the subtree on right of split,
145+
// and record the root of the left subtree.
146+
//
147+
// Proof that start is 0:
148+
// With C = bitCeil(len([start, end))):
149+
// - By definition, end - start <= C.
150+
// - Since the subtree is valid, start is a multiple of C (start = k * C).
151+
// - In this case, start < split < end <= start + C and
152+
// so k * C < split < (k+1) * C
153+
// - Since split and C are both powers of 2:
154+
// - If split < C, then if k >= 1, split < C <= start, contradicting
155+
// start < split.
156+
// - If split >= C, split must be a multiple of C, but no multiple of
157+
// C lies strictly between k * C and (k + 1) * C.
158+
// - Thus, k must be 0, meaning start is 0.
159+
default:
160+
return append(
161+
refSubtreeConsistencyProof(entries[split:], size-split, 0, end-split, hasher, false),
162+
refRootHash(entries[:split], hasher))
163+
}
164+
}
165+
105166
// downToPowerOfTwo returns the largest power of two smaller than x.
106167
func downToPowerOfTwo(x uint64) uint64 {
107168
if x < 2 {

testonly/tree.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,18 @@ func (t *Tree) ConsistencyProof(size1, size2 uint64) ([][]byte, error) {
139139
return nodes.Rehash(t.getNodes(nodes.IDs), t.hasher.HashChildren)
140140
}
141141

142+
// SubtreeConsistencyProof returns the subtree consistency proof between the
143+
// [start, end) subtree and a parent tree of size |size|.
144+
// It requires end <= Size(), and size <= Size(). May panic otherwise.
145+
// May return an error if the subtree boundaries are not valid.
146+
func (t *Tree) SubtreeConsistencyProof(start, end, size uint64) ([][]byte, error) {
147+
nodes, err := proof.SubtreeConsistency(start, end, size)
148+
if err != nil {
149+
return nil, err
150+
}
151+
return nodes.Rehash(t.getNodes(nodes.IDs), t.hasher.HashChildren)
152+
}
153+
142154
func (t *Tree) getNodes(ids []compact.NodeID) [][]byte {
143155
hashes := make([][]byte, len(ids))
144156
for i, id := range ids {

testonly/tree_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,43 @@ func TestTreeConsistencyProofFuzz(t *testing.T) {
196196
}
197197
}
198198

199+
func TestSubtreeTreeConsistencyProof(t *testing.T) {
200+
entries := LeafInputs()
201+
mt := newTree(entries)
202+
validateTree(t, mt, 8)
203+
204+
if _, err := mt.SubtreeConsistencyProof(0, 6, 3); err == nil {
205+
t.Error("SubtreeConsistencyProof(0, 6, 3) succeeded unexpectedly (size < end)")
206+
}
207+
if _, err := mt.SubtreeConsistencyProof(3, 3, 8); err == nil {
208+
t.Error("SubtreeConsistencyProof(3, 3, 8) succeeded unexpectedly (start >= end)")
209+
}
210+
if _, err := mt.SubtreeConsistencyProof(1, 3, 8); err == nil {
211+
t.Error("SubtreeConsistencyProof(1, 3, 8) succeeded unexpectedly (invalid subtree)")
212+
}
213+
214+
maxSize := uint64(len(entries))
215+
for end := uint64(1); end <= maxSize; end++ {
216+
for size := end; size <= maxSize; size++ {
217+
for start := range end {
218+
if err := isSubtreeValid(start, end); err != nil {
219+
continue
220+
}
221+
t.Run(fmt.Sprintf("%d:%d:%d", start, end, size), func(t *testing.T) {
222+
got, err := mt.SubtreeConsistencyProof(start, end, size)
223+
if err != nil {
224+
t.Fatalf("SubtreeConsistencyProof: %v", err)
225+
}
226+
want := refSubtreeConsistencyProof(entries[:size], size, start, end, mt.hasher, true)
227+
if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" {
228+
t.Errorf("SubtreeConsistencyProof: diff (-got +want)\n%s", diff)
229+
}
230+
})
231+
}
232+
}
233+
}
234+
}
235+
199236
func TestTreeAppend(t *testing.T) {
200237
entries := genEntries(256)
201238
mt1 := newTree(entries)

0 commit comments

Comments
 (0)