Skip to content

Commit 920a621

Browse files
committed
testonly + reftests
tests: better comments, remove prints
1 parent 255e881 commit 920a621

3 files changed

Lines changed: 102 additions & 0 deletions

File tree

testonly/reference_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,65 @@ 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+
// - C is the largest power of 2 within [start, end). Otherwise
150+
// [start, end) would be at least 2C long, which contradicts
151+
// C's definition.
152+
// - `split` is a power of two smaller than end. Since C is the largest
153+
// power of 2 within [start, end), split must be smaller than C.
154+
// - This gives us: `start` < split < C.
155+
// - Since `start` is a multiple of C, and `start` is smaller than C,
156+
// `start` MUST be 0.
157+
default:
158+
return append(
159+
refSubtreeConsistencyProof(entries[split:], size-split, 0, end-split, hasher, false),
160+
refRootHash(entries[:split], hasher))
161+
}
162+
}
163+
105164
// downToPowerOfTwo returns the largest power of two smaller than x.
106165
func downToPowerOfTwo(x uint64) uint64 {
107166
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,37 @@ 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")
206+
}
207+
208+
maxSize := uint64(len(entries))
209+
for end := uint64(1); end <= maxSize; end++ {
210+
for size := end; size <= maxSize; size++ {
211+
for start := range end {
212+
if err := isSubtreeValid(start, end); err != nil {
213+
continue
214+
}
215+
t.Run(fmt.Sprintf("%d:%d:%d", start, end, size), func(t *testing.T) {
216+
got, err := mt.SubtreeConsistencyProof(start, end, size)
217+
if err != nil {
218+
t.Fatalf("SubtreeConsistencyProof: %v", err)
219+
}
220+
want := refSubtreeConsistencyProof(entries[:size], size, start, end, mt.hasher, true)
221+
if diff := cmp.Diff(got, want, cmpopts.EquateEmpty()); diff != "" {
222+
t.Errorf("SubtreeConsistencyProof: diff (-got +want)\n%s", diff)
223+
}
224+
})
225+
}
226+
}
227+
}
228+
}
229+
199230
func TestTreeAppend(t *testing.T) {
200231
entries := genEntries(256)
201232
mt1 := newTree(entries)

0 commit comments

Comments
 (0)