Skip to content

Commit 0845efa

Browse files
authored
Consistency proof generation tests against reference implementation (transparency-dev#241)
* testonly + reftests tests: better comments, remove prints * comments
1 parent f371b77 commit 0845efa

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

testonly/reference_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,69 @@ 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 based on the recursive algorithm from
108+
// the RFC to be used for cross-checking only.
109+
func refSubtreeConsistencyProof(start, end uint64, entries [][]byte, known bool, hasher merkle.LogHasher) [][]byte {
110+
size := uint64(len(entries))
111+
if start >= end {
112+
return nil
113+
}
114+
if end == 0 || end > size {
115+
return nil
116+
}
117+
// Consistency proof between a tree and itself is empty.
118+
if start == 0 && end == size {
119+
// Record the hash of this subtree if it's not the root for which the proof
120+
// was originally requested (which happens when [start, end) is a full subtree).
121+
if !known {
122+
return [][]byte{refRootHash(entries[:size], hasher)}
123+
}
124+
return nil
125+
}
126+
127+
// At this point: end < size.
128+
split := downToPowerOfTwo(size)
129+
switch {
130+
// The subtree is on the left of split. Prove that the subtree is consistent
131+
// with the subtree on the left of split, and record the root of the right
132+
// subtree.
133+
case end <= split:
134+
return append(
135+
refSubtreeConsistencyProof(start, end, entries[:split], known, hasher),
136+
refRootHash(entries[split:], hasher))
137+
// The subtree is on the right of split. Prove that the subtree is consistent
138+
// with the subtree on the right of split, and record the root of the left
139+
// subtree.
140+
case split <= start:
141+
return append(
142+
refSubtreeConsistencyProof(start-split, end-split, entries[split:], known, hasher),
143+
refRootHash(entries[:split], hasher))
144+
// Otherwise, split is between start and end.
145+
// This means that start is 0.
146+
// Prove that the subtree is consistent with the subtree on right of split,
147+
// and record the root of the left subtree.
148+
//
149+
// Proof that start is 0:
150+
// With C = bitCeil(len([start, end))):
151+
// - By definition, end - start <= C.
152+
// - Since the subtree is valid, start is a multiple of C (start = k * C).
153+
// - In this case, start < split < end <= start + C and
154+
// so k * C < split < (k+1) * C
155+
// - Since split and C are both powers of 2:
156+
// - If split < C, then if k >= 1, split < C <= start, contradicting
157+
// start < split.
158+
// - If split >= C, split must be a multiple of C, but no multiple of
159+
// C lies strictly between k * C and (k + 1) * C.
160+
// - Thus, k must be 0, meaning start is 0.
161+
default:
162+
return append(
163+
refSubtreeConsistencyProof(0, end-split, entries[split:], false, hasher),
164+
refRootHash(entries[:split], hasher))
165+
}
166+
}
167+
105168
// downToPowerOfTwo returns the largest power of two smaller than x.
106169
func downToPowerOfTwo(x uint64) uint64 {
107170
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(start, end, entries[:size], true, mt.hasher)
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)