Skip to content

Commit 96de546

Browse files
committed
cmd/proofgen for subtree inclusion proofs
1 parent 53bebf1 commit 96de546

7 files changed

Lines changed: 347 additions & 6 deletions

File tree

cmd/proofgen/main.go

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
113118
type 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.
294622
type 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.
450782
func 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

470802
func 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)

testdata/subtreeinclusion/errors/everything-zero.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"proof": null,
88
"desc": "empty root",
99
"wantErr": true
10-
}
10+
}

testdata/subtreeinclusion/errors/oob-left.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"proof": null,
88
"desc": "empty root",
99
"wantErr": true
10-
}
10+
}

0 commit comments

Comments
 (0)