@@ -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"`
@@ -290,6 +295,329 @@ func writeInclusionProbe(dir string, probe inclusionProbe) error {
290295 return nil
291296}
292297
298+ // =============================================================================
299+ // Subtree Inclusion Proofs
300+ // =============================================================================
301+
302+ // subtreeInclusionProbe is a parameter set for subtree inclusion proof verification.
303+ type subtreeInclusionProbe struct {
304+ LeafIdx uint64 `json:"leafIdx"`
305+ Start uint64 `json:"start"`
306+ End uint64 `json:"end"`
307+ Root []byte `json:"root"`
308+ LeafHash []byte `json:"leafHash"`
309+ Proof [][]byte `json:"proof"`
310+
311+ Desc string `json:"desc"`
312+ WantError bool `json:"wantErr"`
313+ }
314+
315+ func bitCeil (n uint64 ) uint64 {
316+ if n <= 1 {
317+ return 1
318+ }
319+ return 1 << bits .Len64 (n - 1 )
320+ }
321+
322+ func toSubtreeInclusionProbe (p inclusionProbe ) subtreeInclusionProbe {
323+ return subtreeInclusionProbe {
324+ LeafIdx : p .LeafIdx ,
325+ Start : 0 ,
326+ End : p .TreeSize ,
327+ Root : p .Root ,
328+ LeafHash : p .LeafHash ,
329+ Proof : p .Proof ,
330+ Desc : p .Desc ,
331+ WantError : p .WantError ,
332+ }
333+ }
334+
335+ func shiftSubtreeInclusionProbe (p subtreeInclusionProbe ) subtreeInclusionProbe {
336+ shift := bitCeil (p .End - p .Start )
337+ desc := p .Desc + " - subtree"
338+ leafIdx := p .LeafIdx + shift
339+ if p .LeafIdx == ^ uint64 (0 ) {
340+ leafIdx = ^ uint64 (0 )
341+ }
342+ return subtreeInclusionProbe {
343+ LeafIdx : leafIdx ,
344+ Start : p .Start + shift ,
345+ End : p .End + shift ,
346+ Root : p .Root ,
347+ LeafHash : p .LeafHash ,
348+ Proof : p .Proof ,
349+ Desc : desc ,
350+ WantError : p .WantError ,
351+ }
352+ }
353+
354+ func subtreeInclusionProbes (rootDir string ) error {
355+ for i , p := range inclusionProofs {
356+ dir := filepath .Join (rootDir , strconv .Itoa (i ))
357+ if err := os .MkdirAll (dir , 0755 ); err != nil {
358+ return err
359+ }
360+
361+ leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [p .leafIdx - 1 ])
362+ if err := corruptedSubtreeInclusionProbes (dir , p .leafIdx - 1 , p .size , p .proof , roots [p .size - 1 ], leafHash ); err != nil {
363+ return err
364+ }
365+ }
366+
367+ staticDir := filepath .Join (rootDir , "additional" )
368+ if err := os .MkdirAll (staticDir , 0755 ); err != nil {
369+ return err
370+ }
371+
372+ if err := staticSubtreeInclusionProbes (staticDir ); err != nil {
373+ return err
374+ }
375+
376+ singleEntryDir := filepath .Join (rootDir , "single-entry" )
377+ if err := os .MkdirAll (singleEntryDir , 0755 ); err != nil {
378+ return err
379+ }
380+
381+ if err := singleEntrySubtreeInclusionProbes (singleEntryDir ); err != nil {
382+ return err
383+ }
384+
385+ return nil
386+ }
387+
388+ func corruptedSubtreeInclusionProbes (dir string , leafIdx , treeSize uint64 , proof [][]byte , root , leafHash []byte ) error {
389+ happyPath := inclusionProbe {leafIdx , treeSize , root , leafHash , proof , "happy path" , false }
390+ if err := writeSubtreeInclusionProbePair (dir , happyPath ); err != nil {
391+ return err
392+ }
393+
394+ probes := invalidInclusionProof (leafIdx , treeSize , proof , root , leafHash )
395+ for _ , p := range probes {
396+ if err := writeSubtreeInclusionProbePair (dir , p ); err != nil {
397+ return err
398+ }
399+ }
400+
401+ return nil
402+ }
403+
404+ func singleEntrySubtreeInclusionProbes (dir string ) error {
405+ data := []byte ("data" )
406+ hash := rfc6962 .DefaultHasher .HashLeaf (data )
407+ proof := [][]byte {}
408+ emptyHash := []byte {}
409+
410+ for _ , p := range []struct {
411+ root []byte
412+ leaf []byte
413+ desc string
414+ wantErr bool
415+ }{
416+ {hash , hash , "matching root and leaf" , false },
417+ {hash , emptyHash , "empty leaf" , true },
418+ {emptyHash , hash , "empty root" , true },
419+ {emptyHash , emptyHash , "empty root and leaf" , true },
420+ } {
421+ probe := inclusionProbe {0 , 1 , p .root , p .leaf , proof , p .desc , p .wantErr }
422+ if err := writeSubtreeInclusionProbePair (dir , probe ); err != nil {
423+ return err
424+ }
425+ }
426+
427+ return nil
428+ }
429+
430+ func staticSubtreeInclusionProbes (rootDir string ) error {
431+ proof := [][]byte {}
432+
433+ probes := []struct {
434+ index , size uint64
435+ }{{0 , 0 }, {0 , 1 }, {1 , 0 }, {2 , 1 }}
436+ for i , p := range probes {
437+ dir := filepath .Join (rootDir , strconv .Itoa (i ))
438+ err := os .MkdirAll (dir , 0755 )
439+ if err != nil {
440+ return err
441+ }
442+
443+ randomLeaf := inclusionProbe {p .index , p .size , []byte {}, sha256SomeHash , proof , "random leaf" , true }
444+ if err := writeSubtreeInclusionProbePair (dir , randomLeaf ); err != nil {
445+ return err
446+ }
447+
448+ emptyRoot := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , []byte {}, proof , "empty root" , true }
449+ if err := writeSubtreeInclusionProbePair (dir , emptyRoot ); err != nil {
450+ return err
451+ }
452+
453+ emptyRootRandomLeaf := inclusionProbe {p .index , p .size , sha256EmptyTreeHash , sha256SomeHash , proof , "empty root and random leaf" , true }
454+ if err := writeSubtreeInclusionProbePair (dir , emptyRootRandomLeaf ); err != nil {
455+ return err
456+ }
457+ }
458+
459+ return nil
460+ }
461+
462+ func errorSubtreeInclusionProbes (rootDir string ) error {
463+ dir := filepath .Join (rootDir , "errors" )
464+ if err := os .MkdirAll (dir , 0755 ); err != nil {
465+ return err
466+ }
467+
468+ leafHash := rfc6962 .DefaultHasher .HashLeaf (leaves [0 ])
469+
470+ tests := []struct {
471+ filename string
472+ probe subtreeInclusionProbe
473+ }{
474+ {
475+ filename : "everything-zero.json" ,
476+ probe : subtreeInclusionProbe {
477+ LeafIdx : 0 ,
478+ Start : 0 ,
479+ End : 0 ,
480+ Root : sha256EmptyTreeHash ,
481+ LeafHash : leafHash ,
482+ Proof : nil ,
483+ Desc : "empty root" ,
484+ WantError : true ,
485+ },
486+ },
487+ {
488+ filename : "start-equals-end.json" ,
489+ probe : subtreeInclusionProbe {
490+ LeafIdx : 0 ,
491+ Start : 1 ,
492+ End : 1 ,
493+ Root : sha256EmptyTreeHash ,
494+ LeafHash : leafHash ,
495+ Proof : nil ,
496+ Desc : "empty root" ,
497+ WantError : true ,
498+ },
499+ },
500+ {
501+ filename : "invalid-subtree.json" ,
502+ probe : subtreeInclusionProbe {
503+ LeafIdx : 3 ,
504+ Start : 3 ,
505+ End : 5 ,
506+ Root : sha256EmptyTreeHash ,
507+ LeafHash : leafHash ,
508+ Proof : nil ,
509+ Desc : "empty root" ,
510+ WantError : true ,
511+ },
512+ },
513+ {
514+ filename : "invalid-large-subtree.json" ,
515+ probe : subtreeInclusionProbe {
516+ LeafIdx : 1 ,
517+ Start : 1 ,
518+ End : 9223372036854775810 ,
519+ Root : sha256EmptyTreeHash ,
520+ LeafHash : leafHash ,
521+ Proof : nil ,
522+ Desc : "empty root" ,
523+ WantError : true ,
524+ },
525+ },
526+ {
527+ filename : "oob-left.json" ,
528+ probe : subtreeInclusionProbe {
529+ LeafIdx : 0 ,
530+ Start : 1 ,
531+ End : 2 ,
532+ Root : sha256EmptyTreeHash ,
533+ LeafHash : leafHash ,
534+ Proof : nil ,
535+ Desc : "empty root" ,
536+ WantError : true ,
537+ },
538+ },
539+ {
540+ filename : "oob-right.json" ,
541+ probe : subtreeInclusionProbe {
542+ LeafIdx : 3 ,
543+ Start : 0 ,
544+ End : 2 ,
545+ Root : sha256EmptyTreeHash ,
546+ LeafHash : leafHash ,
547+ Proof : nil ,
548+ Desc : "empty root" ,
549+ WantError : true ,
550+ },
551+ },
552+ {
553+ filename : "oob-right-2.json" ,
554+ probe : subtreeInclusionProbe {
555+ LeafIdx : 3 ,
556+ Start : 0 ,
557+ End : 3 ,
558+ Root : sha256EmptyTreeHash ,
559+ LeafHash : leafHash ,
560+ Proof : nil ,
561+ Desc : "empty root" ,
562+ WantError : true ,
563+ },
564+ },
565+ {
566+ filename : "start-larger-than-end.json" ,
567+ probe : subtreeInclusionProbe {
568+ LeafIdx : 0 ,
569+ Start : 2 ,
570+ End : 1 ,
571+ Root : sha256EmptyTreeHash ,
572+ LeafHash : leafHash ,
573+ Proof : nil ,
574+ Desc : "empty root" ,
575+ WantError : true ,
576+ },
577+ },
578+ }
579+
580+ for _ , tc := range tests {
581+ if err := writeSubtreeInclusionProbeWithFilename (dir , tc .filename , tc .probe ); err != nil {
582+ return err
583+ }
584+ }
585+
586+ return nil
587+ }
588+
589+ func writeSubtreeInclusionProbeWithFilename (dir , filename string , probe subtreeInclusionProbe ) error {
590+ probeJson , err := json .MarshalIndent (probe , "" , " " )
591+ if err != nil {
592+ return fmt .Errorf ("marshaling probe: %s" , err )
593+ }
594+
595+ fileLocation := filepath .Join (dir , filename )
596+ if err := os .WriteFile (fileLocation , probeJson , 0644 ); err != nil {
597+ return fmt .Errorf ("writing probe: %s: %s" , filename , err )
598+ }
599+
600+ return nil
601+ }
602+
603+ func writeSubtreeInclusionProbe (dir string , probe subtreeInclusionProbe ) error {
604+ fn := fileName (probe .Desc )
605+ return writeSubtreeInclusionProbeWithFilename (dir , fn , probe )
606+ }
607+
608+ func writeSubtreeInclusionProbePair (dir string , p inclusionProbe ) error {
609+ sp1 := toSubtreeInclusionProbe (p )
610+ if err := writeSubtreeInclusionProbe (dir , sp1 ); err != nil {
611+ return err
612+ }
613+ sp2 := shiftSubtreeInclusionProbe (sp1 )
614+ return writeSubtreeInclusionProbe (dir , sp2 )
615+ }
616+
617+ // =============================================================================
618+ // Consistency Proofs
619+ // =============================================================================
620+
293621// consistencyProbe is a parameter set for consistency proof verification.
294622type consistencyProbe struct {
295623 Size1 uint64 `json:"size1"`
@@ -446,6 +774,10 @@ func writeConsistencyProbe(dir string, probe consistencyProbe) error {
446774 return nil
447775}
448776
777+ // =============================================================================
778+ // General Helpers
779+ // =============================================================================
780+
449781// extend explicitly copies |proof| slice and appends |hashes| to it.
450782func extend (proof [][]byte , hashes ... []byte ) [][]byte {
451783 return append (append ([][]byte {}, proof ... ), hashes ... )
@@ -469,6 +801,7 @@ func dh(h string, expLen int) []byte {
469801
470802func fileName (n string ) string {
471803 r := strings .NewReplacer (
804+ " - " , "-" ,
472805 "(" , "" ,
473806 ")" , "" ,
474807 " " , "-" )
@@ -481,6 +814,14 @@ func main() {
481814 log .Fatalf ("writing inclusion test data: %s" , err )
482815 }
483816
817+ subtreeInclusionDir := "testdata/subtreeinclusion"
818+ if err := subtreeInclusionProbes (subtreeInclusionDir ); err != nil {
819+ log .Fatalf ("writing subtree inclusion test data: %s" , err )
820+ }
821+ if err := errorSubtreeInclusionProbes (subtreeInclusionDir ); err != nil {
822+ log .Fatalf ("writing subtree inclusion error test data: %s" , err )
823+ }
824+
484825 consistencyDir := "testdata/consistency"
485826 if err := consistencyProbes (consistencyDir ); err != nil {
486827 log .Fatalf ("writing consistency test data: %s" , err )
0 commit comments