-
Notifications
You must be signed in to change notification settings - Fork 69
/
proof.go
337 lines (302 loc) · 10.2 KB
/
proof.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package ics23
import (
"bytes"
"github.com/pkg/errors"
)
// IavlSpec constrains the format from proofs-iavl (iavl merkle proofs)
var IavlSpec = &ProofSpec{
LeafSpec: &LeafOp{
Prefix: []byte{0},
Hash: HashOp_SHA256,
PrehashValue: HashOp_SHA256,
Length: LengthOp_VAR_PROTO,
},
InnerSpec: &InnerSpec{
ChildOrder: []int32{0, 1},
MinPrefixLength: 4,
MaxPrefixLength: 12,
ChildSize: 33, // (with length byte)
Hash: HashOp_SHA256,
},
}
// TendermintSpec constrains the format from proofs-tendermint (crypto/merkle SimpleProof)
var TendermintSpec = &ProofSpec{
LeafSpec: &LeafOp{
Prefix: []byte{0},
Hash: HashOp_SHA256,
PrehashValue: HashOp_SHA256,
Length: LengthOp_VAR_PROTO,
},
InnerSpec: &InnerSpec{
ChildOrder: []int32{0, 1},
MinPrefixLength: 1,
MaxPrefixLength: 1,
ChildSize: 32, // (no length byte)
Hash: HashOp_SHA256,
},
}
// Calculate determines the root hash that matches a given Commitment proof
// by type switching and calculating root based on proof type
// NOTE: Calculate will return the first calculated root in the proof,
// you must validate that all other embedded ExistenceProofs commit to the same root.
// This can be done with the Verify method
func (p *CommitmentProof) Calculate() (CommitmentRoot, error) {
switch v := p.Proof.(type) {
case *CommitmentProof_Exist:
return v.Exist.Calculate()
case *CommitmentProof_Nonexist:
return v.Nonexist.Calculate()
case *CommitmentProof_Batch:
if len(v.Batch.GetEntries()) == 0 || v.Batch.GetEntries()[0] == nil {
return nil, errors.New("batch proof has empty entry")
}
if e := v.Batch.GetEntries()[0].GetExist(); e != nil {
return e.Calculate()
}
if n := v.Batch.GetEntries()[0].GetNonexist(); n != nil {
return n.Calculate()
}
case *CommitmentProof_Compressed:
proof := Decompress(p)
return proof.Calculate()
default:
return nil, errors.New("unrecognized proof type")
}
return nil, errors.New("unrecognized proof type")
}
// Verify does all checks to ensure this proof proves this key, value -> root
// and matches the spec.
func (p *ExistenceProof) Verify(spec *ProofSpec, root CommitmentRoot, key []byte, value []byte) error {
if err := p.CheckAgainstSpec(spec); err != nil {
return err
}
if !bytes.Equal(key, p.Key) {
return errors.Errorf("Provided key doesn't match proof")
}
if !bytes.Equal(value, p.Value) {
return errors.Errorf("Provided value doesn't match proof")
}
calc, err := p.Calculate()
if err != nil {
return errors.Wrap(err, "Error calculating root")
}
if !bytes.Equal(root, calc) {
return errors.Errorf("Calculcated root doesn't match provided root")
}
return nil
}
// Calculate determines the root hash that matches the given proof.
// You must validate the result is what you have in a header.
// Returns error if the calculations cannot be performed.
func (p *ExistenceProof) Calculate() (CommitmentRoot, error) {
if p.GetLeaf() == nil {
return nil, errors.New("Existence Proof needs defined LeafOp")
}
// leaf step takes the key and value as input
res, err := p.Leaf.Apply(p.Key, p.Value)
if err != nil {
return nil, errors.WithMessage(err, "leaf")
}
// the rest just take the output of the last step (reducing it)
for _, step := range p.Path {
res, err = step.Apply(res)
if err != nil {
return nil, errors.WithMessage(err, "inner")
}
}
return res, nil
}
// Calculate determines the root hash that matches the given nonexistence rpoog.
// You must validate the result is what you have in a header.
// Returns error if the calculations cannot be performed.
func (p *NonExistenceProof) Calculate() (CommitmentRoot, error) {
// A Nonexist proof may have left or right proof nil
switch {
case p.Left != nil:
return p.Left.Calculate()
case p.Right != nil:
return p.Right.Calculate()
default:
return nil, errors.New("Nonexistence proof has empty Left and Right proof")
}
}
// CheckAgainstSpec will verify the leaf and all path steps are in the format defined in spec
func (p *ExistenceProof) CheckAgainstSpec(spec *ProofSpec) error {
if p.GetLeaf() == nil {
return errors.New("Existence Proof needs defined LeafOp")
}
err := p.Leaf.CheckAgainstSpec(spec)
if err != nil {
return errors.WithMessage(err, "leaf")
}
if spec.MinDepth > 0 && len(p.Path) < int(spec.MinDepth) {
return errors.Errorf("InnerOps depth too short: %d", len(p.Path))
}
if spec.MaxDepth > 0 && len(p.Path) > int(spec.MaxDepth) {
return errors.Errorf("InnerOps depth too long: %d", len(p.Path))
}
for _, inner := range p.Path {
if err := inner.CheckAgainstSpec(spec); err != nil {
return errors.WithMessage(err, "inner")
}
}
return nil
}
// Verify does all checks to ensure the proof has valid non-existence proofs,
// and they ensure the given key is not in the CommitmentState
func (p *NonExistenceProof) Verify(spec *ProofSpec, root CommitmentRoot, key []byte) error {
// ensure the existence proofs are valid
var leftKey, rightKey []byte
if p.Left != nil {
if err := p.Left.Verify(spec, root, p.Left.Key, p.Left.Value); err != nil {
return errors.Wrap(err, "left proof")
}
leftKey = p.Left.Key
}
if p.Right != nil {
if err := p.Right.Verify(spec, root, p.Right.Key, p.Right.Value); err != nil {
return errors.Wrap(err, "right proof")
}
rightKey = p.Right.Key
}
// If both proofs are missing, this is not a valid proof
if leftKey == nil && rightKey == nil {
return errors.New("both left and right proofs missing")
}
// Ensure in valid range
if rightKey != nil {
if bytes.Compare(key, rightKey) >= 0 {
return errors.New("key is not left of right proof")
}
}
if leftKey != nil {
if bytes.Compare(key, leftKey) <= 0 {
return errors.New("key is not right of left proof")
}
}
if leftKey == nil {
if !IsLeftMost(spec.InnerSpec, p.Right.Path) {
return errors.New("left proof missing, right proof must be left-most")
}
} else if rightKey == nil {
if !IsRightMost(spec.InnerSpec, p.Left.Path) {
return errors.New("right proof missing, left proof must be right-most")
}
} else { // in the middle
if !IsLeftNeighbor(spec.InnerSpec, p.Left.Path, p.Right.Path) {
return errors.New("right proof missing, left proof must be right-most")
}
}
return nil
}
// IsLeftMost returns true if this is the left-most path in the tree
func IsLeftMost(spec *InnerSpec, path []*InnerOp) bool {
minPrefix, maxPrefix, suffix := getPadding(spec, 0)
// ensure every step has a prefix and suffix defined to be leftmost
for _, step := range path {
if !hasPadding(step, minPrefix, maxPrefix, suffix) {
return false
}
}
return true
}
// IsRightMost returns true if this is the left-most path in the tree
func IsRightMost(spec *InnerSpec, path []*InnerOp) bool {
last := len(spec.ChildOrder) - 1
minPrefix, maxPrefix, suffix := getPadding(spec, int32(last))
// ensure every step has a prefix and suffix defined to be rightmost
for _, step := range path {
if !hasPadding(step, minPrefix, maxPrefix, suffix) {
return false
}
}
return true
}
// IsLeftNeighbor returns true if `right` is the next possible path right of `left`
//
// Find the common suffix from the Left.Path and Right.Path and remove it. We have LPath and RPath now, which must be neighbors.
// Validate that LPath[len-1] is the left neighbor of RPath[len-1]
// For step in LPath[0..len-1], validate step is right-most node
// For step in RPath[0..len-1], validate step is left-most node
func IsLeftNeighbor(spec *InnerSpec, left []*InnerOp, right []*InnerOp) bool {
// count common tail (from end, near root)
left, topleft := left[:len(left)-1], left[len(left)-1]
right, topright := right[:len(right)-1], right[len(right)-1]
for bytes.Equal(topleft.Prefix, topright.Prefix) && bytes.Equal(topleft.Suffix, topright.Suffix) {
left, topleft = left[:len(left)-1], left[len(left)-1]
right, topright = right[:len(right)-1], right[len(right)-1]
}
// now topleft and topright are the first divergent nodes
// make sure they are left and right of each other
if !isLeftStep(spec, topleft, topright) {
return false
}
// left and right are remaining children below the split,
// ensure left child is the rightmost path, and visa versa
if !IsRightMost(spec, left) {
return false
}
if !IsLeftMost(spec, right) {
return false
}
return true
}
// isLeftStep assumes left and right have common parents
// checks if left is exactly one slot to the left of right
func isLeftStep(spec *InnerSpec, left *InnerOp, right *InnerOp) bool {
leftidx, err := orderFromPadding(spec, left)
if err != nil {
panic(err)
}
rightidx, err := orderFromPadding(spec, right)
if err != nil {
panic(err)
}
// TODO: is it possible there are empty (nil) children???
return rightidx == leftidx+1
}
func hasPadding(op *InnerOp, minPrefix, maxPrefix, suffix int) bool {
if len(op.Prefix) < minPrefix {
return false
}
if len(op.Prefix) > maxPrefix {
return false
}
return len(op.Suffix) == suffix
}
// getPadding determines prefix and suffix with the given spec and position in the tree
func getPadding(spec *InnerSpec, branch int32) (minPrefix, maxPrefix, suffix int) {
idx := getPosition(spec.ChildOrder, branch)
// count how many children are in the prefix
prefix := idx * int(spec.ChildSize)
minPrefix = prefix + int(spec.MinPrefixLength)
maxPrefix = prefix + int(spec.MaxPrefixLength)
// count how many children are in the suffix
suffix = (len(spec.ChildOrder) - 1 - idx) * int(spec.ChildSize)
return
}
// getPosition checks where the branch is in the order and returns
// the index of this branch
func getPosition(order []int32, branch int32) int {
if branch < 0 || int(branch) >= len(order) {
panic(errors.Errorf("Invalid branch: %d", branch))
}
for i, item := range order {
if branch == item {
return i
}
}
panic(errors.Errorf("Branch %d not found in order %v", branch, order))
}
// This will look at the proof and determine which order it is...
// So we can see if it is branch 0, 1, 2 etc... to determine neighbors
func orderFromPadding(spec *InnerSpec, inner *InnerOp) (int32, error) {
maxbranch := int32(len(spec.ChildOrder))
for branch := int32(0); branch < maxbranch; branch++ {
minp, maxp, suffix := getPadding(spec, branch)
if hasPadding(inner, minp, maxp, suffix) {
return branch, nil
}
}
return 0, errors.New("Cannot find any valid spacing for this node")
}