Skip to content

Commit

Permalink
feats: add ics23 proof support for cross chain packages (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
yutianwu authored Mar 1, 2023
1 parent f5cb137 commit d065c48
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 28 deletions.
71 changes: 71 additions & 0 deletions core/systemcontracts/upgrade.go

Large diffs are not rendered by default.

30 changes: 25 additions & 5 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidate{},
}

var PrecompiledContractsIsNano = map[common.Address]PrecompiledContract{
var PrecompiledContractsNano = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
Expand All @@ -96,7 +96,7 @@ var PrecompiledContractsIsNano = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidateNano{},
}

var PrecompiledContractsIsMoran = map[common.Address]PrecompiledContract{
var PrecompiledContractsMoran = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
Expand All @@ -111,6 +111,21 @@ var PrecompiledContractsIsMoran = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidateMoran{},
}

var PrecompiledContractsBohr = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},

common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidateBohr{},
}

// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
Expand Down Expand Up @@ -140,6 +155,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
}

var (
PrecompiledAddressesBohr []common.Address
PrecompiledAddressesMoran []common.Address
PrecompiledAddressesNano []common.Address
PrecompiledAddressesBerlin []common.Address
Expand All @@ -161,18 +177,22 @@ func init() {
for k := range PrecompiledContractsBerlin {
PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
}
for k := range PrecompiledContractsIsNano {
for k := range PrecompiledContractsNano {
PrecompiledAddressesNano = append(PrecompiledAddressesNano, k)
}

for k := range PrecompiledContractsIsMoran {
for k := range PrecompiledContractsMoran {
PrecompiledAddressesMoran = append(PrecompiledAddressesMoran, k)
}
for k := range PrecompiledContractsBohr {
PrecompiledAddressesBohr = append(PrecompiledAddressesBohr, k)
}
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsBohr:
return PrecompiledAddressesBohr
case rules.IsMoran:
return PrecompiledAddressesMoran
case rules.IsNano:
Expand Down
37 changes: 33 additions & 4 deletions core/vm/contracts_lightclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,34 @@ func (c *iavlMerkleProofValidateMoran) Run(input []byte) (result []byte, err err
return c.basicIavlMerkleProofValidate.Run(input)
}

type iavlMerkleProofValidateBohr struct {
basicIavlMerkleProofValidate
}

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

func (c *iavlMerkleProofValidateBohr) Run(input []byte) (result []byte, err error) {
c.basicIavlMerkleProofValidate.proofRuntime = lightclient.Ics23CompatibleProofRuntime()
c.basicIavlMerkleProofValidate.verifiers = []merkle.ProofOpVerifier{
forbiddenAbsenceOpVerifier,
singleValueOpVerifier,
multiStoreOpVerifier,
forbiddenSimpleValueOpVerifier,
}
return c.basicIavlMerkleProofValidate.Run(input)
}

func successfulMerkleResult() []byte {
result := make([]byte, merkleProofValidateResultLength)
binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-uint64TypeLength:], 0x01)
return result
}

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

func (c *basicIavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
Expand All @@ -177,15 +203,18 @@ 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 {
return nil, fmt.Errorf("invalid merkle proof")
}

result = make([]byte, merkleProofValidateResultLength)
binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-uint64TypeLength:], 0x01)
return result, nil
return successfulMerkleResult(), nil
}

