diff --git a/x/auth/signing/amino/amino.go b/x/auth/signing/amino/amino.go new file mode 100644 index 000000000000..5788fca75494 --- /dev/null +++ b/x/auth/signing/amino/amino.go @@ -0,0 +1,47 @@ +package amino + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// LegacyAminoJSONHandler is a SignModeHandler that handles SIGN_MODE_LEGACY_AMINO_JSON +type LegacyAminoJSONHandler struct{} + +var _ signing.SignModeHandler = LegacyAminoJSONHandler{} + +// DefaultMode implements SignModeHandler.DefaultMode +func (h LegacyAminoJSONHandler) DefaultMode() types.SignMode { + return types.SignMode_SIGN_MODE_LEGACY_AMINO_JSON +} + +// Modes implements SignModeHandler.Modes +func (LegacyAminoJSONHandler) Modes() []types.SignMode { + return []types.SignMode{types.SignMode_SIGN_MODE_LEGACY_AMINO_JSON} +} + +// DefaultMode implements SignModeHandler.GetSignBytes +func (LegacyAminoJSONHandler) GetSignBytes(mode types.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + if mode != types.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + return nil, fmt.Errorf("expected %s, got %s", types.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, mode) + } + + feeTx, ok := tx.(ante.FeeTx) + if !ok { + return nil, fmt.Errorf("expected FeeTx, got %T", tx) + } + + memoTx, ok := tx.(ante.TxWithMemo) + if !ok { + return nil, fmt.Errorf("expected TxWithMemo, got %T", tx) + } + + return authtypes.StdSignBytes( + data.ChainID, data.AccountNumber, data.AccountSequence, authtypes.StdFee{Amount: feeTx.GetFee(), Gas: feeTx.GetGas()}, tx.GetMsgs(), memoTx.GetMemo(), // nolint:staticcheck // SA1019: authtypes.StdFee is deprecated, will be removed once proto migration is completed + ), nil +} diff --git a/x/auth/signing/amino/amino_test.go b/x/auth/signing/amino/amino_test.go new file mode 100644 index 000000000000..b72eaaffb576 --- /dev/null +++ b/x/auth/signing/amino/amino_test.go @@ -0,0 +1,78 @@ +package amino_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto/secp256k1" + + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing/amino" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + fee := auth.StdFee{ + Amount: coins, + Gas: 10000, + } + memo := "foo" + msgs := []sdk.Msg{ + &bank.MsgSend{ + FromAddress: addr1, + ToAddress: addr2, + Amount: coins, + }, + } + + tx := auth.StdTx{ + Msgs: msgs, + Fee: fee, + Signatures: nil, + Memo: memo, + } + + var ( + chainId = "test-chain" + accNum uint64 = 7 + seqNum uint64 = 7 + ) + + handler := amino.LegacyAminoJSONHandler{} + signingData := signing.SignerData{ + ChainID: chainId, + AccountNumber: accNum, + AccountSequence: seqNum, + } + signBz, err := handler.GetSignBytes(txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, signingData, tx) + require.NoError(t, err) + + expectedSignBz := auth.StdSignBytes(chainId, accNum, seqNum, fee, msgs, memo) + + require.Equal(t, expectedSignBz, signBz) + + // expect error with wrong sign mode + _, err = handler.GetSignBytes(txtypes.SignMode_SIGN_MODE_DIRECT, signingData, tx) + require.Error(t, err) +} + +func TestLegacyAminoJSONHandler_DefaultMode(t *testing.T) { + handler := amino.LegacyAminoJSONHandler{} + require.Equal(t, txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, handler.DefaultMode()) +} + +func TestLegacyAminoJSONHandler_Modes(t *testing.T) { + handler := amino.LegacyAminoJSONHandler{} + require.Equal(t, []txtypes.SignMode{txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON}, handler.Modes()) +} diff --git a/x/auth/signing/handler_map.go b/x/auth/signing/handler_map.go new file mode 100644 index 000000000000..90421db94304 --- /dev/null +++ b/x/auth/signing/handler_map.go @@ -0,0 +1,59 @@ +package signing + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types/tx" +) + +// SignModeHandlerMap is SignModeHandler that aggregates multiple SignModeHandler's into +// a single handler +type SignModeHandlerMap struct { + defaultMode types.SignMode + modes []types.SignMode + signModeHandlers map[types.SignMode]SignModeHandler +} + +var _ SignModeHandler = SignModeHandlerMap{} + +// NewSignModeHandlerMap returns a new SignModeHandlerMap with the provided defaultMode and handlers +func NewSignModeHandlerMap(defaultMode types.SignMode, handlers []SignModeHandler) SignModeHandlerMap { + handlerMap := make(map[types.SignMode]SignModeHandler) + var modes []types.SignMode + + for _, h := range handlers { + for _, m := range h.Modes() { + if _, have := handlerMap[m]; have { + panic(fmt.Errorf("duplicate sign mode handler for mode %s", m)) + } + handlerMap[m] = h + modes = append(modes, m) + } + } + + return SignModeHandlerMap{ + defaultMode: defaultMode, + modes: modes, + signModeHandlers: handlerMap, + } +} + +// DefaultMode implements SignModeHandler.DefaultMode +func (h SignModeHandlerMap) DefaultMode() types.SignMode { + return h.defaultMode +} + +// Modes implements SignModeHandler.Modes +func (h SignModeHandlerMap) Modes() []types.SignMode { + return h.modes +} + +// DefaultMode implements SignModeHandler.GetSignBytes +func (h SignModeHandlerMap) GetSignBytes(mode types.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) { + handler, found := h.signModeHandlers[mode] + if !found { + return nil, fmt.Errorf("can't verify sign mode %s", mode.String()) + } + return handler.GetSignBytes(mode, data, tx) +} diff --git a/x/auth/signing/handler_map_test.go b/x/auth/signing/handler_map_test.go new file mode 100644 index 000000000000..0133c38a1df6 --- /dev/null +++ b/x/auth/signing/handler_map_test.go @@ -0,0 +1,92 @@ +package signing_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto/secp256k1" + + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing/amino" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +func MakeTestHandlerMap() signing.SignModeHandler { + return signing.NewSignModeHandlerMap( + txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + []signing.SignModeHandler{ + amino.LegacyAminoJSONHandler{}, + }, + ) +} + +func TestHandlerMap_GetSignBytes(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + fee := authtypes.StdFee{ + Amount: coins, + Gas: 10000, + } + memo := "foo" + msgs := []sdk.Msg{ + &bank.MsgSend{ + FromAddress: addr1, + ToAddress: addr2, + Amount: coins, + }, + } + + tx := authtypes.StdTx{ + Msgs: msgs, + Fee: fee, + Signatures: nil, + Memo: memo, + } + + var ( + chainId = "test-chain" + accNum uint64 = 7 + seqNum uint64 = 7 + ) + + handler := MakeTestHandlerMap() + aminoJSONHandler := amino.LegacyAminoJSONHandler{} + + signingData := signing.SignerData{ + ChainID: chainId, + AccountNumber: accNum, + AccountSequence: seqNum, + } + signBz, err := handler.GetSignBytes(txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, signingData, tx) + require.NoError(t, err) + + expectedSignBz, err := aminoJSONHandler.GetSignBytes(txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, signingData, tx) + require.NoError(t, err) + + require.Equal(t, expectedSignBz, signBz) + + // expect error with wrong sign mode + _, err = aminoJSONHandler.GetSignBytes(txtypes.SignMode_SIGN_MODE_DIRECT, signingData, tx) + require.Error(t, err) +} + +func TestHandlerMap_DefaultMode(t *testing.T) { + handler := MakeTestHandlerMap() + require.Equal(t, txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, handler.DefaultMode()) +} + +func TestHandlerMap_Modes(t *testing.T) { + handler := MakeTestHandlerMap() + modes := handler.Modes() + require.Contains(t, modes, txtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + require.Len(t, modes, 1) +} diff --git a/x/auth/signing/sign_mode_handler.go b/x/auth/signing/sign_mode_handler.go new file mode 100644 index 000000000000..2ad40e31a2ee --- /dev/null +++ b/x/auth/signing/sign_mode_handler.go @@ -0,0 +1,35 @@ +package signing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" +) + +// SignModeHandler defines a interface to be implemented by types which will handle +// SignMode's by generating sign bytes from a Tx and SignerData +type SignModeHandler interface { + // DefaultMode is the default mode that is to be used with this handler if no + // other mode is specified. This can be useful for testing and CLI usage + DefaultMode() txtypes.SignMode + + // Modes is the list of modes supporting by this handler + Modes() []txtypes.SignMode + + // GetSignBytes returns the sign bytes for the provided SignMode, SignerData and Tx, + // or an error + GetSignBytes(mode txtypes.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) +} + +// SignerData is the specific information needed to sign a transaction that generally +// isn't included in the transaction body itself +type SignerData struct { + // ChainID is the chain that this transaction is targeted + ChainID string + + // AccountNumber is the account number of the signer + AccountNumber uint64 + + // AccountSequence is the account sequence number of the signer that is used + // for replay protection + AccountSequence uint64 +}