@@ -19,6 +19,8 @@ import (
1919 "encoding/json"
2020 "fmt"
2121 "log"
22+ "math"
23+ "math/bits"
2224 "os"
2325 "path/filepath"
2426 "strconv"
@@ -109,6 +111,10 @@ var (
109111 }
110112)
111113
114+ // =============================================================================
115+ // Inclusion Proofs
116+ // =============================================================================
117+
112118// inclusionProbe is a parameter set for inclusion proof verification.
113119type inclusionProbe struct {
114120 LeafIdx uint64 `json:"leafIdx"`
@@ -121,15 +127,18 @@ type inclusionProbe struct {
121127 WantError bool `json:"wantErr"`
122128}
123129
124- func inclusionProbes (rootDir string ) error {
130+ // incProbeWriter writes an inclusionProbe to disk.
131+ type incProbeWriter func (dir string , probe inclusionProbe ) error
132+
133+ func generateInclusionProbes (rootDir string , write incProbeWriter ) error {
125134 for i , p := range inclusionProofs {
126135 dir := filepath .Join (rootDir , strconv .Itoa (i ))
127136 if err := os .MkdirAll (dir , 0755 ); err != nil {
128137 return err
129138 }
130139
131140 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 {
141+ if err := corruptedInclusionProbes (dir , p .leafIdx - 1 , p .size , p .proof , roots [p .size - 1 ], leafHash , write ); err != nil {
133142 return err
134143 }
135144 }
@@ -139,7 +148,7 @@ func inclusionProbes(rootDir string) error {
139148 return err
140149 }
141150
142- if err := staticInclusionProbes (staticDir ); err != nil {
151+ if err := staticInclusionProbes (staticDir , write ); err != nil {
143152 return err
144153 }
145154
@@ -148,7 +157,7 @@ func inclusionProbes(rootDir string) error {
148157 return err
149158 }
150159
151- if err := singleEntryInclusionProbes (singleEntryDir ); err != nil {
160+ if err := singleEntryInclusionProbes (singleEntryDir , write ); err != nil {
152161 return err
153162 }
154163
@@ -197,23 +206,23 @@ func invalidInclusionProof(leafIdx, treeSize uint64, proof [][]byte, root, leafH
197206 return ret
198207}
199208
200- func corruptedInclusionProbes (dir string , leafIdx , treeSize uint64 , proof [][]byte , root , leafHash []byte ) error {
209+ func corruptedInclusionProbes (dir string , leafIdx , treeSize uint64 , proof [][]byte , root , leafHash []byte , write incProbeWriter ) error {
201210 happyPath := inclusionProbe {leafIdx , treeSize , root , leafHash , proof , "happy path" , false }
202- if err := writeInclusionProbe (dir , happyPath ); err != nil {
203- return nil
211+ if err := write (dir , happyPath ); err != nil {
212+ return err
204213 }
205214
206215 probes := invalidInclusionProof (leafIdx , treeSize , proof , root , leafHash )
207216 for _ , p := range probes {
208- if err := writeInclusionProbe (dir , p ); err != nil {
217+ if err := write (dir , p ); err != nil {
209218 return err
210219 }
211220 }
212221
213222 return nil
214223}
215224
216- func singleEntryInclusionProbes (dir string ) error {
225+ func singleEntryInclusionProbes (dir string , write incProbeWriter ) error {
217226 data := []byte ("data" )
218227 // Root and leaf hash for 1-entry tree are the same.
219228 hash := rfc6962 .DefaultHasher .HashLeaf (data )
@@ -234,15 +243,15 @@ func singleEntryInclusionProbes(dir string) error {
234243 } {
235244 probe := inclusionProbe {0 , 1 , p .root , p .leaf , proof , p .desc , p .wantErr }
236245
237- if err := writeInclusionProbe (dir , probe ); err != nil {
246+ if err := write (dir , probe ); err != nil {
238247 return err
239248 }
240249 }
241250
242251 return nil
243252}
244253
245- func staticInclusionProbes (rootDir string ) error {
254+ func staticInclusionProbes (rootDir string , write incProbeWriter ) error {
246255 proof := [][]byte {}
247256
248257 probes := []struct {
@@ -256,17 +265,17 @@ func staticInclusionProbes(rootDir string) error {
256265 }
257266
258267 randomLeaf := inclusionProbe {p .index , p .size , []byte {}, sha256SomeHash , proof , "random leaf" , true }
259- if err := writeInclusionProbe (dir , randomLeaf ); err != nil {
268+ if err := write (dir , randomLeaf ); err != nil {
260269 return err
261270 }
262271
263272 emptyRoot := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , []byte {}, proof , "empty root" , true }
264- if err := writeInclusionProbe (dir , emptyRoot ); err != nil {
273+ if err := write (dir , emptyRoot ); err != nil {
265274 return err
266275 }
267276
268277 emptyRootRandomLeaf := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , sha256SomeHash , proof , "empty root and random leaf" , true }
269- if err := writeInclusionProbe (dir , emptyRootRandomLeaf ); err != nil {
278+ if err := write (dir , emptyRootRandomLeaf ); err != nil {
270279 return err
271280 }
272281 }
@@ -290,6 +299,208 @@ func writeInclusionProbe(dir string, probe inclusionProbe) error {
290299 return nil
291300}
292301
302+ // =============================================================================
303+ // Subtree Inclusion Proofs
304+ // =============================================================================
305+
306+ // subtreeInclusionProbe is a parameter set for subtree inclusion proof verification.
307+ type subtreeInclusionProbe struct {
308+ LeafIdx uint64 `json:"leafIdx"`
309+ Start uint64 `json:"start"`
310+ End uint64 `json:"end"`
311+ Root []byte `json:"root"`
312+ LeafHash []byte `json:"leafHash"`
313+ Proof [][]byte `json:"proof"`
314+
315+ Desc string `json:"desc"`
316+ WantError bool `json:"wantErr"`
317+ }
318+
319+ // bitCeil returns the smallest power of 2 larger than or equal to n.
320+ // MUST NOT be used with n larger than uint64(1)<<63.
321+ func bitCeil (n uint64 ) uint64 {
322+ if n <= 1 {
323+ return 1
324+ }
325+ return uint64 (1 ) << bits .Len64 (n - 1 )
326+ }
327+
328+ func toSubtreeInclusionProbe (p inclusionProbe ) subtreeInclusionProbe {
329+ return subtreeInclusionProbe {
330+ LeafIdx : p .LeafIdx ,
331+ Start : 0 ,
332+ End : p .TreeSize ,
333+ Root : p .Root ,
334+ LeafHash : p .LeafHash ,
335+ Proof : p .Proof ,
336+ Desc : p .Desc ,
337+ WantError : p .WantError ,
338+ }
339+ }
340+
341+ func shiftSubtreeInclusionProbe (p subtreeInclusionProbe ) subtreeInclusionProbe {
342+ shift := bitCeil (p .End - p .Start )
343+ desc := p .Desc + " - subtree"
344+ leafIdx , start , end := p .LeafIdx , p .Start , p .End
345+ // Shift indices independently to allow for pathological cases.
346+ if p .Start <= math .MaxUint64 - shift {
347+ start += shift
348+ }
349+ if p .LeafIdx <= math .MaxUint64 - shift {
350+ leafIdx += shift
351+ }
352+ if p .End <= math .MaxUint64 - shift {
353+ end += shift
354+ }
355+ return subtreeInclusionProbe {
356+ LeafIdx : leafIdx ,
357+ Start : start ,
358+ End : end ,
359+ Root : p .Root ,
360+ LeafHash : p .LeafHash ,
361+ Proof : p .Proof ,
362+ Desc : desc ,
363+ WantError : p .WantError ,
364+ }
365+ }
366+
367+ func errorSubtreeInclusionProbes (rootDir string ) error {
368+ dir := filepath .Join (rootDir , "errors" )
369+ if err := os .MkdirAll (dir , 0755 ); err != nil {
370+ return err
371+ }
372+
373+ leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [0 ])
374+
375+ tests := []subtreeInclusionProbe {
376+ {
377+ LeafIdx : 0 ,
378+ Start : 0 ,
379+ End : 0 ,
380+ Root : sha256EmptyTreeHash ,
381+ LeafHash : leafHash ,
382+ Proof : nil ,
383+ Desc : "everything zero" ,
384+ WantError : true ,
385+ },
386+ {
387+ LeafIdx : 0 ,
388+ Start : 1 ,
389+ End : 1 ,
390+ Root : sha256EmptyTreeHash ,
391+ LeafHash : leafHash ,
392+ Proof : nil ,
393+ Desc : "start equals end" ,
394+ WantError : true ,
395+ },
396+ {
397+ LeafIdx : 3 ,
398+ Start : 3 ,
399+ End : 5 ,
400+ Root : sha256EmptyTreeHash ,
401+ LeafHash : leafHash ,
402+ Proof : nil ,
403+ Desc : "invalid subtree" ,
404+ WantError : true ,
405+ },
406+ {
407+ LeafIdx : 1 ,
408+ Start : 1 ,
409+ End : (1 << 63 ) + 2 ,
410+ Root : sha256EmptyTreeHash ,
411+ LeafHash : leafHash ,
412+ Proof : nil ,
413+ Desc : "invalid large subtree" ,
414+ WantError : true ,
415+ },
416+ {
417+ LeafIdx : 0 ,
418+ Start : 1 ,
419+ End : 2 ,
420+ Root : sha256EmptyTreeHash ,
421+ LeafHash : leafHash ,
422+ Proof : nil ,
423+ Desc : "oob left" ,
424+ WantError : true ,
425+ },
426+ {
427+ LeafIdx : 3 ,
428+ Start : 0 ,
429+ End : 2 ,
430+ Root : sha256EmptyTreeHash ,
431+ LeafHash : leafHash ,
432+ Proof : nil ,
433+ Desc : "oob right" ,
434+ WantError : true ,
435+ },
436+ {
437+ LeafIdx : 3 ,
438+ Start : 0 ,
439+ End : 3 ,
440+ Root : sha256EmptyTreeHash ,
441+ LeafHash : leafHash ,
442+ Proof : nil ,
443+ Desc : "oob right 2" ,
444+ WantError : true ,
445+ },
446+ {
447+ LeafIdx : 0 ,
448+ Start : 2 ,
449+ End : 1 ,
450+ Root : sha256EmptyTreeHash ,
451+ LeafHash : leafHash ,
452+ Proof : nil ,
453+ Desc : "start larger than end" ,
454+ WantError : true ,
455+ },
456+ }
457+
458+ for _ , tc := range tests {
459+ if err := writeSubtreeInclusionProbe (dir , tc ); err != nil {
460+ return err
461+ }
462+ }
463+
464+ return nil
465+ }
466+
467+ func writeSubtreeInclusionProbe (dir string , probe subtreeInclusionProbe ) error {
468+ fn := fileName (probe .Desc )
469+
470+ probeJson , err := json .MarshalIndent (probe , "" , " " )
471+ if err != nil {
472+ return fmt .Errorf ("marshaling probe: %s" , err )
473+ }
474+
475+ fileLocation := filepath .Join (dir , fn )
476+ if err := os .WriteFile (fileLocation , probeJson , 0644 ); err != nil {
477+ return fmt .Errorf ("writing probe: %s: %s" , fn , err )
478+ }
479+
480+ return nil
481+ }
482+
483+ // convertToSubtreeInclusionProbesAndWrite generates subtree inclusion proofs
484+ // from inclusion proofs and writes them.
485+ //
486+ // An inclusion proof for an entry at index in a tree of a given size leads to
487+ // two subtree inclusion proofs:
488+ // - one for for an entry at index in a subtree of the same given size.
489+ // - a second one for an entry shifted by bitCeil(size) for the subtree
490+ // [bitCeil(size), size+bitCeil(size)).
491+ func convertToSubtreeInclusionProbesAndWrite (dir string , p inclusionProbe ) error {
492+ sp1 := toSubtreeInclusionProbe (p )
493+ if err := writeSubtreeInclusionProbe (dir , sp1 ); err != nil {
494+ return err
495+ }
496+ sp2 := shiftSubtreeInclusionProbe (sp1 )
497+ return writeSubtreeInclusionProbe (dir , sp2 )
498+ }
499+
500+ // =============================================================================
501+ // Consistency Proofs
502+ // =============================================================================
503+
293504// consistencyProbe is a parameter set for consistency proof verification.
294505type consistencyProbe struct {
295506 Size1 uint64 `json:"size1"`
@@ -447,6 +658,10 @@ func writeConsistencyProbe(dir string, probe consistencyProbe) error {
447658 return nil
448659}
449660
661+ // =============================================================================
662+ // General Helpers
663+ // =============================================================================
664+
450665// extend explicitly copies |proof| slice and appends |hashes| to it.
451666func extend (proof [][]byte , hashes ... []byte ) [][]byte {
452667 return append (append ([][]byte {}, proof ... ), hashes ... )
@@ -470,6 +685,7 @@ func dh(h string, expLen int) []byte {
470685
471686func fileName (n string ) string {
472687 r := strings .NewReplacer (
688+ " - " , "-" ,
473689 "(" , "" ,
474690 ")" , "" ,
475691 " " , "-" )
@@ -478,10 +694,18 @@ func fileName(n string) string {
478694
479695func main () {
480696 inclusionDir := "testdata/inclusion"
481- if err := inclusionProbes (inclusionDir ); err != nil {
697+ if err := generateInclusionProbes (inclusionDir , writeInclusionProbe ); err != nil {
482698 log .Fatalf ("writing inclusion test data: %s" , err )
483699 }
484700
701+ subtreeInclusionDir := "testdata/subtreeinclusion"
702+ if err := generateInclusionProbes (subtreeInclusionDir , convertToSubtreeInclusionProbesAndWrite ); err != nil {
703+ log .Fatalf ("writing subtree inclusion test data: %s" , err )
704+ }
705+ if err := errorSubtreeInclusionProbes (subtreeInclusionDir ); err != nil {
706+ log .Fatalf ("writing subtree inclusion error test data: %s" , err )
707+ }
708+
485709 consistencyDir := "testdata/consistency"
486710 if err := consistencyProbes (consistencyDir ); err != nil {
487711 log .Fatalf ("writing consistency test data: %s" , err )
0 commit comments