forked from cosmos/iavl
-
Notifications
You must be signed in to change notification settings - Fork 4
/
proof_ics23.go
227 lines (198 loc) · 6.04 KB
/
proof_ics23.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
package iavl
import (
"bytes"
"encoding/binary"
"fmt"
ics23 "github.com/confio/ics23/go"
)
/*
GetMembershipProof will produce a CommitmentProof that the given key (and queries value) exists in the iavl tree.
If the key doesn't exist in the tree, this will return an error.
*/
func (t *ImmutableTree) GetMembershipProof(key []byte) (*ics23.CommitmentProof, error) {
exist, err := createExistenceProof(t, key)
if err != nil {
return nil, err
}
proof := &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: exist,
},
}
return proof, nil
}
/*
GetNonMembershipProof will produce a CommitmentProof that the given key doesn't exist in the iavl tree.
If the key exists in the tree, this will return an error.
*/
func (t *ImmutableTree) GetNonMembershipProof(key []byte) (proof *ics23.CommitmentProof, err error) {
var nonexist *ics23.NonExistenceProof
// TODO: to investigate more and potentially enable fast storage
// introduced in: https://github.com/osmosis-labs/iavl/pull/12
// if t.IsFastCacheEnabled() {
// nonexist, err = t.getNonMembershipProofFast(key)
// } else {
// nonexist, err = t.getNonMembershipProof(key)
// }
nonexist, err = t.getNonMembershipProof(key)
if err != nil {
return nil, err
}
proof = &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Nonexist{
Nonexist: nonexist,
},
}
return proof, nil
}
// getNonMembershipProof using regular strategy
// invariant: fast storage is enabled
func (t *ImmutableTree) getNonMembershipProof(key []byte) (*ics23.NonExistenceProof, error) {
// idx is one node right of what we want....
idx, val := t.GetWithIndex(key)
if val != nil {
return nil, fmt.Errorf("cannot create NonExistanceProof when Key in State")
}
var err error
nonexist := &ics23.NonExistenceProof{
Key: key,
}
if idx >= 1 {
leftkey, _ := t.GetByIndex(idx - 1)
nonexist.Left, err = createExistenceProof(t, leftkey)
if err != nil {
return nil, err
}
}
// this will be nil if nothing right of the queried key
rightkey, _ := t.GetByIndex(idx)
if rightkey != nil {
nonexist.Right, err = createExistenceProof(t, rightkey)
if err != nil {
return nil, err
}
}
return nonexist, nil
}
// getNonMembershipProofFast using fast storage
// invariant: fast storage is enabled
func (t *ImmutableTree) getNonMembershipProofFast(key []byte) (*ics23.NonExistenceProof, error) {
index := 0
var prevKey []byte = nil
var nextKey []byte = nil
done := false
itr := t.Iterator(nil, nil, true)
defer itr.Close()
for ; !done && itr.Valid(); itr.Next() {
switch bytes.Compare(itr.Key(), key) {
case -1:
index++
prevKey = itr.Key()
case 1:
nextKey = itr.Key()
done = true
default:
done = true
}
}
// If next was not set, that means we found the key during iterations above
if done && nextKey == nil {
return nil, fmt.Errorf("cannot create NonExistanceProof when Key in State")
}
var err error
nonexist := &ics23.NonExistenceProof{
Key: key,
}
if prevKey != nil {
nonexist.Left, err = createExistenceProof(t, prevKey)
if err != nil {
return nil, err
}
}
if nextKey != nil {
nonexist.Right, err = createExistenceProof(t, nextKey)
if err != nil {
return nil, err
}
}
return nonexist, nil
}
func createExistenceProof(tree *ImmutableTree, key []byte) (*ics23.ExistenceProof, error) {
value, proof, err := tree.GetWithProof(key)
if err != nil {
return nil, err
}
if value == nil {
return nil, fmt.Errorf("cannot create ExistanceProof when Key not in State")
}
return convertExistenceProof(proof, key, value)
}
// convertExistenceProof will convert the given proof into a valid
// existence proof, if that's what it is.
//
// This is the simplest case of the range proof and we will focus on
// demoing compatibility here
func convertExistenceProof(p *RangeProof, key, value []byte) (*ics23.ExistenceProof, error) {
if len(p.Leaves) != 1 {
return nil, fmt.Errorf("existence proof requires RangeProof to have exactly one leaf")
}
return &ics23.ExistenceProof{
Key: key,
Value: value,
Leaf: convertLeafOp(p.Leaves[0].Version),
Path: convertInnerOps(p.LeftPath),
}, nil
}
func convertLeafOp(version int64) *ics23.LeafOp {
var varintBuf [binary.MaxVarintLen64]byte
// this is adapted from iavl/proof.go:proofLeafNode.Hash()
prefix := convertVarIntToBytes(0, varintBuf)
prefix = append(prefix, convertVarIntToBytes(1, varintBuf)...)
prefix = append(prefix, convertVarIntToBytes(version, varintBuf)...)
return &ics23.LeafOp{
Hash: ics23.HashOp_SHA256,
PrehashValue: ics23.HashOp_SHA256,
Length: ics23.LengthOp_VAR_PROTO,
Prefix: prefix,
}
}
// we cannot get the proofInnerNode type, so we need to do the whole path in one function
func convertInnerOps(path PathToLeaf) []*ics23.InnerOp {
steps := make([]*ics23.InnerOp, 0, len(path))
// lengthByte is the length prefix prepended to each of the sha256 sub-hashes
var lengthByte byte = 0x20
var varintBuf [binary.MaxVarintLen64]byte
// we need to go in reverse order, iavl starts from root to leaf,
// we want to go up from the leaf to the root
for i := len(path) - 1; i >= 0; i-- {
// this is adapted from iavl/proof.go:proofInnerNode.Hash()
prefix := convertVarIntToBytes(int64(path[i].Height), varintBuf)
prefix = append(prefix, convertVarIntToBytes(path[i].Size, varintBuf)...)
prefix = append(prefix, convertVarIntToBytes(path[i].Version, varintBuf)...)
var suffix []byte
if len(path[i].Left) > 0 {
// length prefixed left side
prefix = append(prefix, lengthByte)
prefix = append(prefix, path[i].Left...)
// prepend the length prefix for child
prefix = append(prefix, lengthByte)
} else {
// prepend the length prefix for child
prefix = append(prefix, lengthByte)
// length-prefixed right side
suffix = []byte{lengthByte}
suffix = append(suffix, path[i].Right...)
}
op := &ics23.InnerOp{
Hash: ics23.HashOp_SHA256,
Prefix: prefix,
Suffix: suffix,
}
steps = append(steps, op)
}
return steps
}
func convertVarIntToBytes(orig int64, buf [binary.MaxVarintLen64]byte) []byte {
n := binary.PutVarint(buf[:], orig)
return buf[:n]
}