diff --git a/app/ante/evm_checktx.go b/app/ante/evm_checktx.go index 73878163ce..be23117907 100644 --- a/app/ante/evm_checktx.go +++ b/app/ante/evm_checktx.go @@ -75,45 +75,8 @@ func EvmCheckTxAnte( } func EvmStatelessChecks(ctx sdk.Context, tx sdk.Tx, chainID *big.Int) error { - txBody, ok := tx.(TxBody) - if ok { - body := txBody.GetBody() - if body.Memo != "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs") - } - if body.TimeoutHeight != 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs") - } - if len(body.ExtensionOptions) > 0 || len(body.NonCriticalExtensionOptions) > 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs") - } - } - - txAuth, ok := tx.(TxAuthInfo) - if ok { - authInfo := txAuth.GetAuthInfo() - if len(authInfo.SignerInfos) > 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs") - } - if authInfo.Fee != nil { - if len(authInfo.Fee.Amount) > 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs") - } - if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs") - } - } - } - - txSig, ok := tx.(TxSignaturesV2) - if ok { - sigs, err := txSig.GetSignaturesV2() - if err != nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures") - } - if len(sigs) > 0 { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs") - } + if err := evmante.ValidateNoCosmosTxFields(tx); err != nil { + return err } if len(tx.GetMsgs()) != 1 { diff --git a/giga/deps/xevm/types/ethtx/access_list_tx.go b/giga/deps/xevm/types/ethtx/access_list_tx.go index 6acc097dce..04c8d86774 100644 --- a/giga/deps/xevm/types/ethtx/access_list_tx.go +++ b/giga/deps/xevm/types/ethtx/access_list_tx.go @@ -180,6 +180,19 @@ func (tx AccessListTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/giga/deps/xevm/types/ethtx/associate_tx.go b/giga/deps/xevm/types/ethtx/associate_tx.go index c1b2099365..d30893a319 100644 --- a/giga/deps/xevm/types/ethtx/associate_tx.go +++ b/giga/deps/xevm/types/ethtx/associate_tx.go @@ -37,7 +37,18 @@ func (tx *AssociateTx) GetRawSignatureValues() (v, r, s *big.Int) { func (tx *AssociateTx) SetSignatureValues(_, _, _, _ *big.Int) { panic("not implemented") } func (tx *AssociateTx) AsEthereumData() ethtypes.TxData { panic("not implemented") } -func (tx *AssociateTx) Validate() error { panic("not implemented") } +func (tx *AssociateTx) Validate() error { + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + return nil +} func (tx *AssociateTx) Fee() *big.Int { panic("not implemented") } func (tx *AssociateTx) Cost() *big.Int { panic("not implemented") } diff --git a/giga/deps/xevm/types/ethtx/blob_tx.go b/giga/deps/xevm/types/ethtx/blob_tx.go index e57f82f8e8..9bc9b7330c 100644 --- a/giga/deps/xevm/types/ethtx/blob_tx.go +++ b/giga/deps/xevm/types/ethtx/blob_tx.go @@ -229,6 +229,19 @@ func (tx BlobTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/giga/deps/xevm/types/ethtx/dynamic_fee_tx.go b/giga/deps/xevm/types/ethtx/dynamic_fee_tx.go index 9d67756107..3b777d2ece 100644 --- a/giga/deps/xevm/types/ethtx/dynamic_fee_tx.go +++ b/giga/deps/xevm/types/ethtx/dynamic_fee_tx.go @@ -197,6 +197,19 @@ func (tx DynamicFeeTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/giga/deps/xevm/types/ethtx/legacy_tx.go b/giga/deps/xevm/types/ethtx/legacy_tx.go index ede8ce4e19..b9bdf4b032 100644 --- a/giga/deps/xevm/types/ethtx/legacy_tx.go +++ b/giga/deps/xevm/types/ethtx/legacy_tx.go @@ -170,6 +170,16 @@ func (tx *LegacyTx) Validate() error { } } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/giga/deps/xevm/types/ethtx/semantic_validation.go b/giga/deps/xevm/types/ethtx/semantic_validation.go new file mode 100644 index 0000000000..4da1422952 --- /dev/null +++ b/giga/deps/xevm/types/ethtx/semantic_validation.go @@ -0,0 +1,79 @@ +package ethtx + +import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +func validateHexAddress(fieldName, value string) error { + if len(value) >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X') { + value = value[2:] + } + if len(value) != common.AddressLength*2 { + return fmt.Errorf("invalid %s: wrong length", fieldName) + } + if _, err := hex.DecodeString(value); err != nil { + return fmt.Errorf("invalid %s", fieldName) + } + return nil +} + +func validateHexHash(fieldName, value string) error { + if len(value) >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X') { + value = value[2:] + } + if len(value) != common.HashLength*2 { + return fmt.Errorf("invalid %s: wrong length", fieldName) + } + if _, err := hex.DecodeString(value); err != nil { + return fmt.Errorf("invalid %s", fieldName) + } + return nil +} + +func validateAccessList(accessList AccessList) error { + for _, tuple := range accessList { + if err := validateHexAddress("access list address", tuple.Address); err != nil { + return err + } + for _, storageKey := range tuple.StorageKeys { + if err := validateHexHash("access list storage key", storageKey); err != nil { + return err + } + } + } + return nil +} + +func validateAuthList(authList AuthList) error { + for _, auth := range authList { + if auth.ChainID == nil { + return fmt.Errorf("auth list chain id cannot be nil") + } + if err := validateHexAddress("auth list address", auth.Address); err != nil { + return err + } + if err := validateSignatureValue("auth list v", auth.V, 1); err != nil { + return err + } + if err := validateSignatureValue("auth list r", auth.R, 32); err != nil { + return err + } + if err := validateSignatureValue("auth list s", auth.S, 32); err != nil { + return err + } + } + return nil +} + +func validateSignatureValue(fieldName string, value []byte, maxLen int) error { + if len(value) > maxLen { + return fmt.Errorf("invalid %s: too long", fieldName) + } + if len(value) > 1 && value[0] == 0 { + return fmt.Errorf("invalid %s: leading zero", fieldName) + } + return nil +} diff --git a/giga/deps/xevm/types/ethtx/set_code_tx.go b/giga/deps/xevm/types/ethtx/set_code_tx.go index 80a579e148..3c31b61eee 100644 --- a/giga/deps/xevm/types/ethtx/set_code_tx.go +++ b/giga/deps/xevm/types/ethtx/set_code_tx.go @@ -217,6 +217,22 @@ func (tx SetCodeTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateAuthList(tx.AuthList); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/giga/deps/xevm/types/message_evm_transaction.go b/giga/deps/xevm/types/message_evm_transaction.go index 498478df50..fded73dbc5 100644 --- a/giga/deps/xevm/types/message_evm_transaction.go +++ b/giga/deps/xevm/types/message_evm_transaction.go @@ -42,9 +42,24 @@ func (msg *MsgEVMTransaction) GetSignBytes() []byte { } func (msg *MsgEVMTransaction) ValidateBasic() error { + if msg.Derived != nil && msg.Derived.PubKey == nil { + return sdkerrors.ErrInvalidPubKey + } + txData, err := UnpackTxData(msg.Data) + if err != nil { + return err + } + if _, ok := txData.(*ethtx.AssociateTx); !ok { + if err := txData.Validate(); err != nil { + return err + } + } amsg, isAssociate := msg.GetAssociateTx() - if isAssociate && len(amsg.CustomMessage) > MaxAssociateCustomMessageLength { - return sdkerrors.Wrapf(sdkerrors.ErrTxTooLarge, "custom message can have at most 64 characters") + if isAssociate { + if len(amsg.CustomMessage) > MaxAssociateCustomMessageLength { + return sdkerrors.Wrapf(sdkerrors.ErrTxTooLarge, "custom message can have at most 64 characters") + } + return amsg.Validate() } return nil } diff --git a/giga/tests/giga_test.go b/giga/tests/giga_test.go index 619ba46476..afd0a2988f 100644 --- a/giga/tests/giga_test.go +++ b/giga/tests/giga_test.go @@ -177,7 +177,6 @@ func CreateEVMTransferTxs(t testing.TB, tCtx *GigaTestContext, transfers []EVMTr err = txBuilder.SetMsgs(msg) require.NoError(t, err) txBuilder.SetGasLimit(10000000000) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000000)))) txBytes, err := tc.TxEncoder()(txBuilder.GetTx()) require.NoError(t, err) @@ -666,7 +665,6 @@ func CreateContractDeployTxs(t testing.TB, tCtx *GigaTestContext, deploys []EVMC err = txBuilder.SetMsgs(msg) require.NoError(t, err) txBuilder.SetGasLimit(10000000000) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000000)))) txBytes, err := tc.TxEncoder()(txBuilder.GetTx()) require.NoError(t, err) @@ -727,7 +725,6 @@ func CreateContractCallTxs(t testing.TB, tCtx *GigaTestContext, calls []EVMContr err = txBuilder.SetMsgs(msg) require.NoError(t, err) txBuilder.SetGasLimit(10000000000) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000000)))) txBytes, err := tc.TxEncoder()(txBuilder.GetTx()) require.NoError(t, err) @@ -1733,7 +1730,6 @@ func createCustomEVMTx( err = txBuilder.SetMsgs(msg) require.NoError(t, err) txBuilder.SetGasLimit(10000000000) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000000)))) txBytes, err := tc.TxEncoder()(txBuilder.GetTx()) require.NoError(t, err) diff --git a/giga/tests/harness/builder.go b/giga/tests/harness/builder.go index 447534ad6d..13ae1f25f4 100644 --- a/giga/tests/harness/builder.go +++ b/giga/tests/harness/builder.go @@ -9,7 +9,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/app" - sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/x/evm/config" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" @@ -153,7 +152,6 @@ func EncodeTxForApp(signedTx *ethtypes.Transaction) ([]byte, error) { return nil, err } txBuilder.SetGasLimit(10000000000) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000000)))) txBytes, err := tc.TxEncoder()(txBuilder.GetTx()) if err != nil { diff --git a/occ_tests/utils/utils.go b/occ_tests/utils/utils.go index 33e92b9770..1e87de70e3 100644 --- a/occ_tests/utils/utils.go +++ b/occ_tests/utils/utils.go @@ -14,6 +14,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/sei-cosmos/baseapp" + "github.com/sei-protocol/sei-chain/sei-cosmos/client" clienttx "github.com/sei-protocol/sei-chain/sei-cosmos/client/tx" codectypes "github.com/sei-protocol/sei-chain/sei-cosmos/codec/types" cryptotypes "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types" @@ -228,19 +229,33 @@ func toTxBytes(testCtx *TestContext, msgs []*TestMessage) [][]byte { panic(err) } - tBuilder := tx.WrapTx(&txtype.Tx{ - Body: &txtype.TxBody{ - Messages: []*codectypes.Any{a}, - }, - AuthInfo: &txtype.AuthInfo{ - Fee: &txtype.Fee{ - Amount: Funds(10000000000), - GasLimit: 10000000000, - Payer: testCtx.TestAccounts[0].AccountAddress.String(), - Granter: testCtx.TestAccounts[0].AccountAddress.String(), + var tBuilder client.TxBuilder + if tm.IsEVM { + tBuilder = tx.WrapTx(&txtype.Tx{ + Body: &txtype.TxBody{ + Messages: []*codectypes.Any{a}, }, - }, - }) + AuthInfo: &txtype.AuthInfo{ + Fee: &txtype.Fee{ + GasLimit: 10000000000, + }, + }, + }) + } else { + tBuilder = tx.WrapTx(&txtype.Tx{ + Body: &txtype.TxBody{ + Messages: []*codectypes.Any{a}, + }, + AuthInfo: &txtype.AuthInfo{ + Fee: &txtype.Fee{ + Amount: Funds(10000000000), + GasLimit: 10000000000, + Payer: testCtx.TestAccounts[0].AccountAddress.String(), + Granter: testCtx.TestAccounts[0].AccountAddress.String(), + }, + }, + }) + } if tm.IsEVM { amounts := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000000000)), sdk.NewCoin("uusdc", sdk.NewInt(1000000000000000))) diff --git a/sei-cosmos/x/auth/tx/decoder.go b/sei-cosmos/x/auth/tx/decoder.go index d2dbe6068a..60283b1ebe 100644 --- a/sei-cosmos/x/auth/tx/decoder.go +++ b/sei-cosmos/x/auth/tx/decoder.go @@ -3,6 +3,7 @@ package tx import ( "fmt" + "github.com/gogo/protobuf/proto" "google.golang.org/protobuf/encoding/protowire" "github.com/sei-protocol/sei-chain/sei-cosmos/codec" @@ -47,6 +48,10 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, err.Error()) } + if err := rejectBloatedBody(raw.BodyBytes, &body); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, err.Error()) + } + var authInfo tx.AuthInfo // reject all unknown proto fields in AuthInfo @@ -141,6 +146,21 @@ func rejectNonADR027TxRaw(txBytes []byte) error { return nil } +// rejectBloatedBody rejects tx bodies where the raw wire encoding is larger +// than the canonical re-marshal of the decoded struct. This catches protobuf-level +// bloat (e.g. padded sdk.Int fields, oversized Any.Value) that UnpackAny would +// otherwise silently canonicalize away before validation runs. +func rejectBloatedBody(rawBodyBytes []byte, body *tx.TxBody) error { + canonicalBytes, err := proto.Marshal(body) + if err != nil { + return fmt.Errorf("failed to re-marshal tx body: %w", err) + } + if len(rawBodyBytes) != len(canonicalBytes) { + return fmt.Errorf("tx body wire size (%d) exceeds canonical size (%d)", len(rawBodyBytes), len(canonicalBytes)) + } + return nil +} + // varintMinLength returns the minimum number of bytes necessary to encode an // uint using varint encoding. func varintMinLength(n uint64) int { diff --git a/sei-cosmos/x/auth/tx/encode_decode_test.go b/sei-cosmos/x/auth/tx/encode_decode_test.go index 40f76325dc..6c975eae19 100644 --- a/sei-cosmos/x/auth/tx/encode_decode_test.go +++ b/sei-cosmos/x/auth/tx/encode_decode_test.go @@ -12,7 +12,7 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/codec" codectypes "github.com/sei-protocol/sei-chain/sei-cosmos/codec/types" "github.com/sei-protocol/sei-chain/sei-cosmos/testutil/testdata" - sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" + "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx" signingtypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing" "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/signing" @@ -60,14 +60,13 @@ func TestUnknownFields(t *testing.T) { shouldErr: false, }, { - name: "non-critical fields in TxBody should not error on decode, but should error with amino", + name: "non-critical fields in TxBody should error on decode due to bloat rejection", body: &testdata.TestUpdatedTxBody{ Memo: "foo", SomeNewFieldNonCriticalField: "blah", }, - authInfo: &testdata.TestUpdatedAuthInfo{}, - shouldErr: false, - shouldAminoErr: fmt.Sprintf("%s: %s", aminoNonCriticalFieldsError, sdkerrors.ErrInvalidRequest.Error()), + authInfo: &testdata.TestUpdatedAuthInfo{}, + shouldErr: true, }, { name: "critical fields in TxBody should error on decode", diff --git a/x/evm/ante/no_cosmos_fields.go b/x/evm/ante/no_cosmos_fields.go index 0a156db73d..d2a7b50e57 100644 --- a/x/evm/ante/no_cosmos_fields.go +++ b/x/evm/ante/no_cosmos_fields.go @@ -15,19 +15,65 @@ func NewEVMNoCosmosFieldsDecorator() EVMNoCosmosFieldsDecorator { } func (d EVMNoCosmosFieldsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + if err := ValidateNoCosmosTxFields(tx); err != nil { + return ctx, err + } + return next(ctx, tx, simulate) +} + +type protoTxProvider interface { + GetProtoTx() *txtypes.Tx +} + +// ValidateNoCosmosTxFields rejects Cosmos wrapper fields that EVM txs must not use. +func ValidateNoCosmosTxFields(tx sdk.Tx) error { + if txProto, ok := tx.(protoTxProvider); ok { + pt := txProto.GetProtoTx() + if pt != nil { + if pt.Body != nil { + if pt.Body.Memo != "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs") + } + if pt.Body.TimeoutHeight != 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs") + } + if len(pt.Body.ExtensionOptions) > 0 || len(pt.Body.NonCriticalExtensionOptions) > 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs") + } + } + if pt.AuthInfo != nil { + if len(pt.AuthInfo.SignerInfos) > 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs") + } + if pt.AuthInfo.Fee != nil { + if len(pt.AuthInfo.Fee.Amount) > 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs") + } + if pt.AuthInfo.Fee.Payer != "" || pt.AuthInfo.Fee.Granter != "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs") + } + } + } + if len(pt.Signatures) > 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs") + } + } + return nil + } + txBody, ok := tx.(interface { GetBody() *txtypes.TxBody }) if ok { body := txBody.GetBody() if body.Memo != "" { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs") } if body.TimeoutHeight != 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs") } if len(body.ExtensionOptions) > 0 || len(body.NonCriticalExtensionOptions) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs") } } @@ -37,14 +83,14 @@ func (d EVMNoCosmosFieldsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul if ok { authInfo := txAuth.GetAuthInfo() if len(authInfo.SignerInfos) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs") } if authInfo.Fee != nil { if len(authInfo.Fee.Amount) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs") } if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs") } } } @@ -55,12 +101,12 @@ func (d EVMNoCosmosFieldsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul if ok { sigs, err := txSig.GetSignaturesV2() if err != nil { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures") } if len(sigs) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs") + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs") } } - return next(ctx, tx, simulate) + return nil } diff --git a/x/evm/types/ethtx/access_list_tx.go b/x/evm/types/ethtx/access_list_tx.go index 6acc097dce..04c8d86774 100644 --- a/x/evm/types/ethtx/access_list_tx.go +++ b/x/evm/types/ethtx/access_list_tx.go @@ -180,6 +180,19 @@ func (tx AccessListTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/x/evm/types/ethtx/associate_tx.go b/x/evm/types/ethtx/associate_tx.go index c1b2099365..d30893a319 100644 --- a/x/evm/types/ethtx/associate_tx.go +++ b/x/evm/types/ethtx/associate_tx.go @@ -37,7 +37,18 @@ func (tx *AssociateTx) GetRawSignatureValues() (v, r, s *big.Int) { func (tx *AssociateTx) SetSignatureValues(_, _, _, _ *big.Int) { panic("not implemented") } func (tx *AssociateTx) AsEthereumData() ethtypes.TxData { panic("not implemented") } -func (tx *AssociateTx) Validate() error { panic("not implemented") } +func (tx *AssociateTx) Validate() error { + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + return nil +} func (tx *AssociateTx) Fee() *big.Int { panic("not implemented") } func (tx *AssociateTx) Cost() *big.Int { panic("not implemented") } diff --git a/x/evm/types/ethtx/blob_tx.go b/x/evm/types/ethtx/blob_tx.go index e57f82f8e8..9bc9b7330c 100644 --- a/x/evm/types/ethtx/blob_tx.go +++ b/x/evm/types/ethtx/blob_tx.go @@ -229,6 +229,19 @@ func (tx BlobTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/x/evm/types/ethtx/dynamic_fee_tx.go b/x/evm/types/ethtx/dynamic_fee_tx.go index 9d67756107..3b777d2ece 100644 --- a/x/evm/types/ethtx/dynamic_fee_tx.go +++ b/x/evm/types/ethtx/dynamic_fee_tx.go @@ -197,6 +197,19 @@ func (tx DynamicFeeTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/x/evm/types/ethtx/legacy_tx.go b/x/evm/types/ethtx/legacy_tx.go index ede8ce4e19..b9bdf4b032 100644 --- a/x/evm/types/ethtx/legacy_tx.go +++ b/x/evm/types/ethtx/legacy_tx.go @@ -170,6 +170,16 @@ func (tx *LegacyTx) Validate() error { } } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/x/evm/types/ethtx/semantic_validation.go b/x/evm/types/ethtx/semantic_validation.go new file mode 100644 index 0000000000..4da1422952 --- /dev/null +++ b/x/evm/types/ethtx/semantic_validation.go @@ -0,0 +1,79 @@ +package ethtx + +import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +func validateHexAddress(fieldName, value string) error { + if len(value) >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X') { + value = value[2:] + } + if len(value) != common.AddressLength*2 { + return fmt.Errorf("invalid %s: wrong length", fieldName) + } + if _, err := hex.DecodeString(value); err != nil { + return fmt.Errorf("invalid %s", fieldName) + } + return nil +} + +func validateHexHash(fieldName, value string) error { + if len(value) >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X') { + value = value[2:] + } + if len(value) != common.HashLength*2 { + return fmt.Errorf("invalid %s: wrong length", fieldName) + } + if _, err := hex.DecodeString(value); err != nil { + return fmt.Errorf("invalid %s", fieldName) + } + return nil +} + +func validateAccessList(accessList AccessList) error { + for _, tuple := range accessList { + if err := validateHexAddress("access list address", tuple.Address); err != nil { + return err + } + for _, storageKey := range tuple.StorageKeys { + if err := validateHexHash("access list storage key", storageKey); err != nil { + return err + } + } + } + return nil +} + +func validateAuthList(authList AuthList) error { + for _, auth := range authList { + if auth.ChainID == nil { + return fmt.Errorf("auth list chain id cannot be nil") + } + if err := validateHexAddress("auth list address", auth.Address); err != nil { + return err + } + if err := validateSignatureValue("auth list v", auth.V, 1); err != nil { + return err + } + if err := validateSignatureValue("auth list r", auth.R, 32); err != nil { + return err + } + if err := validateSignatureValue("auth list s", auth.S, 32); err != nil { + return err + } + } + return nil +} + +func validateSignatureValue(fieldName string, value []byte, maxLen int) error { + if len(value) > maxLen { + return fmt.Errorf("invalid %s: too long", fieldName) + } + if len(value) > 1 && value[0] == 0 { + return fmt.Errorf("invalid %s: leading zero", fieldName) + } + return nil +} diff --git a/x/evm/types/ethtx/set_code_tx.go b/x/evm/types/ethtx/set_code_tx.go index 80a579e148..3c31b61eee 100644 --- a/x/evm/types/ethtx/set_code_tx.go +++ b/x/evm/types/ethtx/set_code_tx.go @@ -217,6 +217,22 @@ func (tx SetCodeTx) Validate() error { } } + if err := validateAccessList(tx.Accesses); err != nil { + return err + } + if err := validateAuthList(tx.AuthList); err != nil { + return err + } + if err := validateSignatureValue("v", tx.V, 32); err != nil { + return err + } + if err := validateSignatureValue("r", tx.R, 32); err != nil { + return err + } + if err := validateSignatureValue("s", tx.S, 32); err != nil { + return err + } + chainID := tx.GetChainID() if chainID == nil { diff --git a/x/evm/types/message_evm_transaction.go b/x/evm/types/message_evm_transaction.go index 78e9595d6f..5525631be4 100644 --- a/x/evm/types/message_evm_transaction.go +++ b/x/evm/types/message_evm_transaction.go @@ -42,9 +42,24 @@ func (msg *MsgEVMTransaction) GetSignBytes() []byte { } func (msg *MsgEVMTransaction) ValidateBasic() error { + if msg.Derived != nil && msg.Derived.PubKey == nil { + return sdkerrors.ErrInvalidPubKey + } + txData, err := UnpackTxData(msg.Data) + if err != nil { + return err + } + if _, ok := txData.(*ethtx.AssociateTx); !ok { + if err := txData.Validate(); err != nil { + return err + } + } amsg, isAssociate := msg.GetAssociateTx() - if isAssociate && len(amsg.CustomMessage) > MaxAssociateCustomMessageLength { - return sdkerrors.Wrapf(sdkerrors.ErrTxTooLarge, "custom message can have at most 64 characters") + if isAssociate { + if len(amsg.CustomMessage) > MaxAssociateCustomMessageLength { + return sdkerrors.Wrapf(sdkerrors.ErrTxTooLarge, "custom message can have at most 64 characters") + } + return amsg.Validate() } return nil }