Search Apps Documentation Source Content File Folder Download Copy Actions Download

ops.gno

8.64 Kb · 306 lines
  1package ics23
  2
  3import (
  4	"bytes"
  5	"encoding/binary"
  6	"errors"
  7
  8	// adds sha256 capability to crypto.SHA256
  9
 10	"crypto/sha256"
 11
 12	"gno.land/p/nt/ufmt/v0"
 13)
 14
 15type HashOp int32
 16
 17const (
 18	// NO_HASH is the default if no data passed. Note this is an illegal argument some places.
 19	HashOp_NO_HASH HashOp = 0
 20	HashOp_SHA256  HashOp = 1
 21)
 22
 23// LengthOp defines how to process the key and value of the LeafOp
 24// to include length information. After encoding the length with the given
 25// algorithm, the length will be prepended to the key and value bytes.
 26// (Each one with it's own encoded length)
 27type LengthOp int32
 28
 29const (
 30	// NO_PREFIX don't include any length info
 31	LengthOp_NO_PREFIX LengthOp = 0
 32	// VAR_PROTO uses protobuf (and go-amino) varint encoding of the length
 33	LengthOp_VAR_PROTO LengthOp = 1
 34)
 35
 36// validateIavlOps validates the prefix to ensure it begins with
 37// the height, size, and version of the IAVL tree. Each varint must be a bounded value.
 38// In addition, the remaining bytes are validated to ensure they correspond to the correct
 39// length. The layerNum is the inverse of the tree depth, i.e. depth=0 means leaf, depth>=1 means inner node
 40func validateIavlOps(op opType, layerNum int) error {
 41	r := bytes.NewReader(op.GetPrefix())
 42
 43	height, err := binary.ReadVarint(r)
 44	if err != nil {
 45		return ufmt.Errorf("failed to read IAVL height varint: %v", err)
 46	}
 47	if int(height) < 0 || int(height) < layerNum {
 48		return ufmt.Errorf("IAVL height (%d) must be non-negative and greater than or equal to the layer number (%d)", height, layerNum)
 49	}
 50
 51	size, err := binary.ReadVarint(r)
 52	if err != nil {
 53		return ufmt.Errorf("failed to read IAVL size varint: %v", err)
 54	}
 55
 56	if int(size) < 0 {
 57		return ufmt.Errorf("IAVL size must be non-negative")
 58	}
 59
 60	version, err := binary.ReadVarint(r)
 61	if err != nil {
 62		return ufmt.Errorf("failed to read IAVL version varint: %v", err)
 63	}
 64
 65	if int(version) < 0 {
 66		return ufmt.Errorf("IAVL version must be non-negative")
 67	}
 68
 69	// grab the length of the remainder of the prefix
 70	remLen := r.Len()
 71	if layerNum == 0 {
 72		// leaf node
 73		if remLen != 0 {
 74			return ufmt.Errorf("expected remaining prefix length to be 0, got: %d", remLen)
 75		}
 76		if height != 0 {
 77			return ufmt.Errorf("expected leaf node height to be 0, got: %d", remLen)
 78		}
 79		if size != 1 {
 80			return ufmt.Errorf("expected leaf node size to be 1, got: %d", remLen)
 81		}
 82	} else {
 83		// inner node
 84		//
 85		// when the child comes from the left, the suffix if filled in
 86		// prefix: height | size | version | length byte (1 remainder)
 87		//
 88		// when the child comes from the right, the suffix is empty
 89		// prefix: height | size | version | length byte | 32 byte hash | next length byte (34 remainder)
 90		if remLen != 1 && remLen != 34 {
 91			return ufmt.Errorf("remainder of prefix must be of length 1 or 34, got: %d", remLen)
 92		}
 93		if op.GetHash() != HashOp_SHA256 {
 94			return ufmt.Errorf("IAVL hash op must be %v", HashOp_SHA256)
 95		}
 96	}
 97	return nil
 98}
 99
