Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

feat!: Reject not replay-protected tx to prevent replay attack #1124

Merged
merged 12 commits into from
Jun 13, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### State Machine Breaking

* (evm) [tharsis#1124](https://github.com/tharsis/ethermint/pull/1124) Reject non-replay-protected tx in ante handler to prevent replay attack

### Bug Fixes

* (evm) [tharsis#1118](https://github.com/tharsis/ethermint/pull/1118) Fix `Type()` `Account` method `EmptyCodeHash` comparison
Expand Down
7 changes: 6 additions & 1 deletion app/ante/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}

sender, err := signer.Sender(msgEthTx.AsTransaction())
ethTx := msgEthTx.AsTransaction()
if params.RejectUnprotected && !ethTx.Protected() {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "eth tx is not replay-protected")
}

sender, err := signer.Sender(ethTx)
if err != nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrorInvalidSigner,
Expand Down
30 changes: 22 additions & 8 deletions app/ante/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,46 @@ import (
)

func (suite AnteTestSuite) TestEthSigVerificationDecorator() {
dec := ante.NewEthSigVerificationDecorator(suite.app.EvmKeeper)
addr, privKey := tests.NewAddrKey()

signedTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
signedTx.From = addr.Hex()
err := signedTx.Sign(suite.ethSigner, tests.NewSigner(privKey))
suite.Require().NoError(err)

unprotectedTx := evmtypes.NewTxContract(nil, 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
unprotectedTx.From = addr.Hex()
err = unprotectedTx.Sign(ethtypes.HomesteadSigner{}, tests.NewSigner(privKey))
suite.Require().NoError(err)

testCases := []struct {
name string
tx sdk.Tx
reCheckTx bool
expPass bool
name string
tx sdk.Tx
rejectUnprotected bool
reCheckTx bool
expPass bool
}{
{"ReCheckTx", &invalidTx{}, true, false},
{"invalid transaction type", &invalidTx{}, false, false},
{"ReCheckTx", &invalidTx{}, true, true, false},
{"invalid transaction type", &invalidTx{}, true, false, false},
{
"invalid sender",
evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &addr, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil),
true,
false,
false,
},
{"successful signature verification", signedTx, false, true},
{"successful signature verification", signedTx, true, false, true},
{"invalid, not replay-protected", unprotectedTx, true, false, false},
{"successful, don't reject unprotected", unprotectedTx, false, false, true},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.evmParamsOption = func(params *evmtypes.Params) {
params.RejectUnprotected = tc.rejectUnprotected
}
suite.SetupTest()
dec := ante.NewEthSigVerificationDecorator(suite.app.EvmKeeper)
_, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, NextFn)

if tc.expPass {
Expand All @@ -51,6 +64,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() {
}
})
}
suite.evmParamsOption = nil
}

func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
Expand Down
1 change: 1 addition & 0 deletions app/ante/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func (suite *AnteTestSuite) SetupTest() {
genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis)
}
evmGenesis := evmtypes.DefaultGenesisState()
evmGenesis.Params.RejectUnprotected = true
if !suite.enableLondonHF {
maxInt := sdk.NewInt(math.MaxInt64)
evmGenesis.Params.ChainConfig.LondonBlock = &maxInt
Expand Down
1 change: 1 addition & 0 deletions docs/api/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ Params defines the EVM module parameters
| `enable_call` | [bool](#bool) | | enable call toggles state transitions that use the vm.Call function |
| `extra_eips` | [int64](#int64) | repeated | extra eips defines the additional EIPs for the vm.Config |
| `chain_config` | [ChainConfig](#ethermint.evm.v1.ChainConfig) | | chain config defines the EVM chain configuration parameters |
| `reject_unprotected` | [bool](#bool) | | reject replay-unprotected transactions |



Expand Down
2 changes: 2 additions & 0 deletions proto/ethermint/evm/v1/evm.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ message Params {
(gogoproto.moretags) = "yaml:\"chain_config\"",
(gogoproto.nullable) = false
];
// reject replay-unprotected transactions
bool reject_unprotected = 6;
}

// ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values
Expand Down
10 changes: 10 additions & 0 deletions x/evm/keeper/migrations.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
v2 "github.com/tharsis/ethermint/x/evm/migrations/v2"
)

// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper Keeper
Expand All @@ -11,3 +16,8 @@ func NewMigrator(keeper Keeper) Migrator {
keeper: keeper,
}
}

// Migrate1to2 migrates the store from consensus version v1 to v2
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return v2.MigrateStore(ctx, &m.keeper.paramSpace)
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 6 additions & 2 deletions x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(_ *codec.LegacyAmino) {

// ConsensusVersion returns the consensus state-breaking version for the module.
func (AppModuleBasic) ConsensusVersion() uint64 {
return 1
return 2
}

// DefaultGenesis returns default genesis state as raw bytes for the evm
Expand Down Expand Up @@ -123,7 +123,11 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), am.keeper)
types.RegisterQueryServer(cfg.QueryServer(), am.keeper)

_ = keeper.NewMigrator(*am.keeper)
m := keeper.NewMigrator(*am.keeper)
err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2)
if err != nil {
panic(err)
}
}

// Route returns the message routing key for the evm module.
Expand Down
Loading