@@ -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,203 @@ 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 )- shift {
345+ leafIdx += shift
346+ } else {
347+ leafIdx = ^ uint64 (0 )
348+ }
349+ return subtreeInclusionProbe {
350+ LeafIdx : leafIdx ,
351+ Start : p .Start + shift ,
352+ End : p .End + shift ,
353+ Root : p .Root ,
354+ LeafHash : p .LeafHash ,
355+ Proof : p .Proof ,
356+ Desc : desc ,
357+ WantError : p .WantError ,
358+ }
359+ }
360+
361+ func errorSubtreeInclusionProbes (rootDir string ) error {
362+ dir := filepath .Join (rootDir , "errors" )
363+ if err := os .MkdirAll (dir , 0755 ); err != nil {
364+ return err
365+ }
366+
367+ leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [0 ])
368+
369+ tests := []subtreeInclusionProbe {
370+ {
371+ LeafIdx : 0 ,
372+ Start : 0 ,
373+ End : 0 ,
374+ Root : sha256EmptyTreeHash ,
375+ LeafHash : leafHash ,
376+ Proof : nil ,
377+ Desc : "everything zero" ,
378+ WantError : true ,
379+ },
380+ {
381+ LeafIdx : 0 ,
382+ Start : 1 ,
383+ End : 1 ,
384+ Root : sha256EmptyTreeHash ,
385+ LeafHash : leafHash ,
386+ Proof : nil ,
387+ Desc : "start equals end" ,
388+ WantError : true ,
389+ },
390+ {
391+ LeafIdx : 3 ,
392+ Start : 3 ,
393+ End : 5 ,
394+ Root : sha256EmptyTreeHash ,
395+ LeafHash : leafHash ,
396+ Proof : nil ,
397+ Desc : "invalid subtree" ,
398+ WantError : true ,
399+ },
400+ {
401+ LeafIdx : 1 ,
402+ Start : 1 ,
403+ End : (1 << 63 ) + 2 ,
404+ Root : sha256EmptyTreeHash ,
405+ LeafHash : leafHash ,
406+ Proof : nil ,
407+ Desc : "invalid large subtree" ,
408+ WantError : true ,
409+ },
410+ {
411+ LeafIdx : 0 ,
412+ Start : 1 ,
413+ End : 2 ,
414+ Root : sha256EmptyTreeHash ,
415+ LeafHash : leafHash ,
416+ Proof : nil ,
417+ Desc : "oob left" ,
418+ WantError : true ,
419+ },
420+ {
421+ LeafIdx : 3 ,
422+ Start : 0 ,
423+ End : 2 ,
424+ Root : sha256EmptyTreeHash ,
425+ LeafHash : leafHash ,
426+ Proof : nil ,
427+ Desc : "oob right" ,
428+ WantError : true ,
429+ },
430+ {
431+ LeafIdx : 3 ,
432+ Start : 0 ,
433+ End : 3 ,
434+ Root : sha256EmptyTreeHash ,
435+ LeafHash : leafHash ,
436+ Proof : nil ,
437+ Desc : "oob right 2" ,
438+ WantError : true ,
439+ },
440+ {
441+ LeafIdx : 0 ,
442+ Start : 2 ,
443+ End : 1 ,
444+ Root : sha256EmptyTreeHash ,
445+ LeafHash : leafHash ,
446+ Proof : nil ,
447+ Desc : "start larger than end" ,
448+ WantError : true ,
449+ },
450+ }
451+
452+ for _ , tc := range tests {
453+ if err := writeSubtreeInclusionProbe (dir , tc ); err != nil {
454+ return err
455+ }
456+ }
457+
458+ return nil
459+ }
460+
461+ func writeSubtreeInclusionProbe (dir string , probe subtreeInclusionProbe ) error {
462+ fn := fileName (probe .Desc )
463+
464+ probeJson , err := json .MarshalIndent (probe , "" , " " )
465+ if err != nil {
466+ return fmt .Errorf ("marshaling probe: %s" , err )
467+ }
468+
469+ fileLocation := filepath .Join (dir , fn )
470+ if err := os .WriteFile (fileLocation , probeJson , 0644 ); err != nil {
471+ return fmt .Errorf ("writing probe: %s: %s" , fn , err )
472+ }
473+
474+ return nil
475+ }
476+
477+ // convertToSubtreeInclusionProbesAndWrite generates subtree inclusion proofs
478+ // from inclusion proofs and writes them.
479+ //
480+ // An inclusion proof for an entry at index in a tree of a given size leads to
481+ // two subtree inclusion proofs:
482+ // - one for for an entry at index in a subtree of the same given size.
483+ // - a second one for an entry shifted by bitCeil(size) for the subtree
484+ // [bitCeil(size), size+bitCeil(size)).
485+ func convertToSubtreeInclusionProbesAndWrite (dir string , p inclusionProbe ) error {
486+ sp1 := toSubtreeInclusionProbe (p )
487+ if err := writeSubtreeInclusionProbe (dir , sp1 ); err != nil {
488+ return err
489+ }
490+ sp2 := shiftSubtreeInclusionProbe (sp1 )
491+ return writeSubtreeInclusionProbe (dir , sp2 )
492+ }
493+
494+ // =============================================================================
495+ // Consistency Proofs
496+ // =============================================================================
497+
293498// consistencyProbe is a parameter set for consistency proof verification.
294499type consistencyProbe struct {
295500 Size1 uint64 `json:"size1"`
@@ -447,6 +652,10 @@ func writeConsistencyProbe(dir string, probe consistencyProbe) error {
447652 return nil
448653}
449654
655+ // =============================================================================
656+ // General Helpers
657+ // =============================================================================
658+
450659// extend explicitly copies |proof| slice and appends |hashes| to it.
451660func extend (proof [][]byte , hashes ... []byte ) [][]byte {
452661 return append (append ([][]byte {}, proof ... ), hashes ... )
@@ -470,6 +679,7 @@ func dh(h string, expLen int) []byte {
470679
471680func fileName (n string ) string {
472681 r := strings .NewReplacer (
682+ " - " , "-" ,
473683 "(" , "" ,
474684 ")" , "" ,
475685 " " , "-" )
@@ -478,10 +688,18 @@ func fileName(n string) string {
478688
479689func main () {
480690 inclusionDir := "testdata/inclusion"
481- if err := inclusionProbes (inclusionDir ); err != nil {
691+ if err := generateInclusionProbes (inclusionDir , writeInclusionProbe ); err != nil {
482692 log .Fatalf ("writing inclusion test data: %s" , err )
483693 }
484694
695+ subtreeInclusionDir := "testdata/subtreeinclusion"
696+ if err := generateInclusionProbes (subtreeInclusionDir , convertToSubtreeInclusionProbesAndWrite ); err != nil {
697+ log .Fatalf ("writing subtree inclusion test data: %s" , err )
698+ }
699+ if err := errorSubtreeInclusionProbes (subtreeInclusionDir ); err != nil {
700+ log .Fatalf ("writing subtree inclusion error test data: %s" , err )
701+ }
702+
485703 consistencyDir := "testdata/consistency"
486704 if err := consistencyProbes (consistencyDir ); err != nil {
487705 log .Fatalf ("writing consistency test data: %s" , err )
0 commit comments