Skip to content

Commit

Permalink
support legacy merkle proof validation
Browse files Browse the repository at this point in the history
  • Loading branch information
yutianwu committed Dec 2, 2022
1 parent 619f002 commit 49a570d
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 149 deletions.
48 changes: 17 additions & 31 deletions core/vm/contracts_lightclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,43 +153,23 @@ func (c *iavlMerkleProofValidateMoran) Run(input []byte) (result []byte, err err
return c.basicIavlMerkleProofValidate.Run(input)
}

type iavlMerkleProofValidateBohr struct{}
type iavlMerkleProofValidateBohr struct {
basicIavlMerkleProofValidate
}

func (c *iavlMerkleProofValidateBohr) RequiredGas(_ []byte) uint64 {
return params.IAVLMerkleProofValidateGas
}

// input:
// | version | proof |
// | 8 bytes | |
func (c *iavlMerkleProofValidateBohr) Run(input []byte) (result []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v\n", r)
}
}()

if uint64(len(input)) <= precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input: input should include %d bytes payload length and payload", precompileContractInputMetaDataLength)
}

payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-uint64TypeLength : precompileContractInputMetaDataLength])
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength+uint64TypeLength {
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength+uint64TypeLength, len(input))
}

version := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength : precompileContractInputMetaDataLength+uint64TypeLength])

kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength+uint64TypeLength:])
if err != nil {
return
}
valid := kvmp.ValidateIcs23(int64(version))
if !valid {
return nil, fmt.Errorf("invalid merkle proof")
c.basicIavlMerkleProofValidate.proofRuntime = lightclient.Ics23CompatibleProofRuntime()
c.basicIavlMerkleProofValidate.verifiers = []merkle.ProofOpVerifier{
forbiddenAbsenceOpVerifier,
singleValueOpVerifier,
multiStoreOpVerifier,
forbiddenSimpleValueOpVerifier,
}

return successfulMerkleResult(), nil
return c.basicIavlMerkleProofValidate.Run(input)
}

