@@ -19,6 +19,7 @@ import (
1919 "encoding/json"
2020 "fmt"
2121 "log"
22+ "math/bits"
2223 "os"
2324 "path/filepath"
2425 "strconv"
@@ -109,6 +110,10 @@ var (
109110 }
110111)
111112
113+ // =============================================================================
114+ // Inclusion Proofs
115+ // =============================================================================
116+
112117// inclusionProbe is a parameter set for inclusion proof verification.
113118type inclusionProbe struct {
114119 LeafIdx uint64 `json:"leafIdx"`
@@ -121,15 +126,18 @@ type inclusionProbe struct {
121126 WantError bool `json:"wantErr"`
122127}
123128
124- func inclusionProbes (rootDir string ) error {
129+ // incProbeWriter writes an inclusionProbe to disk.
130+ type incProbeWriter func (dir string , probe inclusionProbe ) error
131+
132+ func generateInclusionProbes (rootDir string , write incProbeWriter ) error {
125133 for i , p := range inclusionProofs {
126134 dir := filepath .Join (rootDir , strconv .Itoa (i ))
127135 if err := os .MkdirAll (dir , 0755 ); err != nil {
128136 return err
129137 }
130138
131139 leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [p .leafIdx - 1 ])
132- if err := corruptedInclusionProbes (dir , p .leafIdx - 1 , p .size , p .proof , roots [p .size - 1 ], leafHash ); err != nil {
140+ if err := corruptedInclusionProbes (dir , p .leafIdx - 1 , p .size , p .proof , roots [p .size - 1 ], leafHash , write ); err != nil {
133141 return err
134142 }
135143 }
@@ -139,7 +147,7 @@ func inclusionProbes(rootDir string) error {
139147 return err
140148 }
141149
142- if err := staticInclusionProbes (staticDir ); err != nil {
150+ if err := staticInclusionProbes (staticDir , write ); err != nil {
143151 return err
144152 }
145153
@@ -148,7 +156,7 @@ func inclusionProbes(rootDir string) error {
148156 return err
149157 }
150158
151- if err := singleEntryInclusionProbes (singleEntryDir ); err != nil {
159+ if err := singleEntryInclusionProbes (singleEntryDir , write ); err != nil {
152160 return err
153161 }
154162
@@ -197,23 +205,23 @@ func invalidInclusionProof(leafIdx, treeSize uint64, proof [][]byte, root, leafH
197205 return ret
198206}
199207
200- func corruptedInclusionProbes (dir string , leafIdx , treeSize uint64 , proof [][]byte , root , leafHash []byte ) error {
208+ func corruptedInclusionProbes (dir string , leafIdx , treeSize uint64 , proof [][]byte , root , leafHash []byte , write incProbeWriter ) error {
201209 happyPath := inclusionProbe {leafIdx , treeSize , root , leafHash , proof , "happy path" , false }
202- if err := writeInclusionProbe (dir , happyPath ); err != nil {
203- return nil
210+ if err := write (dir , happyPath ); err != nil {
211+ return err
204212 }
205213
206214 probes := invalidInclusionProof (leafIdx , treeSize , proof , root , leafHash )
207215 for _ , p := range probes {
208- if err := writeInclusionProbe (dir , p ); err != nil {
216+ if err := write (dir , p ); err != nil {
209217 return err
210218 }
211219 }
212220
213221 return nil
214222}
215223
216- func singleEntryInclusionProbes (dir string ) error {
224+ func singleEntryInclusionProbes (dir string , write incProbeWriter ) error {
217225 data := []byte ("data" )
218226 // Root and leaf hash for 1-entry tree are the same.
219227 hash := rfc6962 .DefaultHasher .HashLeaf (data )
@@ -234,15 +242,15 @@ func singleEntryInclusionProbes(dir string) error {
234242 } {
235243 probe := inclusionProbe {0 , 1 , p .root , p .leaf , proof , p .desc , p .wantErr }
236244
237- if err := writeInclusionProbe (dir , probe ); err != nil {
245+ if err := write (dir , probe ); err != nil {
238246 return err
239247 }
240248 }
241249
242250 return nil
243251}
244252
245- func staticInclusionProbes (rootDir string ) error {
253+ func staticInclusionProbes (rootDir string , write incProbeWriter ) error {
246254 proof := [][]byte {}
247255
248256 probes := []struct {
@@ -256,17 +264,17 @@ func staticInclusionProbes(rootDir string) error {
256264 }
257265
258266 randomLeaf := inclusionProbe {p .index , p .size , []byte {}, sha256SomeHash , proof , "random leaf" , true }
259- if err := writeInclusionProbe (dir , randomLeaf ); err != nil {
267+ if err := write (dir , randomLeaf ); err != nil {
260268 return err
261269 }
262270
263271 emptyRoot := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , []byte {}, proof , "empty root" , true }
264- if err := writeInclusionProbe (dir , emptyRoot ); err != nil {
272+ if err := write (dir , emptyRoot ); err != nil {
265273 return err
266274 }
267275
268276 emptyRootRandomLeaf := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , sha256SomeHash , proof , "empty root and random leaf" , true }
269- if err := writeInclusionProbe (dir , emptyRootRandomLeaf ); err != nil {
277+ if err := write (dir , emptyRootRandomLeaf ); err != nil {
270278 return err
271279 }
272280 }
@@ -290,6 +298,201 @@ func writeInclusionProbe(dir string, probe inclusionProbe) error {
290298 return nil
291299}
292300
301+ // =============================================================================
302+ // Subtree Inclusion Proofs
303+ // =============================================================================
304+
305+ // subtreeInclusionProbe is a parameter set for subtree inclusion proof verification.
306+ type subtreeInclusionProbe struct {
307+ LeafIdx uint64 `json:"leafIdx"`
308+ Start uint64 `json:"start"`
309+ End uint64 `json:"end"`
310+ Root []byte `json:"root"`
311+ LeafHash []byte `json:"leafHash"`
312+ Proof [][]byte `json:"proof"`
313+
314+ Desc string `json:"desc"`
315+ WantError bool `json:"wantErr"`
316+ }
317+
318+ // bitCeil returns the smallest power of 2 larger than or equal to n.
319+ // MUST NOT be used with n larger than uint64(1)<<63.
320+ func bitCeil (n uint64 ) uint64 {
321+ if n <= 1 {
322+ return 1
323+ }
324+ return uint64 (1 ) << bits .Len64 (n - 1 )
325+ }
326+
327+ func toSubtreeInclusionProbe (p inclusionProbe ) subtreeInclusionProbe {
328+ return subtreeInclusionProbe {
329+ LeafIdx : p .LeafIdx ,
330+ Start : 0 ,
331+ End : p .TreeSize ,
332+ Root : p .Root ,
333+ LeafHash : p .LeafHash ,
334+ Proof : p .Proof ,
335+ Desc : p .Desc ,
336+ WantError : p .WantError ,
337+ }
338+ }
339+
340+ func shiftSubtreeInclusionProbe (p subtreeInclusionProbe ) subtreeInclusionProbe {
341+ shift := bitCeil (p .End - p .Start )
342+ desc := p .Desc + " - subtree"
343+ leafIdx := p .LeafIdx
344+ if leafIdx < ^ uint64 (0 ) {
345+ leafIdx += shift
346+ }
347+ return subtreeInclusionProbe {
348+ LeafIdx : leafIdx ,
349+ Start : p .Start + shift ,
350+ End : p .End + shift ,
351+ Root : p .Root ,
352+ LeafHash : p .LeafHash ,
353+ Proof : p .Proof ,
354+ Desc : desc ,
355+ WantError : p .WantError ,
356+ }
357+ }
358+
359+ func errorSubtreeInclusionProbes (rootDir string ) error {
360+ dir := filepath .Join (rootDir , "errors" )
361+ if err := os .MkdirAll (dir , 0755 ); err != nil {
362+ return err
363+ }
364+
365+ leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [0 ])
366+
367+ tests := []subtreeInclusionProbe {
368+ {
369+ LeafIdx : 0 ,
370+ Start : 0 ,
371+ End : 0 ,
372+ Root : sha256EmptyTreeHash ,
373+ LeafHash : leafHash ,
374+ Proof : nil ,
375+ Desc : "everything zero" ,
376+ WantError : true ,
377+ },
378+ {
379+ LeafIdx : 0 ,
380+ Start : 1 ,
381+ End : 1 ,
382+ Root : sha256EmptyTreeHash ,
383+ LeafHash : leafHash ,
384+ Proof : nil ,
385+ Desc : "start equals end" ,
386+ WantError : true ,
387+ },
388+ {
389+ LeafIdx : 3 ,
390+ Start : 3 ,
391+ End : 5 ,
392+ Root : sha256EmptyTreeHash ,
393+ LeafHash : leafHash ,
394+ Proof : nil ,
395+ Desc : "invalid subtree" ,
396+ WantError : true ,
397+ },
398+ {
399+ LeafIdx : 1 ,
400+ Start : 1 ,
401+ End : (1 << 63 ) + 2 ,
402+ Root : sha256EmptyTreeHash ,
403+ LeafHash : leafHash ,
404+ Proof : nil ,
405+ Desc : "invalid large subtree" ,
406+ WantError : true ,
407+ },
408+ {
409+ LeafIdx : 0 ,
410+ Start : 1 ,
411+ End : 2 ,
412+ Root : sha256EmptyTreeHash ,
413+ LeafHash : leafHash ,
414+ Proof : nil ,
415+ Desc : "oob left" ,
416+ WantError : true ,
417+ },
418+ {
419+ LeafIdx : 3 ,
420+ Start : 0 ,
421+ End : 2 ,
422+ Root : sha256EmptyTreeHash ,
423+ LeafHash : leafHash ,
424+ Proof : nil ,
425+ Desc : "oob right" ,
426+ WantError : true ,
427+ },
428+ {
429+ LeafIdx : 3 ,
430+ Start : 0 ,
431+ End : 3 ,
432+ Root : sha256EmptyTreeHash ,
433+ LeafHash : leafHash ,
434+ Proof : nil ,
435+ Desc : "oob right 2" ,
436+ WantError : true ,
437+ },
438+ {
439+ LeafIdx : 0 ,
440+ Start : 2 ,
441+ End : 1 ,
442+ Root : sha256EmptyTreeHash ,
443+ LeafHash : leafHash ,
444+ Proof : nil ,
445+ Desc : "start larger than end" ,
446+ WantError : true ,
447+ },
448+ }
449+
450+ for _ , tc := range tests {
451+ if err := writeSubtreeInclusionProbe (dir , tc ); err != nil {
452+ return err
453+ }
454+ }
455+
456+ return nil
457+ }
458+
459+ func writeSubtreeInclusionProbe (dir string , probe subtreeInclusionProbe ) error {
460+ fn := fileName (probe .Desc )
461+
462+ probeJson , err := json .MarshalIndent (probe , "" , " " )
463+ if err != nil {
464+ return fmt .Errorf ("marshaling probe: %s" , err )
465+ }
466+
467+ fileLocation := filepath .Join (dir , fn )
468+ if err := os .WriteFile (fileLocation , probeJson , 0644 ); err != nil {
469+ return fmt .Errorf ("writing probe: %s: %s" , fn , err )
470+ }
471+
472+ return nil
473+ }
474+
475+ // convertToSubtreeInclusionProbesAndWrite generates subtree inclusion proofs
476+ // from inclusion proofs and writes them.
477+ //
478+ // An inclusion proof for an entry at index in a tree of a given size leads to
479+ // two subtree inclusion proofs:
480+ // - one for for an entry at index in a subtree of the same given size.
481+ // - a second one for an entry shifted by bitCeil(size) for the subtree
482+ // [bitCeil(size), size+bitCeil(size)).
483+ func convertToSubtreeInclusionProbesAndWrite (dir string , p inclusionProbe ) error {
484+ sp1 := toSubtreeInclusionProbe (p )
485+ if err := writeSubtreeInclusionProbe (dir , sp1 ); err != nil {
486+ return err
487+ }
488+ sp2 := shiftSubtreeInclusionProbe (sp1 )
489+ return writeSubtreeInclusionProbe (dir , sp2 )
490+ }
491+
492+ // =============================================================================
493+ // Consistency Proofs
494+ // =============================================================================
495+
293496// consistencyProbe is a parameter set for consistency proof verification.
294497type consistencyProbe struct {
295498 Size1 uint64 `json:"size1"`
@@ -447,6 +650,10 @@ func writeConsistencyProbe(dir string, probe consistencyProbe) error {
447650 return nil
448651}
449652
653+ // =============================================================================
654+ // General Helpers
655+ // =============================================================================
656+
450657// extend explicitly copies |proof| slice and appends |hashes| to it.
451658func extend (proof [][]byte , hashes ... []byte ) [][]byte {
452659 return append (append ([][]byte {}, proof ... ), hashes ... )
@@ -470,6 +677,7 @@ func dh(h string, expLen int) []byte {
470677
471678func fileName (n string ) string {
472679 r := strings .NewReplacer (
680+ " - " , "-" ,
473681 "(" , "" ,
474682 ")" , "" ,
475683 " " , "-" )
@@ -478,10 +686,18 @@ func fileName(n string) string {
478686
479687func main () {
480688 inclusionDir := "testdata/inclusion"
481- if err := inclusionProbes (inclusionDir ); err != nil {
689+ if err := generateInclusionProbes (inclusionDir , writeInclusionProbe ); err != nil {
482690 log .Fatalf ("writing inclusion test data: %s" , err )
483691 }
484692
693+ subtreeInclusionDir := "testdata/subtreeinclusion"
694+ if err := generateInclusionProbes (subtreeInclusionDir , convertToSubtreeInclusionProbesAndWrite ); err != nil {
695+ log .Fatalf ("writing subtree inclusion test data: %s" , err )
696+ }
697+ if err := errorSubtreeInclusionProbes (subtreeInclusionDir ); err != nil {
698+ log .Fatalf ("writing subtree inclusion error test data: %s" , err )
699+ }
700+
485701 consistencyDir := "testdata/consistency"
486702 if err := consistencyProbes (consistencyDir ); err != nil {
487703 log .Fatalf ("writing consistency test data: %s" , err )
0 commit comments