diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc0dda6..21a6f7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,12 +43,16 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (log) [#948](https://github.com/tharsis/ethermint/pull/948) redirect go-ethereum's logs to Cosmos SDK logger. +### Features +* (ante) [#950](https://github.com/tharsis/ethermint/pull/950) Add support for EIP712 signed Cosmos transactions + ### Bug Fixes * (rpc) [#955](https://github.com/tharsis/ethermint/pull/955) Fix websocket server push duplicated messages to subscriber. * (rpc) [tharsis#953](https://github.com/tharsis/ethermint/pull/953) Add `eth_signTypedData` api support. * (log) [#948](https://github.com/tharsis/ethermint/pull/948) redirect go-ethereum's logs to cosmos-sdk logger. + ## [v0.10.0-beta1] - 2022-02-15 ### API Breaking diff --git a/app/ante/ante.go b/app/ante/ante.go index 1c6ab912..fbf1e9c7 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -39,6 +39,9 @@ func NewAnteHandler(options HandlerOptions) sdk.AnteHandler { case "/ethermint.evm.v1.ExtensionOptionsEthereumTx": // handle as *evmtypes.MsgEthereumTx anteHandler = newEthAnteHandler(options) + case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": + // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation + anteHandler = newCosmosAnteHandlerEip712(options) default: return ctx, sdkerrors.Wrapf( sdkerrors.ErrUnknownExtensionOptions, diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 00b6a02f..2548ba30 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -1,6 +1,7 @@ package ante_test import ( + "github.com/cosmos/cosmos-sdk/types/tx/signing" "math/big" "strings" @@ -292,6 +293,101 @@ func (suite AnteTestSuite) TestAnteHandler() { return txBuilder.GetTx() }, false, false, false, }, + { + "success - DeliverTx EIP712 signed Cosmos Tx with MsgSend", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, + { + "success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg", + func() sdk.Tx { + from := acc.GetAddress() + coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)) + amount := sdk.NewCoins(coinAmount) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + txBuilder.SetGasLimit(uint64(300000)) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(30)))) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with empty signature", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + sigsV2 := signing.SignatureV2{} + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + sigsV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + Sequence: nonce - 1, + } + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + sigsV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_UNSPECIFIED, + }, + Sequence: nonce, + } + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, } for _, tc := range testCases { diff --git a/app/ante/eip712.go b/app/ante/eip712.go new file mode 100644 index 00000000..9de0d191 --- /dev/null +++ b/app/ante/eip712.go @@ -0,0 +1,272 @@ +package ante + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "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/legacy/legacytx" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/tharsis/ethermint/crypto/ethsecp256k1" + "github.com/tharsis/ethermint/ethereum/eip712" + ethermint "github.com/tharsis/ethermint/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +var ethermintCodec codec.ProtoCodecMarshaler + +func init() { + registry := codectypes.NewInterfaceRegistry() + ethermint.RegisterInterfaces(registry) + ethermintCodec = codec.NewProtoCodec(registry) +} + +// Eip712SigVerificationDecorator Verify all signatures for a tx and return an error if any are invalid. Note, +// the Eip712SigVerificationDecorator decorator will not get executed on ReCheck. +// +// CONTRACT: Pubkeys are set in context for all signers before this decorator runs +// CONTRACT: Tx must implement SigVerifiableTx interface +type Eip712SigVerificationDecorator struct { + ak evmtypes.AccountKeeper + signModeHandler authsigning.SignModeHandler +} + +// NewEip712SigVerificationDecorator creates a new Eip712SigVerificationDecorator +func NewEip712SigVerificationDecorator(ak evmtypes.AccountKeeper, signModeHandler authsigning.SignModeHandler) Eip712SigVerificationDecorator { + return Eip712SigVerificationDecorator{ + ak: ak, + signModeHandler: signModeHandler, + } +} + +// AnteHandle handles validation of EIP712 signed cosmos txs. +// it is not run on RecheckTx +func (svd Eip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // no need to verify signatures on recheck tx + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", tx) + } + + authSignTx, ok := tx.(authsigning.Tx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", tx) + } + + // stdSigs contains the sequence number, account number, and signatures. + // When simulating, this would just be a 0-length slice. + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + signerAddrs := sigTx.GetSigners() + + // EIP712 allows just one signature + if len(sigs) != 1 { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signers (%d); EIP712 signatures allows just one signature", len(sigs)) + } + + // check that signer length and signature length are the same + if len(sigs) != len(signerAddrs) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) + } + + // EIP712 has just one signature, avoid looping here and only read index 0 + i := 0 + sig := sigs[i] + + acc, err := authante.GetSignerAcc(ctx, svd.ak, signerAddrs[i]) + if err != nil { + return ctx, err + } + + // retrieve pubkey + pubKey := acc.GetPubKey() + if !simulate && pubKey == nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + } + + // Check account sequence number. + if sig.Sequence != acc.GetSequence() { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrWrongSequence, + "account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, + ) + } + + // retrieve signer data + genesis := ctx.BlockHeight() == 0 + chainID := ctx.ChainID() + + var accNum uint64 + if !genesis { + accNum = acc.GetAccountNumber() + } + + signerData := authsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNum, + Sequence: acc.GetSequence(), + } + + if simulate { + return next(ctx, tx, simulate) + } + + if err := VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, authSignTx); err != nil { + errMsg := fmt.Errorf("signature verification failed; please verify account number (%d) and chain-id (%s): %w", accNum, chainID, err) + return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg.Error()) + } + + return next(ctx, tx, simulate) +} + +// VerifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes +// and single vs multi-signatures. +func VerifySignature( + pubKey cryptotypes.PubKey, + signerData authsigning.SignerData, + sigData signing.SignatureData, + _ authsigning.SignModeHandler, + tx authsigning.Tx, +) error { + switch data := sigData.(type) { + case *signing.SingleSignatureData: + if data.SignMode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + return sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "unexpected SignatureData %T: wrong SignMode", sigData) + } + + // Note: this prevents the user from sending thrash data in the signature field + if len(data.Signature) != 0 { + return sdkerrors.Wrap(sdkerrors.ErrTooManySignatures, "invalid signature value; EIP712 must have the cosmos transaction signature empty") + } + + // @contract: this code is reached only when Msg has Web3Tx extension (so this custom Ante handler flow), + // and the signature is SIGN_MODE_LEGACY_AMINO_JSON which is supported for EIP712 for now + + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return sdkerrors.Wrap(sdkerrors.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(), + }, + msgs, tx.GetMemo(), + ) + + signerChainID, err := ethermint.ParseChainID(signerData.ChainID) + if err != nil { + return sdkerrors.Wrapf(err, "failed to parse chainID: %s", signerData.ChainID) + } + + txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain any extensions") + } + opts := txWithExtensions.GetExtensionOptions() + if len(opts) != 1 { + return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain expected amount of extension options") + } + + var optIface ethermint.ExtensionOptionsWeb3TxI + + if err := ethermintCodec.UnpackAny(opts[0], &optIface); err != nil { + return sdkerrors.Wrap(err, "failed to proto-unpack ExtensionOptionsWeb3Tx") + } + + extOpt, ok := optIface.(*ethermint.ExtensionOptionsWeb3Tx) + if !ok { + return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "unknown extension option") + } + + if extOpt.TypedDataChainID != signerChainID.Uint64() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "invalid chainID") + } + + if len(extOpt.FeePayer) == 0 { + return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "no feePayer on ExtensionOptionsWeb3Tx") + } + feePayer, err := sdk.AccAddressFromBech32(extOpt.FeePayer) + if err != nil { + return sdkerrors.Wrap(err, "failed to parse feePayer from ExtensionOptionsWeb3Tx") + } + + feeDelegation := &eip712.FeeDelegationOptions{ + FeePayer: feePayer, + } + + typedData, err := eip712.WrapTxToTypedData(ethermintCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation) + if err != nil { + return sdkerrors.Wrap(err, "failed to pack tx data in EIP712 object") + } + + sigHash, err := eip712.ComputeTypedDataHash(typedData) + if err != nil { + return err + } + + feePayerSig := extOpt.FeePayerSig + if len(feePayerSig) != ethcrypto.SignatureLength { + return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "signature length doesn't match typical [R||S||V] signature 65 bytes") + } + + // Remove the recovery offset if needed (ie. Metamask eip712 signature) + if feePayerSig[ethcrypto.RecoveryIDOffset] == 27 || feePayerSig[ethcrypto.RecoveryIDOffset] == 28 { + feePayerSig[ethcrypto.RecoveryIDOffset] -= 27 + } + + feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, feePayerSig) + if err != nil { + return sdkerrors.Wrap(err, "failed to recover delegated fee payer from sig") + } + + ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey) + if err != nil { + return sdkerrors.Wrap(err, "failed to unmarshal recovered fee payer pubkey") + } + + pk := ðsecp256k1.PubKey{ + Key: ethcrypto.CompressPubkey(ecPubKey), + } + + if !pubKey.Equals(pk) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "feePayer pubkey %s is different from transaction pubkey %s", pubKey, pk) + } + + recoveredFeePayerAcc := sdk.AccAddress(pk.Address().Bytes()) + + if !recoveredFeePayerAcc.Equals(feePayer) { + return sdkerrors.Wrapf(sdkerrors.ErrorInvalidSigner, "failed to verify delegated fee payer %s signature", recoveredFeePayerAcc) + } + + // VerifySignature of ethsecp256k1 accepts 64 byte signature [R||S] + // WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there + if !secp256k1.VerifySignature(pubKey.Bytes(), sigHash, feePayerSig[:len(feePayerSig)-1]) { + return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "unable to verify signer signature of EIP712 typed data") + } + + return nil + default: + return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, "unexpected SignatureData %T", sigData) + } +} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 1defef7b..cb6220fc 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -79,3 +79,26 @@ func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { ibcante.NewAnteDecorator(options.IBCChannelKeeper), ) } + +func newCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler { + return sdk.ChainAnteDecorators( + RejectMessagesDecorator{}, // reject MsgEthereumTxs + ante.NewSetUpContextDecorator(), + // NOTE: extensions option decorator removed + // ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + // Note: signature verification uses EIP instead of the cosmos signature validator + NewEip712SigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewAnteDecorator(options.IBCChannelKeeper), + ) +} diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index b1c19928..76c74806 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -1,6 +1,13 @@ package ante_test import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" + types2 "github.com/cosmos/cosmos-sdk/x/bank/types" + types3 "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tharsis/ethermint/ethereum/eip712" + "github.com/tharsis/ethermint/types" "math" "testing" "time" @@ -191,6 +198,88 @@ func (suite *AnteTestSuite) CreateTestTxBuilder( return txBuilder } +func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + // Build MsgSend + recipient := sdk.AccAddress(common.Address{}.Bytes()) + msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(1)))) + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend) +} + +func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + // Build MsgSend + valEthAddr := tests.GenerateAddress() + valAddr := sdk.ValAddress(valEthAddr.Bytes()) + msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))) + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend) +} + +func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder( + from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg, +) client.TxBuilder { + var err error + + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, from) + suite.Require().NoError(err) + + pc, err := types.ParseChainID(chainId) + suite.Require().NoError(err) + ethChainId := pc.Uint64() + + // GenerateTypedData TypedData + var ethermintCodec codec.ProtoCodecMarshaler + fee := legacytx.NewStdFee(gas, gasAmount) + accNumber := suite.app.AccountKeeper.GetAccount(suite.ctx, from).GetAccountNumber() + + data := legacytx.StdSignBytes(chainId, accNumber, nonce, 0, fee, []sdk.Msg{msg}, "") + typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msg, data, &eip712.FeeDelegationOptions{ + FeePayer: from, + }) + suite.Require().NoError(err) + + sigHash, err := eip712.ComputeTypedDataHash(typedData) + suite.Require().NoError(err) + + // Sign typedData + keyringSigner := tests.NewSigner(priv) + signature, pubKey, err := keyringSigner.SignByAddress(from, sigHash) + suite.Require().NoError(err) + signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + + // Add ExtensionOptionsWeb3Tx extension + var option *codectypes.Any + option, err = codectypes.NewAnyWithValue(&types.ExtensionOptionsWeb3Tx{ + FeePayer: from.String(), + TypedDataChainID: ethChainId, + FeePayerSig: signature, + }) + suite.Require().NoError(err) + + suite.clientCtx.TxConfig.SignModeHandler() + txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + suite.Require().True(ok) + + builder.SetExtensionOptions(option) + builder.SetFeeAmount(gasAmount) + builder.SetGasLimit(gas) + + sigsV2 := signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + Sequence: nonce, + } + + err = builder.SetSignatures(sigsV2) + suite.Require().NoError(err) + + err = builder.SetMsgs(msg) + suite.Require().NoError(err) + + return builder +} + var _ sdk.Tx = &invalidTx{} type invalidTx struct{} diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go new file mode 100644 index 00000000..35a05dc2 --- /dev/null +++ b/ethereum/eip712/eip712.go @@ -0,0 +1,449 @@ +package eip712 + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "reflect" + "strings" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" +) + +// ComputeTypedDataHash computes keccak hash of typed data for signing. +func ComputeTypedDataHash(typedData apitypes.TypedData) ([]byte, error) { + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + err = sdkerrors.Wrap(err, "failed to pack and hash typedData EIP712Domain") + return nil, err + } + + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + err = sdkerrors.Wrap(err, "failed to pack and hash typedData primary type") + return nil, err + } + + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) + return crypto.Keccak256(rawData), nil +} + +// WrapTxToTypedData is an ultimate method that wraps Amino-encoded Cosmos Tx JSON data +// into an EIP712-compatible TypedData request. +func WrapTxToTypedData( + cdc codectypes.AnyUnpacker, + chainID uint64, + msg sdk.Msg, + data []byte, + feeDelegation *FeeDelegationOptions, +) (apitypes.TypedData, error) { + txData := make(map[string]interface{}) + + if err := json.Unmarshal(data, &txData); err != nil { + return apitypes.TypedData{}, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, "failed to JSON unmarshal data") + } + + domain := apitypes.TypedDataDomain{ + Name: "Cosmos Web3", + Version: "1.0.0", + ChainId: math.NewHexOrDecimal256(int64(chainID)), + VerifyingContract: "cosmos", + Salt: "0", + } + + msgTypes, err := extractMsgTypes(cdc, "MsgValue", msg) + if err != nil { + return apitypes.TypedData{}, err + } + + if feeDelegation != nil { + feeInfo, ok := txData["fee"].(map[string]interface{}) + if !ok { + return apitypes.TypedData{}, sdkerrors.Wrap(sdkerrors.ErrInvalidType, "cannot parse fee from tx data") + } + + feeInfo["feePayer"] = feeDelegation.FeePayer.String() + + // also patching msgTypes to include feePayer + msgTypes["Fee"] = []apitypes.Type{ + {Name: "feePayer", Type: "string"}, + {Name: "amount", Type: "Coin[]"}, + {Name: "gas", Type: "string"}, + } + } + + typedData := apitypes.TypedData{ + Types: msgTypes, + PrimaryType: "Tx", + Domain: domain, + Message: txData, + } + + return typedData, nil +} + +type FeeDelegationOptions struct { + FeePayer sdk.AccAddress +} + +func extractMsgTypes(cdc codectypes.AnyUnpacker, msgTypeName string, msg sdk.Msg) (apitypes.Types, error) { + rootTypes := apitypes.Types{ + "EIP712Domain": { + { + Name: "name", + Type: "string", + }, + { + Name: "version", + Type: "string", + }, + { + Name: "chainId", + Type: "uint256", + }, + { + Name: "verifyingContract", + Type: "string", + }, + { + Name: "salt", + Type: "string", + }, + }, + "Tx": { + {Name: "account_number", Type: "string"}, + {Name: "chain_id", Type: "string"}, + {Name: "fee", Type: "Fee"}, + {Name: "memo", Type: "string"}, + {Name: "msgs", Type: "Msg[]"}, + {Name: "sequence", Type: "string"}, + // Note timeout_height was removed because it was not getting filled with the legacyTx + // {Name: "timeout_height", Type: "string"}, + }, + "Fee": { + {Name: "amount", Type: "Coin[]"}, + {Name: "gas", Type: "string"}, + }, + "Coin": { + {Name: "denom", Type: "string"}, + {Name: "amount", Type: "string"}, + }, + "Msg": { + {Name: "type", Type: "string"}, + {Name: "value", Type: msgTypeName}, + }, + msgTypeName: {}, + } + + if err := walkFields(cdc, rootTypes, msgTypeName, msg); err != nil { + return nil, err + } + + return rootTypes, nil +} + +const typeDefPrefix = "_" + +func walkFields(cdc codectypes.AnyUnpacker, typeMap apitypes.Types, rootType string, in interface{}) (err error) { + defer doRecover(&err) + + t := reflect.TypeOf(in) + v := reflect.ValueOf(in) + + for { + if t.Kind() == reflect.Ptr || + t.Kind() == reflect.Interface { + t = t.Elem() + v = v.Elem() + + continue + } + + break + } + + return traverseFields(cdc, typeMap, rootType, typeDefPrefix, t, v) +} + +type cosmosAnyWrapper struct { + Type string `json:"type"` + Value interface{} `json:"value"` +} + +func traverseFields( + cdc codectypes.AnyUnpacker, + typeMap apitypes.Types, + rootType string, + prefix string, + t reflect.Type, + v reflect.Value, +) error { + n := t.NumField() + + if prefix == typeDefPrefix { + if len(typeMap[rootType]) == n { + return nil + } + } else { + typeDef := sanitizeTypedef(prefix) + if len(typeMap[typeDef]) == n { + return nil + } + } + + for i := 0; i < n; i++ { + var field reflect.Value + if v.IsValid() { + field = v.Field(i) + } + + fieldType := t.Field(i).Type + fieldName := jsonNameFromTag(t.Field(i).Tag) + + if fieldType == cosmosAnyType { + any, ok := field.Interface().(*codectypes.Any) + if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) + } + + anyWrapper := &cosmosAnyWrapper{ + Type: any.TypeUrl, + } + + if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { + return sdkerrors.Wrap(err, "failed to unpack Any in msg struct") + } + + fieldType = reflect.TypeOf(anyWrapper) + field = reflect.ValueOf(anyWrapper) + + // then continue as normal + } + + for { + if fieldType.Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + + if field.IsValid() { + field = field.Elem() + } + + continue + } + + if fieldType.Kind() == reflect.Interface { + fieldType = reflect.TypeOf(field.Interface()) + continue + } + + if field.Kind() == reflect.Ptr { + field = field.Elem() + continue + } + + break + } + + var isCollection bool + if fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice { + if field.Len() == 0 { + // skip empty collections from type mapping + continue + } + + fieldType = fieldType.Elem() + field = field.Index(0) + isCollection = true + } + + for { + if fieldType.Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + + if field.IsValid() { + field = field.Elem() + } + + continue + } + + if fieldType.Kind() == reflect.Interface { + fieldType = reflect.TypeOf(field.Interface()) + continue + } + + if field.Kind() == reflect.Ptr { + field = field.Elem() + continue + } + + break + } + + fieldPrefix := fmt.Sprintf("%s.%s", prefix, fieldName) + + ethTyp := typToEth(fieldType) + if len(ethTyp) > 0 { + if prefix == typeDefPrefix { + typeMap[rootType] = append(typeMap[rootType], apitypes.Type{ + Name: fieldName, + Type: ethTyp, + }) + } else { + typeDef := sanitizeTypedef(prefix) + typeMap[typeDef] = append(typeMap[typeDef], apitypes.Type{ + Name: fieldName, + Type: ethTyp, + }) + } + + continue + } + + if fieldType.Kind() == reflect.Struct { + + var fieldTypedef string + + if isCollection { + fieldTypedef = sanitizeTypedef(fieldPrefix) + "[]" + } else { + fieldTypedef = sanitizeTypedef(fieldPrefix) + } + + if prefix == typeDefPrefix { + typeMap[rootType] = append(typeMap[rootType], apitypes.Type{ + Name: fieldName, + Type: fieldTypedef, + }) + } else { + typeDef := sanitizeTypedef(prefix) + typeMap[typeDef] = append(typeMap[typeDef], apitypes.Type{ + Name: fieldName, + Type: fieldTypedef, + }) + } + + if err := traverseFields(cdc, typeMap, rootType, fieldPrefix, fieldType, field); err != nil { + return err + } + + continue + } + } + + return nil +} + +func jsonNameFromTag(tag reflect.StructTag) string { + jsonTags := tag.Get("json") + parts := strings.Split(jsonTags, ",") + return parts[0] +} + +// _.foo_bar.baz -> TypeFooBarBaz +// +// this is needed for Geth's own signing code which doesn't +// tolerate complex type names +func sanitizeTypedef(str string) string { + buf := new(bytes.Buffer) + parts := strings.Split(str, ".") + + for _, part := range parts { + if part == "_" { + buf.WriteString("Type") + continue + } + + subparts := strings.Split(part, "_") + for _, subpart := range subparts { + buf.WriteString(strings.Title(subpart)) + } + } + + return buf.String() +} + +var ( + hashType = reflect.TypeOf(common.Hash{}) + addressType = reflect.TypeOf(common.Address{}) + bigIntType = reflect.TypeOf(big.Int{}) + cosmIntType = reflect.TypeOf(sdk.Int{}) + cosmosAnyType = reflect.TypeOf(&codectypes.Any{}) +) + +// typToEth supports only basic types and arrays of basic types. +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md +func typToEth(typ reflect.Type) string { + const str = "string" + + switch typ.Kind() { + case reflect.String: + return str + case reflect.Bool: + return "bool" + case reflect.Int: + return "int64" + case reflect.Int8: + return "int8" + case reflect.Int16: + return "int16" + case reflect.Int32: + return "int32" + case reflect.Int64: + return "int64" + case reflect.Uint: + return "uint64" + case reflect.Uint8: + return "uint8" + case reflect.Uint16: + return "uint16" + case reflect.Uint32: + return "uint32" + case reflect.Uint64: + return "uint64" + case reflect.Slice: + ethName := typToEth(typ.Elem()) + if len(ethName) > 0 { + return ethName + "[]" + } + case reflect.Array: + ethName := typToEth(typ.Elem()) + if len(ethName) > 0 { + return ethName + "[]" + } + case reflect.Ptr: + if typ.Elem().ConvertibleTo(bigIntType) || + typ.Elem().ConvertibleTo(cosmIntType) { + return str + } + case reflect.Struct: + if typ.ConvertibleTo(hashType) || + typ.ConvertibleTo(addressType) || + typ.ConvertibleTo(bigIntType) || + typ.ConvertibleTo(cosmIntType) { + return str + } + } + + return "" +} + +func doRecover(err *error) { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + e = sdkerrors.Wrap(e, "panicked with error") + *err = e + return + } + + *err = fmt.Errorf("%v", r) + } +} diff --git a/go.mod b/go.mod index e82ae930..55d64415 100644 --- a/go.mod +++ b/go.mod @@ -17,15 +17,12 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/holiman/uint256 v1.2.0 github.com/improbable-eng/grpc-web v0.15.0 - github.com/klauspost/compress v1.11.9 // indirect github.com/miguelmota/go-ethereum-hdwallet v0.1.1 github.com/onsi/ginkgo v1.16.5 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 github.com/rs/cors v1.8.2 - github.com/rs/zerolog v1.26.0 // indirect github.com/spf13/cast v1.4.1 github.com/spf13/cobra v1.3.0 github.com/spf13/viper v1.10.1 @@ -34,8 +31,6 @@ require ( github.com/tendermint/tendermint v0.34.14 github.com/tendermint/tm-db v0.6.7 github.com/tyler-smith/go-bip39 v1.1.0 - go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 // indirect google.golang.org/genproto v0.0.0-20220211171837-173942840c17 google.golang.org/grpc v1.44.0 google.golang.org/protobuf v1.27.1 @@ -46,8 +41,8 @@ require ( filippo.io/edwards25519 v1.0.0-beta.2 // indirect github.com/99designs/keyring v1.1.6 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect - github.com/DataDog/zstd v1.4.8 // indirect - github.com/StackExchange/wmi v1.2.1 // indirect + github.com/DataDog/zstd v1.4.5 // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/Workiva/go-datastructures v1.0.52 // indirect github.com/armon/go-metrics v0.3.10 // indirect @@ -77,15 +72,16 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect - github.com/go-ole/go-ole v1.2.5 // indirect + github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect + github.com/google/btree v1.0.0 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.1.5 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect @@ -101,6 +97,7 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect + github.com/klauspost/compress v1.11.7 // indirect github.com/lib/pq v1.10.2 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.5 // indirect @@ -113,7 +110,6 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onsi/gomega v1.17.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -122,7 +118,9 @@ require ( github.com/prometheus/common v0.29.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/prometheus/tsdb v0.7.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/rjeczalik/notify v0.9.1 // indirect + github.com/rs/zerolog v1.23.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/afero v1.6.0 // indirect @@ -133,15 +131,17 @@ require ( github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tklauser/go-sysconf v0.3.7 // indirect - github.com/tklauser/numcpus v0.2.3 // indirect + github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/tklauser/numcpus v0.2.2 // indirect github.com/zondax/hid v0.9.0 // indirect + go.etcd.io/bbolt v1.3.6 // indirect + golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.7 // indirect + golang.org/x/tools v0.1.5 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 // indirect diff --git a/go.sum b/go.sum index 76ea83bf..a3704494 100644 --- a/go.sum +++ b/go.sum @@ -75,9 +75,8 @@ github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1: github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= -github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= @@ -88,9 +87,8 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= @@ -361,9 +359,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -371,7 +368,6 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= @@ -440,9 +436,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -485,9 +480,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -654,9 +648,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.9 h1:5OCMOdde1TCT2sookEuVeEZzA8bmRSFV3AwPDZAG8AA= -github.com/klauspost/compress v1.11.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -801,9 +794,8 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -909,9 +901,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= @@ -928,10 +919,8 @@ github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= -github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= -github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1038,12 +1027,10 @@ github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg= -github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8= -github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1077,7 +1064,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1296,7 +1282,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1458,9 +1443,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 0e5a3d03..57a963ce 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -7,6 +7,8 @@ import ( "math" "math/big" + "github.com/tharsis/ethermint/ethereum/eip712" + "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/ethereum/go-ethereum/core/vm" @@ -446,18 +448,11 @@ func (e *PublicAPI) SignTypedData(address common.Address, typedData apitypes.Typ return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) } - domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + sigHash, err := eip712.ComputeTypedDataHash(typedData) if err != nil { return nil, err } - typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err != nil { - return nil, err - } - - rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) - sigHash := crypto.Keccak256(rawData) // Sign the requested hash with the wallet signature, _, err := e.clientCtx.Keyring.SignByAddress(from, sigHash) if err != nil {