From 3ab761b8c4d49726f655a745c0392645b9a1ee6b Mon Sep 17 00:00:00 2001 From: Tomas Guerra <54514587+GAtom22@users.noreply.github.com> Date: Wed, 19 Oct 2022 21:05:50 -0300 Subject: [PATCH] [issue-#1389] add multisig tx support (#1390) * [issue-#1389] add multisig tx support * [issue-#1389] add changes in CHANGELOG improvements * [issue-#1389] fix style issue * [issue-#1389] fix style issue * [issue-#1389] add tests --- CHANGELOG.md | 1 + app/ante/ante.go | 45 +++++++++++++++++-- app/ante/ante_test.go | 101 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06659b47..be40b4d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc) [#1352](https://github.com/evmos/ethermint/pull/1352) Make the grpc queries run concurrently, don't block the consensus state machine. * (cli) [#1360](https://github.com/evmos/ethermint/pull/1360) Introduce a new `grpc-only` flag, such that when enabled, will start the node in a query-only mode. Note, gRPC MUST be enabled with this flag. * (rpc) [#1378](https://github.com/evmos/ethermint/pull/1378) Add support for EVM RPC metrics +* (ante) [#1390](https://github.com/evmos/ethermint/pull/1390) Added multisig tx support. ### Bug Fixes diff --git a/app/ante/ante.go b/app/ante/ante.go index 666cfed6..20770690 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -6,6 +6,7 @@ import ( tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -99,12 +100,48 @@ var _ authante.SignatureVerificationGasConsumer = DefaultSigVerificationGasConsu func DefaultSigVerificationGasConsumer( meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params, ) error { - // support for ethereum ECDSA secp256k1 keys - _, ok := sig.PubKey.(*ethsecp256k1.PubKey) - if ok { + pubkey := sig.PubKey + switch pubkey := pubkey.(type) { + case *ethsecp256k1.PubKey: meter.ConsumeGas(secp256k1VerifyCost, "ante verify: eth_secp256k1") return nil + + case multisig.PubKey: + // Multisig keys + multisignature, ok := sig.Data.(*signing.MultiSignatureData) + if !ok { + return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data) + } + return ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence) + + default: + return authante.DefaultSigVerificationGasConsumer(meter, sig, params) + } +} + +// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature +func ConsumeMultisignatureVerificationGas( + meter sdk.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey, + params authtypes.Params, accSeq uint64, +) error { + size := sig.BitArray.Count() + sigIndex := 0 + + for i := 0; i < size; i++ { + if !sig.BitArray.GetIndex(i) { + continue + } + sigV2 := signing.SignatureV2{ + PubKey: pubkey.GetPubKeys()[i], + Data: sig.Signatures[sigIndex], + Sequence: accSeq, + } + err := DefaultSigVerificationGasConsumer(meter, sigV2, params) + if err != nil { + return err + } + sigIndex++ } - return authante.DefaultSigVerificationGasConsumer(meter, sig, params) + return nil } diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index b8821db9..9056ddaa 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -2,13 +2,22 @@ package ante_test import ( "errors" + "fmt" "math/big" "strings" "time" sdkmath "cosmossdk.io/math" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/authz" @@ -16,6 +25,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" ethparams "github.com/ethereum/go-ethereum/params" + "github.com/evmos/ethermint/app/ante" + "github.com/evmos/ethermint/crypto/ethsecp256k1" "github.com/evmos/ethermint/tests" evmtypes "github.com/evmos/ethermint/x/evm/types" @@ -914,3 +925,93 @@ func (suite AnteTestSuite) TestAnteHandlerWithParams() { } suite.evmParamsOption = nil } + +func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() { + params := authtypes.DefaultParams() + msg := []byte{1, 2, 3, 4} + cdc := simapp.MakeTestEncodingConfig().Amino + + p := authtypes.DefaultParams() + skR1, _ := secp256r1.GenPrivKey() + pkSet1, sigSet1, err := generatePubKeysAndSignatures(5, msg, false) + suite.Require().NoError(err) + + multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1) + multisignature1 := multisig.NewMultisig(len(pkSet1)) + expectedCost1 := expectedGasCostByKeys(pkSet1) + + for i := 0; i < len(pkSet1); i++ { + stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} + sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) + suite.Require().NoError(err) + err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) + suite.Require().NoError(err) + } + + type args struct { + meter sdk.GasMeter + sig signing.SignatureData + pubkey cryptotypes.PubKey + params authtypes.Params + } + tests := []struct { + name string + args args + gasConsumed uint64 + shouldErr bool + }{ + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, p.SigVerifyCostED25519, true}, + {"PubKeyEthSecp256k1", args{sdk.NewInfiniteGasMeter(), nil, pkSet1[0], params}, 21_000, false}, + {"PubKeySecp256r1", args{sdk.NewInfiniteGasMeter(), nil, skR1.PubKey(), params}, p.SigVerifyCostSecp256r1(), false}, + {"Multisig", args{sdk.NewInfiniteGasMeter(), multisignature1, multisigKey1, params}, expectedCost1, false}, + {"unknown key", args{sdk.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, + } + for _, tt := range tests { + sigV2 := signing.SignatureV2{ + PubKey: tt.args.pubkey, + Data: tt.args.sig, + Sequence: 0, // Arbitrary account sequence + } + err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + + if tt.shouldErr { + suite.Require().NotNil(err) + } else { + suite.Require().Nil(err) + suite.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + } + } +} + +func generatePubKeysAndSignatures(n int, msg []byte, _ bool) (pubkeys []cryptotypes.PubKey, signatures [][]byte, err error) { + pubkeys = make([]cryptotypes.PubKey, n) + signatures = make([][]byte, n) + for i := 0; i < n; i++ { + privkey, err := ethsecp256k1.GenerateKey() + if err != nil { + return nil, nil, err + } + + pubkeys[i] = privkey.PubKey() + signatures[i], _ = privkey.Sign(msg) + } + return +} + +func expectedGasCostByKeys(pubkeys []cryptotypes.PubKey) uint64 { + cost := uint64(0) + for _, pubkey := range pubkeys { + pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) + switch { + case strings.Contains(pubkeyType, "ed25519"): + cost += authtypes.DefaultParams().SigVerifyCostED25519 + case strings.Contains(pubkeyType, "ethsecp256k1"): + cost += 21_000 + case strings.Contains(pubkeyType, "secp256k1"): + cost += authtypes.DefaultParams().SigVerifyCostSecp256k1 + default: + panic("unexpected key type") + } + } + return cost +}