@@ -16,6 +16,7 @@ package proof
1616
1717import (
1818 "fmt"
19+ "math/bits"
1920 "testing"
2021
2122 "github.com/google/go-cmp/cmp"
@@ -130,6 +131,179 @@ func TestInclusion(t *testing.T) {
130131 }
131132}
132133
134+ // TestSubtreeInclusion contains subtree inclusion proof tests. For reference, consider the
135+ // following example of a tree from RFC 6962:
136+ //
137+ // aaaaa <== Level 4
138+ // / \
139+ // ... ...
140+ // / \
141+ // / \
142+ // / \
143+ // aaaa bbbb <== Level 3
144+ // / \ / \
145+ // / \ / \
146+ // / \ / \
147+ // / \ / \
148+ // / \ / \
149+ // aaa bbb ccc ddd <== Level 2
150+ // / \ / \ / \ / \
151+ // / \ / \ / \ / \
152+ // / \ / \ / \ / \
153+ // aa bb cc dd ee ff gg hh ii <== Level 1
154+ // / \ / \ / \ / \ / \ / \ / \ / \ / \
155+ // a b c d e f g h i j k l m n o p q r <== Level 0
156+ // | | | | | | | | | | | | | | | | | |
157+ // d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d12 d14 d16
158+ // | | | |
159+ // d11 d13 d15 d17
160+ //
161+ // Our storage node layers are always populated from the bottom up, hence the
162+ // gaps above ii.
163+ func TestSubtreeInclusion (t * testing.T ) {
164+ id := compact .NewNodeID
165+ nodes := func (ids ... compact.NodeID ) Nodes {
166+ return Nodes {IDs : ids }
167+ }
168+ rehash := func (begin , end int , ids ... compact.NodeID ) Nodes {
169+ return Nodes {IDs : ids , begin : begin , end : end }
170+ }
171+ for _ , tc := range []struct {
172+ index uint64 // Leaf index in the requested tree.
173+ start uint64 // The smallest index of the subtree.
174+ end uint64 // The largest index of the subtree + 1.
175+ want Nodes
176+ wantErr bool
177+ }{
178+ // Errors.
179+ {start : 0 , end : 0 , index : 0 , wantErr : true }, // everything at 0
180+ {start : 1 , end : 1 , index : 0 , wantErr : true }, // start = end
181+ {start : 2 , end : 1 , index : 0 , wantErr : true }, // start > end
182+ {start : 1 , end : 2 , index : 0 , wantErr : true }, // index out of bounds left
183+ {start : 0 , end : 2 , index : 3 , wantErr : true }, // index out of bounds right
184+ {start : 0 , end : 3 , index : 3 , wantErr : true }, // index out of bounds right
185+ {start : 3 , end : 5 , index : 3 , wantErr : true }, // start not multiple of bit_ceil(len)
186+ {start : 1 , end : 1 << 63 + 2 , index : 1 , wantErr : true }, // start not multiple of bit_ceil(len) with big tree
187+
188+ // Small trees.
189+ {start : 0 , end : 1 , index : 0 , want : Nodes {IDs : []compact.NodeID {}}},
190+ {start : 0 , end : 2 , index : 0 , want : nodes (id (0 , 1 ))}, // b
191+ {start : 0 , end : 2 , index : 1 , want : nodes (id (0 , 0 ))}, // a
192+ {start : 0 , end : 3 , index : 1 , want : rehash (1 , 2 , id (0 , 0 ), id (0 , 2 ))}, // a c
193+
194+ // Small subtrees.
195+ // Small tree shifted by bit_ceil(len).
196+ {start : 1 , end : 2 , index : 1 , want : Nodes {IDs : []compact.NodeID {}}},
197+ {start : 2 , end : 3 , index : 2 , want : Nodes {IDs : []compact.NodeID {}}},
198+ {start : 7 , end : 8 , index : 7 , want : Nodes {IDs : []compact.NodeID {}}},
199+ {start : 2 , end : 4 , index : 2 , want : nodes (id (0 , 3 ))}, // d
200+ {start : 2 , end : 4 , index : 3 , want : nodes (id (0 , 2 ))}, // c
201+ {start : 4 , end : 7 , index : 4 , want : rehash (1 , 2 ,
202+ id (0 , 5 ), id (0 , 6 ))}, // f, j
203+ {start : 4 , end : 7 , index : 6 , want : nodes (id (1 , 2 ))}, // i
204+
205+ // Tree of size 7.
206+ {start : 0 , end : 7 , index : 0 , want : rehash (2 , 4 , // bbb=hash(cc,g)
207+ id (0 , 1 ), id (1 , 1 ), id (0 , 6 ), id (1 , 2 ))}, // b bb g cc
208+ {start : 0 , end : 7 , index : 1 , want : rehash (2 , 4 , // bbb=hash(cc,g)
209+ id (0 , 0 ), id (1 , 1 ), id (0 , 6 ), id (1 , 2 ))}, // a bb g cc
210+ {start : 0 , end : 7 , index : 2 , want : rehash (2 , 4 , // bbb=hash(cc,g)
211+ id (0 , 3 ), id (1 , 0 ), id (0 , 6 ), id (1 , 2 ))}, // d aa g cc
212+ {start : 0 , end : 7 , index : 3 , want : rehash (2 , 4 , // bbb=hash(cc,g)
213+ id (0 , 2 ), id (1 , 0 ), id (0 , 6 ), id (1 , 2 ))}, // c aa g cc
214+ {start : 0 , end : 7 , index : 4 , want : rehash (1 , 2 ,
215+ id (0 , 5 ), id (0 , 6 ), id (2 , 0 ))}, // f g aaa
216+ {start : 0 , end : 7 , index : 5 , want : rehash (1 , 2 ,
217+ id (0 , 4 ), id (0 , 6 ), id (2 , 0 ))}, // e g aaa
218+ {start : 0 , end : 7 , index : 6 , want : nodes (id (1 , 2 ), id (2 , 0 ))}, // i k
219+
220+ // Subtree of size 7.
221+ // Tree of size 7 shifted by bit_ceil(len).
222+ {start : 8 , end : 15 , index : 8 , want : rehash (2 , 4 , // ddd=hash(gg,o)
223+ id (0 , 9 ), id (1 , 5 ), id (0 , 14 ), id (1 , 6 ))}, // j ff o gg
224+ {start : 8 , end : 15 , index : 9 , want : rehash (2 , 4 , // ddd=hash(gg,o)
225+ id (0 , 8 ), id (1 , 5 ), id (0 , 14 ), id (1 , 6 ))}, // j ff o gg
226+ {start : 8 , end : 15 , index : 10 , want : rehash (2 , 4 , // ddd=hash(gg,o)
227+ id (0 , 11 ), id (1 , 4 ), id (0 , 14 ), id (1 , 6 ))}, // l ee o gg
228+ {start : 8 , end : 15 , index : 11 , want : rehash (2 , 4 , // ddd=hash(gg, o)
229+ id (0 , 10 ), id (1 , 4 ), id (0 , 14 ), id (1 , 6 ))}, // k ee o gg
230+ {start : 8 , end : 15 , index : 12 , want : rehash (1 , 2 ,
231+ id (0 , 13 ), id (0 , 14 ), id (2 , 2 ))}, // n o ccc
232+ {start : 8 , end : 15 , index : 13 , want : rehash (1 , 2 ,
233+ id (0 , 12 ), id (0 , 14 ), id (2 , 2 ))}, // m o ccc
234+ {start : 8 , end : 15 , index : 14 , want : nodes (id (1 , 6 ), id (2 , 2 ))}, // gg ccc
235+
236+ // Smaller trees within a bigger stored tree.
237+ // start = 0
238+ {start : 0 , end : 4 , index : 2 , want : nodes (id (0 , 3 ), id (1 , 0 ))}, // d aa
239+ {start : 0 , end : 5 , index : 3 , want : rehash (2 , 3 , id (0 , 2 ), id (1 , 0 ), id (0 , 4 ))}, // c aa e
240+ {start : 0 , end : 6 , index : 3 , want : rehash (2 , 3 , id (0 , 2 ), id (1 , 0 ), id (1 , 2 ))}, // c aa i
241+ {start : 0 , end : 6 , index : 4 , want : nodes (id (0 , 5 ), id (2 , 0 ))}, // f aaa
242+ {start : 0 , end : 7 , index : 1 , want : rehash (2 , 4 , // bbb=hash(cc,g)
243+ id (0 , 0 ), id (1 , 1 ), id (0 , 6 ), id (1 , 2 ))}, // a bb g cc
244+ {start : 0 , end : 7 , index : 3 , want : rehash (2 , 4 , // bbb=hash(cc,g)
245+ id (0 , 2 ), id (1 , 0 ), id (0 , 6 ), id (1 , 2 ))}, // c aa g cc
246+ // Shifted by bit_ceil(len).
247+ {start : 4 , end : 8 , index : 6 , want : nodes (id (0 , 7 ), id (1 , 2 ))}, // h cc
248+ {start : 8 , end : 13 , index : 11 , want : rehash (2 , 3 , id (0 , 10 ), id (1 , 4 ), id (0 , 12 ))}, // k ee m
249+ {start : 8 , end : 14 , index : 11 , want : rehash (2 , 3 , id (0 , 10 ), id (1 , 4 ), id (1 , 6 ))}, // k, ee, gg
250+ {start : 8 , end : 14 , index : 12 , want : nodes (id (0 , 13 ), id (2 , 2 ))}, // n ccc
251+ {start : 8 , end : 15 , index : 9 , want : rehash (2 , 4 , // ddd=hash(gg,o)
252+ id (0 , 8 ), id (1 , 5 ), id (0 , 14 ), id (1 , 6 ))}, // i ff o gg
253+ {start : 8 , end : 15 , index : 11 , want : rehash (2 , 4 , // bbb=hash(cc,g)
254+ id (0 , 10 ), id (1 , 4 ), id (0 , 14 ), id (1 , 6 ))}, // k ff q gg
255+
256+ // Some rehashes in the middle of the returned list.
257+ {start : 0 , end : 15 , index : 10 , want : rehash (2 , 4 ,
258+ id (0 , 11 ), id (1 , 4 ),
259+ id (0 , 14 ), id (1 , 6 ),
260+ id (3 , 0 ),
261+ )},
262+ {start : 16 , end : 31 , index : 26 , want : rehash (2 , 4 ,
263+ id (0 , 27 ), id (1 , 12 ),
264+ id (0 , 30 ), id (1 , 14 ),
265+ id (3 , 2 ),
266+ )},
267+ {start : 0 , end : 31 , index : 24 , want : rehash (2 , 4 ,
268+ id (0 , 25 ), id (1 , 13 ),
269+ id (0 , 30 ), id (1 , 14 ),
270+ id (3 , 2 ), id (4 , 0 ),
271+ )},
272+ {start : 32 , end : 63 , index : 56 , want : rehash (2 , 4 ,
273+ id (0 , 57 ), id (1 , 29 ),
274+ id (0 , 62 ), id (1 , 30 ),
275+ id (3 , 6 ), id (4 , 2 ),
276+ )},
277+ {start : 0 , end : 95 , index : 81 , want : rehash (3 , 6 ,
278+ id (0 , 80 ), id (1 , 41 ), id (2 , 21 ),
279+ id (0 , 94 ), id (1 , 46 ), id (2 , 22 ),
280+ id (4 , 4 ), id (6 , 0 ),
281+ )},
282+ {start : 128 , end : 223 , index : 209 , want : rehash (3 , 6 ,
283+ id (0 , 208 ), id (1 , 105 ), id (2 , 53 ),
284+ id (0 , 222 ), id (1 , 110 ), id (2 , 54 ),
285+ id (4 , 12 ), id (6 , 2 ),
286+ )},
287+ } {
288+ t .Run (fmt .Sprintf ("%d:%d:%d" , tc .start , tc .end , tc .index ), func (t * testing.T ) {
289+ proof , err := SubtreeInclusion (tc .index , tc .start , tc .end )
290+ if tc .wantErr {
291+ if err == nil {
292+ t .Fatal ("accepted bad params" )
293+ }
294+ return
295+ } else if err != nil {
296+ t .Fatalf ("Inclusion: %v" , err )
297+ }
298+ // Ignore the ephemeral node, it is tested separately.
299+ proof .ephem = compact.NodeID {}
300+ if diff := cmp .Diff (tc .want , proof , cmp .AllowUnexported (Nodes {})); diff != "" {
301+ t .Errorf ("paths mismatch:\n %v" , diff )
302+ }
303+ })
304+ }
305+ }
306+
133307// TestConsistency contains consistency proof tests. For reference, consider
134308// the following example:
135309//
@@ -248,6 +422,22 @@ func TestInclusionSucceedsUpToTreeSize(t *testing.T) {
248422 }
249423}
250424
425+ func TestInclusionSubtreeSucceedsUpToTreeSize (t * testing.T ) {
426+ const maxSize = uint64 (555 )
427+ for sbe := uint64 (1 ); sbe <= maxSize ; sbe ++ {
428+ for sbs := uint64 (0 ); sbs < sbe ; sbs ++ {
429+ if bc := uint64 (1 ) << bits .Len64 (sbe - sbs - 1 ); bc != 0 {
430+ continue
431+ }
432+ for i := sbs ; i < sbe ; i ++ {
433+ if _ , err := SubtreeInclusion (i , sbs , sbe ); err != nil {
434+ t .Errorf ("SubtreeInclusion(i:%d, sbs:%d, sbe: %d) = %v" , i , sbs , sbe , err )
435+ }
436+ }
437+ }
438+ }
439+ }
440+
251441func TestConsistencySucceedsUpToTreeSize (t * testing.T ) {
252442 const maxSize = uint64 (100 )
253443 for s1 := uint64 (1 ); s1 < maxSize ; s1 ++ {
@@ -305,6 +495,73 @@ func TestEphem(t *testing.T) {
305495 }
306496}
307497
498+ func TestEphemSubtree (t * testing.T ) {
499+ id := compact .NewNodeID
500+ for _ , tc := range []struct {
501+ index uint64
502+ start uint64
503+ end uint64
504+ want compact.NodeID
505+ }{
506+ // Edge case: For perfect trees (resp. subtree) the ephemeral node is the
507+ // sibling of the root (resp subtree root). However, it will not be used in
508+ // the proof, as the corresponding subtree is empty.
509+ {index : 3 , start : 0 , end : 32 , want : id (5 , 1 )},
510+ {index : 35 , start : 32 , end : 64 , want : id (5 , 2 )},
511+
512+ // start = 0
513+ {index : 0 , start : 0 , end : 9 , want : id (3 , 1 )},
514+ {index : 0 , start : 0 , end : 13 , want : id (3 , 1 )},
515+ {index : 7 , start : 0 , end : 13 , want : id (3 , 1 )},
516+ {index : 8 , start : 0 , end : 13 , want : id (2 , 3 )},
517+ {index : 11 , start : 0 , end : 13 , want : id (2 , 3 )},
518+ // More edge cases when the computed ephemeral node is not used in the
519+ // proof, because it is fully outside the tree border.
520+ {index : 12 , start : 0 , end : 13 , want : id (0 , 13 )},
521+ {index : 13 , start : 0 , end : 14 , want : id (1 , 7 )},
522+ // Shifted by bit_ceil(len).
523+ {index : 16 , start : 16 , end : 25 , want : id (3 , 3 )},
524+ {index : 16 , start : 16 , end : 29 , want : id (3 , 3 )},
525+ {index : 23 , start : 16 , end : 29 , want : id (3 , 3 )},
526+ {index : 24 , start : 16 , end : 29 , want : id (2 , 7 )},
527+ {index : 27 , start : 16 , end : 29 , want : id (2 , 7 )},
528+ // More edge cases when the computed ephemeral node is not used in the
529+ // proof, because it is fully outside the tree border.
530+ {index : 28 , start : 16 , end : 29 , want : id (0 , 29 )},
531+ {index : 29 , start : 16 , end : 30 , want : id (1 , 15 )},
532+
533+ // There is only one node (level 0, index 1024) in the right subtree, but
534+ // the ephemeral node is at level 10 rather than level 0. This is because
535+ // for the purposes of the proof this node is *effectively* at level 10.
536+ {index : 123 , start : 0 , end : 1025 , want : id (10 , 1 )},
537+ // Shifted by bit_ceil(len).
538+ {index : 2171 , start : 2048 , end : 3073 , want : id (10 , 3 )},
539+
540+ {index : 0 , start : 0 , end : 0xFFFF , want : id (15 , 1 )},
541+ {index : 0xF000 , start : 0 , end : 0xFFFF , want : id (11 , 0x1F )},
542+ {index : 0xFF00 , start : 0 , end : 0xFFFF , want : id (7 , 0x1FF )},
543+ {index : 0xFFF0 , start : 0 , end : 0xFFFF , want : id (3 , 0x1FFF )},
544+ {index : 0xFFFF - 1 , start : 0 , end : 0xFFFF , want : id (0 , 0xFFFF )},
545+ // Shifted by bit_ceil(len).
546+ {index : 0x10000 , start : 0x10000 , end : 0x1FFFF , want : id (15 , 3 )},
547+ {index : 0x1F000 , start : 0x10000 , end : 0x1FFFF , want : id (11 , 0x3F )},
548+ {index : 0x1FF00 , start : 0x10000 , end : 0x1FFFF , want : id (7 , 0x3FF )},
549+ {index : 0x1FFF0 , start : 0x10000 , end : 0x1FFFF , want : id (3 , 0x3FFF )},
550+ {index : 0x1FFFF - 1 , start : 0x10000 , end : 0x1FFFF , want : id (0 , 0x1FFFF )},
551+ } {
552+ t .Run (fmt .Sprintf ("%d:%d:%d" , tc .index , tc .start , tc .end ), func (t * testing.T ) {
553+ nodes , err := SubtreeInclusion (tc .index , tc .start , tc .end )
554+ if err != nil {
555+ t .Fatalf ("SubtreeInclusion: %v" , err )
556+ }
557+ got , _ , _ := nodes .Ephem ()
558+ if want := tc .want ; got != want {
559+ t .Errorf ("Ephem: got %+v, want %+v" , got , want )
560+ }
561+ })
562+ }
563+ }
564+
308565func TestRehash (t * testing.T ) {
309566 th := rfc6962 .DefaultHasher
310567 h := [][]byte {
0 commit comments