diff --git a/app/ante/ante.go b/app/ante/ante.go index 649444a2d..13629a26f 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -63,7 +63,7 @@ func NewAnteHandler( opts := txWithExtensions.GetExtensionOptions() if len(opts) > 0 { switch typeURL := opts[0].GetTypeUrl(); typeURL { - case "/ethermint.evm.v1beta1.ExtensionOptionsEthereumTx": + case "/ethermint.evm.v1alpha1.ExtensionOptionsEthereumTx": // handle as *evmtypes.MsgEthereumTx anteHandler = sdk.ChainAnteDecorators( @@ -79,7 +79,7 @@ func NewAnteHandler( NewEthIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator. ) - case "/ethermint.evm.v1beta1.ExtensionOptionsWeb3Tx": + case "/ethermint.evm.v1alpha1.ExtensionOptionsWeb3Tx": // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation switch tx.(type) { diff --git a/app/ante/eth.go b/app/ante/eth.go index 21bbb5ffc..e835bfdaa 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -290,7 +290,7 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx if balance.Amount.BigInt().Cmp(msgEthTx.Cost()) < 0 { return ctx, sdkerrors.Wrapf( sdkerrors.ErrInsufficientFunds, - "sender balance < tx gas cost (%s%s < %s%s)", balance.String(), evmDenom, msgEthTx.Cost().String(), evmDenom, + "sender balance < tx gas cost (%s < %s%s)", balance.String(), msgEthTx.Cost().String(), evmDenom, ) } diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 7e7164f15..25b203af6 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -17,9 +17,11 @@ import ( "github.com/cosmos/ethermint/app" ante "github.com/cosmos/ethermint/app/ante" "github.com/cosmos/ethermint/crypto/ethsecp256k1" + "github.com/cosmos/ethermint/tests" ethermint "github.com/cosmos/ethermint/types" evmtypes "github.com/cosmos/ethermint/x/evm/types" + "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -102,7 +104,10 @@ func newTestSDKTx( func (suite *AnteTestSuite) newTestEthTx(msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey) (sdk.Tx, error) { privkey := ðsecp256k1.PrivKey{Key: priv.Bytes()} - if err := msg.Sign(suite.chainID, privkey.ToECDSA()); err != nil { + signer := tests.NewSigner(privkey) + msg.From = common.BytesToAddress(privkey.PubKey().Address().Bytes()).String() + + if err := msg.Sign(suite.chainID, signer); err != nil { return nil, err } diff --git a/crypto/ethsecp256k1/ethsecp256k1.go b/crypto/ethsecp256k1/ethsecp256k1.go index fb0577452..2a22ae635 100644 --- a/crypto/ethsecp256k1/ethsecp256k1.go +++ b/crypto/ethsecp256k1/ethsecp256k1.go @@ -7,7 +7,6 @@ import ( "fmt" ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -105,10 +104,14 @@ func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error { } // Sign creates a recoverable ECDSA signature on the secp256k1 curve over the -// Keccak256 hash of the provided message. The produced signature is 65 bytes +// provided hash of the message. The produced signature is 65 bytes // where the last byte contains the recovery ID. -func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { - return ethcrypto.Sign(ethcrypto.Keccak256Hash(msg).Bytes(), privKey.ToECDSA()) +func (privKey PrivKey) Sign(digestBz []byte) ([]byte, error) { + if len(digestBz) != ethcrypto.DigestLength { + digestBz = ethcrypto.Keccak256Hash(digestBz).Bytes() + } + + return ethcrypto.Sign(digestBz, privKey.ToECDSA()) } // ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type. @@ -190,12 +193,14 @@ func (pubKey *PubKey) UnmarshalAminoJSON(bz []byte) error { // VerifySignature verifies that the ECDSA public key created a given signature over // the provided message. It will calculate the Keccak256 hash of the message // prior to verification. +// +// CONTRACT: The signature should be in [R || S] format. func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool { - if len(sig) == 65 { - // remove recovery ID if contained in the signature + if len(sig) == ethcrypto.SignatureLength { + // remove recovery ID (V) if contained in the signature sig = sig[:len(sig)-1] } // the signature needs to be in [R || S] format when provided to VerifySignature - return secp256k1.VerifySignature(pubKey.Key, ethcrypto.Keccak256Hash(msg).Bytes(), sig) + return ethcrypto.VerifySignature(pubKey.Key, ethcrypto.Keccak256Hash(msg).Bytes(), sig) } diff --git a/ethereum/rpc/eth_api.go b/ethereum/rpc/eth_api.go index c63402f79..ca218e28f 100644 --- a/ethereum/rpc/eth_api.go +++ b/ethereum/rpc/eth_api.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "math/big" "strings" "sync" @@ -23,6 +24,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -288,13 +290,96 @@ func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, // Sign signs the provided data using the private key of address via Geth's signature standard. func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { e.logger.Debugln("eth_sign", "address", address.Hex(), "data", common.Bytes2Hex(data)) - return nil, errors.New("eth_sign not supported") + + from := sdk.AccAddress(address.Bytes()) + + _, err := e.clientCtx.Keyring.KeyByAddress(from) + if err != nil { + e.logger.Errorln("failed to find key in keyring", "address", address.String()) + return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) + } + + // Sign the requested hash with the wallet + signature, _, err := e.clientCtx.Keyring.SignByAddress(from, data) + if err != nil { + e.logger.Panicln("keyring.SignByAddress failed") + return nil, err + } + + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil } // SendTransaction sends an Ethereum transaction. func (e *PublicEthAPI) SendTransaction(args types.SendTxArgs) (common.Hash, error) { e.logger.Debugln("eth_sendTransaction", "args", args) - return common.Hash{}, errors.New("eth_sendTransaction not supported") + + // Look up the wallet containing the requested signer + _, err := e.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes())) + if err != nil { + e.logger.WithError(err).Errorln("failed to find key in keyring", "address", args.From) + return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) + } + + args, err = e.setTxDefaults(args) + if err != nil { + return common.Hash{}, err + } + + tx := args.ToTransaction() + + // Assemble transaction from fields + builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + if !ok { + e.logger.WithError(err).Panicln("clientCtx.TxConfig.NewTxBuilder returns unsupported builder") + } + + option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) + if err != nil { + e.logger.WithError(err).Panicln("codectypes.NewAnyWithValue failed to pack an obvious value") + return common.Hash{}, err + } + + builder.SetExtensionOptions(option) + err = builder.SetMsgs(tx.GetMsgs()...) + if err != nil { + e.logger.WithError(err).Panicln("builder.SetMsgs failed") + } + + fees := sdk.NewCoins(ethermint.NewPhotonCoin(sdk.NewIntFromBigInt(tx.Fee()))) + builder.SetFeeAmount(fees) + builder.SetGasLimit(tx.GetGas()) + + if err := tx.ValidateBasic(); err != nil { + e.logger.Debugln("tx failed basic validation", "error", err) + return common.Hash{}, err + } + + // Sign transaction + if err := tx.Sign(e.chainIDEpoch, e.clientCtx.Keyring); err != nil { + e.logger.Debugln("failed to sign tx", "error", err) + return common.Hash{}, err + } + + // Encode transaction by default Tx encoder + txEncoder := e.clientCtx.TxConfig.TxEncoder() + txBytes, err := txEncoder(tx) + if err != nil { + return common.Hash{}, err + } + + txHash := common.BytesToHash(txBytes) + + // Broadcast transaction in sync mode (default) + // NOTE: If error is encountered on the node, the broadcast will not return an error + asyncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastAsync) + if _, err := asyncCtx.BroadcastTx(txBytes); err != nil { + e.logger.WithError(err).Errorln("failed to broadcast Eth tx") + return txHash, err + } + + // Return transaction hash + return txHash, nil } // SendRawTransaction send a raw Ethereum transaction. @@ -317,13 +402,13 @@ func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, erro option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) if err != nil { - e.logger.Panicln("codectypes.NewAnyWithValue failed to pack an obvious value") + e.logger.WithError(err).Panicln("codectypes.NewAnyWithValue failed to pack an obvious value") } builder.SetExtensionOptions(option) err = builder.SetMsgs(ethereumTx.GetMsgs()...) if err != nil { - e.logger.Panicln("builder.SetMsgs failed") + e.logger.WithError(err).Panicln("builder.SetMsgs failed") } fees := sdk.NewCoins(ethermint.NewPhotonCoin(sdk.NewIntFromBigInt(ethereumTx.Fee()))) @@ -354,8 +439,11 @@ func (e *PublicEthAPI) Call(args types.CallArgs, blockNr types.BlockNumber, _ *t simRes, err := e.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit)) if err != nil { return []byte{}, err - } else if len(simRes.Result.Log) > 0 { + } + + if len(simRes.Result.Log) > 0 { var logs []types.SDKTxLogs + if err := json.Unmarshal([]byte(simRes.Result.Log), &logs); err != nil { e.logger.WithError(err).Errorln("failed to unmarshal simRes.Result.Log") } @@ -366,6 +454,7 @@ func (e *PublicEthAPI) Call(args types.CallArgs, blockNr types.BlockNumber, _ *t e.logger.WithError(err).Warningln("call result decoding failed") return []byte{}, err } + return []byte{}, types.ErrRevertedWith(data.Ret) } } @@ -771,3 +860,74 @@ func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, bl StorageProof: storageProofs, }, nil } + +// setTxDefaults populates tx message with default values in case they are not +// provided on the args +func (e *PublicEthAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs, error) { + + // Get nonce (sequence) from sender account + from := sdk.AccAddress(args.From.Bytes()) + + if args.GasPrice == nil { + // TODO: Change to either: + // - min gas price from context once available through server/daemon, or + // - suggest a gas price based on the previous included txs + args.GasPrice = (*hexutil.Big)(big.NewInt(ethermint.DefaultGasPrice)) + } + + if args.Nonce != nil { + // get the nonce from the account retriever + // ignore error in case tge account doesn't exist yet + _, nonce, _ := e.clientCtx.AccountRetriever.GetAccountNumberSequence(e.clientCtx, from) + args.Nonce = (*hexutil.Uint64)(&nonce) + } + + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { + return args, errors.New("both 'data' and 'input' are set and not equal. Please use 'input' to pass transaction call data") + } + + if args.To == nil { + // Contract creation + var input []byte + if args.Data != nil { + input = *args.Data + } else if args.Input != nil { + input = *args.Input + } + + if len(input) == 0 { + return args, errors.New(`contract creation without any data provided`) + } + } + + if args.Gas == nil { + // For backwards-compatibility reason, we try both input and data + // but input is preferred. + input := args.Input + if input == nil { + input = args.Data + } + + callArgs := rpctypes.CallArgs{ + From: &args.From, // From shouldn't be nil + To: args.To, + Gas: args.Gas, + GasPrice: args.GasPrice, + Value: args.Value, + Data: input, + AccessList: args.AccessList, + } + estimated, err := e.EstimateGas(callArgs) + if err != nil { + return args, err + } + args.Gas = &estimated + e.logger.Debugln("estimate gas usage automatically", "gas", args.Gas) + } + + if args.ChainID == nil { + args.ChainID = (*hexutil.Big)(e.chainIDEpoch) + } + + return args, nil +} diff --git a/ethereum/rpc/types/types.go b/ethereum/rpc/types/types.go index 875b4ed50..2f744d338 100644 --- a/ethereum/rpc/types/types.go +++ b/ethereum/rpc/types/types.go @@ -1,6 +1,7 @@ package types import ( + evmtypes "github.com/cosmos/ethermint/x/evm/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -67,6 +68,33 @@ type SendTxArgs struct { ChainID *hexutil.Big `json:"chainId,omitempty"` } +// ToTransaction converts the arguments to an ethereum transaction. +// This assumes that setTxDefaults has been called. +func (args *SendTxArgs) ToTransaction() *evmtypes.MsgEthereumTx { + var input []byte + if args.Input != nil { + input = *args.Input + } else if args.Data != nil { + input = *args.Data + } + + data := &evmtypes.TxData{ + To: args.To.Bytes(), + ChainID: args.ChainID.ToInt().Bytes(), + Nonce: uint64(*args.Nonce), + GasLimit: uint64(*args.Gas), + GasPrice: args.GasPrice.ToInt().Bytes(), + Amount: args.Value.ToInt().Bytes(), + Input: input, + Accesses: evmtypes.NewAccessList(args.AccessList), + } + + return &evmtypes.MsgEthereumTx{ + Data: data, + From: args.From.String(), + } +} + // CallArgs represents the arguments for a call. type CallArgs struct { From *common.Address `json:"from"` diff --git a/go.mod b/go.mod index 8db3c7046..40e53b1d8 100644 --- a/go.mod +++ b/go.mod @@ -43,9 +43,9 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2 github.com/xlab/suplog v1.3.0 - golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 + golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 // indirect - google.golang.org/genproto v0.0.0-20210505142820-a42aa055cf76 + google.golang.org/genproto v0.0.0-20210510173355-fb37daa5cd7a google.golang.org/grpc v1.37.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 2abb10f2c..0fa191153 100644 --- a/go.sum +++ b/go.sum @@ -916,8 +916,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 h1:QuAh/1Gwc0d+u9walMU1NqzhRemNegsv5esp2ALQIY4= -golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1161,8 +1161,8 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210505142820-a42aa055cf76 h1:0pBp6vCQyvmttnWa4c74n/y2U7bAQeIUVyVvZpb7Fyo= -google.golang.org/genproto v0.0.0-20210505142820-a42aa055cf76/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210510173355-fb37daa5cd7a h1:tzkHckzMzgPr8SC4taTC3AldLr4+oJivSoq1xf/nhsc= +google.golang.org/genproto v0.0.0-20210510173355-fb37daa5cd7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/tests/signer.go b/tests/signer.go new file mode 100644 index 000000000..03259baa5 --- /dev/null +++ b/tests/signer.go @@ -0,0 +1,52 @@ +package tests + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ethermint/crypto/ethsecp256k1" +) + +var _ keyring.Signer = &Signer{} + +// Signer defines a type that is used on testing for signing MsgEthereumTx +type Signer struct { + privKey cryptotypes.PrivKey +} + +func NewSigner(sk cryptotypes.PrivKey) keyring.Signer { + return &Signer{ + privKey: sk, + } +} + +// Sign signs the message using the underlying private key +func (s Signer) Sign(_ string, msg []byte) ([]byte, cryptotypes.PubKey, error) { + if s.privKey.Type() != ethsecp256k1.KeyType { + return nil, nil, fmt.Errorf( + "invalid private key type for signing ethereum tx; expected %s, got %s", + ethsecp256k1.KeyType, + s.privKey.Type(), + ) + } + + sig, err := s.privKey.Sign(msg) + if err != nil { + return nil, nil, err + } + + return sig, s.privKey.PubKey(), nil +} + +// SignByAddress sign byte messages with a user key providing the address. +func (s Signer) SignByAddress(address sdk.Address, msg []byte) ([]byte, cryptotypes.PubKey, error) { + signer := sdk.AccAddress(s.privKey.PubKey().Address()) + if !signer.Equals(address) { + return nil, nil, fmt.Errorf("address mismatch: signer %s ≠ given address %s", signer, address) + } + + return s.Sign("", msg) +} diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 53c616ac2..1577b452b 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -1,7 +1,6 @@ package evm_test import ( - "crypto/ecdsa" "encoding/json" "math/big" "strings" @@ -14,13 +13,14 @@ import ( "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ethermint/app" "github.com/cosmos/ethermint/crypto/ethsecp256k1" + "github.com/cosmos/ethermint/tests" ethermint "github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/x/evm" "github.com/cosmos/ethermint/x/evm/types" @@ -37,9 +37,9 @@ type EvmTestSuite struct { codec codec.BinaryMarshaler chainID *big.Int - privKey *ethsecp256k1.PrivKey - from ethcmn.Address - to sdk.AccAddress + signer keyring.Signer + from ethcmn.Address + to sdk.AccAddress } func (suite *EvmTestSuite) SetupTest() { @@ -49,16 +49,17 @@ func (suite *EvmTestSuite) SetupTest() { suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-888", Time: time.Now().UTC()}) suite.handler = evm.NewHandler(suite.app.EvmKeeper) suite.codec = suite.app.AppCodec() - suite.chainID = big.NewInt(888) + suite.chainID = suite.chainID privKey, err := ethsecp256k1.GenerateKey() suite.Require().NoError(err) suite.to = sdk.AccAddress(privKey.PubKey().Address()) - suite.privKey, err = ethsecp256k1.GenerateKey() + privKey, err = ethsecp256k1.GenerateKey() suite.Require().NoError(err) + suite.signer = tests.NewSigner(privKey) suite.from = ethcmn.BytesToAddress(privKey.PubKey().Address().Bytes()) } @@ -80,14 +81,16 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { "passed", func() { suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100)) - tx = types.NewMsgEthereumTx(suite.chainID, 0, &suite.from, big.NewInt(100), 0, big.NewInt(10000), nil, nil) + to := ethcmn.BytesToAddress(suite.to) + tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil) + tx.From = suite.from.String() // parse context chain ID to big.Int chainID, err := ethermint.ParseChainID(suite.ctx.ChainID()) suite.Require().NoError(err) // sign transaction - err = tx.Sign(chainID, suite.privKey.ToECDSA()) + err = tx.Sign(chainID, suite.signer) suite.Require().NoError(err) }, true, @@ -102,7 +105,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { suite.Require().NoError(err) // sign transaction - err = tx.Sign(chainID, suite.privKey.ToECDSA()) + err = tx.Sign(chainID, suite.signer) suite.Require().NoError(err) }, false, @@ -173,12 +176,11 @@ func (suite *EvmTestSuite) TestHandlerLogs() { gasLimit := uint64(100000) gasPrice := big.NewInt(1000000) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029") tx := types.NewMsgEthereumTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - err = tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err := suite.handler(suite.ctx, tx) @@ -204,13 +206,12 @@ func (suite *EvmTestSuite) TestQueryTxLogs() { gasLimit := uint64(100000) gasPrice := big.NewInt(1000000) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - // send contract deployment transaction with an event in the constructor bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029") tx := types.NewMsgEthereumTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - err = tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err := suite.handler(suite.ctx, tx) @@ -291,12 +292,11 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { gasLimit := uint64(100000000) gasPrice := big.NewInt(10000) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - bytecode := common.FromHex("0x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a36102c4806100dc6000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c010000000000000000000000000000000000000000000000000000000090048063893d20e814610058578063a6f9dae1146100a2575b600080fd5b6100606100e6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100e4600480360360208110156100b857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061010f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f43616c6c6572206973206e6f74206f776e65720000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a3806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820f397f2733a89198bc7fed0764083694c5b828791f39ebcbc9e414bccef14b48064736f6c63430005100032") tx := types.NewMsgEthereumTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err := suite.handler(suite.ctx, tx) @@ -313,7 +313,9 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { storeAddr := "0xa6f9dae10000000000000000000000006a82e4a67715c8412a9114fbd2cbaefbc8181424" bytecode = common.FromHex(storeAddr) tx = types.NewMsgEthereumTx(suite.chainID, 2, &receiver, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err = tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err = suite.handler(suite.ctx, tx) @@ -325,7 +327,8 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { // query - getOwner bytecode = common.FromHex("0x893d20e8") tx = types.NewMsgEthereumTx(suite.chainID, 2, &receiver, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + err = tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err = suite.handler(suite.ctx, tx) @@ -342,15 +345,12 @@ func (suite *EvmTestSuite) TestSendTransaction() { gasLimit := uint64(21000) gasPrice := big.NewInt(0x55ae82600) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - pub := priv.ToECDSA().Public().(*ecdsa.PublicKey) - - suite.app.EvmKeeper.SetBalance(suite.ctx, ethcrypto.PubkeyToAddress(*pub), big.NewInt(100)) + suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100)) // send simple value transfer with gasLimit=21000 tx := types.NewMsgEthereumTx(suite.chainID, 1, ðcmn.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil) - err = tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) result, err := suite.handler(suite.ctx, tx) @@ -418,12 +418,11 @@ func (suite *EvmTestSuite) TestOutOfGasWhenDeployContract() { suite.ctx = suite.ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) gasPrice := big.NewInt(10000) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - bytecode := common.FromHex("0x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a36102c4806100dc6000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c010000000000000000000000000000000000000000000000000000000090048063893d20e814610058578063a6f9dae1146100a2575b600080fd5b6100606100e6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100e4600480360360208110156100b857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061010f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f43616c6c6572206973206e6f74206f776e65720000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a3806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820f397f2733a89198bc7fed0764083694c5b828791f39ebcbc9e414bccef14b48064736f6c63430005100032") tx := types.NewMsgEthereumTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB) @@ -447,13 +446,12 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() { gasLimit := uint64(1000000) gasPrice := big.NewInt(10000) - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err, "failed to create key") - bytecode := common.FromHex("0xa6f9dae10000000000000000000000006a82e4a67715c8412a9114fbd2cbaefbc8181424") tx := types.NewMsgEthereumTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode, nil) - tx.Sign(big.NewInt(888), priv.ToECDSA()) + tx.From = suite.from.String() + + err := tx.Sign(suite.chainID, suite.signer) suite.Require().NoError(err) snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB) diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index efe7fb844..e93da9362 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -1,18 +1,18 @@ package types import ( - "crypto/ecdsa" "fmt" "io" "math/big" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" - ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) @@ -150,13 +150,17 @@ func (msg MsgEthereumTx) GetSignBytes() []byte { // RLPSignBytes returns the RLP hash of an Ethereum transaction message with a // given chainID used for signing. func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash { + if msg.Data.ChainID != nil { + chainID = new(big.Int).SetBytes(msg.Data.ChainID) + } + var accessList *ethtypes.AccessList if msg.Data.Accesses != nil { accessList = msg.Data.Accesses.ToEthAccessList() } return rlpHash([]interface{}{ - new(big.Int).SetBytes(msg.Data.ChainID), + chainID, msg.Data.Nonce, new(big.Int).SetBytes(msg.Data.GasPrice), msg.Data.GasLimit, @@ -167,19 +171,6 @@ func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash { }) } -// RLPSignHomesteadBytes returns the RLP hash of an Ethereum transaction message with a -// a Homestead layout without chainID. -func (msg MsgEthereumTx) RLPSignHomesteadBytes() ethcmn.Hash { - return rlpHash([]interface{}{ - msg.Data.Nonce, - msg.Data.GasPrice, - msg.Data.GasLimit, - msg.To(), - msg.Data.Amount, - msg.Data.Input, - }) -} - // EncodeRLP implements the rlp.Encoder interface. func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error { return rlp.Encode(w, &msg.Data) @@ -202,19 +193,31 @@ func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error { } // Sign calculates a secp256k1 ECDSA signature and signs the transaction. It -// takes a private key and chainID to sign an Ethereum transaction according to -// EIP155 standard. It mutates the transaction as it populates the V, R, S +// takes a keyring signer and the chainID to sign an Ethereum transaction according to +// EIP155 standard. +// This method mutates the transaction as it populates the V, R, S // fields of the Transaction's Signature. -func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) error { +// The function will fail if the sender address is not defined for the msg or if +// the sender is not registered on the keyring +func (msg *MsgEthereumTx) Sign(chainID *big.Int, signer keyring.Signer) error { + from := msg.GetFrom() + if from == nil { + return fmt.Errorf("sender address not defined for message") + } + txHash := msg.RLPSignBytes(chainID) - sig, err := ethcrypto.Sign(txHash[:], priv) + sig, _, err := signer.SignByAddress(from, txHash[:]) if err != nil { return err } - if len(sig) != 65 { - return fmt.Errorf("wrong size for signature: got %d, want 65", len(sig)) + if len(sig) != crypto.SignatureLength { + return fmt.Errorf( + "wrong size for signature: got %d, want %d", + len(sig), + crypto.SignatureLength, + ) } r := new(big.Int).SetBytes(sig[:32]) @@ -279,10 +282,13 @@ func (msg *MsgEthereumTx) GetFrom() sdk.AccAddress { return ethcmn.HexToAddress(msg.From).Bytes() } +// AsTransaction creates an Ethereum Transaction type from the msg fields func (msg MsgEthereumTx) AsTransaction() *ethtypes.Transaction { return ethtypes.NewTx(msg.Data.AsEthereumData()) } +// AsMessage creates an Ethereum core.Message from the msg fields. This method +// fails if the sender address is not defined func (msg MsgEthereumTx) AsMessage() (core.Message, error) { if msg.From == "" { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "'from' address cannot be empty") diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index 57523d650..a61c458df 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -2,38 +2,66 @@ package types import ( "bytes" - "fmt" "math/big" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ethermint/crypto/ethsecp256k1" + "github.com/cosmos/ethermint/tests" ethcmn "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -func TestMsgEthereumTx(t *testing.T) { - addr := GenerateEthAddress() +type MsgsTestSuite struct { + suite.Suite - msg := NewMsgEthereumTx(nil, 0, &addr, nil, 100000, nil, []byte("test"), nil) - require.NotNil(t, msg) - require.EqualValues(t, msg.Data.To, addr.Bytes()) - require.Equal(t, msg.Route(), RouterKey) - require.Equal(t, msg.Type(), TypeMsgEthereumTx) - require.NotNil(t, msg.To()) - require.Equal(t, msg.GetMsgs(), []sdk.Msg{msg}) - require.Panics(t, func() { msg.GetSigners() }) - require.Panics(t, func() { msg.GetSignBytes() }) - - msg = NewMsgEthereumTxContract(nil, 0, nil, 100000, nil, []byte("test"), nil) - require.NotNil(t, msg) - require.Nil(t, msg.Data.To) - require.Nil(t, msg.To()) + signer keyring.Signer + from ethcmn.Address + to ethcmn.Address + chainID *big.Int } -func TestMsgEthereumTxValidation(t *testing.T) { +func TestMsgsTestSuite(t *testing.T) { + suite.Run(t, new(MsgsTestSuite)) +} + +func (suite *MsgsTestSuite) SetupTest() { + privFrom, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + + privTo, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + + suite.signer = tests.NewSigner(privFrom) + suite.from = crypto.PubkeyToAddress(privFrom.ToECDSA().PublicKey) + suite.to = crypto.PubkeyToAddress(privTo.ToECDSA().PublicKey) + suite.chainID = big.NewInt(1) +} + +func (suite *MsgsTestSuite) TestMsgEthereumTx_Constructor() { + msg := NewMsgEthereumTx(nil, 0, &suite.to, nil, 100000, nil, []byte("test"), nil) + + suite.Require().Equal(msg.Data.To, suite.to.Bytes()) + suite.Require().Equal(msg.Route(), RouterKey) + suite.Require().Equal(msg.Type(), TypeMsgEthereumTx) + suite.Require().NotNil(msg.To()) + suite.Require().Equal(msg.GetMsgs(), []sdk.Msg{msg}) + suite.Require().Panics(func() { msg.GetSigners() }) + suite.Require().Panics(func() { msg.GetSignBytes() }) + + msg = NewMsgEthereumTxContract(nil, 0, nil, 100000, nil, []byte("test"), nil) + suite.Require().NotNil(msg) + suite.Require().Nil(msg.Data.To) + suite.Require().Nil(msg.To()) +} + +func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { testCases := []struct { msg string amount *big.Int @@ -41,68 +69,81 @@ func TestMsgEthereumTxValidation(t *testing.T) { expectPass bool }{ {msg: "pass", amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true}, - {msg: "invalid amount", amount: big.NewInt(-1), gasPrice: big.NewInt(100000), expectPass: false}, - {msg: "invalid gas price", amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: false}, - {msg: "invalid gas price", amount: big.NewInt(100), gasPrice: big.NewInt(0), expectPass: false}, + // NOTE: these can't be effectively tested because the SetBytes function from big.Int only sets + // the absolute value + {msg: "negative amount", amount: big.NewInt(-1), gasPrice: big.NewInt(1000), expectPass: true}, + {msg: "negative gas price", amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: true}, + {msg: "zero gas price", amount: big.NewInt(100), gasPrice: big.NewInt(0), expectPass: true}, } for i, tc := range testCases { - msg := NewMsgEthereumTx(nil, 0, nil, tc.amount, 0, tc.gasPrice, nil, nil) + msg := NewMsgEthereumTx(suite.chainID, 0, nil, tc.amount, 0, tc.gasPrice, nil, nil) + err := msg.ValidateBasic() if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "valid test %d failed: %s", i, tc.msg) + suite.Require().NoError(err, "valid test %d failed: %s", i, tc.msg) } else { - require.NotNil(t, msg.ValidateBasic(), "invalid test %d passed: %s", i, tc.msg) + suite.Require().Error(err, "invalid test %d passed: %s", i, tc.msg) } } } -func TestMsgEthereumTxRLPSignBytes(t *testing.T) { - addr := ethcmn.BytesToAddress([]byte("test_address")) - chainID := big.NewInt(3) - - msg := NewMsgEthereumTx(chainID, 0, &addr, nil, 100000, nil, []byte("test"), nil) - hash := msg.RLPSignBytes(chainID) - require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash)) -} - -func TestMsgEthereumTxRLPEncode(t *testing.T) { - addr := ethcmn.BytesToAddress([]byte("test_address")) - expMsg := NewMsgEthereumTx(big.NewInt(1), 0, &addr, nil, 100000, nil, []byte("test"), nil) +func (suite *MsgsTestSuite) TestMsgEthereumTx_EncodeRLP() { + expMsg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil) raw, err := rlp.EncodeToBytes(&expMsg) - require.NoError(t, err) + suite.Require().NoError(err) msg := &MsgEthereumTx{} err = rlp.Decode(bytes.NewReader(raw), &msg) - require.NoError(t, err) - require.Equal(t, expMsg.Data, msg.Data) + suite.Require().NoError(err) + suite.Require().Equal(expMsg.Data, msg.Data) } -// func TestMsgEthereumTxSig(t *testing.T) { -// chainID := big.NewInt(3) +func (suite *MsgsTestSuite) TestMsgEthereumTx_RLPSignBytes() { + msg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil) + suite.NotPanics(func() { _ = msg.RLPSignBytes(suite.chainID) }) +} -// priv1, _ := ethsecp256k1.GenerateKey() -// priv2, _ := ethsecp256k1.GenerateKey() -// addr1 := ethcmn.BytesToAddress(priv1.PubKey().Address().Bytes()) -// addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes()) +func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() { + msg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil) -// // require valid signature passes validation -// msg := NewMsgEthereumTx(nil, 0, &addr1, nil, 100000, nil, []byte("test"), nil) -// err := msg.Sign(chainID, priv1.ToECDSA()) -// require.Nil(t, err) + testCases := []struct { + msg string + malleate func() + expectPass bool + }{ + { + "pass", + func() { msg.From = suite.from.Hex() }, + true, + }, + { + "no from address ", + func() { msg.From = "" }, + false, + }, + { + "from address ≠ signer address", + func() { msg.From = suite.to.Hex() }, + false, + }, + } -// signer, err := msg.VerifySig(chainID) -// require.NoError(t, err) -// require.Equal(t, addr1, signer) -// require.NotEqual(t, addr2, signer) + for i, tc := range testCases { + tc.malleate() + err := msg.Sign(suite.chainID, suite.signer) + if tc.expectPass { + suite.Require().NoError(err, "valid test %d failed: %s", i, tc.msg) -// // require invalid chain ID fail validation -// msg = NewMsgEthereumTx(nil, 0, &addr1, nil, 100000, nil, []byte("test"), nil) -// err = msg.Sign(chainID, priv1.ToECDSA()) -// require.Nil(t, err) + tx := msg.AsTransaction() + signer := ethtypes.NewEIP2930Signer(suite.chainID) -// signer, err = msg.VerifySig(big.NewInt(4)) -// require.Error(t, err) -// require.Equal(t, ethcmn.Address{}, signer) -// } + sender, err := ethtypes.Sender(signer, tx) + suite.Require().NoError(err, tc.msg) + suite.Require().Equal(msg.From, sender.Hex(), tc.msg) + } else { + suite.Require().Error(err, "invalid test %d passed: %s", i, tc.msg) + } + } +} diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 7de2829e8..11db906dd 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -2,8 +2,6 @@ package types import ( "bytes" - "fmt" - "math/big" log "github.com/xlab/suplog" @@ -13,25 +11,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcmn "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -// ValidateSigner attempts to validate a signer for a given slice of bytes over -// which a signature and signer is given. An error is returned if address -// derived from the signature and bytes signed does not match the given signer. -func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error { - pk, err := ethcrypto.SigToPub(signBytes, sig) - - if err != nil { - return errors.Wrap(err, "failed to derive public key from signature") - } else if ethcrypto.PubkeyToAddress(*pk) != signer { - return fmt.Errorf("invalid signature for signer: %s", signer) - } - - return nil -} - func rlpHash(x interface{}) (hash ethcmn.Hash) { hasher := sha3.NewLegacyKeccak256() _ = rlp.Encode(hasher, x) @@ -88,45 +70,6 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) { // ---------------------------------------------------------------------------- // Auxiliary -// recoverEthSig recovers a signature according to the Ethereum specification and -// returns the sender or an error. -// -// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F -// nolint: gocritic -func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) { - if Vb.BitLen() > 8 { - return ethcmn.Address{}, errors.New("invalid signature") - } - - V := byte(Vb.Uint64() - 27) - if !ethcrypto.ValidateSignatureValues(V, R, S, true) { - return ethcmn.Address{}, errors.New("invalid signature") - } - - // encode the signature in uncompressed format - r, s := R.Bytes(), S.Bytes() - sig := make([]byte, 65) - - copy(sig[32-len(r):32], r) - copy(sig[64-len(s):64], s) - sig[64] = V - - // recover the public key from the signature - pub, err := ethcrypto.Ecrecover(sigHash[:], sig) - if err != nil { - return ethcmn.Address{}, err - } - - if len(pub) == 0 || pub[0] != 4 { - return ethcmn.Address{}, errors.New("invalid public key") - } - - var addr ethcmn.Address - copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:]) - - return addr, nil -} - // IsEmptyHash returns true if the hash corresponds to an empty ethereum hex hash. func IsEmptyHash(hash string) bool { return bytes.Equal(ethcmn.HexToHash(hash).Bytes(), ethcmn.Hash{}.Bytes())