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

fix: Signature only flag bug on tx sign command 7632 #8106

Merged
merged 47 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
351c28f
fix: Signature only flag bug on tx sign command 7632
robert-zaremba Dec 7, 2020
4308f1c
Update client/context.go
robert-zaremba Dec 8, 2020
942d954
Merge branch 'master' into robert/fix-cli-sign
robert-zaremba Dec 8, 2020
86e1ca7
Update client/context.go
robert-zaremba Dec 8, 2020
50568a4
Merge branch 'master' into robert/fix-cli-sign
sahith-narahari Dec 8, 2020
09984c6
use named return value and closure (#8111)
Dec 8, 2020
b97a939
set the right 'append' logic for signing transactions
robert-zaremba Dec 8, 2020
7d96902
cleanup
robert-zaremba Dec 8, 2020
a0d26fb
update tx.Sign interface by adding overwrite option
robert-zaremba Dec 8, 2020
ac324b3
Update Changelog
robert-zaremba Dec 8, 2020
1b16901
sign command cleanup
robert-zaremba Dec 8, 2020
9d5ce97
implementation and changelog update
robert-zaremba Dec 9, 2020
e774b31
fix SignTx and tx.Sign calls
robert-zaremba Dec 9, 2020
e731b5a
fix: sign didn't write to a file
robert-zaremba Dec 9, 2020
4784d93
update flags description
robert-zaremba Dec 9, 2020
2048a8a
Merge branch 'master' into robert/fix-cli-sign
robert-zaremba Dec 9, 2020
b1e3cbd
Add tx.Sign tests
robert-zaremba Dec 9, 2020
edc19dc
fix grpc/server_test.go
robert-zaremba Dec 9, 2020
8818f64
Update client/tx/tx.go
robert-zaremba Dec 9, 2020
7e39258
changelog update
robert-zaremba Dec 10, 2020
3ed4d36
Add test to verify matching signatures
robert-zaremba Dec 10, 2020
cd6b700
cli_test: add integration tests for sign CMD
robert-zaremba Dec 10, 2020
fe3acef
add output-file flag test
robert-zaremba Dec 10, 2020
c2c74c6
add flagAmino test
robert-zaremba Dec 10, 2020
82d28b8
Update x/auth/client/cli/tx_sign.go
robert-zaremba Dec 10, 2020
87d027a
Update x/auth/client/cli/tx_sign.go
Dec 10, 2020
b8256a8
update amino serialization test
robert-zaremba Dec 10, 2020
a00e8ec
TestSign: adding unit test for signing with different modes
robert-zaremba Dec 10, 2020
c0c1c48
Add test with Multi Signers into Robert's TxSign PR (#8142)
amaury1093 Dec 11, 2020
6405295
cleanups
robert-zaremba Dec 11, 2020
9b9b9dc
Merge branch 'master' into robert/fix-cli-sign
robert-zaremba Dec 11, 2020
d61f340
client.Sign: raise error when signing tx with multiple signers in Direct
robert-zaremba Dec 11, 2020
8cbcff1
add more tests
robert-zaremba Dec 11, 2020
25e4915
Update client/tx/tx_test.go
robert-zaremba Dec 12, 2020
17f210b
fix TestGetBroadcastCommand_WithoutOfflineFlag
robert-zaremba Dec 12, 2020
46b50b2
Any.UnsafeSetCachedValue
robert-zaremba Dec 14, 2020
8e55e13
fix note packed messages in tx builder
robert-zaremba Dec 14, 2020
a263570
reorder unit tests
robert-zaremba Dec 14, 2020
68d9b37
Changelog update
robert-zaremba Dec 14, 2020
febbca9
cleaning / linting
robert-zaremba Dec 14, 2020
ae2ce73
cli_tes: copy validator object instead of modifying it's shared codec
robert-zaremba Dec 14, 2020
1407caa
x/auth cli_test: remove custom codec creation in tests
robert-zaremba Dec 14, 2020
d71fe5a
Merge branch 'master' into robert/fix-cli-sign
mergify[bot] Dec 14, 2020
df7be40
Update CHANGELOG.md
clevinson Dec 14, 2020
94c42a0
updates to CHANGELOG.md
clevinson Dec 14, 2020
48659f8
remove unused method
robert-zaremba Dec 14, 2020
5547aaa
add new instance of transaction builder for TestSign
robert-zaremba Dec 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,17 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes

* (crypto) [\#7966](https://github.com/cosmos/cosmos-sdk/issues/7966) `Bip44Params` `String()` function now correctly returns the absolute HD path by adding the `m/` prefix.
* (x/auth/client/cli) [\#7632](https://github.com/cosmos/cosmos-sdk/issues/7632) fixing regression bugs in transaction signing.
clevinson marked this conversation as resolved.
Show resolved Hide resolved


### API Breaking

* [\#8080](https://github.com/cosmos/cosmos-sdk/pull/8080) Updated the `codec.Marshaler` interface
* Moved `MarshalAny` and `UnmarshalAny` helper functions to `codec.Marshaler` and renamed to `MarshalInterface` and `UnmarshalInterface` respectively. These functions must take interface as a parameter (not a concrete type nor `Any` object). Underneath they use `Any` wrapping for correct protobuf serialization.
* (client) [\#8107](https://github.com/cosmos/cosmos-sdk/pull/8107) Renamed `PrintOutput` and `PrintOutputLegacy` methods of the `context.Client` object to `PrintProto` and `PrintObjectLegacy`.
* (x/auth/tx) [\#8106](https://github.com/cosmos/cosmos-sdk/pull/8106) change related to missing append functionality in client transaction signing
+ added `overwriteSig` argument to `x/auth/client.SignTx` and `client/tx.Sign` functions.
+ removed `x/auth/tx.go:wrapper.GetSignatures`. The `wrapper` provides `TxBuilder` functionality, and it's a private structure. That function was not used at all and it's not exposed through the `TxBuilder` interface.


## [v0.40.0-rc3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0-rc3) - 2020-11-06
Expand Down
10 changes: 8 additions & 2 deletions client/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,20 @@ func (ctx Context) WithInterfaceRegistry(interfaceRegistry codectypes.InterfaceR
return ctx
}

// PrintString prints the raw string to ctx.Output or os.Stdout
// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
func (ctx Context) PrintString(str string) error {
return ctx.PrintBytes([]byte(str))
}

// PrintBytes prints the raw bytes to ctx.Output if it's defined, otherwise to os.Stdout.
// NOTE: for printing a complex state object, you should use ctx.PrintOutput
func (ctx Context) PrintBytes(o []byte) error {
writer := ctx.Output
if writer == nil {
writer = os.Stdout
}

_, err := writer.Write([]byte(str))
_, err := writer.Write(o)
return err
}

Expand Down
41 changes: 32 additions & 9 deletions client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
}
}

err = Sign(txf, clientCtx.GetFromName(), tx)
err = Sign(txf, clientCtx.GetFromName(), tx, true)
if err != nil {
return err
}
Expand Down Expand Up @@ -375,10 +375,21 @@ func SignWithPrivKey(
return sigV2, nil
}

// Sign signs a given tx with the provided name and passphrase. The bytes signed
// over are canconical. The resulting signature will be set on the transaction.
func checkMultipleSigners(mode signing.SignMode, tx authsigning.Tx) error {
if mode == signing.SignMode_SIGN_MODE_DIRECT &&
len(tx.GetSigners()) > 1 {
return sdkerrors.Wrap(sdkerrors.ErrNotSupported, "Signing in DIRECT mode is only supported for transactions with one signer only")
}
return nil
}

// Sign signs a given tx with a named key. The bytes signed over are canconical.
// The resulting signature will be added to the transaction builder overwriting the previous
// ones if overwrite=true (otherwise, the signature will be appended).
// Signing a transaction with mutltiple signers in the DIRECT mode is not supprted and will
// return an error.
// An error is returned upon failure.
func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
func Sign(txf Factory, name string, txBuilder client.TxBuilder, overwriteSig bool) error {
if txf.keybase == nil {
return errors.New("keybase must be set prior to signing a transaction")
}
Expand All @@ -388,12 +399,14 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
// use the SignModeHandler's default mode if unspecified
signMode = txf.txConfig.SignModeHandler().DefaultMode()
}
if err := checkMultipleSigners(signMode, txBuilder.GetTx()); err != nil {
return err
}

key, err := txf.keybase.Key(name)
if err != nil {
return err
}

pubKey := key.GetPubKey()
signerData := authsigning.SignerData{
ChainID: txf.chainID,
Expand All @@ -418,18 +431,25 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
Data: &sigData,
Sequence: txf.Sequence(),
}
var prevSignatures []signing.SignatureV2
if !overwriteSig {
prevSignatures, err = txBuilder.GetTx().GetSignaturesV2()
if err != nil {
return err
}
}
if err := txBuilder.SetSignatures(sig); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be something slightly more complicated going on here that we need to be careful about. If you read above, we are only calling this SetSignatures() method with an empty sig so that the underlying txBuilder can set the SignModes which is necessary for generating the bytesToSign below.

In the case of --append flag, I think that we actually want to call SetSignatures() with the pre-existing list of existing signatures + the empty new signature.

e.g. i think the full overwrite logic here needs to be something more like:

	if overwriteSig {
        	if err := txBuilder.SetSignatures(sig); err != nil {
	        	return err
	        }
	} else {
		prevSignatures, err = txBuilder.GetTx().GetSignaturesV2()
		if err != nil {
			return err
		}
                sigs := append(prevSignatures, sig)
        	if err := txBuilder.SetSignatures(sigs); err != nil {
	        	return err
	        }
        }

Would be good to get confirmation from @amaurymartiny but i think this is correct logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test to verify the matching signatures. It works. The reason the signatures are reset here is to set the right signing mode.

The wrapper and client signing functions are not very clear. As I wrote in the summary above, it should probably be rewritten.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the edge case that might be missing here is if you have two different signatures with different sign modes. Does the test work in that case as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added one more tests for signing with a different factory which has a different mode:

{"should succeed to append a signature with different mode",
	txfAmino, from1, false, []cryptotypes.PubKey{pubKey2, pubKey1}, []int{0, 0}},

In fact your patch doesn't work, because the wrapper (TxBuilder) is wrongly implemented - it doesn't know which key it should use to sign nor the mode to use. That logic is handled independently, through txFactory and maybe totally different then the mode set in the TxBuilder signature placeholder. In my opinion this is error prone, but that's something for a new task.

return err
}

// Generate the bytes to be signed.
signBytes, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
bytesToSign, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
if err != nil {
return err
}

// Sign those bytes
sigBytes, _, err := txf.keybase.Sign(name, signBytes)
sigBytes, _, err := txf.keybase.Sign(name, bytesToSign)
if err != nil {
return err
}
Expand All @@ -445,8 +465,11 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
Sequence: txf.Sequence(),
}

// And here the tx is populated with the signature
return txBuilder.SetSignatures(sig)
if overwriteSig {
return txBuilder.SetSignatures(sig)
}
prevSignatures = append(prevSignatures, sig)
return txBuilder.SetSignatures(prevSignatures...)
}

// GasEstimateResponse defines a response definition for tx gas estimation.
Expand Down
123 changes: 93 additions & 30 deletions client/tx/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
Expand Down Expand Up @@ -121,49 +123,110 @@ func TestBuildUnsignedTx(t *testing.T) {
}

func TestSign(t *testing.T) {
requireT := require.New(t)
path := hd.CreateHDPath(118, 0, 0).String()
kr, err := keyring.New(t.Name(), "test", t.TempDir(), nil)
require.NoError(t, err)

var from = "test_sign"
requireT.NoError(err)

_, seed, err := kr.NewMnemonic(from, keyring.English, path, hd.Secp256k1)
require.NoError(t, err)
require.NoError(t, kr.Delete(from))
var from1 = "test_key1"
var from2 = "test_key2"

info, err := kr.NewAccount(from, seed, "", path, hd.Secp256k1)
require.NoError(t, err)

txf := tx.Factory{}.
WithTxConfig(NewTestTxConfig()).
WithAccountNumber(50).
WithSequence(23).
WithFees("50stake").
WithMemo("memo").
WithChainID("test-chain")
// create a new key using a mnemonic generator and test if we can reuse seed to recreate that account
_, seed, err := kr.NewMnemonic(from1, keyring.English, path, hd.Secp256k1)
requireT.NoError(err)
requireT.NoError(kr.Delete(from1))
info1, _, err := kr.NewMnemonic(from1, keyring.English, path, hd.Secp256k1)
requireT.NoError(err)

msg := banktypes.NewMsgSend(info.GetAddress(), sdk.AccAddress("to"), nil)
txn, err := tx.BuildUnsignedTx(txf, msg)
require.NoError(t, err)
info2, err := kr.NewAccount(from2, seed, "", path, hd.Secp256k1)
requireT.NoError(err)

t.Log("should failed if txf without keyring")
err = tx.Sign(txf, from, txn)
require.Error(t, err)
pubKey1 := info1.GetPubKey()
pubKey2 := info2.GetPubKey()
requireT.NotEqual(pubKey1.Bytes(), pubKey2.Bytes())
t.Log("Pub keys:", pubKey1, pubKey2)

txf = tx.Factory{}.
WithKeybase(kr).
txfNoKeybase := tx.Factory{}.
WithTxConfig(NewTestTxConfig()).
WithAccountNumber(50).
WithSequence(23).
WithFees("50stake").
WithMemo("memo").
WithChainID("test-chain")
txfDirect := txfNoKeybase.
WithKeybase(kr).
WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT)
txfAmino := txfDirect.
WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
msg1 := banktypes.NewMsgSend(info1.GetAddress(), sdk.AccAddress("to"), nil)
msg2 := banktypes.NewMsgSend(info2.GetAddress(), sdk.AccAddress("to"), nil)
txb, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
requireT.NoError(err)
txbSimple, err := tx.BuildUnsignedTx(txfNoKeybase, msg2)
requireT.NoError(err)

t.Log("should succeed if txf with keyring")
err = tx.Sign(txf, from, txn)
require.NoError(t, err)
testCases := []struct {
name string
txf tx.Factory
txb client.TxBuilder
from string
overwrite bool
expectedPKs []cryptotypes.PubKey
matchingSigs []int // if not nil, check matching signature against old ones.
}{
{"should fail if txf without keyring",
txfNoKeybase, txb, from1, true, nil, nil},
{"should fail for non existing key",
txfAmino, txb, "unknown", true, nil, nil},
{"should succeed with keyring Amino",
txfAmino, txbSimple, from1, true, []cryptotypes.PubKey{pubKey1}, nil},

/**** test double sign AMINO ****/
{"should sign tx2 Amino",
txfAmino, txb, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
{"should append a second signature and not overwrite Amino",
txfAmino, txb, from2, false, []cryptotypes.PubKey{pubKey1, pubKey2}, []int{0, 0}},
{"should overwrite a signature Amino",
txfAmino, txb, from2, true, []cryptotypes.PubKey{pubKey2}, []int{1, 0}},
{"should fail to append a signature with different mode Direct",
txfDirect, txb, from1, false, []cryptotypes.PubKey{}, nil},

/**** test double sign DIRECT
signing transaction with more than 2 signers should fail in DIRECT mode ****/
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
{"should fail to sign tx2 DIRECT",
txfDirect, txb, from1, false, []cryptotypes.PubKey{}, nil},
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
{"should fail to overwrite tx2 DIRECT",
txfDirect, txb, from1, true, []cryptotypes.PubKey{}, nil},

/**** test simple DIRECT ****/
{"should succeed with keyring DIRECT",
txfDirect, txbSimple, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
}
var prevSigs []signingtypes.SignatureV2
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = tx.Sign(tc.txf, tc.from, tc.txb, tc.overwrite)
if len(tc.expectedPKs) == 0 {
requireT.Error(err)
} else {
requireT.NoError(err)
sigs := testSigners(requireT, tc.txb.GetTx(), tc.expectedPKs...)
if tc.matchingSigs != nil {
requireT.Equal(prevSigs[tc.matchingSigs[0]], sigs[tc.matchingSigs[1]])
}
prevSigs = sigs
}
})
}
}

