fear(eip712): Add EIP-712 encoding for multiple messages of the same type (#1483)
* Add EIP-712 encoding for multiple messages of the same type * Fix Protobuf encoding bug * Add ante tests * Refactor naming and minor implementation details * Test empty transaction coverage * Address revisions for code clarity * Move aminoMessage type definition
This commit is contained in:
parent
f0f3810bf4
commit
9c41edb674
@ -387,7 +387,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
from, grantee, &banktypes.SendAuthorization{SpendLimit: gasAmount}, &expiresAt,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, privKey, "ethermint_9000-1", gas, gasAmount, msg).GetTx()
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, privKey, "ethermint_9000-1", gas, gasAmount, msg).GetTx()
|
||||
}, false, false, true,
|
||||
},
|
||||
|
||||
@ -457,6 +457,28 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, true,
|
||||
},
|
||||
{
|
||||
"success- DeliverTx EIP712 Multiple MsgSend",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))
|
||||
amount := sdk.NewCoins(coinAmount)
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712MultipleMsgSend(from, privKey, "ethermint_9000-1", gas, amount)
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, true,
|
||||
},
|
||||
{
|
||||
"fails - DeliverTx EIP712 Multiple Signers",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))
|
||||
amount := sdk.NewCoins(coinAmount)
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712MultipleSignerMsgs(from, privKey, "ethermint_9000-1", gas, amount)
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, false,
|
||||
},
|
||||
{
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
|
||||
func() sdk.Tx {
|
||||
|
@ -272,7 +272,7 @@ func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress
|
||||
// Build MsgSend
|
||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(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 {
|
||||
@ -280,7 +280,7 @@ func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAdd
|
||||
valEthAddr := tests.GenerateAddress()
|
||||
valAddr := sdk.ValAddress(valEthAddr.Bytes())
|
||||
msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -296,7 +296,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddre
|
||||
sdk.OneInt(),
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -313,7 +313,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddr
|
||||
sdk.OneInt(),
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, deposit sdk.Coins) client.TxBuilder {
|
||||
@ -321,7 +321,7 @@ func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress,
|
||||
suite.Require().True(ok)
|
||||
msgSubmit, err := types5.NewMsgSubmitProposal(proposal, deposit, from)
|
||||
suite.Require().NoError(err)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSubmit)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgSubmit)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -335,7 +335,7 @@ func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress,
|
||||
grantedAddr := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, granted.Bytes())
|
||||
msgGrant, err := feegrant.NewMsgGrantAllowance(basic, from, grantedAddr.GetAddress())
|
||||
suite.Require().NoError(err)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgGrant)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgGrant)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -346,7 +346,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgEdit)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgEdit)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -359,12 +359,12 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddres
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgEvidence)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgEvidence)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgVoteV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
msgVote := govtypes.NewMsgVote(from, 1, govtypes.VoteOption_VOTE_OPTION_YES, "")
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgVote)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgVote)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
@ -403,14 +403,28 @@ func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MsgExec(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||
msgExec := authz.NewMsgExec(from, []sdk.Msg{msgSend})
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, &msgExec)
|
||||
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, &msgExec)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MultipleMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msgSend, msgSend, msgSend})
|
||||
}
|
||||
|
||||
// Fails
|
||||
func (suite *AnteTestSuite) CreateTestEIP712MultipleSignerMsgs(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||
msgSend1 := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||
msgSend2 := types2.NewMsgSend(recipient, from, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msgSend1, msgSend2})
|
||||
}
|
||||
|
||||
// StdSignBytes returns the bytes to sign for a transaction.
|
||||
@ -451,8 +465,14 @@ func StdSignBytes(cdc *codec.LegacyAmino, chainID string, accnum uint64, sequenc
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
func (suite *AnteTestSuite) CreateTestEIP712SingleMessageTxBuilder(
|
||||
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg,
|
||||
) client.TxBuilder {
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msg})
|
||||
}
|
||||
|
||||
func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg,
|
||||
) client.TxBuilder {
|
||||
var err error
|
||||
|
||||
@ -473,8 +493,8 @@ func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
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}, "", nil)
|
||||
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msg, data, &eip712.FeeDelegationOptions{
|
||||
data := legacytx.StdSignBytes(chainId, accNumber, nonce, 0, fee, msgs, "", nil)
|
||||
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msgs[0], data, &eip712.FeeDelegationOptions{
|
||||
FeePayer: from,
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
@ -517,7 +537,7 @@ func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
err = builder.SetSignatures(sigsV2)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = builder.SetMsgs(msg)
|
||||
err = builder.SetMsgs(msgs...)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return builder
|
||||
|
@ -93,6 +93,9 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
||||
signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
|
||||
}
|
||||
|
||||
// Fixed test address
|
||||
testAddress := suite.createTestAddress()
|
||||
|
||||
testCases := []struct {
|
||||
title string
|
||||
chainId string
|
||||
@ -176,7 +179,30 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
title: "Fails - Two MsgVotes",
|
||||
title: "Succeeds - Two Single-Signer MsgDelegate",
|
||||
fee: txtypes.Fee{
|
||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||
GasLimit: 20000,
|
||||
},
|
||||
memo: "",
|
||||
msgs: []sdk.Msg{
|
||||
stakingtypes.NewMsgDelegate(
|
||||
testAddress,
|
||||
sdk.ValAddress(suite.createTestAddress()),
|
||||
suite.makeCoins("photon", math.NewInt(1))[0],
|
||||
),
|
||||
stakingtypes.NewMsgDelegate(
|
||||
testAddress,
|
||||
sdk.ValAddress(suite.createTestAddress()),
|
||||
suite.makeCoins("photon", math.NewInt(5))[0],
|
||||
),
|
||||
},
|
||||
accountNumber: 25,
|
||||
sequence: 78,
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
title: "Fails - Two MsgVotes with Different Signers",
|
||||
fee: txtypes.Fee{
|
||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||
GasLimit: 20000,
|
||||
@ -196,10 +222,22 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
||||
},
|
||||
accountNumber: 25,
|
||||
sequence: 78,
|
||||
expectSuccess: false, // Multiple messages are currently not allowed
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
title: "Fails - MsgSend + MsgVote",
|
||||
title: "Fails - Empty transaction",
|
||||
fee: txtypes.Fee{
|
||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||
GasLimit: 20000,
|
||||
},
|
||||
memo: "",
|
||||
msgs: []sdk.Msg{},
|
||||
accountNumber: 25,
|
||||
sequence: 78,
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
title: "Fails - Single-Signer MsgSend + MsgVote",
|
||||
fee: txtypes.Fee{
|
||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||
GasLimit: 20000,
|
||||
@ -207,12 +245,12 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
||||
memo: "",
|
||||
msgs: []sdk.Msg{
|
||||
govtypes.NewMsgVote(
|
||||
suite.createTestAddress(),
|
||||
testAddress,
|
||||
5,
|
||||
govtypes.OptionNo,
|
||||
),
|
||||
banktypes.NewMsgSend(
|
||||
suite.createTestAddress(),
|
||||
testAddress,
|
||||
suite.createTestAddress(),
|
||||
suite.makeCoins("photon", math.NewInt(50)),
|
||||
),
|
||||
|
@ -1,13 +1,14 @@
|
||||
package eip712
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/simapp/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
|
||||
cosmosTypes "github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
txTypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
|
||||
apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
@ -16,9 +17,14 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
type aminoMessage struct {
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
var (
|
||||
ethermintProtoCodec codec.ProtoCodecMarshaler
|
||||
ethermintAminoCodec *codec.LegacyAmino
|
||||
protoCodec codec.ProtoCodecMarshaler
|
||||
aminoCodec *codec.LegacyAmino
|
||||
)
|
||||
|
||||
// SetEncodingConfig set the encoding config to the singleton codecs (Amino and Protobuf).
|
||||
@ -26,8 +32,8 @@ var (
|
||||
// populated with all relevant message types. As a result, we must call this method on app
|
||||
// initialization with the app's encoding config.
|
||||
func SetEncodingConfig(cfg params.EncodingConfig) {
|
||||
ethermintAminoCodec = cfg.Amino
|
||||
ethermintProtoCodec = codec.NewProtoCodec(cfg.InterfaceRegistry)
|
||||
aminoCodec = cfg.Amino
|
||||
protoCodec = codec.NewProtoCodec(cfg.InterfaceRegistry)
|
||||
}
|
||||
|
||||
// Get the EIP-712 object hash for the given SignDoc bytes by first decoding the bytes into
|
||||
@ -59,67 +65,69 @@ func GetEIP712TypedDataForMsg(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
// Attempt to decode as both Amino and Protobuf since the message format is unknown.
|
||||
// If either decode works, we can move forward with the corresponding typed data.
|
||||
typedDataAmino, errAmino := decodeAminoSignDoc(signDocBytes)
|
||||
if errAmino == nil && verifyEIP712Payload(typedDataAmino) {
|
||||
if errAmino == nil && isValidEIP712Payload(typedDataAmino) {
|
||||
return typedDataAmino, nil
|
||||
}
|
||||
typedDataProtobuf, errProtobuf := decodeProtobufSignDoc(signDocBytes)
|
||||
if errProtobuf == nil && verifyEIP712Payload(typedDataProtobuf) {
|
||||
if errProtobuf == nil && isValidEIP712Payload(typedDataProtobuf) {
|
||||
return typedDataProtobuf, nil
|
||||
}
|
||||
|
||||
return apitypes.TypedData{}, fmt.Errorf("could not decode sign doc as either Amino or Protobuf. amino: %v protobuf: %v", errAmino, errProtobuf)
|
||||
return apitypes.TypedData{}, fmt.Errorf("could not decode sign doc as either Amino or Protobuf.\n amino: %v\n protobuf: %v", errAmino, errProtobuf)
|
||||
}
|
||||
|
||||
// verifyEIP712Payload ensures that the given TypedData does not contain empty fields from
|
||||
// isValidEIP712Payload ensures that the given TypedData does not contain empty fields from
|
||||
// an improper initialization.
|
||||
func verifyEIP712Payload(typedData apitypes.TypedData) bool {
|
||||
func isValidEIP712Payload(typedData apitypes.TypedData) bool {
|
||||
return len(typedData.Message) != 0 && len(typedData.Types) != 0 && typedData.PrimaryType != "" && typedData.Domain != apitypes.TypedDataDomain{}
|
||||
}
|
||||
|
||||
// Attempt to decode the SignDoc bytes as an Amino SignDoc and return an error on failure
|
||||
// decodeAminoSignDoc attempts to decode the provided sign doc (bytes) as an Amino payload
|
||||
// and returns a signable EIP-712 TypedData object.
|
||||
func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
var aminoDoc legacytx.StdSignDoc
|
||||
|
||||
if err := ethermintAminoCodec.UnmarshalJSON(signDocBytes, &aminoDoc); err != nil {
|
||||
if err := aminoCodec.UnmarshalJSON(signDocBytes, &aminoDoc); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
// Unwrap fees
|
||||
var fees legacytx.StdFee
|
||||
if err := ethermintAminoCodec.UnmarshalJSON(aminoDoc.Fee, &fees); err != nil {
|
||||
if err := aminoCodec.UnmarshalJSON(aminoDoc.Fee, &fees); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
if len(aminoDoc.Msgs) != 1 {
|
||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of messages in SignDoc, expected 1 but got %v", len(aminoDoc.Msgs))
|
||||
// Validate payload messages
|
||||
msgs := make([]sdk.Msg, len(aminoDoc.Msgs))
|
||||
for i, jsonMsg := range aminoDoc.Msgs {
|
||||
var m sdk.Msg
|
||||
if err := aminoCodec.UnmarshalJSON(jsonMsg, &m); err != nil {
|
||||
return apitypes.TypedData{}, fmt.Errorf("failed to unmarshal sign doc message: %w", err)
|
||||
}
|
||||
msgs[i] = m
|
||||
}
|
||||
|
||||
var msg cosmosTypes.Msg
|
||||
if err := ethermintAminoCodec.UnmarshalJSON(aminoDoc.Msgs[0], &msg); err != nil {
|
||||
return apitypes.TypedData{}, fmt.Errorf("failed to unmarshal first message: %w", err)
|
||||
if err := validatePayloadMessages(msgs); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
// By default, use first address in list of signers to cover fee
|
||||
// Currently, support only one signer
|
||||
if len(msg.GetSigners()) != 1 {
|
||||
return apitypes.TypedData{}, errors.New("expected exactly one signer for message")
|
||||
}
|
||||
// Use first message for fee payer and type inference
|
||||
msg := msgs[0]
|
||||
|
||||
// By convention, the fee payer is the first address in the list of signers.
|
||||
feePayer := msg.GetSigners()[0]
|
||||
feeDelegation := &FeeDelegationOptions{
|
||||
FeePayer: feePayer,
|
||||
}
|
||||
|
||||
// Parse ChainID
|
||||
chainID, err := ethermint.ParseChainID(aminoDoc.ChainID)
|
||||
if err != nil {
|
||||
return apitypes.TypedData{}, errors.New("invalid chain ID passed as argument")
|
||||
}
|
||||
|
||||
typedData, err := WrapTxToTypedData(
|
||||
ethermintProtoCodec,
|
||||
protoCodec,
|
||||
chainID.Uint64(),
|
||||
msg,
|
||||
signDocBytes, // Amino StdSignDocBytes
|
||||
signDocBytes,
|
||||
feeDelegation,
|
||||
)
|
||||
if err != nil {
|
||||
@ -129,21 +137,19 @@ func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
return typedData, nil
|
||||
}
|
||||
|
||||
// Attempt to decode the SignDoc bytes as a Protobuf SignDoc and return an error on failure
|
||||
// decodeProtobufSignDoc attempts to decode the provided sign doc (bytes) as a Protobuf payload
|
||||
// and returns a signable EIP-712 TypedData object.
|
||||
func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
// Decode sign doc
|
||||
signDoc := &txTypes.SignDoc{}
|
||||
if err := signDoc.Unmarshal(signDocBytes); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
// Decode auth info
|
||||
authInfo := &txTypes.AuthInfo{}
|
||||
if err := authInfo.Unmarshal(signDoc.AuthInfoBytes); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
// Decode body
|
||||
body := &txTypes.TxBody{}
|
||||
if err := body.Unmarshal(signDoc.BodyBytes); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
@ -154,60 +160,60 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
return apitypes.TypedData{}, errors.New("body contains unsupported fields: TimeoutHeight, ExtensionOptions, or NonCriticalExtensionOptions")
|
||||
}
|
||||
|
||||
// Verify single message
|
||||
if len(body.Messages) != 1 {
|
||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of messages, expected 1 got %v", len(body.Messages))
|
||||
if len(authInfo.SignerInfos) != 1 {
|
||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of signer infos provided, expected 1 got %v", len(authInfo.SignerInfos))
|
||||
}
|
||||
|
||||
// Decode signer info (single signer for now)
|
||||
// Validate payload messages
|
||||
msgs := make([]sdk.Msg, len(body.Messages))
|
||||
for i, protoMsg := range body.Messages {
|
||||
var m sdk.Msg
|
||||
if err := protoCodec.UnpackAny(protoMsg, &m); err != nil {
|
||||
return apitypes.TypedData{}, fmt.Errorf("could not unpack message object with error %w", err)
|
||||
}
|
||||
msgs[i] = m
|
||||
}
|
||||
|
||||
if err := validatePayloadMessages(msgs); err != nil {
|
||||
return apitypes.TypedData{}, err
|
||||
}
|
||||
|
||||
// Use first message for fee payer and type inference
|
||||
msg := msgs[0]
|
||||
|
||||
signerInfo := authInfo.SignerInfos[0]
|
||||
|
||||
// Parse ChainID
|
||||
chainID, err := ethermint.ParseChainID(signDoc.ChainId)
|
||||
if err != nil {
|
||||
return apitypes.TypedData{}, fmt.Errorf("invalid chain ID passed as argument: %w", err)
|
||||
}
|
||||
|
||||
// Create StdFee
|
||||
stdFee := &legacytx.StdFee{
|
||||
Amount: authInfo.Fee.Amount,
|
||||
Gas: authInfo.Fee.GasLimit,
|
||||
}
|
||||
|
||||
// Parse Message (single message only)
|
||||
var msg cosmosTypes.Msg
|
||||
if err := ethermintProtoCodec.UnpackAny(body.Messages[0], &msg); err != nil {
|
||||
return apitypes.TypedData{}, fmt.Errorf("could not unpack message object with error %w", err)
|
||||
}
|
||||
|
||||
// Verify single signer (single signer for now)
|
||||
if len(msg.GetSigners()) != 1 {
|
||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of signers, expected 1 got %v", len(authInfo.SignerInfos))
|
||||
}
|
||||
|
||||
// Init fee payer
|
||||
feePayer := msg.GetSigners()[0]
|
||||
feeDelegation := &FeeDelegationOptions{
|
||||
FeePayer: feePayer,
|
||||
}
|
||||
|
||||
// Get tip
|
||||
tip := authInfo.Tip
|
||||
|
||||
// Create Legacy SignBytes (expected type for WrapTxToTypedData)
|
||||
// WrapTxToTypedData expects the payload as an Amino Sign Doc
|
||||
signBytes := legacytx.StdSignBytes(
|
||||
signDoc.ChainId,
|
||||
signDoc.AccountNumber,
|
||||
signerInfo.Sequence,
|
||||
body.TimeoutHeight,
|
||||
*stdFee,
|
||||
[]cosmosTypes.Msg{msg},
|
||||
msgs,
|
||||
body.Memo,
|
||||
tip,
|
||||
)
|
||||
|
||||
typedData, err := WrapTxToTypedData(
|
||||
ethermintProtoCodec,
|
||||
protoCodec,
|
||||
chainID.Uint64(),
|
||||
msg,
|
||||
signBytes,
|
||||
@ -219,3 +225,61 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||
|
||||
return typedData, nil
|
||||
}
|
||||
|
||||
// validatePayloadMessages ensures that the transaction messages can be represented in an EIP-712
|
||||
// encoding by checking that messages exist, are of the same type, and share a single signer.
|
||||
func validatePayloadMessages(msgs []sdk.Msg) error {
|
||||
if len(msgs) == 0 {
|
||||
return errors.New("unable to build EIP-712 payload: transaction does contain any messages")
|
||||
}
|
||||
|
||||
var msgType string
|
||||
var msgSigner sdk.AccAddress
|
||||
|
||||
for i, m := range msgs {
|
||||
t, err := getMsgType(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(m.GetSigners()) != 1 {
|
||||
return errors.New("unable to build EIP-712 payload: expect exactly 1 signer")
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
msgType = t
|
||||
msgSigner = m.GetSigners()[0]
|
||||
continue
|
||||
}
|
||||
|
||||
if t != msgType {
|
||||
return errors.New("unable to build EIP-712 payload: different types of messages detected")
|
||||
}
|
||||
|
||||
if !msgSigner.Equals(m.GetSigners()[0]) {
|
||||
return errors.New("unable to build EIP-712 payload: multiple signers detected")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMsgType returns the message type prefix for the given Cosmos SDK Msg
|
||||
func getMsgType(msg sdk.Msg) (string, error) {
|
||||
jsonBytes, err := aminoCodec.MarshalJSON(msg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var jsonMsg aminoMessage
|
||||
if err := json.Unmarshal(jsonBytes, &jsonMsg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Verify Type was successfully filled in
|
||||
if jsonMsg.Type == "" {
|
||||
return "", errors.New("could not decode message: type is missing")
|
||||
}
|
||||
|
||||
return jsonMsg.Type, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user