Skip to content

Commit 216c761

Browse files
committed
cmd/proofgen for subtree inclusion proofs
1 parent 2acfad1 commit 216c761

9 files changed

Lines changed: 247 additions & 29 deletions

File tree

cmd/proofgen/main.go

Lines changed: 233 additions & 15 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"`
@@ -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.
294499
type 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.
451660
func 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

471680
func fileName(n string) string {
472681
r := strings.NewReplacer(
682+
" - ", "-",
473683
"(", "",
474684
")", "",
475685
" ", "-")
@@ -478,10 +688,18 @@ func fileName(n string) string {
478688

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

testdata/subtreeinclusion/errors/everything-zero.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
66
"leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
77
"proof": null,
8-
"desc": "empty root",
8+
"desc": "everything zero",
99
"wantErr": true
10-
}
10+
}

testdata/subtreeinclusion/errors/invalid-large-subtree.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
66
"leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
77
"proof": null,
8-
"desc": "empty root",
8+
"desc": "invalid large subtree",
99
"wantErr": true
1010
}

testdata/subtreeinclusion/errors/invalid-subtree.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"root": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
66
"leafHash": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
77
"proof": null,
8-
"desc": "empty root",
8+
"desc": "invalid subtree",
99
"wantErr": true
1010
}

0 commit comments

Comments
 (0)