Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic sign mode infrastructure #6371

Merged
merged 12 commits into from
Jun 9, 2020
47 changes: 47 additions & 0 deletions x/auth/signing/amino/amino.go
Original file line number Diff line number Diff line change
@@ -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 {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
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
}
78 changes: 78 additions & 0 deletions x/auth/signing/amino/amino_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
59 changes: 59 additions & 0 deletions x/auth/signing/handler_map.go
Original file line number Diff line number Diff line change
@@ -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 {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
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)
}
92 changes: 92 additions & 0 deletions x/auth/signing/handler_map_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
35 changes: 35 additions & 0 deletions x/auth/signing/sign_mode_handler.go
Original file line number Diff line number Diff line change
@@ -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)
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
}