Skip to content

Commit bda39b0

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

9 files changed

Lines changed: 245 additions & 29 deletions

File tree

cmd/proofgen/main.go

Lines changed: 231 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,201 @@ 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) {
345+
leafIdx += shift
346+
}
347+
return subtreeInclusionProbe{
348+
LeafIdx: leafIdx,
349+
Start: p.Start + shift,
350+
End: p.End + shift,
351+
Root: p.Root,
352+
LeafHash: p.LeafHash,
353+
Proof: p.Proof,
354+
Desc: desc,
355+
WantError: p.WantError,
356+
}
357+
}
358+
359+
func errorSubtreeInclusionProbes(rootDir string) error {
360+
dir := filepath.Join(rootDir, "errors")
361+
if err := os.MkdirAll(dir, 0755); err != nil {
362+
return err
363+
}
364+
365+
leafHash := rfc6962.DefaultHasher.HashLeaf(leaves[0])
366+
367+
tests := []subtreeInclusionProbe{
368+
{
369+
LeafIdx: 0,
370+
Start: 0,
371+
End: 0,
372+
Root: sha256EmptyTreeHash,
373+
LeafHash: leafHash,
374+
Proof: nil,
375+
Desc: "everything zero",
376+
WantError: true,
377+
},
378+
{
379+
LeafIdx: 0,
380+
Start: 1,
381+
End: 1,
382+
Root: sha256EmptyTreeHash,
383+
LeafHash: leafHash,
384+
Proof: nil,
385+
Desc: "start equals end",
386+
WantError: true,
387+
},
388+
{
389+
LeafIdx: 3,
390+
Start: 3,
391+
End: 5,
392+
Root: sha256EmptyTreeHash,
393+
LeafHash: leafHash,
394+
Proof: nil,
395+
Desc: "invalid subtree",
396+
WantError: true,
397+
},
398+
{
399+
LeafIdx: 1,
400+
Start: 1,
401+
End: (1 << 63) + 2,
402+
Root: sha256EmptyTreeHash,
403+
LeafHash: leafHash,
404+
Proof: nil,
405+
Desc: "invalid large subtree",
406+
WantError: true,
407+
},
408+
{
409+
LeafIdx: 0,
410+
Start: 1,
411+
End: 2,
412+
Root: sha256EmptyTreeHash,
413+
LeafHash: leafHash,
414+
Proof: nil,
415+
Desc: "oob left",
416+
WantError: true,
417+
},
418+
{
419+
LeafIdx: 3,
420+
Start: 0,
421+
End: 2,
422+
Root: sha256EmptyTreeHash,
423+
LeafHash: leafHash,
424+
Proof: nil,
425+
Desc: "oob right",
426+
WantError: true,
427+
},
428+
{
429+
LeafIdx: 3,
430+
Start: 0,
431+
End: 3,
432+
Root: sha256EmptyTreeHash,
433+
LeafHash: leafHash,
434+
Proof: nil,
435+
Desc: "oob right 2",
436+
WantError: true,
437+
},
438+
{
439+
LeafIdx: 0,
440+
Start: 2,
441+
End: 1,
442+
Root: sha256EmptyTreeHash,
443+
LeafHash: leafHash,
444+
Proof: nil,
445+
Desc: "start larger than end",
446+
WantError: true,
447+
},
448+
}
449+
450+
for _, tc := range tests {
451+
if err := writeSubtreeInclusionProbe(dir, tc); err != nil {
452+
return err
453+
}
454+
}
455+
456+
return nil
457+
}
458+
459+
func writeSubtreeInclusionProbe(dir string, probe subtreeInclusionProbe) error {
460+
fn := fileName(probe.Desc)
461+
462+
probeJson, err := json.MarshalIndent(probe, "", " ")
463+
if err != nil {
464+
return fmt.Errorf("marshaling probe: %s", err)
465+
}
466+
467+
fileLocation := filepath.Join(dir, fn)
468+
if err := os.WriteFile(fileLocation, probeJson, 0644); err != nil {
469+
return fmt.Errorf("writing probe: %s: %s", fn, err)
470+
}
471+
472+
return nil
473+
}
474+
475+
// convertToSubtreeInclusionProbesAndWrite generates subtree inclusion proofs
476+
// from inclusion proofs and writes them.
477+
//
478+
// An inclusion proof for an entry at index in a tree of a given size leads to
479+
// two subtree inclusion proofs:
480+
// - one for for an entry at index in a subtree of the same given size.
481+
// - a second one for an entry shifted by bitCeil(size) for the subtree
482+
// [bitCeil(size), size+bitCeil(size)).
483+
func convertToSubtreeInclusionProbesAndWrite(dir string, p inclusionProbe) error {
484+
sp1 := toSubtreeInclusionProbe(p)
485+
if err := writeSubtreeInclusionProbe(dir, sp1); err != nil {
486+
return err
487+
}
488+
sp2 := shiftSubtreeInclusionProbe(sp1)
489+
return writeSubtreeInclusionProbe(dir, sp2)
490+
}
491+
492+
// =============================================================================
493+
// Consistency Proofs
494+
// =============================================================================
495+
293496
// consistencyProbe is a parameter set for consistency proof verification.
294497
type consistencyProbe struct {
295498
Size1 uint64 `json:"size1"`
@@ -447,6 +650,10 @@ func writeConsistencyProbe(dir string, probe consistencyProbe) error {
447650
return nil
448651
}
449652

653+
// =============================================================================
654+
// General Helpers
655+
// =============================================================================
656+
450657
// extend explicitly copies |proof| slice and appends |hashes| to it.
451658
func extend(proof [][]byte, hashes ...[]byte) [][]byte {
452659
return append(append([][]byte{}, proof...), hashes...)
@@ -470,6 +677,7 @@ func dh(h string, expLen int) []byte {
470677

471678
func fileName(n string) string {
472679
r := strings.NewReplacer(
680+
" - ", "-",
473681
"(", "",
474682
")", "",
475683
" ", "-")
@@ -478,10 +686,18 @@ func fileName(n string) string {
478686

479687
func main() {
480688
inclusionDir := "testdata/inclusion"
481-
if err := inclusionProbes(inclusionDir); err != nil {
689+
if err := generateInclusionProbes(inclusionDir, writeInclusionProbe); err != nil {
482690
log.Fatalf("writing inclusion test data: %s", err)
483691
}
484692

693+
subtreeInclusionDir := "testdata/subtreeinclusion"
694+
if err := generateInclusionProbes(subtreeInclusionDir, convertToSubtreeInclusionProbesAndWrite); err != nil {
695+
log.Fatalf("writing subtree inclusion test data: %s", err)
696+
}
697+
if err := errorSubtreeInclusionProbes(subtreeInclusionDir); err != nil {
698+
log.Fatalf("writing subtree inclusion error test data: %s", err)
699+
}
700+
485701
consistencyDir := "testdata/consistency"
486702
if err := consistencyProbes(consistencyDir); err != nil {
487703
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)