diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a5f03d8..ce723e660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [\#811](https://github.com/cosmos/evm/pull/811) Use sdk's DefaultBondDenom for default evm denom in genesis. - [\#823](https://github.com/cosmos/evm/pull/823) Remove authz dependency from test suite and EvmApp interface - [\#829](https://github.com/cosmos/evm/pull/829) Seperate test app interface +- [\#860](https://github.com/cosmos/evm/pull/860) Repalce legacytx.StdSignBytes with the aminojson sign mode handler in eip712. ### FEATURES diff --git a/ante/cosmos/eip712.go b/ante/cosmos/eip712.go index e800cee0f..3f7273246 100644 --- a/ante/cosmos/eip712.go +++ b/ante/cosmos/eip712.go @@ -1,6 +1,7 @@ package cosmos import ( + "context" "fmt" "strconv" @@ -12,7 +13,10 @@ import ( "github.com/cosmos/evm/crypto/ethsecp256k1" "github.com/cosmos/evm/ethereum/eip712" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" errorsmod "cosmossdk.io/errors" + txsigning "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -21,11 +25,10 @@ import ( errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) -var evmCodec codec.ProtoCodecMarshaler +var evmCodec codec.Codec func init() { registry := codectypes.NewInterfaceRegistry() @@ -136,6 +139,7 @@ func (svd LegacyEip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, ChainID: chainID, AccountNumber: accNum, Sequence: acc.GetSequence(), + Address: acc.GetAddress().String(), } if simulate { @@ -177,17 +181,35 @@ func VerifySignature( return errorsmod.Wrap(errortypes.ErrNoSignatures, "tx doesn't contain any msgs to verify signature") } - txBytes := legacytx.StdSignBytes( - signerData.ChainID, - signerData.AccountNumber, - signerData.Sequence, - tx.GetTimeoutHeight(), - legacytx.StdFee{ - Amount: tx.GetFee(), - Gas: tx.GetGas(), + anyMsgs, err := eip712.ToAnyMsgs(msgs) + if err != nil { + return err + } + feeAmount := eip712.ToFeeAmount(tx.GetFee()) + txData := txsigning.TxData{ + Body: &txv1beta1.TxBody{ + Messages: anyMsgs, + Memo: tx.GetMemo(), + TimeoutHeight: tx.GetTimeoutHeight(), }, - msgs, tx.GetMemo(), - ) + AuthInfo: &txv1beta1.AuthInfo{ + Fee: &txv1beta1.Fee{ + Amount: feeAmount, + GasLimit: tx.GetGas(), + }, + }, + } + signModeHandler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) + signer := txsigning.SignerData{ + ChainID: signerData.ChainID, + AccountNumber: signerData.AccountNumber, + Sequence: signerData.Sequence, + Address: signerData.Address, + } + txBytes, err := signModeHandler.GetSignBytes(context.Background(), signer, txData) + if err != nil { + return errorsmod.Wrap(err, "failed to get sign bytes using aminojson") + } signerChainID, err := strconv.ParseUint(signerData.ChainID, 10, 64) if err != nil { diff --git a/ethereum/eip712/encoding.go b/ethereum/eip712/encoding.go index 5292fa4e4..286515eb4 100644 --- a/ethereum/eip712/encoding.go +++ b/ethereum/eip712/encoding.go @@ -1,20 +1,27 @@ package eip712 import ( + "context" "errors" "fmt" apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + errorsmod "cosmossdk.io/errors" + txsigning "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" txTypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" ) var ( - protoCodec codec.ProtoCodecMarshaler + protoCodec codec.Codec aminoCodec *codec.LegacyAmino eip155ChainID uint64 ) @@ -159,22 +166,41 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) { } signerInfo := authInfo.SignerInfos[0] - - stdFee := &legacytx.StdFee{ - Amount: authInfo.Fee.Amount, - Gas: authInfo.Fee.GasLimit, + var pubKey cryptotypes.PubKey + err := protoCodec.UnpackAny(signerInfo.PublicKey, &pubKey) + if err != nil { + return apitypes.TypedData{}, errorsmod.Wrap(err, "failed to unpack signer public key") } - // WrapTxToTypedData expects the payload as an Amino Sign Doc - signBytes := legacytx.StdSignBytes( - signDoc.ChainId, - signDoc.AccountNumber, - signerInfo.Sequence, - body.TimeoutHeight, - *stdFee, - msgs, - body.Memo, - ) + anyMsgs, err := ToAnyMsgs(msgs) + if err != nil { + return apitypes.TypedData{}, err + } + feeAmount := ToFeeAmount(authInfo.Fee.Amount) + txData := txsigning.TxData{ + Body: &txv1beta1.TxBody{ + Messages: anyMsgs, + Memo: body.Memo, + TimeoutHeight: body.TimeoutHeight, + }, + AuthInfo: &txv1beta1.AuthInfo{ + Fee: &txv1beta1.Fee{ + Amount: feeAmount, + GasLimit: authInfo.Fee.GasLimit, + }, + }, + } + signModeHandler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) + signer := txsigning.SignerData{ + ChainID: signDoc.ChainId, + AccountNumber: signDoc.AccountNumber, + Sequence: signerInfo.Sequence, + Address: pubKey.Address().String(), + } + signBytes, err := signModeHandler.GetSignBytes(context.Background(), signer, txData) + if err != nil { + return apitypes.TypedData{}, errorsmod.Wrap(err, "failed to get sign bytes using aminojson") + } typedData, err := WrapTxToTypedData( eip155ChainID, diff --git a/ethereum/eip712/encoding_legacy.go b/ethereum/eip712/encoding_legacy.go index d0efecf50..deafa8e14 100644 --- a/ethereum/eip712/encoding_legacy.go +++ b/ethereum/eip712/encoding_legacy.go @@ -1,12 +1,19 @@ package eip712 import ( + "context" "encoding/json" "errors" "fmt" apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" + errorsmod "cosmossdk.io/errors" + txsigning "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" txTypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -157,14 +164,12 @@ func legacyDecodeProtobufSignDoc(signDocBytes []byte, eip155ChainID uint64) (api // Use first message for fee payer and type inference msg := msgs[0] - signerInfo := authInfo.SignerInfos[0] - - stdFee := &legacytx.StdFee{ - Amount: authInfo.Fee.Amount, - Gas: authInfo.Fee.GasLimit, + var pubKey cryptotypes.PubKey + err := protoCodec.UnpackAny(signerInfo.PublicKey, &pubKey) + if err != nil { + return apitypes.TypedData{}, errorsmod.Wrap(err, "failed to unpack signer public key") } - signers, _, err := protoCodec.GetMsgV1Signers(msg) if err != nil { return apitypes.TypedData{}, err @@ -175,15 +180,36 @@ func legacyDecodeProtobufSignDoc(signDocBytes []byte, eip155ChainID uint64) (api } // WrapTxToTypedData expects the payload as an Amino Sign Doc - signBytes := legacytx.StdSignBytes( - signDoc.ChainId, - signDoc.AccountNumber, - signerInfo.Sequence, - body.TimeoutHeight, - *stdFee, - msgs, - body.Memo, - ) + anyMsgs, err := ToAnyMsgs(msgs) + if err != nil { + return apitypes.TypedData{}, err + } + feeAmount := ToFeeAmount(authInfo.Fee.Amount) + txData := txsigning.TxData{ + Body: &txv1beta1.TxBody{ + Messages: anyMsgs, + Memo: body.Memo, + TimeoutHeight: body.TimeoutHeight, + }, + AuthInfo: &txv1beta1.AuthInfo{ + Fee: &txv1beta1.Fee{ + Amount: feeAmount, + GasLimit: authInfo.Fee.GasLimit, + }, + }, + } + + signModeHandler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{}) + signer := txsigning.SignerData{ + ChainID: signDoc.ChainId, + AccountNumber: signDoc.AccountNumber, + Sequence: signerInfo.Sequence, + Address: pubKey.Address().String(), + } + signBytes, err := signModeHandler.GetSignBytes(context.Background(), signer, txData) + if err != nil { + return apitypes.TypedData{}, errorsmod.Wrap(err, "failed to get sign bytes using aminojson") + } typedData, err := LegacyWrapTxToTypedData( protoCodec, diff --git a/ethereum/eip712/types.go b/ethereum/eip712/types.go index ad6db98f5..c1fa251bc 100644 --- a/ethereum/eip712/types.go +++ b/ethereum/eip712/types.go @@ -10,9 +10,13 @@ import ( "github.com/tidwall/gjson" "golang.org/x/text/cases" "golang.org/x/text/language" + "google.golang.org/protobuf/types/known/anypb" + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" errorsmod "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -387,3 +391,25 @@ func doRecover(err *error) { *err = fmt.Errorf("%v", r) } } + +// ToAnyMsgs helps to convert sdk.Msg slice to []*anypb.Any +func ToAnyMsgs(msgs []sdk.Msg) ([]*anypb.Any, error) { + anyMsgs := make([]*anypb.Any, len(msgs)) + for i, msg := range msgs { + anyMsg, err := types.NewAnyWithValue(msg) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to convert sdk.Msg to Any") + } + anyMsgs[i] = &anypb.Any{TypeUrl: anyMsg.TypeUrl, Value: anyMsg.Value} + } + return anyMsgs, nil +} + +// ToFeeAmount helps to convert sdk.Coins to []*basev1beta1.Coin +func ToFeeAmount(coins sdk.Coins) []*basev1beta1.Coin { + feeAmount := make([]*basev1beta1.Coin, len(coins)) + for i, coin := range coins { + feeAmount[i] = &basev1beta1.Coin{Denom: coin.Denom, Amount: coin.Amount.String()} + } + return feeAmount +} diff --git a/ethereum/eip712/types_test.go b/ethereum/eip712/types_test.go new file mode 100644 index 000000000..3b411c851 --- /dev/null +++ b/ethereum/eip712/types_test.go @@ -0,0 +1,96 @@ +package eip712 + +import ( + "testing" + + "github.com/stretchr/testify/require" + + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func TestToAnyMsgs(t *testing.T) { + msg := &banktypes.MsgSend{ + FromAddress: "cosmos1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ptzkucp", + ToAddress: "cosmos1dx67l23hz9l0k9hcher8xz04uj7wf3yu26l2yn", + Amount: sdk.Coins{sdk.Coin{Amount: math.NewInt(10), Denom: "atest"}}, + } + expectedAny, err := types.NewAnyWithValue(msg) + require.NoError(t, err) + testCases := []struct { + name string + msgs []sdk.Msg + wantLen int + wantError bool + }{ + { + name: "single valid message", + msgs: []sdk.Msg{msg}, + wantLen: 1, + wantError: false, + }, + { + name: "empty slice", + msgs: []sdk.Msg{}, + wantLen: 0, + wantError: false, + }, + { + name: "invalid message (nil)", + msgs: []sdk.Msg{nil}, + wantLen: 0, + wantError: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + anyMsgs, err := ToAnyMsgs(tc.msgs) + if tc.wantError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Len(t, anyMsgs, tc.wantLen) + if tc.wantLen == 1 { + require.Equal(t, expectedAny.TypeUrl, anyMsgs[0].TypeUrl) + require.Equal(t, expectedAny.Value, anyMsgs[0].Value) + } + } + }) + } +} + +func TestToFeeAmount(t *testing.T) { + const denom1 = "atest1" + const denom2 = "atest2" + testCases := []struct { + name string + coins sdk.Coins + expected []*basev1beta1.Coin + }{ + { + name: "single coin", + coins: sdk.NewCoins(sdk.NewInt64Coin(denom1, 100)), + expected: []*basev1beta1.Coin{{Denom: denom1, Amount: "100"}}, + }, + { + name: "multiple coins", + coins: sdk.NewCoins(sdk.NewInt64Coin(denom1, 100), sdk.NewInt64Coin(denom2, 50)), + expected: []*basev1beta1.Coin{{Denom: denom1, Amount: "100"}, {Denom: denom2, Amount: "50"}}, + }, + { + name: "empty coins", + coins: sdk.NewCoins(), + expected: []*basev1beta1.Coin{}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ToFeeAmount(tc.coins) + require.Equal(t, tc.expected, result) + }) + } +}