Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feats: add ics23 proof support for cross chain packages #1149

Merged
merged 8 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
yutianwu marked this conversation as resolved.
Show resolved Hide resolved
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()
yutianwu marked this conversation as resolved.
Show resolved Hide resolved
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