cosmos-sdk/store/v2/proof/proof.go
ipangpang d8c84589fc
style: remove redundant import alias (#23130)
Co-authored-by: hao.wang <hao.wang@haowangdeMacBook-Pro.local>
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: Alex | Skip <alex@skip.money>
2025-01-03 15:04:11 +00:00

231 lines
7.0 KiB
Go

package proof
import (
"crypto/sha256"
ics23 "github.com/cosmos/ics23/go"
"cosmossdk.io/errors/v2"
storeerrors "cosmossdk.io/store/v2/errors"
)
// Proof operation types
const (
ProofOpIAVLCommitment = "ics23:iavl"
ProofOpSimpleMerkleCommitment = "ics23:simple"
ProofOpSMTCommitment = "ics23:smt"
)
var (
leafPrefix = []byte{0}
innerPrefix = []byte{1}
// SimpleMerkleSpec is the ics23 proof spec for simple merkle proofs.
SimpleMerkleSpec = &ics23.ProofSpec{
LeafSpec: &ics23.LeafOp{
Prefix: leafPrefix,
PrehashKey: ics23.HashOp_NO_HASH,
PrehashValue: ics23.HashOp_SHA256,
Hash: ics23.HashOp_SHA256,
Length: ics23.LengthOp_VAR_PROTO,
},
InnerSpec: &ics23.InnerSpec{
ChildOrder: []int32{0, 1},
MinPrefixLength: 1,
MaxPrefixLength: 1,
ChildSize: 32,
Hash: ics23.HashOp_SHA256,
},
}
)
// CommitmentOp implements merkle.ProofOperator by wrapping an ics23 CommitmentProof.
// It also contains a Key field to determine which key the proof is proving.
// NOTE: CommitmentProof currently can either be ExistenceProof or NonexistenceProof
//
// Type and Spec are classified by the kind of merkle proof it represents allowing
// the code to be reused by more types. Spec is never on the wire, but mapped
// from type in the code.
type CommitmentOp struct {
Type string
Key []byte
Spec *ics23.ProofSpec
Proof *ics23.CommitmentProof
}
func NewIAVLCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp {
return CommitmentOp{
Type: ProofOpIAVLCommitment,
Spec: ics23.IavlSpec,
Key: key,
Proof: proof,
}
}
func NewSimpleMerkleCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp {
return CommitmentOp{
Type: ProofOpSimpleMerkleCommitment,
Spec: SimpleMerkleSpec,
Key: key,
Proof: proof,
}
}
func NewSMTCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp {
return CommitmentOp{
Type: ProofOpSMTCommitment,
Spec: ics23.SmtSpec,
Key: key,
Proof: proof,
}
}
func (op CommitmentOp) GetKey() []byte {
return op.Key
}
// Run takes in a list of arguments and attempts to run the proof op against these
// arguments. Returns the root wrapped in [][]byte if the proof op succeeds with
// given args. If not, it will return an error.
//
// CommitmentOp will accept args of length 1 or length 0. If length 1 args is
// passed in, then CommitmentOp will attempt to prove the existence of the key
// with the value provided by args[0] using the embedded CommitmentProof and returns
// the CommitmentRoot of the proof. If length 0 args is passed in, then CommitmentOp
// will attempt to prove the absence of the key in the CommitmentOp and return the
// CommitmentRoot of the proof.
func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) {
// calculate root from proof
root, err := op.Proof.Calculate()
if err != nil {
return nil, errors.Wrapf(storeerrors.ErrInvalidProof, "could not calculate root for proof: %v", err)
}
// Only support an existence proof or nonexistence proof (batch proofs currently unsupported)
switch len(args) {
case 0:
// Args are nil, so we verify the absence of the key.
absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key)
if !absent {
return nil, errors.Wrapf(storeerrors.ErrInvalidProof, "proof did not verify absence of key: %s", string(op.Key))
}
case 1:
// Args is length 1, verify existence of key with value args[0]
if !ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) {
return nil, errors.Wrapf(storeerrors.ErrInvalidProof, "proof did not verify existence of key %s with given value %x", op.Key, args[0])
}
default:
return nil, errors.Wrapf(storeerrors.ErrInvalidProof, "args must be length 0 or 1, got: %d", len(args))
}
return [][]byte{root}, nil
}
// ProofFromByteSlices computes the proof from the given leaves. An iteration will be
// performed for each level of the tree, where each iteration hashes together the bottom most
// nodes. If the length of the bottom most nodes is odd, then the last node will be saved
// for the next iteration.
//
// Example:
// Iteration 1:
// n = 5
// leaves = a, b, c, d, e.
// index = 2 (prove c)
//
// Iteration 2:
// n = 3
// leaves = ab, cd, e
// index = 1 (prove c, so index of cd)
//
// Iteration 3:
// n = 2
// leaves = abcd, e
// index = 0 (prove c, so index of abcd)
//
// Final iteration:
// n = 1
// leaves = abcde
// index = 0
//
// The bitwise & operator allows us to determine if the index or length is odd or even.
// The bitwise ^ operator allows us to increment when the value is even and decrement when it is odd.
func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners []*ics23.InnerOp) {
if len(leaves) == 0 {
return emptyHash(), nil
}
n := len(leaves)
for n > 1 {
// Begin by constructing the proof for the inner node of the requested index.
// A proof of the inner node is skipped only in the case where the requested index
// is the last element and it does not have a leaf pair (resulting in it being
// saved until the next iteration).
if index < n-1 || index&1 == 1 {
inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256}
// If proof index is even then child is from left, suffix is populated
// otherwise, child is from right and the prefix is populated.
if index&1 == 0 {
// inner op(prefix=0x01 | child | suffix=leaves[index+1])
inner.Prefix = innerPrefix
inner.Suffix = leaves[index^1] // XOR op is index+1 because index is even
} else {
// inner op(prefix=0x01 | leaves[index-1] | child | suffix=nil)
inner.Prefix = append(innerPrefix, leaves[index^1]...) // XOR op is index-1 because index is odd
}
inners = append(inners, inner)
}
// hash together all leaf pairs
for i := 0; i < n/2; i++ {
leaves[i] = InnerHash(leaves[2*i], leaves[2*i+1])
}
// save any leftover leaf for the next iteration
if n&1 == 1 {
leaves[n/2] = leaves[n-1]
}
n = (n + 1) / 2 // n + 1 accounts for any leaves which are added to the next iteration
index /= 2
}
rootHash = leaves[0]
return rootHash, inners
}
// ConvertCommitmentOp converts the given merkle proof into an CommitmentOp.
func ConvertCommitmentOp(inners []*ics23.InnerOp, key, value []byte) CommitmentOp {
return NewSimpleMerkleCommitmentOp(key, &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: &ics23.ExistenceProof{
Key: key,
Value: value,
Leaf: SimpleMerkleSpec.LeafSpec,
Path: inners,
},
},
})
}
func emptyHash() []byte {
h := sha256.Sum256([]byte{})
return h[:]
}
// LeafHash computes the hash of a leaf node.
func LeafHash(key, value []byte) ([]byte, error) {
return SimpleMerkleSpec.LeafSpec.Apply(key, value)
}
// InnerHash computes the hash of an inner node as defined by ics23:
// https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L130
func InnerHash(left, right []byte) []byte {
data := make([]byte, len(innerPrefix)+len(left)+len(right))
n := copy(data, innerPrefix)
n += copy(data[n:], left)
copy(data[n:], right)
h := sha256.Sum256(data)
return h[:]
}