From 329d6098a9efdc41898bec8c1bcb51686d331f1a Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 20 May 2020 13:30:56 +0200 Subject: [PATCH 01/13] add some changes --- x/auth/client/cli/batch.go | 51 +++++++++++++++++++++++++++++ x/auth/client/cli/batch_test.go | 35 ++++++++++++++++++++ x/auth/client/cli/testdata/txs.json | 2 ++ x/auth/client/utils/tx.go | 5 +++ x/auth/client/utils/tx_test.go | 32 +++++++++++++++--- 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 x/auth/client/cli/batch.go create mode 100644 x/auth/client/cli/batch_test.go create mode 100644 x/auth/client/cli/testdata/txs.json diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go new file mode 100644 index 0000000000..b53809910b --- /dev/null +++ b/x/auth/client/cli/batch.go @@ -0,0 +1,51 @@ +package cli + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" +) + +func GetBatchSignCommand(codec *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "sign-batch [in-file] [out-file]", + Short: "Sign many standard transactions generated offline", + Long: `Sign a list of transactions created with the --generate-only flag. +It will read StdSignDoc JSONs from [in-file], one transaction per line, and +produce a file of JSON encoded StdSignatures, one per line. + +This command is intended to work offline for security purposes.`, + PreRun: preSignCmd, + RunE: makeBatchSignCmd(codec), + Args: cobra.ExactArgs(2), + } + + return flags.PostCommands(cmd)[0] +} + +func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + _, err := keyring.New(sdk.KeyringServiceName(), + viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) + if err != nil { + return err + } + + txsToSign, err := client.ReadStdTxsFromFile(cdc, args[0]) + if err != nil { + return errors.Wrap(err, "error extracting txs from file") + } + + fmt.Printf("%v\n", txsToSign) + + return nil + } +} diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go new file mode 100644 index 0000000000..8ad50a8d69 --- /dev/null +++ b/x/auth/client/cli/batch_test.go @@ -0,0 +1,35 @@ +package cli + +import ( + "path/filepath" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/tests" + + "github.com/stretchr/testify/require" + "github.com/tendermint/go-amino" +) + +func TestGetBatchSignCommand(t *testing.T) { + cdc := amino.NewCodec() + cmd := GetBatchSignCommand(cdc) + + tempDir, cleanFunc := tests.NewTestCaseDir(t) + t.Cleanup(cleanFunc) + + viper.Set(flags.FlagHome, tempDir) + viper.Set(flags.FlagKeyringBackend, keyring.BackendTest) + + cmd.SetArgs([]string{ + "./testdata/txs.json", + filepath.Join(tempDir, "outputfile"), + }) + + err := cmd.Execute() + require.NoError(t, err) +} diff --git a/x/auth/client/cli/testdata/txs.json b/x/auth/client/cli/testdata/txs.json new file mode 100644 index 0000000000..e266d8aa1c --- /dev/null +++ b/x/auth/client/cli/testdata/txs.json @@ -0,0 +1,2 @@ +{"type":"cosmos-sdk/StdTx","value":{"msg":[{"type":"cosmos-sdk/MsgSend","value":{"from_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","to_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","amount":[{"denom":"stake","amount":"10000"}]}}],"fee":{"amount":[],"gas":"200000"},"signatures":null,"memo":""}} +{"type":"cosmos-sdk/StdTx","value":{"msg":[{"type":"cosmos-sdk/MsgSend","value":{"from_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","to_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","amount":[{"denom":"stake","amount":"20000"}]}}],"fee":{"amount":[],"gas":"200000"},"signatures":null,"memo":""}} \ No newline at end of file diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index 0a5646eb7b..de3a3c9511 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -248,6 +248,11 @@ func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx return } +// ReadStdTxsFromFile reads a list of transactions from a file +func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdTx, error) { + return nil, nil +} + func populateAccountFromState( txBldr authtypes.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress, ) (authtypes.TxBuilder, error) { diff --git a/x/auth/client/utils/tx_test.go b/x/auth/client/utils/tx_test.go index 3ae79f7bdc..cddfcc21f5 100644 --- a/x/auth/client/utils/tx_test.go +++ b/x/auth/client/utils/tx_test.go @@ -7,7 +7,9 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" @@ -102,8 +104,9 @@ func TestReadStdTxFromFile(t *testing.T) { // Write it to the file encodedTx, _ := cdc.MarshalJSON(stdTx) - jsonTxFile := writeToNewTempFile(t, string(encodedTx)) - defer os.Remove(jsonTxFile.Name()) + dir, cleanup := tests.NewTestCaseDir(t) + jsonTxFile := writeToNewTempFile(t, dir, string(encodedTx)) + t.Cleanup(cleanup) // Read it back decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) @@ -111,6 +114,27 @@ func TestReadStdTxFromFile(t *testing.T) { require.Equal(t, decodedTx.Memo, "foomemo") } +func TestReadStdTxsFromFile(t *testing.T) { + //cdc := codec.New() + + var txs []authtypes.StdTx + + // Build some Transactions + for i := 0; i < 2; i++ { + txs = append( + txs, + authtypes.NewStdTx([]sdk.Msg{}, authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}), []authtypes.StdSignature{}, "foomemo"), + ) + } + + _, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + + //decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) + //require.NoError(t, err) + //require.Equal(t, decodedTx.Memo, "foomemo") +} + func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) { msgs := []sdk.Msg{sdk.NewTestMsg(addr)} tx := authtypes.NewStdTx(msgs, authtypes.StdFee{}, []authtypes.StdSignature{}, "") @@ -122,8 +146,8 @@ func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) require.Equal(t, defaultEncoderBytes, encoderBytes) } -func writeToNewTempFile(t *testing.T, data string) *os.File { - fp, err := ioutil.TempFile(os.TempDir(), "client_tx_test") +func writeToNewTempFile(t *testing.T, filepath string, data string) *os.File { + fp, err := ioutil.TempFile(filepath, "client_tx_test") require.NoError(t, err) _, err = fp.WriteString(data) From 60612564185ff09586c6ace2090750d298e9a875 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 20 May 2020 13:31:51 +0200 Subject: [PATCH 02/13] add batch test --- x/auth/client/utils/tx.go | 28 +++++++++++++++++++++- x/auth/client/utils/tx_test.go | 44 ++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index de3a3c9511..e4d16f7403 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -250,7 +250,33 @@ func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx // ReadStdTxsFromFile reads a list of transactions from a file func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdTx, error) { - return nil, nil + bz, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + lines := strings.Split(string(bz), "\n") + + return buildTxsFromJsonLines(cdc, lines) +} + +func buildTxsFromJsonLines(cdc *codec.Codec, jsonTxs []string) ([]authtypes.StdTx, error) { + var txs []authtypes.StdTx + + for _, jsonTx := range jsonTxs { + if len(jsonTx) == 0 { + break + } + + var tx authtypes.StdTx + err := cdc.UnmarshalJSON([]byte(jsonTx), &tx) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + + return txs, nil } func populateAccountFromState( diff --git a/x/auth/client/utils/tx_test.go b/x/auth/client/utils/tx_test.go index cddfcc21f5..46c8c9afff 100644 --- a/x/auth/client/utils/tx_test.go +++ b/x/auth/client/utils/tx_test.go @@ -3,6 +3,7 @@ package utils import ( "encoding/json" "errors" + "fmt" "io/ioutil" "os" "testing" @@ -103,19 +104,23 @@ func TestReadStdTxFromFile(t *testing.T) { stdTx := authtypes.NewStdTx([]sdk.Msg{}, fee, []authtypes.StdSignature{}, "foomemo") // Write it to the file - encodedTx, _ := cdc.MarshalJSON(stdTx) dir, cleanup := tests.NewTestCaseDir(t) - jsonTxFile := writeToNewTempFile(t, dir, string(encodedTx)) t.Cleanup(cleanup) + tempFile := newTempFile(t, dir) + + encodedTx, err := cdc.MarshalJSON(stdTx) + require.NoError(t, err) + writeToFile(t, tempFile, string(encodedTx)) // Read it back - decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) + decodedTx, err := ReadStdTxFromFile(cdc, tempFile.Name()) require.NoError(t, err) require.Equal(t, decodedTx.Memo, "foomemo") } func TestReadStdTxsFromFile(t *testing.T) { - //cdc := codec.New() + cdc := codec.New() + sdk.RegisterCodec(cdc) var txs []authtypes.StdTx @@ -123,16 +128,27 @@ func TestReadStdTxsFromFile(t *testing.T) { for i := 0; i < 2; i++ { txs = append( txs, - authtypes.NewStdTx([]sdk.Msg{}, authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}), []authtypes.StdSignature{}, "foomemo"), + authtypes.NewStdTx([]sdk.Msg{}, authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}), []authtypes.StdSignature{}, fmt.Sprintf("foomemo%d", i)), ) } - _, cleanup := tests.NewTestCaseDir(t) + tempDir, cleanup := tests.NewTestCaseDir(t) t.Cleanup(cleanup) + tempFile := newTempFile(t, tempDir) - //decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name()) - //require.NoError(t, err) - //require.Equal(t, decodedTx.Memo, "foomemo") + // Write it to the file + for _, tx := range txs { + encodedTx, err := cdc.MarshalJSON(&tx) + require.NoError(t, err) + writeToFile(t, tempFile, string(encodedTx)) + } + + txsFromFile, err := ReadStdTxsFromFile(cdc, tempFile.Name()) + require.NoError(t, err) + + for i, txFromFile := range txsFromFile { + require.Equal(t, txFromFile.Memo, txs[i].Memo) + } } func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) { @@ -146,16 +162,18 @@ func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) require.Equal(t, defaultEncoderBytes, encoderBytes) } -func writeToNewTempFile(t *testing.T, filepath string, data string) *os.File { +func newTempFile(t *testing.T, filepath string) *os.File { fp, err := ioutil.TempFile(filepath, "client_tx_test") require.NoError(t, err) - _, err = fp.WriteString(data) - require.NoError(t, err) - return fp } +func writeToFile(t *testing.T, fp *os.File, data string) { + _, err := fp.WriteString(fmt.Sprintf("%s\n", data)) + require.NoError(t, err) +} + func makeCodec() *codec.Codec { var cdc = codec.New() sdk.RegisterCodec(cdc) From fa2b940816b90135e28f7492f78b2d4cad6c5037 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 20 May 2020 14:59:16 +0200 Subject: [PATCH 03/13] make tests pass --- x/auth/client/cli/batch.go | 11 ++++++----- x/auth/client/cli/batch_test.go | 3 --- x/auth/client/utils/tx.go | 1 + 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index b53809910b..16c30c624a 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,10 +3,12 @@ package cli import ( "fmt" + "github.com/cosmos/cosmos-sdk/client/keys" + + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/pkg/errors" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -33,13 +35,12 @@ This command is intended to work offline for security purposes.`, func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - _, err := keyring.New(sdk.KeyringServiceName(), - viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), cmd.InOrStdin()) + _, err := keys.NewKeyBaseFromDir(viper.GetString(flags.FlagHome)) if err != nil { return err } - txsToSign, err := client.ReadStdTxsFromFile(cdc, args[0]) + txsToSign, err := utils.ReadStdTxsFromFile(cdc, args[0]) if err != nil { return errors.Wrap(err, "error extracting txs from file") } diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 8ad50a8d69..beaf964367 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -4,8 +4,6 @@ import ( "path/filepath" "testing" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/viper" @@ -23,7 +21,6 @@ func TestGetBatchSignCommand(t *testing.T) { t.Cleanup(cleanFunc) viper.Set(flags.FlagHome, tempDir) - viper.Set(flags.FlagKeyringBackend, keyring.BackendTest) cmd.SetArgs([]string{ "./testdata/txs.json", diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index e4d16f7403..17f5e4ba39 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "github.com/pkg/errors" "github.com/spf13/viper" From 0c4223cce53697dd05b10e28efed854dec4ac3bd Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 20 May 2020 18:47:38 +0200 Subject: [PATCH 04/13] refactor data --- x/auth/client/cli/batch.go | 6 ++---- x/auth/client/cli/batch_test.go | 7 +++---- x/auth/client/utils/testdata/txs | 2 ++ x/auth/client/utils/tx.go | 8 ++++---- x/auth/client/utils/tx_test.go | 28 +++------------------------- 5 files changed, 14 insertions(+), 37 deletions(-) create mode 100644 x/auth/client/utils/testdata/txs diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 16c30c624a..08c4c2d472 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,17 +3,15 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/client/keys" - - "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" ) func GetBatchSignCommand(codec *codec.Codec) *cobra.Command { diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index beaf964367..78afc9f0a0 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -4,13 +4,12 @@ import ( "path/filepath" "testing" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/tests" - "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/tests" ) func TestGetBatchSignCommand(t *testing.T) { diff --git a/x/auth/client/utils/testdata/txs b/x/auth/client/utils/testdata/txs new file mode 100644 index 0000000000..e332b0e5c1 --- /dev/null +++ b/x/auth/client/utils/testdata/txs @@ -0,0 +1,2 @@ +{"account_number":"24263","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"100000","denom":"uatom"}],"gas":"333000"},"memo":" ","msgs":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"1300000000000","denom":"uatom"},"delegator_address":"cosmos176m2p8l3fps3dal7h8gf9jvrv98tu3rqfdht86","validator_address":"cosmosvaloper1qs8tnw2t8l6amtzvdemnnsq9dzk0ag0z52uzay"}}],"sequence":"37"} +{"account_number":"24263","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"100000","denom":"uatom"}],"gas":"333000"},"memo":" ","msgs":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"1300000000000","denom":"uatom"},"delegator_address":"cosmos176m2p8l3fps3dal7h8gf9jvrv98tu3rqfdht86","validator_address":"cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7t37e7ps9l0kpv"}}],"sequence":"38"} diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index 17f5e4ba39..366aff0e88 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -250,7 +250,7 @@ func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx } // ReadStdTxsFromFile reads a list of transactions from a file -func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdTx, error) { +func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdSignDoc, error) { bz, err := ioutil.ReadFile(filename) if err != nil { return nil, err @@ -261,15 +261,15 @@ func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdTx, e return buildTxsFromJsonLines(cdc, lines) } -func buildTxsFromJsonLines(cdc *codec.Codec, jsonTxs []string) ([]authtypes.StdTx, error) { - var txs []authtypes.StdTx +func buildTxsFromJsonLines(cdc *codec.Codec, jsonTxs []string) ([]authtypes.StdSignDoc, error) { + var txs []authtypes.StdSignDoc for _, jsonTx := range jsonTxs { if len(jsonTx) == 0 { break } - var tx authtypes.StdTx + var tx authtypes.StdSignDoc err := cdc.UnmarshalJSON([]byte(jsonTx), &tx) if err != nil { return nil, err diff --git a/x/auth/client/utils/tx_test.go b/x/auth/client/utils/tx_test.go index 46c8c9afff..0e13d72cd3 100644 --- a/x/auth/client/utils/tx_test.go +++ b/x/auth/client/utils/tx_test.go @@ -122,33 +122,11 @@ func TestReadStdTxsFromFile(t *testing.T) { cdc := codec.New() sdk.RegisterCodec(cdc) - var txs []authtypes.StdTx - - // Build some Transactions - for i := 0; i < 2; i++ { - txs = append( - txs, - authtypes.NewStdTx([]sdk.Msg{}, authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)}), []authtypes.StdSignature{}, fmt.Sprintf("foomemo%d", i)), - ) - } - - tempDir, cleanup := tests.NewTestCaseDir(t) - t.Cleanup(cleanup) - tempFile := newTempFile(t, tempDir) - - // Write it to the file - for _, tx := range txs { - encodedTx, err := cdc.MarshalJSON(&tx) - require.NoError(t, err) - writeToFile(t, tempFile, string(encodedTx)) - } - - txsFromFile, err := ReadStdTxsFromFile(cdc, tempFile.Name()) + txsFromFile, err := ReadStdTxsFromFile(cdc, "./testdata/txs") require.NoError(t, err) - for i, txFromFile := range txsFromFile { - require.Equal(t, txFromFile.Memo, txs[i].Memo) - } + require.Equal(t, uint64(37), txsFromFile[0].Sequence) + require.Equal(t, uint64(38), txsFromFile[1].Sequence) } func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) { From 3623a6a476ee034819c279395c1f5753df947bdb Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 20 May 2020 20:50:21 +0200 Subject: [PATCH 05/13] create multi sign key --- x/auth/client/cli/batch.go | 13 +++- x/auth/client/cli/batch_test.go | 96 ++++++++++++++++++++++++++++- x/auth/client/cli/testdata/txs.json | 4 +- 3 files changed, 108 insertions(+), 5 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 08c4c2d472..41d7db3f8c 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,6 +3,8 @@ package cli import ( "fmt" + keybase "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/pkg/errors" "github.com/spf13/cobra" @@ -33,11 +35,20 @@ This command is intended to work offline for security purposes.`, func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - _, err := keys.NewKeyBaseFromDir(viper.GetString(flags.FlagHome)) + kb, err := keys.NewKeyBaseFromDir(viper.GetString(flags.FlagHome)) if err != nil { return err } + multisigInfo, err := kb.Get(viper.GetString(flags.FlagFrom)) + if err != nil { + return err + } + + if multisigInfo.GetType() != keybase.TypeMulti { + return fmt.Errorf("%q must be of type %s: %s", args[1], keybase.TypeMulti, multisigInfo.GetType()) + } + txsToSign, err := utils.ReadStdTxsFromFile(cdc, args[0]) if err != nil { return errors.Wrap(err, "error extracting txs from file") diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 78afc9f0a0..24f967a6bb 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -1,15 +1,21 @@ package cli import ( + "fmt" "path/filepath" "testing" + "github.com/tendermint/tendermint/crypto/multisig" + "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + keys2 "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/tests" + "github.com/tendermint/tendermint/crypto" ) func TestGetBatchSignCommand(t *testing.T) { @@ -19,13 +25,99 @@ func TestGetBatchSignCommand(t *testing.T) { tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) - viper.Set(flags.FlagHome, tempDir) + _, err := createKeybaseWithMultisigAccount(tempDir) + require.NoError(t, err) + viper.Set(flags.FlagHome, tempDir) + viper.Set(flags.FlagFrom, "multi") cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), }) - err := cmd.Execute() + err = cmd.Execute() require.NoError(t, err) } + +func createKeybaseWithMultisigAccount(dir string) ([]crypto.PubKey, error) { + kb, err := keys.NewKeyBaseFromDir(dir) + if err != nil { + return nil, err + } + + var pubKeys []crypto.PubKey + for i := 0; i < 4; i++ { + mnemonic, _, _ := kb.CreateMnemonic( + fmt.Sprintf("acc%d", i), + keys2.English, + "", + keys2.Secp256k1, + ) + + pubKeys = append(pubKeys, mnemonic.GetPubKey()) + } + + pk := multisig.NewPubKeyMultisigThreshold(2, pubKeys) + if _, err := kb.CreateMulti("multi", pk); err != nil { + return nil, err + } + + return pubKeys, nil +} + +func TestGetBatchSignCommand_Error(t *testing.T) { + tests := []struct { + name string + errorContains string + keybasePrep func() (cleanFunc func(), tempDir string) + }{ + { + name: "not multisign", + errorContains: "must be of type multi", + keybasePrep: func() (func(), string) { + tempDir, cleanFunc := tests.NewTestCaseDir(t) + t.Cleanup(cleanFunc) + + kb, err := keys.NewKeyBaseFromDir(tempDir) + require.NoError(t, err) + + _, _, err = kb.CreateMnemonic("acc1", keys2.English, "", keys2.Secp256k1) + require.NoError(t, err) + + return cleanFunc, tempDir + }, + }, + { + name: "non existing key", + errorContains: "Key acc1 not found", + keybasePrep: func() (func(), string) { + tempDir, cleanFunc := tests.NewTestCaseDir(t) + t.Cleanup(cleanFunc) + + return cleanFunc, tempDir + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + cdc := amino.NewCodec() + cmd := GetBatchSignCommand(cdc) + + cleanFunc, tempDir := tt.keybasePrep() + defer cleanFunc() + + viper.Set(flags.FlagHome, tempDir) + viper.Set(flags.FlagFrom, "acc1") + cmd.SetArgs([]string{ + "./testdata/txs.json", + filepath.Join(tempDir, "outputfile"), + }) + + err := cmd.Execute() + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorContains) + }) + } +} diff --git a/x/auth/client/cli/testdata/txs.json b/x/auth/client/cli/testdata/txs.json index e266d8aa1c..e332b0e5c1 100644 --- a/x/auth/client/cli/testdata/txs.json +++ b/x/auth/client/cli/testdata/txs.json @@ -1,2 +1,2 @@ -{"type":"cosmos-sdk/StdTx","value":{"msg":[{"type":"cosmos-sdk/MsgSend","value":{"from_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","to_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","amount":[{"denom":"stake","amount":"10000"}]}}],"fee":{"amount":[],"gas":"200000"},"signatures":null,"memo":""}} -{"type":"cosmos-sdk/StdTx","value":{"msg":[{"type":"cosmos-sdk/MsgSend","value":{"from_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","to_address":"cosmos12a6p5vj3laer43y0eg0wf3fqya3un2879xauah","amount":[{"denom":"stake","amount":"20000"}]}}],"fee":{"amount":[],"gas":"200000"},"signatures":null,"memo":""}} \ No newline at end of file +{"account_number":"24263","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"100000","denom":"uatom"}],"gas":"333000"},"memo":" ","msgs":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"1300000000000","denom":"uatom"},"delegator_address":"cosmos176m2p8l3fps3dal7h8gf9jvrv98tu3rqfdht86","validator_address":"cosmosvaloper1qs8tnw2t8l6amtzvdemnnsq9dzk0ag0z52uzay"}}],"sequence":"37"} +{"account_number":"24263","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"100000","denom":"uatom"}],"gas":"333000"},"memo":" ","msgs":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"1300000000000","denom":"uatom"},"delegator_address":"cosmos176m2p8l3fps3dal7h8gf9jvrv98tu3rqfdht86","validator_address":"cosmosvaloper13sduv92y3xdhy3rpmhakrc3v7t37e7ps9l0kpv"}}],"sequence":"38"} From 6b96ebf8a51fa717b9ac6af0d0ae5aa10aafd393 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 21 May 2020 12:04:19 +0200 Subject: [PATCH 06/13] add not existing check --- x/auth/client/cli/batch.go | 22 ++++++++++++-------- x/auth/client/cli/batch_test.go | 37 +++++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 41d7db3f8c..fe753a258c 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,8 +3,6 @@ package cli import ( "fmt" - keybase "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/pkg/errors" "github.com/spf13/cobra" @@ -30,6 +28,11 @@ This command is intended to work offline for security purposes.`, Args: cobra.ExactArgs(2), } + cmd.Flags().String( + flagMultisig, "", + "Address of the multisig account on behalf of which the transaction shall be signed", + ) + return flags.PostCommands(cmd)[0] } @@ -40,21 +43,22 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return err } - multisigInfo, err := kb.Get(viper.GetString(flags.FlagFrom)) - if err != nil { - return err + multisigAddrStr := viper.GetString(flagMultisig) + if multisigAddrStr == "" { + return fmt.Errorf("only multisig signature is supported, provide it with %s flag", flagMultisig) } - if multisigInfo.GetType() != keybase.TypeMulti { - return fmt.Errorf("%q must be of type %s: %s", args[1], keybase.TypeMulti, multisigInfo.GetType()) + _, err = kb.Get(viper.GetString(flags.FlagFrom)) + if err != nil { + return errors.Wrap(err, "key not found") } - txsToSign, err := utils.ReadStdTxsFromFile(cdc, args[0]) + _, err = utils.ReadStdTxsFromFile(cdc, args[0]) if err != nil { return errors.Wrap(err, "error extracting txs from file") } - fmt.Printf("%v\n", txsToSign) + //fmt.Printf("%v\n", txsToSign) return nil } diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 24f967a6bb..a2b597e0f9 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -25,11 +25,16 @@ func TestGetBatchSignCommand(t *testing.T) { tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) - _, err := createKeybaseWithMultisigAccount(tempDir) + kb, _, err := createKeybaseWithMultisigAccount(tempDir) require.NoError(t, err) + multiInfo, err := kb.Get("multi") + require.NoError(t, err) + + viper.Reset() viper.Set(flags.FlagHome, tempDir) - viper.Set(flags.FlagFrom, "multi") + viper.Set(flags.FlagFrom, "acc1") + viper.Set(flagMultisig, multiInfo.GetName()) cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), @@ -39,10 +44,10 @@ func TestGetBatchSignCommand(t *testing.T) { require.NoError(t, err) } -func createKeybaseWithMultisigAccount(dir string) ([]crypto.PubKey, error) { +func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKey, error) { kb, err := keys.NewKeyBaseFromDir(dir) if err != nil { - return nil, err + return nil, nil, err } var pubKeys []crypto.PubKey @@ -59,10 +64,10 @@ func createKeybaseWithMultisigAccount(dir string) ([]crypto.PubKey, error) { pk := multisig.NewPubKeyMultisigThreshold(2, pubKeys) if _, err := kb.CreateMulti("multi", pk); err != nil { - return nil, err + return nil, nil, err } - return pubKeys, nil + return kb, pubKeys, nil } func TestGetBatchSignCommand_Error(t *testing.T) { @@ -70,10 +75,11 @@ func TestGetBatchSignCommand_Error(t *testing.T) { name string errorContains string keybasePrep func() (cleanFunc func(), tempDir string) + providedFlags map[string]interface{} }{ { - name: "not multisign", - errorContains: "must be of type multi", + name: "flag multisign not provided", + errorContains: "only multisig signature is supported", keybasePrep: func() (func(), string) { tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) @@ -88,14 +94,18 @@ func TestGetBatchSignCommand_Error(t *testing.T) { }, }, { - name: "non existing key", - errorContains: "Key acc1 not found", + name: "not existing key", + errorContains: "key not found: Key not-existing not found", keybasePrep: func() (func(), string) { tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) return cleanFunc, tempDir }, + providedFlags: map[string]interface{}{ + flagMultisig: "fasdfasdf", + flags.FlagFrom: "not-existing", + }, }, } @@ -108,8 +118,13 @@ func TestGetBatchSignCommand_Error(t *testing.T) { cleanFunc, tempDir := tt.keybasePrep() defer cleanFunc() + viper.Reset() viper.Set(flags.FlagHome, tempDir) - viper.Set(flags.FlagFrom, "acc1") + + for key, val := range tt.providedFlags { + viper.Set(key, val) + } + cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), From 2b76ed3975a598276ca8b2c35e161221fa467187 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 21 May 2020 12:09:54 +0200 Subject: [PATCH 07/13] add multisign address check --- x/auth/client/cli/batch.go | 7 +++++++ x/auth/client/cli/batch_test.go | 7 +++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index fe753a258c..148c8729cb 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,6 +3,8 @@ package cli import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" "github.com/spf13/cobra" @@ -48,6 +50,11 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return fmt.Errorf("only multisig signature is supported, provide it with %s flag", flagMultisig) } + _, err = sdk.AccAddressFromBech32(multisigAddrStr) + if err != nil { + return err + } + _, err = kb.Get(viper.GetString(flags.FlagFrom)) if err != nil { return errors.Wrap(err, "key not found") diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index a2b597e0f9..54d1b322da 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -5,11 +5,10 @@ import ( "path/filepath" "testing" - "github.com/tendermint/tendermint/crypto/multisig" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" @@ -34,7 +33,7 @@ func TestGetBatchSignCommand(t *testing.T) { viper.Reset() viper.Set(flags.FlagHome, tempDir) viper.Set(flags.FlagFrom, "acc1") - viper.Set(flagMultisig, multiInfo.GetName()) + viper.Set(flagMultisig, multiInfo.GetAddress()) cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), @@ -103,7 +102,7 @@ func TestGetBatchSignCommand_Error(t *testing.T) { return cleanFunc, tempDir }, providedFlags: map[string]interface{}{ - flagMultisig: "fasdfasdf", + flagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", flags.FlagFrom: "not-existing", }, }, From 360bad2bb6889056322b47ab462019343bf11fa6 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 21 May 2020 13:16:07 +0200 Subject: [PATCH 08/13] add temp commit --- x/auth/client/cli/batch.go | 19 +++++++++--- x/auth/client/cli/batch_test.go | 54 +++++++++++++++++++++++++++------ x/auth/client/cli/tx_sign.go | 6 ++-- x/auth/client/utils/tx.go | 8 ++--- x/auth/types/stdtx.go | 4 +-- 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 148c8729cb..d0fd3681bd 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -3,6 +3,8 @@ package cli import ( "fmt" + "github.com/cosmos/cosmos-sdk/x/auth/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" @@ -31,7 +33,7 @@ This command is intended to work offline for security purposes.`, } cmd.Flags().String( - flagMultisig, "", + FlagMultisig, "", "Address of the multisig account on behalf of which the transaction shall be signed", ) @@ -45,9 +47,9 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return err } - multisigAddrStr := viper.GetString(flagMultisig) + multisigAddrStr := viper.GetString(FlagMultisig) if multisigAddrStr == "" { - return fmt.Errorf("only multisig signature is supported, provide it with %s flag", flagMultisig) + return fmt.Errorf("only multisig signature is supported, provide it with %s flag", FlagMultisig) } _, err = sdk.AccAddressFromBech32(multisigAddrStr) @@ -55,16 +57,23 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return err } - _, err = kb.Get(viper.GetString(flags.FlagFrom)) + from, err := kb.Get(viper.GetString(flags.FlagFrom)) if err != nil { return errors.Wrap(err, "key not found") } - _, err = utils.ReadStdTxsFromFile(cdc, args[0]) + txs, err := utils.ReadStdTxsFromFile(cdc, args[0]) if err != nil { return errors.Wrap(err, "error extracting txs from file") } + for _, tx := range txs { + _, err = types.MakeSignature(nil, from.GetName(), "", tx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error signing tx %d", tx.Sequence)) + } + } + //fmt.Printf("%v\n", txsToSign) return nil diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 54d1b322da..408aee92b8 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -1,10 +1,18 @@ -package cli +package cli_test import ( "fmt" "path/filepath" "testing" + "github.com/cosmos/go-bip39" + + "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + + "github.com/cosmos/cosmos-sdk/x/staking" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" @@ -19,7 +27,10 @@ import ( func TestGetBatchSignCommand(t *testing.T) { cdc := amino.NewCodec() - cmd := GetBatchSignCommand(cdc) + sdk.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + + cmd := cli.GetBatchSignCommand(cdc) tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) @@ -33,7 +44,7 @@ func TestGetBatchSignCommand(t *testing.T) { viper.Reset() viper.Set(flags.FlagHome, tempDir) viper.Set(flags.FlagFrom, "acc1") - viper.Set(flagMultisig, multiInfo.GetAddress()) + viper.Set(cli.FlagMultisig, multiInfo.GetAddress()) cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), @@ -51,14 +62,37 @@ func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKe var pubKeys []crypto.PubKey for i := 0; i < 4; i++ { - mnemonic, _, _ := kb.CreateMnemonic( + //_, seed, _ := kb.CreateMnemonic( + // fmt.Sprintf("acc%d", i), + // keys2.English, + // "", + // keys2.Secp256k1, + //) + + // read entropy seed straight from crypto.Rand and convert to mnemonic + entropySeed, err := bip39.NewEntropy(256) + if err != nil { + return nil, nil, err + } + + mnemonic, err := bip39.NewMnemonic(entropySeed[:]) + if err != nil { + return nil, nil, err + } + + account, err := kb.CreateAccount( fmt.Sprintf("acc%d", i), - keys2.English, + mnemonic, + "", "", - keys2.Secp256k1, + 0, + 0, ) + if err != nil { + return nil, nil, err + } - pubKeys = append(pubKeys, mnemonic.GetPubKey()) + pubKeys = append(pubKeys, account.GetPubKey()) } pk := multisig.NewPubKeyMultisigThreshold(2, pubKeys) @@ -102,8 +136,8 @@ func TestGetBatchSignCommand_Error(t *testing.T) { return cleanFunc, tempDir }, providedFlags: map[string]interface{}{ - flagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", - flags.FlagFrom: "not-existing", + cli.FlagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", + flags.FlagFrom: "not-existing", }, }, } @@ -112,7 +146,7 @@ func TestGetBatchSignCommand_Error(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { cdc := amino.NewCodec() - cmd := GetBatchSignCommand(cdc) + cmd := cli.GetBatchSignCommand(cdc) cleanFunc, tempDir := tt.keybasePrep() defer cleanFunc() diff --git a/x/auth/client/cli/tx_sign.go b/x/auth/client/cli/tx_sign.go index 8ae87cb40c..3ca729dac9 100644 --- a/x/auth/client/cli/tx_sign.go +++ b/x/auth/client/cli/tx_sign.go @@ -18,7 +18,7 @@ import ( ) const ( - flagMultisig = "multisig" + FlagMultisig = "multisig" flagAppend = "append" flagValidateSigs = "validate-signatures" flagOffline = "offline" @@ -58,7 +58,7 @@ be generated via the 'multisign' command. } cmd.Flags().String( - flagMultisig, "", + FlagMultisig, "", "Address of the multisig account on behalf of which the transaction shall be signed", ) cmd.Flags().Bool( @@ -113,7 +113,7 @@ func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error // if --signature-only is on, then override --append var newTx types.StdTx generateSignatureOnly := viper.GetBool(flagSigOnly) - multisigAddrStr := viper.GetString(flagMultisig) + multisigAddrStr := viper.GetString(FlagMultisig) if multisigAddrStr != "" { var multisigAddr sdk.AccAddress diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index 366aff0e88..c409dbbffb 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -250,7 +250,7 @@ func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx } // ReadStdTxsFromFile reads a list of transactions from a file -func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdSignDoc, error) { +func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdSignMsg, error) { bz, err := ioutil.ReadFile(filename) if err != nil { return nil, err @@ -261,15 +261,15 @@ func ReadStdTxsFromFile(cdc *codec.Codec, filename string) ([]authtypes.StdSignD return buildTxsFromJsonLines(cdc, lines) } -func buildTxsFromJsonLines(cdc *codec.Codec, jsonTxs []string) ([]authtypes.StdSignDoc, error) { - var txs []authtypes.StdSignDoc +func buildTxsFromJsonLines(cdc *codec.Codec, jsonTxs []string) ([]authtypes.StdSignMsg, error) { + var txs []authtypes.StdSignMsg for _, jsonTx := range jsonTxs { if len(jsonTx) == 0 { break } - var tx authtypes.StdSignDoc + var tx authtypes.StdSignMsg err := cdc.UnmarshalJSON([]byte(jsonTx), &tx) if err != nil { return nil, err diff --git a/x/auth/types/stdtx.go b/x/auth/types/stdtx.go index 9e71b1127d..a732418408 100644 --- a/x/auth/types/stdtx.go +++ b/x/auth/types/stdtx.go @@ -170,12 +170,12 @@ type StdSignDoc struct { func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee StdFee, msgs []sdk.Msg, memo string) []byte { var msgsBytes []json.RawMessage for _, msg := range msgs { - msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes())) + msgsBytes = append(msgsBytes, msg.GetSignBytes()) } bz, err := ModuleCdc.MarshalJSON(StdSignDoc{ AccountNumber: accnum, ChainID: chainID, - Fee: json.RawMessage(fee.Bytes()), + Fee: fee.Bytes(), Memo: memo, Msgs: msgsBytes, Sequence: sequence, From 166a23a674680c7101fe218afd2ab25c8805c8b2 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 21 May 2020 17:19:44 +0200 Subject: [PATCH 09/13] add passphrase to the game --- x/auth/client/cli/batch.go | 9 ++++++++- x/auth/client/cli/batch_test.go | 26 +++++++++----------------- x/auth/client/utils/tx_test.go | 3 +++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index d0fd3681bd..4bf58163d8 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -18,6 +18,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/client/utils" ) +const ( + FlagPassPhrase = "passphrase" +) + func GetBatchSignCommand(codec *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "sign-batch [in-file] [out-file]", @@ -37,6 +41,8 @@ This command is intended to work offline for security purposes.`, "Address of the multisig account on behalf of which the transaction shall be signed", ) + cmd.Flags().String(FlagPassPhrase, "", "The passphrase of the key needed to sign the transaction.") + return flags.PostCommands(cmd)[0] } @@ -67,8 +73,9 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return errors.Wrap(err, "error extracting txs from file") } + passphrase := viper.GetString(FlagPassPhrase) for _, tx := range txs { - _, err = types.MakeSignature(nil, from.GetName(), "", tx) + _, err = types.MakeSignature(nil, from.GetName(), passphrase, tx) if err != nil { return errors.Wrap(err, fmt.Sprintf("error signing tx %d", tx.Sequence)) } diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 408aee92b8..05d66e6f80 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -5,14 +5,6 @@ import ( "path/filepath" "testing" - "github.com/cosmos/go-bip39" - - "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - - "github.com/cosmos/cosmos-sdk/x/staking" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" @@ -22,9 +14,15 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" keys2 "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/go-bip39" "github.com/tendermint/tendermint/crypto" ) +const passphrase = "012345678" + func TestGetBatchSignCommand(t *testing.T) { cdc := amino.NewCodec() sdk.RegisterCodec(cdc) @@ -45,6 +43,8 @@ func TestGetBatchSignCommand(t *testing.T) { viper.Set(flags.FlagHome, tempDir) viper.Set(flags.FlagFrom, "acc1") viper.Set(cli.FlagMultisig, multiInfo.GetAddress()) + viper.Set(cli.FlagPassPhrase, passphrase) + cmd.SetArgs([]string{ "./testdata/txs.json", filepath.Join(tempDir, "outputfile"), @@ -62,14 +62,6 @@ func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKe var pubKeys []crypto.PubKey for i := 0; i < 4; i++ { - //_, seed, _ := kb.CreateMnemonic( - // fmt.Sprintf("acc%d", i), - // keys2.English, - // "", - // keys2.Secp256k1, - //) - - // read entropy seed straight from crypto.Rand and convert to mnemonic entropySeed, err := bip39.NewEntropy(256) if err != nil { return nil, nil, err @@ -84,7 +76,7 @@ func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKe fmt.Sprintf("acc%d", i), mnemonic, "", - "", + passphrase, 0, 0, ) diff --git a/x/auth/client/utils/tx_test.go b/x/auth/client/utils/tx_test.go index 0e13d72cd3..2ac0920af0 100644 --- a/x/auth/client/utils/tx_test.go +++ b/x/auth/client/utils/tx_test.go @@ -8,6 +8,8 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/assert" @@ -121,6 +123,7 @@ func TestReadStdTxFromFile(t *testing.T) { func TestReadStdTxsFromFile(t *testing.T) { cdc := codec.New() sdk.RegisterCodec(cdc) + types.RegisterCodec(cdc) txsFromFile, err := ReadStdTxsFromFile(cdc, "./testdata/txs") require.NoError(t, err) From 484143d4bd188d90dad788fcf14f5fa667cd0ac2 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 21 May 2020 17:36:43 +0200 Subject: [PATCH 10/13] add test with invalid keybase --- x/auth/client/cli/batch_test.go | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 05d66e6f80..69cced457a 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -96,52 +96,60 @@ func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKe } func TestGetBatchSignCommand_Error(t *testing.T) { - tests := []struct { + tts := []struct { name string errorContains string - keybasePrep func() (cleanFunc func(), tempDir string) + keybasePrep func(tempDir string) providedFlags map[string]interface{} }{ { name: "flag multisign not provided", errorContains: "only multisig signature is supported", - keybasePrep: func() (func(), string) { - tempDir, cleanFunc := tests.NewTestCaseDir(t) - t.Cleanup(cleanFunc) - + keybasePrep: func(tempDir string) { kb, err := keys.NewKeyBaseFromDir(tempDir) require.NoError(t, err) _, _, err = kb.CreateMnemonic("acc1", keys2.English, "", keys2.Secp256k1) require.NoError(t, err) - - return cleanFunc, tempDir }, }, { name: "not existing key", errorContains: "key not found: Key not-existing not found", - keybasePrep: func() (func(), string) { - tempDir, cleanFunc := tests.NewTestCaseDir(t) - t.Cleanup(cleanFunc) - - return cleanFunc, tempDir + keybasePrep: func(tempDir string) { }, providedFlags: map[string]interface{}{ cli.FlagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", flags.FlagFrom: "not-existing", }, }, + { + name: "invalid passphrase", + errorContains: "invalid account password", + keybasePrep: func(tempDir string) { + createKeybaseWithMultisigAccount(tempDir) + }, + providedFlags: map[string]interface{}{ + cli.FlagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", + flags.FlagFrom: "acc1", + }, + }, } - for _, tt := range tests { + cdc := amino.NewCodec() + sdk.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + + for _, tt := range tts { tt := tt + tempDir, cleanFunc := tests.NewTestCaseDir(t) + t.Run(tt.name, func(t *testing.T) { - cdc := amino.NewCodec() + defer cleanFunc() + cmd := cli.GetBatchSignCommand(cdc) - cleanFunc, tempDir := tt.keybasePrep() - defer cleanFunc() + tt.keybasePrep(tempDir) viper.Reset() viper.Set(flags.FlagHome, tempDir) From 5148dceba7bcb8c35d47c2034d8d848dbd6f833f Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Fri, 22 May 2020 21:17:12 +0200 Subject: [PATCH 11/13] temp commit --- x/auth/client/cli/batch.go | 50 ++++++++++++++++----- x/auth/client/cli/batch_test.go | 80 ++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 4bf58163d8..b0f8af1771 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -1,21 +1,22 @@ package cli import ( + json2 "encoding/json" "fmt" - - "github.com/cosmos/cosmos-sdk/x/auth/types" - - sdk "github.com/cosmos/cosmos-sdk/types" + "io" + "os" "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/auth/types" ) const ( @@ -24,7 +25,7 @@ const ( func GetBatchSignCommand(codec *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "sign-batch [in-file] [out-file]", + Use: "sign-batch [in-file]", Short: "Sign many standard transactions generated offline", Long: `Sign a list of transactions created with the --generate-only flag. It will read StdSignDoc JSONs from [in-file], one transaction per line, and @@ -33,7 +34,7 @@ produce a file of JSON encoded StdSignatures, one per line. This command is intended to work offline for security purposes.`, PreRun: preSignCmd, RunE: makeBatchSignCmd(codec), - Args: cobra.ExactArgs(2), + Args: cobra.ExactArgs(1), } cmd.Flags().String( @@ -42,6 +43,8 @@ This command is intended to work offline for security purposes.`, ) cmd.Flags().String(FlagPassPhrase, "", "The passphrase of the key needed to sign the transaction.") + cmd.Flags().String(client.FlagOutputDocument, "", + "write the resulto to the given file instead of the default location") return flags.PostCommands(cmd)[0] } @@ -53,6 +56,11 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return err } + out, err := setOutput() + if err != nil { + return errors.Wrap(err, "error with output") + } + multisigAddrStr := viper.GetString(FlagMultisig) if multisigAddrStr == "" { return fmt.Errorf("only multisig signature is supported, provide it with %s flag", FlagMultisig) @@ -75,14 +83,36 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) passphrase := viper.GetString(FlagPassPhrase) for _, tx := range txs { - _, err = types.MakeSignature(nil, from.GetName(), passphrase, tx) + signature, err := types.MakeSignature(nil, from.GetName(), passphrase, tx) if err != nil { return errors.Wrap(err, fmt.Sprintf("error signing tx %d", tx.Sequence)) } - } - //fmt.Printf("%v\n", txsToSign) + json, err := json2.Marshal(signature) + if err != nil { + return errors.Wrap(err, "error marshalling signature") + } + + _, err = fmt.Fprintf(out, "%s\n", json) + if err != nil { + return errors.Wrap(err, "error writing to output") + } + } return nil } } + +func setOutput() (io.Writer, error) { + outputFlag := viper.GetString(client.FlagOutputDocument) + if outputFlag == "" { + return os.Stdout, nil + } + + out, err := os.OpenFile(outputFlag, os.O_RDWR, 0644) + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 69cced457a..5c4552f64c 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -2,9 +2,17 @@ package cli_test import ( "fmt" + "io" + "io/ioutil" + "os" "path/filepath" + "strings" "testing" + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" @@ -33,6 +41,10 @@ func TestGetBatchSignCommand(t *testing.T) { tempDir, cleanFunc := tests.NewTestCaseDir(t) t.Cleanup(cleanFunc) + outputFile, err := os.Create(filepath.Join(tempDir, "the-output")) + require.NoError(t, err) + defer outputFile.Close() + kb, _, err := createKeybaseWithMultisigAccount(tempDir) require.NoError(t, err) @@ -44,14 +56,79 @@ func TestGetBatchSignCommand(t *testing.T) { viper.Set(flags.FlagFrom, "acc1") viper.Set(cli.FlagMultisig, multiInfo.GetAddress()) viper.Set(cli.FlagPassPhrase, passphrase) + viper.Set(flags.FlagOutputDocument, outputFile.Name()) cmd.SetArgs([]string{ "./testdata/txs.json", - filepath.Join(tempDir, "outputfile"), }) err = cmd.Execute() require.NoError(t, err) + + // Validate Result + inputFile, err := os.Open("./testdata/txs.json") + require.NoError(t, err) + + validateSignatures(t, cdc, inputFile, outputFile) +} + +func validateSignatures(t *testing.T, cdc *codec.Codec, inputFile io.Reader, outputFile io.Reader) { + inputData, err := ioutil.ReadAll(inputFile) + require.NoError(t, err) + + outputData, err := ioutil.ReadAll(outputFile) + require.NoError(t, err) + + txs := extractTxs(t, cdc, inputData) + signatures := extractSignatures(t, cdc, outputData) + + if len(txs) != len(signatures) { + t.Errorf("must be same amount of txs and signatures: '%d' txs, '%d' signatures", len(txs), len(signatures)) + } +} + +func extractTxs(t *testing.T, cdc *codec.Codec, inputData []byte) []auth.StdSignMsg { + inputLines := strings.Split(string(inputData), "\n") + + var parsedTxs []auth.StdSignMsg + for _, txLine := range inputLines { + if len(txLine) == 0 { + break + } + + var parsedTx auth.StdSignMsg + + err := cdc.UnmarshalJSON([]byte(txLine), &parsedTx) + if err != nil { + t.Errorf("error extracting tx: %s", err) + } + + parsedTxs = append(parsedTxs, parsedTx) + } + + return parsedTxs +} + +func extractSignatures(t *testing.T, cdc *codec.Codec, outputData []byte) []auth.StdSignature { + outputLines := strings.Split(string(outputData), "\n") + + var parsedSigs []auth.StdSignature + for _, sigLine := range outputLines { + if len(sigLine) == 0 { + break + } + + var parsedSig auth.StdSignature + + err := cdc.UnmarshalJSON([]byte(sigLine), &parsedSig) + if err != nil { + t.Errorf("error extracting tx: %s", err) + } + + parsedSigs = append(parsedSigs, parsedSig) + } + + return parsedSigs } func createKeybaseWithMultisigAccount(dir string) (keys2.Keybase, []crypto.PubKey, error) { @@ -160,7 +237,6 @@ func TestGetBatchSignCommand_Error(t *testing.T) { cmd.SetArgs([]string{ "./testdata/txs.json", - filepath.Join(tempDir, "outputfile"), }) err := cmd.Execute() From 875b9c7b3e1db29ea4e3c080b387909a421a5c15 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Fri, 22 May 2020 21:59:05 +0200 Subject: [PATCH 12/13] verify bytes --- x/auth/client/cli/batch.go | 3 +-- x/auth/client/cli/batch_test.go | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index b0f8af1771..9e0c5285f5 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -1,7 +1,6 @@ package cli import ( - json2 "encoding/json" "fmt" "io" "os" @@ -88,7 +87,7 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return errors.Wrap(err, fmt.Sprintf("error signing tx %d", tx.Sequence)) } - json, err := json2.Marshal(signature) + json, err := cdc.MarshalJSON(signature) if err != nil { return errors.Wrap(err, "error marshalling signature") } diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 5c4552f64c..8408b194c2 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -9,10 +9,6 @@ import ( "strings" "testing" - "github.com/cosmos/cosmos-sdk/codec" - - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/go-amino" @@ -20,9 +16,11 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" keys2 "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/go-bip39" @@ -34,6 +32,7 @@ const passphrase = "012345678" func TestGetBatchSignCommand(t *testing.T) { cdc := amino.NewCodec() sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) staking.RegisterCodec(cdc) cmd := cli.GetBatchSignCommand(cdc) @@ -85,6 +84,10 @@ func validateSignatures(t *testing.T, cdc *codec.Codec, inputFile io.Reader, out if len(txs) != len(signatures) { t.Errorf("must be same amount of txs and signatures: '%d' txs, '%d' signatures", len(txs), len(signatures)) } + + for i := 0; i < len(txs); i++ { + require.True(t, signatures[i].PubKey.VerifyBytes(txs[i].Bytes(), signatures[i].Signature)) + } } func extractTxs(t *testing.T, cdc *codec.Codec, inputData []byte) []auth.StdSignMsg { From 1e3a13e26fca242a4126930b9075c657cc8b46e7 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Fri, 22 May 2020 22:02:31 +0200 Subject: [PATCH 13/13] remove multisign input --- x/auth/client/cli/batch.go | 16 ---------------- x/auth/client/cli/batch_test.go | 17 ++--------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/x/auth/client/cli/batch.go b/x/auth/client/cli/batch.go index 9e0c5285f5..d0fb4eec85 100644 --- a/x/auth/client/cli/batch.go +++ b/x/auth/client/cli/batch.go @@ -13,7 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" "github.com/cosmos/cosmos-sdk/x/auth/types" ) @@ -36,11 +35,6 @@ This command is intended to work offline for security purposes.`, Args: cobra.ExactArgs(1), } - cmd.Flags().String( - FlagMultisig, "", - "Address of the multisig account on behalf of which the transaction shall be signed", - ) - cmd.Flags().String(FlagPassPhrase, "", "The passphrase of the key needed to sign the transaction.") cmd.Flags().String(client.FlagOutputDocument, "", "write the resulto to the given file instead of the default location") @@ -60,16 +54,6 @@ func makeBatchSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) return errors.Wrap(err, "error with output") } - multisigAddrStr := viper.GetString(FlagMultisig) - if multisigAddrStr == "" { - return fmt.Errorf("only multisig signature is supported, provide it with %s flag", FlagMultisig) - } - - _, err = sdk.AccAddressFromBech32(multisigAddrStr) - if err != nil { - return err - } - from, err := kb.Get(viper.GetString(flags.FlagFrom)) if err != nil { return errors.Wrap(err, "key not found") diff --git a/x/auth/client/cli/batch_test.go b/x/auth/client/cli/batch_test.go index 8408b194c2..6e18ca4273 100644 --- a/x/auth/client/cli/batch_test.go +++ b/x/auth/client/cli/batch_test.go @@ -182,25 +182,13 @@ func TestGetBatchSignCommand_Error(t *testing.T) { keybasePrep func(tempDir string) providedFlags map[string]interface{} }{ - { - name: "flag multisign not provided", - errorContains: "only multisig signature is supported", - keybasePrep: func(tempDir string) { - kb, err := keys.NewKeyBaseFromDir(tempDir) - require.NoError(t, err) - - _, _, err = kb.CreateMnemonic("acc1", keys2.English, "", keys2.Secp256k1) - require.NoError(t, err) - }, - }, { name: "not existing key", errorContains: "key not found: Key not-existing not found", keybasePrep: func(tempDir string) { }, providedFlags: map[string]interface{}{ - cli.FlagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", - flags.FlagFrom: "not-existing", + flags.FlagFrom: "not-existing", }, }, { @@ -210,8 +198,7 @@ func TestGetBatchSignCommand_Error(t *testing.T) { createKeybaseWithMultisigAccount(tempDir) }, providedFlags: map[string]interface{}{ - cli.FlagMultisig: "cosmos1pf7m2k50lv0pc27wjz3452vu2xqs8yevxhv7w3", - flags.FlagFrom: "acc1", + flags.FlagFrom: "acc1", }, }, }