From 562124266829ba1d0eb325c1ec229ebf6a03893d Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 30 Aug 2018 17:36:14 -0400 Subject: [PATCH] Revert tx logic to use embedded txs with dedicated addr --- handlers/ante.go | 34 +++-- types/test_common.go | 121 +++++++++++++++ types/tx.go | 339 +++++++++++++++++++++++++------------------ types/tx_test.go | 158 +++++++++----------- types/utils.go | 2 +- types/utils_test.go | 16 +- types/wire.go | 3 - 7 files changed, 419 insertions(+), 254 deletions(-) create mode 100644 types/test_common.go diff --git a/handlers/ante.go b/handlers/ante.go index 473e1abb..f9ff96a2 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -9,8 +9,6 @@ import ( "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" - - ethtypes "github.com/ethereum/go-ethereum/core/types" ) const ( @@ -39,7 +37,7 @@ func AnteHandler(am auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHand switch tx := tx.(type) { case types.Transaction: - gasLimit = int64(tx.Data.GasLimit) + gasLimit = int64(tx.Data().GasLimit) handler = handleEthTx case auth.StdTx: gasLimit = tx.Fee.Gas @@ -91,16 +89,28 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Cont return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true } - // validate signature - gethTx := ethTx.ConvertTx(chainID) - signer := ethtypes.NewEIP155Signer(chainID) + sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante: verify Ethereum signature") - _, err := signer.Sender(&gethTx) + addr, err := ethTx.VerifySig(chainID) if err != nil { return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true } - return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data.GasLimit)}, false + acc := am.GetAccount(sdkCtx, addr.Bytes()) + + // validate the account nonce (referred to as sequence in the AccountMapper) + seq := acc.GetSequence() + if ethTx.Data().AccountNonce != uint64(seq) { + return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true + } + + err = acc.SetSequence(seq + 1) + if err != nil { + return sdkCtx, sdk.ErrInternal(err.Error()).Result(), true + } + + am.SetAccount(sdkCtx, acc) + return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false } // handleEmbeddedTx implements an ante handler for an SDK transaction. It @@ -122,15 +132,15 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk for i, sig := range stdTx.Signatures { signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes()) - signerAcc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) + acc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) if err.Code() != sdk.CodeOK { - return sdkCtx, err.Result(), false + return sdkCtx, err.Result(), true } // TODO: Fees! - am.SetAccount(sdkCtx, signerAcc) - signerAccs[i] = signerAcc + am.SetAccount(sdkCtx, acc) + signerAccs[i] = acc } newCtx := auth.WithSigners(sdkCtx, signerAccs) diff --git a/types/test_common.go b/types/test_common.go new file mode 100644 index 00000000..6dc76eb6 --- /dev/null +++ b/types/test_common.go @@ -0,0 +1,121 @@ +// nolint +package types + +import ( + "crypto/ecdsa" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + ethcmn "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + ethcrypto "github.com/ethereum/go-ethereum/crypto" +) + +var ( + TestSDKAddr = GenerateEthAddress() + TestChainID = big.NewInt(3) + + TestPrivKey1, _ = ethcrypto.GenerateKey() + TestPrivKey2, _ = ethcrypto.GenerateKey() + + TestAddr1 = PrivKeyToEthAddress(TestPrivKey1) + TestAddr2 = PrivKeyToEthAddress(TestPrivKey2) +) + +func NewTestCodec() *wire.Codec { + codec := wire.NewCodec() + + RegisterWire(codec) + auth.RegisterWire(codec) + wire.RegisterCrypto(codec) + codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) + + return codec +} + +func NewTestStdFee() auth.StdFee { + return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) +} + +func NewTestStdTx( + chainID *big.Int, msgs []sdk.Msg, accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee, +) sdk.Tx { + + sigs := make([]auth.StdSignature, len(pKeys)) + + for i, priv := range pKeys { + signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "") + + sig, err := ethcrypto.Sign(signBytes, priv) + if err != nil { + panic(err) + } + + sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + + return auth.NewStdTx(msgs, fee, sigs, "") +} + +func NewTestGethTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*ethtypes.Transaction { + + txs := make([]*ethtypes.Transaction, len(pKeys)) + + for i, privKey := range pKeys { + ethTx := ethtypes.NewTransaction( + uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{}, + ) + + signer := ethtypes.NewEIP155Signer(chainID) + + ethTx, err := ethtypes.SignTx(ethTx, signer, privKey) + if err != nil { + panic(err) + } + + txs[i] = ethTx + } + + return txs +} + +func NewTestEthTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*Transaction { + + txs := make([]*Transaction, len(pKeys)) + + for i, privKey := range pKeys { + ethTx := NewTransaction( + uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{}, + ) + + ethTx.Sign(chainID, privKey) + txs[i] = ethTx + } + + return txs +} + +func NewTestSDKTxs( + codec *wire.Codec, chainID *big.Int, to ethcmn.Address, msgs []sdk.Msg, + accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee, +) []*Transaction { + + txs := make([]*Transaction, len(pKeys)) + stdTx := NewTestStdTx(chainID, msgs, accNums, seqs, pKeys, fee) + payload := codec.MustMarshalBinary(stdTx) + + for i, privKey := range pKeys { + ethTx := NewTransaction(uint64(seqs[i]), to, big.NewInt(10), 1000, big.NewInt(100), payload) + + ethTx.Sign(chainID, privKey) + txs[i] = ethTx + } + + return txs +} diff --git a/types/tx.go b/types/tx.go index 3c3b7dd2..00a696c9 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,8 +1,10 @@ package types import ( + "bytes" "crypto/ecdsa" "fmt" + "io" "math/big" "sync/atomic" @@ -13,17 +15,20 @@ import ( ethcrypto "github.com/ethereum/go-ethereum/crypto" ethsha "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/rlp" - "github.com/pkg/errors" ) -var _ sdk.Tx = (*Transaction)(nil) - const ( // TypeTxEthereum reflects an Ethereum Transaction type. TypeTxEthereum = "Ethereum" ) +// ---------------------------------------------------------------------------- +// Ethereum transaction +// ---------------------------------------------------------------------------- + +var _ sdk.Tx = (*Transaction)(nil) + type ( // Transaction implements the Ethereum transaction structure as an exact // replica. It implements the Cosmos sdk.Tx interface. Due to the private @@ -33,7 +38,7 @@ type ( // Note: The transaction also implements the sdk.Msg interface to perform // basic validation that is done in the BaseApp. Transaction struct { - Data TxData + data TxData // caches hash atomic.Value @@ -41,96 +46,136 @@ type ( from atomic.Value } - // TxData defines internal Ethereum transaction information + // TxData implements the Ethereum transaction data structure as an exact + // copy. It is used solely as intended in Ethereum abiding by the protocol + // except for the payload field which may embed a Cosmos SDK transaction. TxData struct { AccountNonce uint64 `json:"nonce"` - Price sdk.Int `json:"gasPrice"` + Price *big.Int `json:"gasPrice"` GasLimit uint64 `json:"gas"` - Recipient *ethcmn.Address `json:"to"` // nil means contract creation - Amount sdk.Int `json:"value"` + Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation + Amount *big.Int `json:"value"` Payload []byte `json:"input"` - Signature *EthSignature `json:"signature"` + + // signature values + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` // hash is only used when marshaling to JSON - Hash *ethcmn.Hash `json:"hash"` + Hash *ethcmn.Hash `json:"hash" rlp:"-"` } - // EthSignature reflects an Ethereum signature. We wrap this in a structure - // to support Amino serialization of transactions. - EthSignature struct { - v, r, s *big.Int + // sigCache is used to cache the derived sender and contains the signer used + // to derive it. + sigCache struct { + signer ethtypes.Signer + from ethcmn.Address } ) -// NewEthSignature returns a new instantiated Ethereum signature. -func NewEthSignature(v, r, s *big.Int) *EthSignature { - return &EthSignature{v, r, s} -} - -func (es *EthSignature) sanitize() { - if es.v == nil { - es.v = new(big.Int) - } - if es.r == nil { - es.r = new(big.Int) - } - if es.s == nil { - es.s = new(big.Int) - } -} - -// MarshalAmino defines a custom encoding scheme for a EthSignature. -func (es EthSignature) MarshalAmino() ([3]string, error) { - es.sanitize() - return ethSigMarshalAmino(es) -} - -// UnmarshalAmino defines a custom decoding scheme for a EthSignature. -func (es *EthSignature) UnmarshalAmino(raw [3]string) error { - es.sanitize() - return ethSigUnmarshalAmino(es, raw) -} - -// NewTransaction mimics ethereum's NewTransaction function. It returns a -// reference to a new Ethereum Transaction. +// NewTransaction returns a reference to a new Ethereum transaction. func NewTransaction( - nonce uint64, to ethcmn.Address, amount sdk.Int, - gasLimit uint64, gasPrice sdk.Int, payload []byte, -) Transaction { + nonce uint64, to ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, +) *Transaction { + + return newTransaction(nonce, &to, amount, gasLimit, gasPrice, payload) +} + +// NewContractCreation returns a reference to a new Ethereum transaction +// designated for contract creation. +func NewContractCreation( + nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, +) *Transaction { + + return newTransaction(nonce, nil, amount, gasLimit, gasPrice, payload) +} + +func newTransaction( + nonce uint64, to *ethcmn.Address, amount *big.Int, + gasLimit uint64, gasPrice *big.Int, payload []byte, +) *Transaction { if len(payload) > 0 { payload = ethcmn.CopyBytes(payload) } txData := TxData{ - Recipient: &to, AccountNonce: nonce, + Recipient: to, Payload: payload, GasLimit: gasLimit, - Amount: amount, - Price: gasPrice, - Signature: NewEthSignature(new(big.Int), new(big.Int), new(big.Int)), + Amount: new(big.Int), + Price: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), } - return Transaction{Data: txData} + if amount != nil { + txData.Amount.Set(amount) + } + if gasPrice != nil { + txData.Price.Set(gasPrice) + } + + return &Transaction{data: txData} +} + +// Data returns the Transaction's data. +func (tx Transaction) Data() TxData { + return tx.data +} + +// EncodeRLP implements the rlp.Encoder interface. +func (tx *Transaction) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &tx.data) +} + +// DecodeRLP implements the rlp.Decoder interface. +func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { + _, size, _ := s.Kind() + err := s.Decode(&tx.data) + if err == nil { + tx.size.Store(ethcmn.StorageSize(rlp.ListSize(size))) + } + + return err +} + +// Hash hashes the RLP encoding of a transaction. +func (tx *Transaction) Hash() ethcmn.Hash { + if hash := tx.hash.Load(); hash != nil { + return hash.(ethcmn.Hash) + } + + v := rlpHash(tx) + tx.hash.Store(v) + return v +} + +// SigHash returns the RLP hash of a transaction with a given chainID used for +// signing. +func (tx Transaction) SigHash(chainID *big.Int) ethcmn.Hash { + return rlpHash([]interface{}{ + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Recipient, + tx.data.Amount, + tx.data.Payload, + chainID, uint(0), uint(0), + }) } // 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 // fields of the Transaction's Signature. -func (tx *Transaction) Sign(chainID sdk.Int, priv *ecdsa.PrivateKey) { - h := rlpHash([]interface{}{ - tx.Data.AccountNonce, - tx.Data.Price.BigInt(), - tx.Data.GasLimit, - tx.Data.Recipient, - tx.Data.Amount.BigInt(), - tx.Data.Payload, - chainID.BigInt(), uint(0), uint(0), - }) +func (tx *Transaction) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { + txHash := tx.SigHash(chainID) - sig, err := ethcrypto.Sign(h[:], priv) + sig, err := ethcrypto.Sign(txHash[:], priv) if err != nil { panic(err) } @@ -147,13 +192,48 @@ func (tx *Transaction) Sign(chainID sdk.Int, priv *ecdsa.PrivateKey) { v = new(big.Int).SetBytes([]byte{sig[64] + 27}) } else { v = big.NewInt(int64(sig[64] + 35)) - chainIDMul := new(big.Int).Mul(chainID.BigInt(), big.NewInt(2)) + chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) v.Add(v, chainIDMul) } - tx.Data.Signature.v = v - tx.Data.Signature.r = r - tx.Data.Signature.s = s + tx.data.V = v + tx.data.R = r + tx.data.S = s +} + +// VerifySig attempts to verify a Transaction's signature for a given chainID. +// A derived address is returned upon success or an error if recovery fails. +func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) { + signer := ethtypes.NewEIP155Signer(chainID) + + if sc := tx.from.Load(); sc != nil { + sigCache := sc.(sigCache) + // If the signer used to derive from in a previous + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + // do not allow recovery for transactions with an unprotected chainID + if chainID.Sign() == 0 { + return ethcmn.Address{}, errors.New("invalid chainID") + } + + txHash := tx.SigHash(chainID) + sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID) + + pub, err := ethcrypto.Ecrecover(txHash[:], sig) + if err != nil { + return ethcmn.Address{}, err + } + + var addr ethcmn.Address + copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:]) + + tx.from.Store(sigCache{signer: signer, from: addr}) + return addr, nil } // Type implements the sdk.Msg interface. It returns the type of the @@ -165,11 +245,11 @@ func (tx Transaction) Type() string { // ValidateBasic implements the sdk.Msg interface. It performs basic validation // checks of a Transaction. If returns an sdk.Error if validation fails. func (tx Transaction) ValidateBasic() sdk.Error { - if tx.Data.Price.Sign() != 1 { + if tx.data.Price.Sign() != 1 { return ErrInvalidValue(DefaultCodespace, "price must be positive") } - if tx.Data.Amount.Sign() != 1 { + if tx.data.Amount.Sign() != 1 { return ErrInvalidValue(DefaultCodespace, "amount must be positive") } @@ -192,44 +272,57 @@ func (tx Transaction) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} } -// ConvertTx attempts to converts a Transaction to a new Ethereum transaction -// with the signature set. The signature if first recovered and then a new -// Transaction is created with that signature. If setting the signature fails, -// a panic will be triggered. -// -// TODO: To be removed in #470 -func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { - gethTx := ethtypes.NewTransaction( - tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(), - tx.Data.GasLimit, tx.Data.Price.BigInt(), tx.Data.Payload, - ) - - sig := recoverEthSig(tx.Data.Signature, chainID) - signer := ethtypes.NewEIP155Signer(chainID) - - gethTx, err := gethTx.WithSignature(signer, sig) - if err != nil { - panic(errors.Wrap(err, "failed to convert transaction with a given signature")) - } - - return *gethTx +// hasEmbeddedTx returns a boolean reflecting if the transaction contains an +// SDK transaction or not based on the recipient address. +func (tx Transaction) hasEmbeddedTx(addr ethcmn.Address) bool { + return bytes.Equal(tx.data.Recipient.Bytes(), addr.Bytes()) } -// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes, -// attempts to decode them into a valid sdk.Tx. -func TxDecoder(codec *wire.Codec) sdk.TxDecoder { +// GetEmbeddedTx returns the embedded SDK transaction from an Ethereum +// transaction. It returns an error if decoding the inner transaction fails. +// +// CONTRACT: The payload field of an Ethereum transaction must contain a valid +// encoded SDK transaction. +func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) { + var etx sdk.Tx + + err := codec.UnmarshalBinary(tx.data.Payload, &etx) + if err != nil { + return etx, sdk.ErrTxDecode("failed to decode embedded transaction") + } + + return etx, nil +} + +// ---------------------------------------------------------------------------- +// Utilities +// ---------------------------------------------------------------------------- + +// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an +// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or +// returning an error if decoding fails. +func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx = Transaction{} + if len(txBytes) == 0 { - return nil, sdk.ErrTxDecode("txBytes are empty") + return nil, sdk.ErrTxDecode("transaction bytes are empty") } - var tx sdk.Tx - - // The given codec should have all the appropriate message types - // registered. - err := codec.UnmarshalBinary(txBytes, &tx) + err := rlp.DecodeBytes(txBytes, &tx) if err != nil { - return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error()) + return nil, sdk.ErrTxDecode("failed to decode transaction").TraceSDK(err.Error()) + } + + // If the transaction is routed as an SDK transaction, decode and return + // the embedded SDK transaction. + if tx.hasEmbeddedTx(sdkAddress) { + etx, err := tx.GetEmbeddedTx(codec) + if err != nil { + return nil, err + } + + return etx, nil } return tx, nil @@ -237,20 +330,20 @@ func TxDecoder(codec *wire.Codec) sdk.TxDecoder { } // recoverEthSig recovers a signature according to the Ethereum specification. -func recoverEthSig(es *EthSignature, chainID *big.Int) []byte { +func recoverEthSig(R, S, Vb, chainID *big.Int) []byte { var v byte - r, s := es.r.Bytes(), es.s.Bytes() + r, s := R.Bytes(), S.Bytes() sig := make([]byte, 65) copy(sig[32-len(r):32], r) copy(sig[64-len(s):64], s) if chainID.Sign() == 0 { - v = byte(es.v.Uint64() - 27) + v = byte(Vb.Uint64() - 27) } else { chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) - V := new(big.Int).Sub(es.v, chainIDMul) + V := new(big.Int).Sub(Vb, chainIDMul) v = byte(V.Uint64() - 35) } @@ -259,43 +352,11 @@ func recoverEthSig(es *EthSignature, chainID *big.Int) []byte { return sig } -func rlpHash(x interface{}) (h ethcmn.Hash) { +func rlpHash(x interface{}) (hash ethcmn.Hash) { hasher := ethsha.NewKeccak256() rlp.Encode(hasher, x) - hasher.Sum(h[:0]) - - return h -} - -func ethSigMarshalAmino(es EthSignature) (raw [3]string, err error) { - vb, err := es.v.MarshalText() - if err != nil { - return raw, err - } - rb, err := es.r.MarshalText() - if err != nil { - return raw, err - } - sb, err := es.s.MarshalText() - if err != nil { - return raw, err - } - - raw[0], raw[1], raw[2] = string(vb), string(rb), string(sb) - return raw, err -} - -func ethSigUnmarshalAmino(es *EthSignature, raw [3]string) (err error) { - if err = es.v.UnmarshalText([]byte(raw[0])); err != nil { - return - } - if err = es.r.UnmarshalText([]byte(raw[1])); err != nil { - return - } - if err = es.s.UnmarshalText([]byte(raw[2])); err != nil { - return - } + hasher.Sum(hash[:0]) return } diff --git a/types/tx_test.go b/types/tx_test.go index 7a75caa9..0df8a8a7 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -3,83 +3,57 @@ package types import ( "crypto/ecdsa" "fmt" + "math/big" "testing" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ethcmn "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" ) -var ( - testChainID = sdk.NewInt(3) +func TestTransactionRLPEncode(t *testing.T) { + txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}) + gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}) - testPrivKey1, _ = ethcrypto.GenerateKey() - testPrivKey2, _ = ethcrypto.GenerateKey() + txRLP, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) - testAddr1 = PrivKeyToEthAddress(testPrivKey1) - testAddr2 = PrivKeyToEthAddress(testPrivKey2) -) + gtxRLP, err := rlp.EncodeToBytes(gtxs[0]) + require.NoError(t, err) -func newTestCodec() *wire.Codec { - codec := wire.NewCodec() - - RegisterWire(codec) - codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil) - codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) - wire.RegisterCrypto(codec) - - return codec + require.Equal(t, gtxRLP, txRLP) } -func newStdFee() auth.StdFee { - return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) -} +func TestTransactionRLPDecode(t *testing.T) { + txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}) + gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}) -func newTestStdTx( - chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, - accNums []int64, seqs []int64, fee auth.StdFee, -) sdk.Tx { + txRLP, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) - sigs := make([]auth.StdSignature, len(pKeys)) + gtxRLP, err := rlp.EncodeToBytes(gtxs[0]) + require.NoError(t, err) - for i, priv := range pKeys { - signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), msgs, "") + var ( + decodedTx Transaction + decodedGtx ethtypes.Transaction + ) - sig, err := ethcrypto.Sign(signBytes, priv) - if err != nil { - panic(err) - } + err = rlp.DecodeBytes(txRLP, &decodedTx) + require.NoError(t, err) - sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} - } + err = rlp.DecodeBytes(gtxRLP, &decodedGtx) + require.NoError(t, err) - return auth.NewStdTx(msgs, fee, sigs, "") -} - -func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction { - txs := make([]Transaction, len(pKeys)) - - for i, priv := range pKeys { - emintTx := NewTransaction( - uint64(i), addrs[i], sdk.NewInt(10), 100, sdk.NewInt(100), nil, - ) - - emintTx.Sign(chainID, priv) - - txs[i] = emintTx - } - - return txs + require.Equal(t, decodedGtx.Hash(), decodedTx.Hash()) } func TestValidation(t *testing.T) { - ethTxs := newTestEthTxs( - testChainID, - []*ecdsa.PrivateKey{testPrivKey1}, - []ethcmn.Address{testAddr1}, + ethTxs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) testCases := []struct { @@ -89,13 +63,13 @@ func TestValidation(t *testing.T) { }{ {ethTxs[0], func(msg sdk.Msg) sdk.Msg { return msg }, false}, {ethTxs[0], func(msg sdk.Msg) sdk.Msg { - tx := msg.(Transaction) - tx.Data.Price = sdk.NewInt(-1) + tx := msg.(*Transaction) + tx.data.Price = big.NewInt(-1) return tx }, true}, {ethTxs[0], func(msg sdk.Msg) sdk.Msg { - tx := msg.(Transaction) - tx.Data.Amount = sdk.NewInt(-1) + tx := msg.(*Transaction) + tx.data.Amount = big.NewInt(-1) return tx }, true}, } @@ -112,59 +86,57 @@ func TestValidation(t *testing.T) { } } -func TestTransactionGetMsgs(t *testing.T) { - ethTxs := newTestEthTxs( - testChainID, - []*ecdsa.PrivateKey{testPrivKey1}, - []ethcmn.Address{testAddr1}, +func TestTransactionVerifySig(t *testing.T) { + txs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) - msgs := ethTxs[0].GetMsgs() - require.Len(t, msgs, 1) - require.Equal(t, ethTxs[0], msgs[0]) + addr, err := txs[0].VerifySig(TestChainID) + require.NoError(t, err) + require.Equal(t, TestAddr1, addr) - expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} - etx := newTestStdTx( - testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1}, - []int64{0}, []int64{0}, newStdFee(), - ) - - msgs = etx.GetMsgs() - require.Len(t, msgs, len(expectedMsgs)) - require.Equal(t, expectedMsgs, msgs) + addr, err = txs[0].VerifySig(big.NewInt(100)) + require.Error(t, err) + require.NotEqual(t, TestAddr1, addr) } func TestTxDecoder(t *testing.T) { - testCodec := newTestCodec() - txDecoder := TxDecoder(testCodec) + testCodec := NewTestCodec() + txDecoder := TxDecoder(testCodec, TestSDKAddr) msgs := []sdk.Msg{sdk.NewTestMsg()} // create a non-SDK Ethereum transaction - emintTx := NewTransaction( - uint64(0), testAddr1, sdk.NewInt(10), 100, sdk.NewInt(100), nil, + txs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) - emintTx.Sign(testChainID, testPrivKey1) + + txBytes, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) // require the transaction to properly decode into a Transaction - txBytes := testCodec.MustMarshalBinary(emintTx) - tx, err := txDecoder(txBytes) + decodedTx, err := txDecoder(txBytes) require.NoError(t, err) - require.Equal(t, emintTx, tx) + require.IsType(t, Transaction{}, decodedTx) + require.Equal(t, txs[0].data, (decodedTx.(Transaction)).data) // create a SDK (auth.StdTx) transaction and encode - stdTx := newTestStdTx( - testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1}, - []int64{0}, []int64{0}, newStdFee(), + txs = NewTestSDKTxs( + testCodec, TestChainID, TestSDKAddr, msgs, []int64{0}, []int64{0}, + []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee(), ) - // require the transaction to properly decode into a Transaction - txBytes = testCodec.MustMarshalBinary(stdTx) - tx, err = txDecoder(txBytes) + txBytes, err = rlp.EncodeToBytes(txs[0]) require.NoError(t, err) - require.Equal(t, stdTx, tx) + + // require the transaction to properly decode into a Transaction + stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee()) + decodedTx, err = txDecoder(txBytes) + require.NoError(t, err) + require.IsType(t, auth.StdTx{}, decodedTx) + require.Equal(t, stdTx, decodedTx) // require the decoding to fail when no transaction bytes are given - tx, err = txDecoder([]byte{}) + decodedTx, err = txDecoder([]byte{}) require.Error(t, err) - require.Nil(t, tx) + require.Nil(t, decodedTx) } diff --git a/types/utils.go b/types/utils.go index 9c4f5328..7135a583 100644 --- a/types/utils.go +++ b/types/utils.go @@ -35,7 +35,7 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error { pk, err := ethcrypto.SigToPub(signBytes, sig) if err != nil { - return errors.Wrap(err, "signature verification failed") + 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) } diff --git a/types/utils_test.go b/types/utils_test.go index 39c9e291..12e27d8f 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -10,23 +10,27 @@ import ( ) func TestValidateSigner(t *testing.T) { - msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} + msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(TestAddr1.Bytes()))} // create message signing structure and bytes - signBytes := GetStdTxSignBytes(testChainID.String(), 0, 0, newStdFee(), msgs, "") + signBytes := GetStdTxSignBytes(TestChainID.String(), 0, 0, NewTestStdFee(), msgs, "") // require signing not to fail - sig, err := ethcrypto.Sign(signBytes, testPrivKey1) + sig, err := ethcrypto.Sign(signBytes, TestPrivKey1) require.NoError(t, err) // require signature to be valid - err = ValidateSigner(signBytes, sig, testAddr1) + err = ValidateSigner(signBytes, sig, TestAddr1) require.NoError(t, err) - sig, err = ethcrypto.Sign(signBytes, testPrivKey2) + sig, err = ethcrypto.Sign(signBytes, TestPrivKey2) require.NoError(t, err) // require signature to be invalid - err = ValidateSigner(signBytes, sig, testAddr1) + err = ValidateSigner(signBytes, sig, TestAddr1) + require.Error(t, err) + + // require invalid signature bytes return an error + err = ValidateSigner([]byte{}, sig, TestAddr2) require.Error(t, err) } diff --git a/types/wire.go b/types/wire.go index 23f69aeb..07e27c6f 100644 --- a/types/wire.go +++ b/types/wire.go @@ -16,7 +16,4 @@ func init() { func RegisterWire(codec *wire.Codec) { sdk.RegisterWire(codec) codec.RegisterConcrete(&Account{}, "types/Account", nil) - codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil) - codec.RegisterConcrete(TxData{}, "types/TxData", nil) - codec.RegisterConcrete(Transaction{}, "types/Transaction", nil) }