func forbiddenAbsenceOpVerifier(op merkle.ProofOperator) error {
Expand Down
40 changes: 39 additions & 1 deletion core/vm/contracts_lightclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (

"github.com/stretchr/testify/assert"

"github.com/ethereum/go-ethereum/core/vm/lightclient"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/core/vm/lightclient"
)

const (
Expand Down Expand Up @@ -113,6 +114,43 @@ func TestTmHeaderValidateAndMerkleProofValidate(t *testing.T) {
require.Equal(t, expectedResult, success)
}

func TestIcs23Proof(t *testing.T) {
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("0a300a0a69637332333a6961766c120477696e641a1c0a1a0a0477696e641205626c6f77731a0b0801180120012a030002040a9d010a0c69637332333a73696d706c6512036962631a87010a84010a036962631220141acb8632cfb808f293f2649cb9aabaca74fc18640900ffd0d48e2994b2a1521a090801180120012a0100222708011201011a205f0ba08283de309300409486e978a3ea59d82bccc838b07c7d39bd87c16a5034222708011201011a20455b81ef5591150bd24d3e57a769f65518b16de93487f0fab02271b3d69e2852")
require.NoError(t, err)

merkleProofInput := make([]byte, 32+32+len(key)+32+len(value)+32+len(proofBytes))
copy(merkleProofInput[:32], "ibc")
binary.BigEndian.PutUint64(merkleProofInput[32+24:32+32], uint64(len(key)))
copy(merkleProofInput[32+32:32+32+len(key)], key)

binary.BigEndian.PutUint64(merkleProofInput[32+32+len(key)+24:32+32+len(key)+32], uint64(len(value)))
copy(merkleProofInput[32+32+len(key)+32:32+32+len(key)+32+len(value)], value)

copy(merkleProofInput[32+32+len(key)+32+len(value):32+32+len(key)+32+len(value)+32], appHash)
copy(merkleProofInput[32+32+len(key)+32+len(value)+32:], proofBytes)

totalLengthPrefix := make([]byte, 32)
binary.BigEndian.PutUint64(totalLengthPrefix[0:8], 0)
binary.BigEndian.PutUint64(totalLengthPrefix[8:16], 0)
binary.BigEndian.PutUint64(totalLengthPrefix[16:24], 0)
binary.BigEndian.PutUint64(totalLengthPrefix[24:], uint64(len(merkleProofInput)))

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

validator := iavlMerkleProofValidateBohr{}
success, err := validator.Run(input)
require.NoError(t, err)
expectedResult := make([]byte, 32)
binary.BigEndian.PutUint64(expectedResult[24:], 0x01)
require.Equal(t, expectedResult, success)
}

func TestMerkleProofValidateMoran(t *testing.T) {
// Bytest1 is the inputs of exploit tx 0x05356fd06ce56a9ec5b4eaf9c075abd740cae4c21eab1676440ab5cd2fe5c57a
bytest1, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000005086962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000100380200000000010dd9ac0000000000000000000000000000000000000000000000000000000000000093000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f10072cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a523180a8d020a066961766c3a76120e00000100380200000000010dd9ac1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20121a1f9c4eca726c725796c5375fc4158986ced08e498dc8268ef94d8ed1891612001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd9ac12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143")
Expand Down
9 changes: 6 additions & 3 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import (
"sync/atomic"
"time"

"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

// emptyCodeHash is used by create to ensure deployment is disallowed to already
Expand All @@ -51,10 +52,12 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsBohr:
precompiles = PrecompiledContractsBohr
case evm.chainRules.IsMoran:
precompiles = PrecompiledContractsIsMoran
precompiles = PrecompiledContractsMoran
case evm.chainRules.IsNano:
precompiles = PrecompiledContractsIsNano
precompiles = PrecompiledContractsNano
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul:
Expand Down
107 changes: 107 additions & 0 deletions core/vm/lightclient/ics23_proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package lightclient

import (
"fmt"

"github.com/bnb-chain/ics23"
"github.com/tendermint/tendermint/crypto/merkle"
)

const (
ProofOpIAVLCommitment = "ics23:iavl"
ProofOpSimpleMerkleCommitment = "ics23:simple"
)

// 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
Spec *ics23.ProofSpec
Key []byte
Proof *ics23.CommitmentProof
}

var _ merkle.ProofOperator = CommitmentOp{}

// CommitmentOpDecoder takes a merkle.ProofOp and attempts to decode it into a CommitmentOp ProofOperator
// The proofOp.Data is just a marshalled CommitmentProof. The Key of the CommitmentOp is extracted
// from the unmarshalled proof.
func CommitmentOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
var spec *ics23.ProofSpec
switch pop.Type {
case ProofOpIAVLCommitment:
spec = ics23.IavlSpec
case ProofOpSimpleMerkleCommitment:
spec = ics23.TendermintSpec
default:
return nil, fmt.Errorf("unexpected ProofOp.Type; got %s, want supported ics23 subtypes 'ProofOpIAVLCommitment' or 'ProofOpSimpleMerkleCommitment'", pop.Type)
}

proof := &ics23.CommitmentProof{}
err := proof.Unmarshal(pop.Data)
if err != nil {
return nil, err
}

op := CommitmentOp{
Type: pop.Type,
Key: pop.Key,
Spec: spec,
Proof: proof,
}
return op, nil
}

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 return 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) {
if _, ok := op.Proof.Proof.(*ics23.CommitmentProof_Exist); !ok {
return nil, fmt.Errorf("only exist proof supported")
}

// calculate root from proof
root, err := op.Proof.Calculate()
if err != nil {
return nil, fmt.Errorf("could not calculate root for proof: %v", err)
}
if len(args) != 1 {
return nil, fmt.Errorf("args must be length 1, got: %d", len(args))
}

// 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, fmt.Errorf("proof did not verify existence of key %s with given value %x", op.Key, args[0])
}

return [][]byte{root}, nil
}

// ProofOp implements ProofOperator interface and converts a CommitmentOp
// into a merkle.ProofOp format that can later be decoded by CommitmentOpDecoder
// back into a CommitmentOp for proof verification
func (op CommitmentOp) ProofOp() merkle.ProofOp {
bz, err := op.Proof.Marshal()
if err != nil {
panic(err.Error())
}
return merkle.ProofOp{
Type: op.Type,
Key: op.Key,
Data: bz,
}
}
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
Loading

0 comments on commit d065c48

Please sign in to comment.