Skip to content

Commit c03ae4c

Browse files
committed
Add tests for existence proof
1 parent 60546fe commit c03ae4c

File tree

6 files changed

+197
-74
lines changed

6 files changed

+197
-74
lines changed

go/ops.go

+11-20
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,20 @@ import (
99
fmt "fmt"
1010
)
1111

12-
// Calculate determines the root hash that matches the given proof.
13-
// You must validate the result is what you have in a header.
14-
// Returns error if the calculations cannot be performed.
15-
func (p *ExistanceProof) Calculate() ([]byte, error) {
16-
if len(p.Steps) == 0 {
17-
return nil, fmt.Errorf("Existence Proof needs at least one step")
18-
}
19-
20-
first, rem := p.Steps[0], p.Steps[1:]
21-
// first step takes the key and value as input
22-
res, err := first.Apply(p.Key, p.Value)
23-
if err != nil {
24-
return nil, err
12+
func WrapLeaf(leaf *LeafOp) *ProofOp {
13+
return &ProofOp{
14+
Op: &ProofOp_Leaf{
15+
Leaf: leaf,
16+
},
2517
}
18+
}
2619

27-
// the rest just take the output of the last step (reducing it)
28-
for _, step := range rem {
29-
res, err = step.Apply(res)
30-
if err != nil {
31-
return nil, err
32-
}
20+
func WrapInner(inner *InnerOp) *ProofOp {
21+
return &ProofOp{
22+
Op: &ProofOp_Inner{
23+
Inner: inner,
24+
},
3325
}
34-
return res, nil
3526
}
3627

3728
func (op *ProofOp) Apply(args ...[]byte) ([]byte, error) {

go/ops_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ func TestInnerOp(t *testing.T) {
173173
}
174174
})
175175
}
176-
177176
}
178177

179178
func fromHex(data string) []byte {

go/proof.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package proofs
2+
3+
import fmt "fmt"
4+
5+
// Calculate determines the root hash that matches the given proof.
6+
// You must validate the result is what you have in a header.
7+
// Returns error if the calculations cannot be performed.
8+
func (p *ExistenceProof) Calculate() ([]byte, error) {
9+
if len(p.Steps) == 0 {
10+
return nil, fmt.Errorf("Existence Proof needs at least one step")
11+
}
12+
13+
first, rem := p.Steps[0], p.Steps[1:]
14+
// first step takes the key and value as input
15+
res, err := first.Apply(p.Key, p.Value)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
// the rest just take the output of the last step (reducing it)
21+
for _, step := range rem {
22+
res, err = step.Apply(res)
23+
if err != nil {
24+
return nil, err
25+
}
26+
}
27+
return res, nil
28+
}

go/proof_test.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package proofs
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestExistenceProof(t *testing.T) {
9+
cases := map[string]struct {
10+
proof *ExistenceProof
11+
isErr bool
12+
expected []byte
13+
}{
14+
"must have at least one step": {
15+
proof: &ExistenceProof{
16+
Key: []byte("foo"),
17+
Value: []byte("bar"),
18+
},
19+
isErr: true,
20+
},
21+
// copied from ops_test / TestLeafOp
22+
"executes one leaf step": {
23+
proof: &ExistenceProof{
24+
Key: []byte("food"),
25+
Value: []byte("some longer text"),
26+
Steps: []*ProofOp{
27+
WrapLeaf(&LeafOp{
28+
Hash: HashOp_SHA256,
29+
Length: LengthOp_VAR_PROTO,
30+
}),
31+
},
32+
},
33+
expected: fromHex("b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265"),
34+
},
35+
"cannot execute two leafs": {
36+
proof: &ExistenceProof{
37+
Key: []byte("food"),
38+
Value: []byte("some longer text"),
39+
Steps: []*ProofOp{
40+
WrapLeaf(&LeafOp{
41+
Hash: HashOp_SHA256,
42+
Length: LengthOp_VAR_PROTO,
43+
}),
44+
WrapLeaf(&LeafOp{
45+
Hash: HashOp_SHA256,
46+
Length: LengthOp_VAR_PROTO,
47+
}),
48+
},
49+
},
50+
isErr: true,
51+
},
52+
"cannot execute inner first": {
53+
proof: &ExistenceProof{
54+
Key: []byte("food"),
55+
Value: []byte("some longer text"),
56+
Steps: []*ProofOp{
57+
WrapInner(&InnerOp{
58+
Hash: HashOp_SHA256,
59+
Prefix: fromHex("deadbeef00cafe00"),
60+
}),
61+
},
62+
},
63+
isErr: true,
64+
},
65+
"executes lead then inner op": {
66+
proof: &ExistenceProof{
67+
Key: []byte("food"),
68+
Value: []byte("some longer text"),
69+
Steps: []*ProofOp{
70+
WrapLeaf(&LeafOp{
71+
Hash: HashOp_SHA256,
72+
Length: LengthOp_VAR_PROTO,
73+
}),
74+
// output: b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265
75+
WrapInner(&InnerOp{
76+
Hash: HashOp_SHA256,
77+
Prefix: fromHex("deadbeef00cafe00"),
78+
}),
79+
// echo -n deadbeef00cafe00b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265 | xxd -r -p | sha256sum
80+
},
81+
},
82+
expected: fromHex("836ea236a6902a665c2a004c920364f24cad52ded20b1e4f22c3179bfe25b2a9"),
83+
},
84+
}
85+
86+
for name, tc := range cases {
87+
t.Run(name, func(t *testing.T) {
88+
res, err := tc.proof.Calculate()
89+
// short-circuit with error case
90+
if tc.isErr {
91+
if err == nil {
92+
t.Fatal("Expected error, but got none")
93+
}
94+
return
95+
}
96+
97+
if err != nil {
98+
t.Fatal(err)
99+
}
100+
if !bytes.Equal(res, tc.expected) {
101+
t.Errorf("Bad result: %s vs %s", toHex(res), toHex(tc.expected))
102+
}
103+
})
104+
}
105+
}

go/proofs.pb.go

+52-52
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proofs.proto

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEq
5858
in the ProofSpec is valuable to prevent this mutability. And why all trees should
5959
length-prefix the data before hashing it.
6060
*/
61-
message ExistanceProof {
61+
message ExistenceProof {
6262
bytes key = 1;
6363
bytes value = 2;
6464
repeated ProofOp steps = 3;

0 commit comments

Comments
 (0)