t.Log("should fail for non existing key")
err = tx.Sign(txf, "non_existing_key", txn)
require.Error(t, err)
func testSigners(require *require.Assertions, tr signing.Tx, pks ...cryptotypes.PubKey) []signingtypes.SignatureV2 {
sigs, err := tr.GetSignaturesV2()
require.Len(sigs, len(pks))
require.NoError(err)
require.Len(sigs, len(pks))
for i := range pks {
require.True(sigs[i].PubKey.Equals(pks[i]), "Signature is signed with a wrong pubkey. Got: %s, expected: %s", sigs[i].PubKey, pks[i])
}
return sigs
}
2 changes: 1 addition & 1 deletion server/grpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() {
WithSignMode(signing.SignMode_SIGN_MODE_DIRECT)

// Sign Tx.
err := authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false)
err := authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false, true)
s.Require().NoError(err)

txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
Expand Down
2 changes: 1 addition & 1 deletion simapp/simd/cmd/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func InitTestnet(
WithKeybase(kb).
WithTxConfig(clientCtx.TxConfig)

if err := tx.Sign(txFactory, nodeDirName, txBuilder); err != nil {
if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion testutil/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ func New(t *testing.T, cfg Config) *Network {
WithKeybase(kb).
WithTxConfig(cfg.TxConfig)

err = tx.Sign(txFactory, nodeDirName, txBuilder)
err = tx.Sign(txFactory, nodeDirName, txBuilder, true)
require.NoError(t, err)

txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
Expand Down
4 changes: 4 additions & 0 deletions types/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ var (
// the same resource and one of them fails.
ErrConflict = Register(RootCodespace, 36, "conflict")

// ErrNotSupported is returned when we call a branch of a code which is currently not
// supported.
ErrNotSupported = Register(RootCodespace, 37, "feature not supported")

// ErrPanic is only set when we recover from a panic, so we know to
// redact potentially sensitive system info
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
Expand Down
4 changes: 4 additions & 0 deletions x/auth/client/cli/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ $ <appd> tx broadcast ./mytxn.json
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
if err != nil {
return err
}

if offline, _ := cmd.Flags().GetBool(flags.FlagOffline); offline {
return errors.New("cannot broadcast tx during offline mode")
Expand Down
Loading