100// validateTendermintOps validates the prefix to ensure it begins with []byte{1}.
101func validateTendermintOps(op *InnerOp) error {
102	if len(op.Prefix) == 0 {
103		return ufmt.Errorf("inner op prefix must not be empty")
104	}
105
106	innerPrefix := []byte{1}
107	if op.Suffix != nil {
108		if !bytes.Equal(op.Prefix, innerPrefix) {
109			return ufmt.Errorf("expected inner op prefix: %v, got: %v", innerPrefix, op.Prefix)
110		}
111	}
112
113	if !bytes.HasPrefix(op.Prefix, innerPrefix) {
114		return ufmt.Errorf("expected inner op prefix to begin with: %v, got: %v", innerPrefix, op.Prefix[:1])
115	}
116
117	return nil
118}
119
120// Apply will calculate the leaf hash given the key and value being proven
121func (op *LeafOp) Apply(key []byte, value []byte) ([]byte, error) {
122	if len(key) == 0 {
123		return nil, errors.New("leaf op needs key")
124	}
125	if len(value) == 0 {
126		return nil, errors.New("leaf op needs value")
127	}
128	pkey, err := prepareLeafData(op.PrehashKey, op.Length, key)
129	if err != nil {
130		return nil, ufmt.Errorf("prehash key, %v", err)
131	}
132	pvalue, err := prepareLeafData(op.PrehashValue, op.Length, value)
133	if err != nil {
134		return nil, ufmt.Errorf("prehash value, %v", err)
135	}
136
137	data := op.Prefix
138	data = append(data, pkey...)
139	data = append(data, pvalue...)
140
141	return doHash(op.Hash, data)
142}
143
144// Apply will calculate the hash of the next step, given the hash of the previous step
145func (op *InnerOp) Apply(child []byte) ([]byte, error) {
146	if len(child) == 0 {
147		return nil, errors.New("inner op needs child value")
148	}
149	preimage := op.Prefix
150	preimage = append(preimage, child...)
151	preimage = append(preimage, op.Suffix...)
152	return doHash(op.Hash, preimage)
153}
154
155// CheckAgainstSpec will verify the LeafOp is in the format defined in spec
156func (op *LeafOp) CheckAgainstSpec(spec *ProofSpec) error {
157	if spec == nil {
158		return errors.New("op and spec must be non-nil")
159	}
160	lspec := spec.LeafSpec
161	if lspec == nil {
162		return errors.New("spec.LeafSpec must be non-nil")
163	}
164
165	if spec.SpecEquals(IavlSpec()) {
166		err := validateIavlOps(op, 0)
167		if err != nil {
168			return err
169		}
170	}
171
172	if op.Hash != lspec.Hash {
173		return ufmt.Errorf("unexpected HashOp: %d", op.Hash)
174	}
175	if op.PrehashKey != lspec.PrehashKey {
176		return ufmt.Errorf("unexpected PrehashKey: %d", op.PrehashKey)
177	}
178	if op.PrehashValue != lspec.PrehashValue {
179		return ufmt.Errorf("unexpected PrehashValue: %d", op.PrehashValue)
180	}
181	if op.Length != lspec.Length {
182		return ufmt.Errorf("unexpected LengthOp: %d", op.Length)
183	}
184	if !bytes.HasPrefix(op.Prefix, lspec.Prefix) {
185		return ufmt.Errorf("leaf Prefix doesn't start with %X", lspec.Prefix)
186	}
187	return nil
188}
189
190// CheckAgainstSpec will verify the InnerOp is in the format defined in spec
191func (op *InnerOp) CheckAgainstSpec(spec *ProofSpec, b int) error {
192	if spec == nil {
193		return errors.New("op and spec must be both non-nil")
194	}
195	if spec.InnerSpec == nil {
196		return errors.New("spec.InnerSpec must be non-nil")
197	}
198	if spec.LeafSpec == nil {
199		return errors.New("spec.LeafSpec must be non-nil")
200	}
201
202	if op.Hash != spec.InnerSpec.Hash {
203		return ufmt.Errorf("unexpected HashOp: %d", op.Hash)
204	}
205
206	if spec.SpecEquals(IavlSpec()) {
207		err := validateIavlOps(op, b)
208		if err != nil {
209			return err
210		}
211	}
212
213	if spec.SpecEquals(TendermintSpec()) {
214		err := validateTendermintOps(op)
215		if err != nil {
216			return err
217		}
218	}
219
220	leafPrefix := spec.LeafSpec.Prefix
221	if bytes.HasPrefix(op.Prefix, leafPrefix) {
222		return ufmt.Errorf("inner Prefix starts with %X", leafPrefix)
223	}
224	if len(op.Prefix) < int(spec.InnerSpec.MinPrefixLength) {
225		return ufmt.Errorf("innerOp prefix too short (%d)", len(op.Prefix))
226	}
227	maxLeftChildBytes := (len(spec.InnerSpec.ChildOrder) - 1) * int(spec.InnerSpec.ChildSize)
228	if len(op.Prefix) > int(spec.InnerSpec.MaxPrefixLength)+maxLeftChildBytes {
229		return ufmt.Errorf("innerOp prefix too long (%d)", len(op.Prefix))
230	}
231
232	if spec.InnerSpec.ChildSize <= 0 {
233		return errors.New("spec.InnerSpec.ChildSize must be >= 1")
234	}
235
236	if spec.InnerSpec.MaxPrefixLength >= spec.InnerSpec.MinPrefixLength+spec.InnerSpec.ChildSize {
237		return errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize")
238	}
239
240	// ensures soundness, with suffix having to be of correct length
241	if len(op.Suffix)%int(spec.InnerSpec.ChildSize) != 0 {
242		return ufmt.Errorf("InnerOp suffix malformed")
243	}
244
245	return nil
246}
247
248// doHash will preform the specified hash on the preimage.
249// if hashOp == NONE, it will return an error (use doHashOrNoop if you want different behavior)
250func doHash(hashOp HashOp, preimage []byte) ([]byte, error) {
251	switch hashOp {
252	case HashOp_SHA256:
253		h := sha256.Sum256(preimage)
254		return h[:], nil
255	}
256	return nil, ufmt.Errorf("unsupported hashop: %d", hashOp)
257}
258
259func prepareLeafData(hashOp HashOp, lengthOp LengthOp, data []byte) ([]byte, error) {
260	// TODO: lengthop before or after hash ???
261	hdata, err := doHashOrNoop(hashOp, data)
262	if err != nil {
263		return nil, err
264	}
265
266	return doLengthOp(lengthOp, hdata)
267}
268
269type opType interface {
270	GetPrefix() []byte
271	GetHash() HashOp
272}
273
274// doLengthOp will calculate the proper prefix and return it prepended
275//
276//	doLengthOp(op, data) -> length(data) || data
277func doLengthOp(lengthOp LengthOp, data []byte) ([]byte, error) {
278	switch lengthOp {
279	case LengthOp_NO_PREFIX:
280		return data, nil
281	case LengthOp_VAR_PROTO:
282		res := append(encodeVarintProto(len(data)), data...)
283		return res, nil
284	}
285	return nil, ufmt.Errorf("unsupported lengthop: %d", lengthOp)
286}
287
288func encodeVarintProto(l int) []byte {
289	// avoid multiple allocs for normal case
290	res := make([]byte, 0, 8)
291	for l >= 1<<7 {
292		res = append(res, uint8(l&0x7f|0x80))
293		l >>= 7
294	}
295	res = append(res, uint8(l))
296	return res
297}
298
299// doHashOrNoop will return the preimage untouched if hashOp == NONE,
300// otherwise, perform doHash
301func doHashOrNoop(hashOp HashOp, preimage []byte) ([]byte, error) {
302	if hashOp == HashOp_NO_HASH {
303		return preimage, nil
304	}
305	return doHash(hashOp, preimage)
306}