Skip to content

Commit ecc40b1

Browse files
authored
Generate subtree inclusion proofs with cmd/proofgen (#238)
* cmd/proofgen for subtree inclusion proofs * better checks at boundaries
1 parent 8245ca7 commit ecc40b1

9 files changed

Lines changed: 253 additions & 29 deletions

File tree

cmd/proofgen/main.go

Lines changed: 239 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
113119
type 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.
294505
type 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.
451666
func 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

471686
func fileName(n string) string {
472687
r := strings.NewReplacer(
688+
" - ", "-",
473689
"(", "",
474690
")", "",
475691
" ", "-")
@@ -478,10 +694,18 @@ func fileName(n string) string {
478694

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

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
}

0 commit comments

Comments
 (0)