func successfulMerkleResult() []byte {
Expand All @@ -199,7 +179,8 @@ func successfulMerkleResult() []byte {
}

type basicIavlMerkleProofValidate struct {
verifiers []merkle.ProofOpVerifier
verifiers []merkle.ProofOpVerifier
proofRuntime *merkle.ProofRuntime
}

func (c *basicIavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
Expand All @@ -222,6 +203,11 @@ func (c *basicIavlMerkleProofValidate) Run(input []byte) (result []byte, err err
if err != nil {
return nil, err
}
if c.proofRuntime == nil {
kvmp.SetProofRuntime(lightclient.DefaultProofRuntime())
} else {
kvmp.SetProofRuntime(c.proofRuntime)
}
kvmp.SetVerifiers(c.verifiers)
valid := kvmp.Validate()
if !valid {
Expand Down
9 changes: 2 additions & 7 deletions core/vm/contracts_lightclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ func TestTmHeaderValidateAndMerkleProofValidate(t *testing.T) {
}

func TestIcs23Proof(t *testing.T) {
appHash, err := hex.DecodeString("9e29c388c7fa0a27fd22c447d671da114261fa668ccbbc073731e570bf189c92")
appHash, err := hex.DecodeString("ae6d1123fc362b3297bfb19c9f9fabbcbd1e2555b923dead261905b8a2ff6db6")
require.NoError(t, err)
key, err := hex.DecodeString("77696e64")
require.NoError(t, err)
value, err := hex.DecodeString("626c6f7773")
require.NoError(t, err)
proofBytes, err := hex.DecodeString("0a300a0a69637332333a6961766c120477696e641a1c0a1a0a0477696e641205626c6f77731a0b0801180120012a030002040a9d010a0c69637332333a73696d706c6512036962631a87010a84010a036962631220fb110273d89f0fd5620a0133bdac38249b8d0a2f1ce29d2fc08c3708a24240bd1a090801180120012a0100222708011201011a209db623f0b6cc658e2fb36b66282625a13c51aaa2a7c5e77dec3f8d49fba5cf2f222708011201011a207893b6019c4768f1e82e4e5809ba59e300ee48c0d3ceaafb082d6ca44614ab4e")
proofBytes, err := hex.DecodeString("0a300a0a69637332333a6961766c120477696e641a1c0a1a0a0477696e641205626c6f77731a0b0801180120012a030002040a9d010a0c69637332333a73696d706c6512036962631a87010a84010a036962631220141acb8632cfb808f293f2649cb9aabaca74fc18640900ffd0d48e2994b2a1521a090801180120012a0100222708011201011a205f0ba08283de309300409486e978a3ea59d82bccc838b07c7d39bd87c16a5034222708011201011a20455b81ef5591150bd24d3e57a769f65518b16de93487f0fab02271b3d69e2852")
require.NoError(t, err)

merkleProofInput := make([]byte, 32+32+len(key)+32+len(value)+32+len(proofBytes))
Expand All @@ -141,11 +141,6 @@ func TestIcs23Proof(t *testing.T) {
binary.BigEndian.PutUint64(totalLengthPrefix[16:24], 0)
binary.BigEndian.PutUint64(totalLengthPrefix[24:], uint64(len(merkleProofInput)))

versionBytes := make([]byte, uint64TypeLength)
binary.BigEndian.PutUint64(versionBytes, 2)

merkleProofInput = append(versionBytes, merkleProofInput...)

input := append(totalLengthPrefix, merkleProofInput...)

validator := iavlMerkleProofValidateBohr{}
Expand Down
82 changes: 0 additions & 82 deletions core/vm/lightclient/ics23_proof.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package lightclient

import (
"bytes"
"fmt"

"github.com/pkg/errors"

"github.com/bnb-chain/ics23"
"github.com/tendermint/tendermint/crypto/merkle"
)
Expand Down Expand Up @@ -104,82 +101,3 @@ func (op CommitmentOp) ProofOp() merkle.ProofOp {
Data: bz,
}
}

func Ics23ProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(ProofOpIAVLCommitment, CommitmentOpDecoder)
prt.RegisterOpDecoder(ProofOpSimpleMerkleCommitment, CommitmentOpDecoder)
return
}

func VerifyValue(root []byte, version int64, proof *merkle.Proof, keyPath string, value []byte) error {
poz, err := Ics23ProofRuntime().DecodeProof(proof)
if err != nil {
return fmt.Errorf("decoding proof erorr, err=%s", err.Error())
}

if len(poz) != 2 {
return fmt.Errorf("length of proof ops should be 2")
}

keys, err := merkle.KeyPathToKeys(keyPath)
if err != nil {
return fmt.Errorf("get keys error, err=%s", err.Error())
}

if len(keys) != 2 {
return fmt.Errorf("length of keys should be 2")
}

storeKey, valueKey := keys[0], keys[1]

iavlPo := poz[0]
if iavlPo.ProofOp().Type != ProofOpIAVLCommitment {
return fmt.Errorf("invalid proof op type, should be %s", ProofOpIAVLCommitment)
}

if !bytes.Equal(valueKey, iavlPo.GetKey()) {
return fmt.Errorf("invalid proof of key, require %X, got %X", iavlPo.GetKey(), valueKey)
}

iavlRoot, err := iavlPo.Run([][]byte{value})
if err != nil {
return fmt.Errorf("invalid iavl proof, err=%s", err.Error())
}

if len(iavlRoot) != 1 {
return fmt.Errorf("invalid return of iavl proof")
}

// calculate store hash
storeInfo := StoreInfo{
Name: string(storeKey),
Core: StoreCore{
CommitID: CommitID{
Version: version,
Hash: iavlRoot[0],
},
},
}
storeHash := storeInfo.Hash()

simplePo := poz[1]
if simplePo.ProofOp().Type != ProofOpSimpleMerkleCommitment {
return fmt.Errorf("invalid proof op type, should be %s", ProofOpSimpleMerkleCommitment)
}
if !bytes.Equal(storeKey, simplePo.GetKey()) {
return fmt.Errorf("invalid proof of key, require %X, got %X", simplePo.GetKey(), storeKey)
}
storeRoot, err := simplePo.Run([][]byte{storeHash})
if err != nil {
return fmt.Errorf("invalid simple proof, err=%s", err.Error())
}
if len(storeRoot) != 1 {
return fmt.Errorf("invald return of simple proof")
}

if !bytes.Equal(root, storeRoot[0]) {
return errors.Errorf("calculated root hash is invalid: expected %+v but got %+v", root, storeRoot[0])
}
return nil
}
15 changes: 11 additions & 4 deletions core/vm/lightclient/multistoreproof.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,6 @@ func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) {
return nil, cmn.NewError("key %v not found in multistore proof", op.key)
}

//-----------------------------------------------------------------------------

// XXX: This should be managed by the rootMultiStore which may want to register
// more proof ops?
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
Expand All @@ -136,3 +132,14 @@ func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
return
}

func Ics23CompatibleProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder)
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
prt.RegisterOpDecoder(ProofOpIAVLCommitment, CommitmentOpDecoder)
prt.RegisterOpDecoder(ProofOpSimpleMerkleCommitment, CommitmentOpDecoder)
return
}
3 changes: 0 additions & 3 deletions core/vm/lightclient/rootmultistore.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ func (cid CommitID) String() string {
//----------------------------------------
// CommitInfo

// NOTE: Keep CommitInfo a simple immutable struct.
type CommitInfo struct {

// Version
Expand All @@ -39,7 +38,6 @@ type CommitInfo struct {

// Hash returns the simple merkle root hash of the stores sorted by name.
func (ci CommitInfo) Hash() []byte {
// TODO cache to ci.hash []byte
m := make(map[string][]byte, len(ci.StoreInfos))
for _, storeInfo := range ci.StoreInfos {
m[storeInfo.Name] = storeInfo.Hash()
Expand Down Expand Up @@ -71,7 +69,6 @@ type StoreCore struct {
// ... maybe add more state
}

// Implements merkle.Hasher.
func (si StoreInfo) Hash() []byte {
// Doesn't write Name, since merkle.SimpleHashFromMap() will
// include them via the keys.
Expand Down
30 changes: 8 additions & 22 deletions core/vm/lightclient/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,43 +216,29 @@ type KeyValueMerkleProof struct {
AppHash []byte
Proof *merkle.Proof

verifiers []merkle.ProofOpVerifier
verifiers []merkle.ProofOpVerifier
proofRuntime *merkle.ProofRuntime
}

func (kvmp *KeyValueMerkleProof) SetProofRuntime(prt *merkle.ProofRuntime) {
kvmp.proofRuntime = prt
}

func (kvmp *KeyValueMerkleProof) SetVerifiers(verifiers []merkle.ProofOpVerifier) {
kvmp.verifiers = verifiers
}

func (kvmp *KeyValueMerkleProof) Validate() bool {
prt := DefaultProofRuntime()

kp := merkle.KeyPath{}
kp = kp.AppendKey([]byte(kvmp.StoreName), merkle.KeyEncodingURL)
kp = kp.AppendKey(kvmp.Key, merkle.KeyEncodingURL)

if len(kvmp.Value) == 0 {
err := prt.VerifyAbsence(kvmp.Proof, kvmp.AppHash, kp.String(), kvmp.verifiers...)
err := kvmp.proofRuntime.VerifyAbsence(kvmp.Proof, kvmp.AppHash, kp.String(), kvmp.verifiers...)
return err == nil
}

err := prt.VerifyValue(kvmp.Proof, kvmp.AppHash, kp.String(), kvmp.Value, kvmp.verifiers...)
return err == nil
}

func (kvmp *KeyValueMerkleProof) ValidateIcs23(version int64) bool {
kp := merkle.KeyPath{}
kp = kp.AppendKey([]byte(kvmp.StoreName), merkle.KeyEncodingURL)
kp = kp.AppendKey(kvmp.Key, merkle.KeyEncodingURL)

// does not support absence
if len(kvmp.Value) == 0 {
return false
}

err := VerifyValue(kvmp.AppHash, version, kvmp.Proof, kp.String(), kvmp.Value)
if err != nil {
println(err.Error())
}
err := kvmp.proofRuntime.VerifyValue(kvmp.Proof, kvmp.AppHash, kp.String(), kvmp.Value, kvmp.verifiers...)
return err == nil
}

Expand Down

0 comments on commit 49a570d

Please sign in to comment.