cosmos-sdk/store/v2/smt/ics23.go

125 lines
3.0 KiB
Go

// Here we implement proof generation according to the ICS-23 specification:
// https://github.com/cosmos/ibc/tree/master/spec/core/ics-023-vector-commitments
package smt
import (
"crypto/sha256"
"fmt"
dbm "github.com/cosmos/cosmos-sdk/db"
ics23 "github.com/confio/ics23/go"
)
func createIcs23Proof(store *Store, key []byte) (*ics23.CommitmentProof, error) {
ret := &ics23.CommitmentProof{}
path := sha256.Sum256(key)
has, err := store.tree.Has(key)
if err != nil {
return nil, err
}
if has { // Membership proof
value, err := store.values.Get(path[:])
if err != nil {
return nil, err
}
if value == nil {
return nil, fmt.Errorf("value not found for: %v", key)
}
proof, err := store.tree.Prove(key)
if err != nil {
return nil, err
}
ret.Proof = &ics23.CommitmentProof_Exist{&ics23.ExistenceProof{
Key: path[:],
Value: value,
Leaf: ics23.SmtSpec.LeafSpec,
Path: convertInnerOps(path[:], proof.SideNodes),
}}
} else { // Non-membership
nonexist, err := toNonExistenceProof(store, path)
if err != nil {
return nil, err
}
ret.Proof = &ics23.CommitmentProof_Nonexist{nonexist}
}
return ret, nil
}
func toNonExistenceProof(store *Store, path [32]byte) (*ics23.NonExistenceProof, error) {
// Seek to our neighbors via the backing DB
getNext := func(it dbm.Iterator) (*ics23.ExistenceProof, error) {
defer it.Close()
if it.Next() {
value, err := store.values.Get(it.Key())
if err != nil {
return nil, err
}
if value == nil {
return nil, fmt.Errorf("value not found for: %v", it.Value())
}
proof, err := store.tree.Prove(it.Value()) // pass the preimage to Prove
if err != nil {
return nil, err
}
return &ics23.ExistenceProof{
Key: it.Key(),
Value: value,
Leaf: ics23.SmtSpec.LeafSpec,
Path: convertInnerOps(it.Key(), proof.SideNodes),
}, nil
}
return nil, nil
}
var lproof, rproof *ics23.ExistenceProof
it, err := store.preimages.ReverseIterator(nil, path[:])
if err != nil {
return nil, err
}
lproof, err = getNext(it)
if err != nil {
return nil, err
}
it, err = store.preimages.Iterator(path[:], nil)
if err != nil {
return nil, err
}
rproof, err = getNext(it)
if err != nil {
return nil, err
}
return &ics23.NonExistenceProof{
Key: path[:],
Left: lproof,
Right: rproof,
}, nil
}
func convertInnerOps(path []byte, sideNodes [][]byte) []*ics23.InnerOp {
depth := len(sideNodes)
inners := make([]*ics23.InnerOp, 0, depth)
for i := 0; i < len(sideNodes); i++ {
op := &ics23.InnerOp{
Hash: ics23.HashOp_SHA256,
Prefix: []byte{1},
}
if getBitAtFromMSB(path[:], depth-1-i) == 1 {
// right child is on path
op.Prefix = append(op.Prefix, sideNodes[i]...)
} else {
op.Suffix = sideNodes[i]
}
inners = append(inners, op)
}
return inners
}
// getBitAtFromMSB gets the bit at an offset from the most significant bit
func getBitAtFromMSB(data []byte, position int) int {
if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 {
return 1
}
return 0
}