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