Skip to content

Commit 3a9e696

Browse files
robert-zarembaclevinsonsahith-narahariAlessio Tregliaamaury1093
authored
fix: Signature only flag bug on tx sign command 7632 (#8106)
* fix: Signature only flag bug on tx sign command 7632 * Update client/context.go Co-authored-by: Cory <[email protected]> * Update client/context.go Co-authored-by: Cory <[email protected]> * use named return value and closure (#8111) This is to correctly handle deferred Close() calls on writable files. * set the right 'append' logic for signing transactions * cleanup * update tx.Sign interface by adding overwrite option * Update Changelog * sign command cleanup * implementation and changelog update * fix SignTx and tx.Sign calls * fix: sign didn't write to a file * update flags description * Add tx.Sign tests * fix grpc/server_test.go * Update client/tx/tx.go Co-authored-by: Cory <[email protected]> * changelog update * Add test to verify matching signatures * cli_test: add integration tests for sign CMD * add output-file flag test * add flagAmino test * Update x/auth/client/cli/tx_sign.go Co-authored-by: Alessio Treglia <[email protected]> * Update x/auth/client/cli/tx_sign.go * update amino serialization test * TestSign: adding unit test for signing with different modes * Add test with Multi Signers into Robert's TxSign PR (#8142) * Add test with Multi Signers * remove true false * Use SIGN_MODE_DIRECT * Fix litn * Use correct pubkeys * Correct accNum and seq * Use amino * cleanups * client.Sign: raise error when signing tx with multiple signers in Direct + added more unit tests * add more tests * Update client/tx/tx_test.go Co-authored-by: Cory <[email protected]> * fix TestGetBroadcastCommand_WithoutOfflineFlag * Any.UnsafeSetCachedValue * fix note packed messages in tx builder * reorder unit tests * Changelog update * cleaning / linting * cli_tes: copy validator object instead of modifying it's shared codec * x/auth cli_test: remove custom codec creation in tests * Update CHANGELOG.md * updates to CHANGELOG.md * remove unused method * add new instance of transaction builder for TestSign Co-authored-by: Cory <[email protected]> Co-authored-by: SaReN <[email protected]> Co-authored-by: Alessio Treglia <[email protected]> Co-authored-by: Amaury <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 5aaae12 commit 3a9e696

File tree

22 files changed

+416
-200
lines changed

22 files changed

+416
-200
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,24 @@ Ref: https://keepachangelog.com/en/1.0.0/
5555

5656
* (x/upgrade) [\#7979](https://github.com/cosmos/cosmos-sdk/pull/7979) keeper pubkey storage serialization migration from bech32 to protobuf.
5757

58+
59+
### Features
60+
* (codec/types) [\#8106](https://github.com/cosmos/cosmos-sdk/pull/8106) Adding `NewAnyWithCustomTypeURL` to correctly marshal Messages in TxBuilder.
61+
5862
### Bug Fixes
5963

6064
* (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.
65+
* (x/auth/client/cli) [\#8106](https://github.com/cosmos/cosmos-sdk/pull/8106) fixing regression bugs in transaction signing.
66+
6167

6268
### API Breaking
6369

6470
* [\#8080](https://github.com/cosmos/cosmos-sdk/pull/8080) Updated the `codec.Marshaler` interface
6571
* 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.
6672
* (client) [\#8107](https://github.com/cosmos/cosmos-sdk/pull/8107) Renamed `PrintOutput` and `PrintOutputLegacy` methods of the `context.Client` object to `PrintProto` and `PrintObjectLegacy`.
73+
* (x/auth/tx) [\#8106](https://github.com/cosmos/cosmos-sdk/pull/8106) change related to missing append functionality in client transaction signing
74+
+ added `overwriteSig` argument to `x/auth/client.SignTx` and `client/tx.Sign` functions.
75+
+ 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.
6776

6877

6978
## [v0.40.0-rc3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0-rc3) - 2020-11-06

client/context.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,20 @@ func (ctx Context) WithInterfaceRegistry(interfaceRegistry codectypes.InterfaceR
205205
return ctx
206206
}
207207

208-
// PrintString prints the raw string to ctx.Output or os.Stdout
208+
// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
209209
func (ctx Context) PrintString(str string) error {
210+
return ctx.PrintBytes([]byte(str))
211+
}
212+
213+
// PrintBytes prints the raw bytes to ctx.Output if it's defined, otherwise to os.Stdout.
214+
// NOTE: for printing a complex state object, you should use ctx.PrintOutput
215+
func (ctx Context) PrintBytes(o []byte) error {
210216
writer := ctx.Output
211217
if writer == nil {
212218
writer = os.Stdout
213219
}
214220

215-
_, err := writer.Write([]byte(str))
221+
_, err := writer.Write(o)
216222
return err
217223
}
218224

client/tx/tx.go

+32-9
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
117117
}
118118
}
119119

120-
err = Sign(txf, clientCtx.GetFromName(), tx)
120+
err = Sign(txf, clientCtx.GetFromName(), tx, true)
121121
if err != nil {
122122
return err
123123
}
@@ -375,10 +375,21 @@ func SignWithPrivKey(
375375
return sigV2, nil
376376
}
377377

378-
// Sign signs a given tx with the provided name and passphrase. The bytes signed
379-
// over are canconical. The resulting signature will be set on the transaction.
378+
func checkMultipleSigners(mode signing.SignMode, tx authsigning.Tx) error {
379+
if mode == signing.SignMode_SIGN_MODE_DIRECT &&
380+
len(tx.GetSigners()) > 1 {
381+
return sdkerrors.Wrap(sdkerrors.ErrNotSupported, "Signing in DIRECT mode is only supported for transactions with one signer only")
382+
}
383+
return nil
384+
}
385+
386+
// Sign signs a given tx with a named key. The bytes signed over are canconical.
387+
// The resulting signature will be added to the transaction builder overwriting the previous
388+
// ones if overwrite=true (otherwise, the signature will be appended).
389+
// Signing a transaction with mutltiple signers in the DIRECT mode is not supprted and will
390+
// return an error.
380391
// An error is returned upon failure.
381-
func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
392+
func Sign(txf Factory, name string, txBuilder client.TxBuilder, overwriteSig bool) error {
382393
if txf.keybase == nil {
383394
return errors.New("keybase must be set prior to signing a transaction")
384395
}
@@ -388,12 +399,14 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
388399
// use the SignModeHandler's default mode if unspecified
389400
signMode = txf.txConfig.SignModeHandler().DefaultMode()
390401
}
402+
if err := checkMultipleSigners(signMode, txBuilder.GetTx()); err != nil {
403+
return err
404+
}
391405

392406
key, err := txf.keybase.Key(name)
393407
if err != nil {
394408
return err
395409
}
396-
397410
pubKey := key.GetPubKey()
398411
signerData := authsigning.SignerData{
399412
ChainID: txf.chainID,
@@ -418,18 +431,25 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
418431
Data: &sigData,
419432
Sequence: txf.Sequence(),
420433
}
434+
var prevSignatures []signing.SignatureV2
435+
if !overwriteSig {
436+
prevSignatures, err = txBuilder.GetTx().GetSignaturesV2()
437+
if err != nil {
438+
return err
439+
}
440+
}
421441
if err := txBuilder.SetSignatures(sig); err != nil {
422442
return err
423443
}
424444

425445
// Generate the bytes to be signed.
426-
signBytes, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
446+
bytesToSign, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx())
427447
if err != nil {
428448
return err
429449
}
430450

431451
// Sign those bytes
432-
sigBytes, _, err := txf.keybase.Sign(name, signBytes)
452+
sigBytes, _, err := txf.keybase.Sign(name, bytesToSign)
433453
if err != nil {
434454
return err
435455
}
@@ -445,8 +465,11 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder) error {
445465
Sequence: txf.Sequence(),
446466
}
447467

448-
// And here the tx is populated with the signature
449-
return txBuilder.SetSignatures(sig)
468+
if overwriteSig {
469+
return txBuilder.SetSignatures(sig)
470+
}
471+
prevSignatures = append(prevSignatures, sig)
472+
return txBuilder.SetSignatures(prevSignatures...)
450473
}
451474

452475
// GasEstimateResponse defines a response definition for tx gas estimation.

client/tx/tx_test.go

+93-30
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import (
1010
"github.com/cosmos/cosmos-sdk/client/tx"
1111
"github.com/cosmos/cosmos-sdk/crypto/hd"
1212
"github.com/cosmos/cosmos-sdk/crypto/keyring"
13+
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
1314
"github.com/cosmos/cosmos-sdk/simapp"
1415
sdk "github.com/cosmos/cosmos-sdk/types"
1516
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
17+
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
1618
"github.com/cosmos/cosmos-sdk/x/auth/signing"
1719
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
1820
)
@@ -121,49 +123,110 @@ func TestBuildUnsignedTx(t *testing.T) {
121123
}
122124

123125
func TestSign(t *testing.T) {
126+
requireT := require.New(t)
124127
path := hd.CreateHDPath(118, 0, 0).String()
125128
kr, err := keyring.New(t.Name(), "test", t.TempDir(), nil)
126-
require.NoError(t, err)
127-
128-
var from = "test_sign"
129+
requireT.NoError(err)
129130

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

134-
info, err := kr.NewAccount(from, seed, "", path, hd.Secp256k1)
135-
require.NoError(t, err)
136-
137-
txf := tx.Factory{}.
138-
WithTxConfig(NewTestTxConfig()).
139-
WithAccountNumber(50).
140-
WithSequence(23).
141-
WithFees("50stake").
142-
WithMemo("memo").
143-
WithChainID("test-chain")
134+
// create a new key using a mnemonic generator and test if we can reuse seed to recreate that account
135+
_, seed, err := kr.NewMnemonic(from1, keyring.English, path, hd.Secp256k1)
136+
requireT.NoError(err)
137+
requireT.NoError(kr.Delete(from1))
138+
info1, _, err := kr.NewMnemonic(from1, keyring.English, path, hd.Secp256k1)
139+
requireT.NoError(err)
144140

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

149-
t.Log("should failed if txf without keyring")
150-
err = tx.Sign(txf, from, txn)
151-
require.Error(t, err)
144+
pubKey1 := info1.GetPubKey()
145+
pubKey2 := info2.GetPubKey()
146+
requireT.NotEqual(pubKey1.Bytes(), pubKey2.Bytes())
147+
t.Log("Pub keys:", pubKey1, pubKey2)
152148

153-
txf = tx.Factory{}.
154-
WithKeybase(kr).
149+
txfNoKeybase := tx.Factory{}.
155150
WithTxConfig(NewTestTxConfig()).
156151
WithAccountNumber(50).
157152
WithSequence(23).
158153
WithFees("50stake").
159154
WithMemo("memo").
160155
WithChainID("test-chain")
156+
txfDirect := txfNoKeybase.
157+
WithKeybase(kr).
158+
WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT)
159+
txfAmino := txfDirect.
160+
WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
161+
msg1 := banktypes.NewMsgSend(info1.GetAddress(), sdk.AccAddress("to"), nil)
162+
msg2 := banktypes.NewMsgSend(info2.GetAddress(), sdk.AccAddress("to"), nil)
163+
txb, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
164+
requireT.NoError(err)
165+
txb2, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
166+
requireT.NoError(err)
167+
txbSimple, err := tx.BuildUnsignedTx(txfNoKeybase, msg2)
168+
requireT.NoError(err)
161169

162-
t.Log("should succeed if txf with keyring")
163-
err = tx.Sign(txf, from, txn)
164-
require.NoError(t, err)
170+
testCases := []struct {
171+
name string
172+
txf tx.Factory
173+
txb client.TxBuilder
174+
from string
175+
overwrite bool
176+
expectedPKs []cryptotypes.PubKey
177+
matchingSigs []int // if not nil, check matching signature against old ones.
178+
}{
179+
{"should fail if txf without keyring",
180+
txfNoKeybase, txb, from1, true, nil, nil},
181+
{"should fail for non existing key",
182+
txfAmino, txb, "unknown", true, nil, nil},
183+
{"amino: should succeed with keyring",
184+
txfAmino, txbSimple, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
185+
{"direct: should succeed with keyring",
186+
txfDirect, txbSimple, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
187+
188+
/**** test double sign Amino mode ****/
189+
{"amino: should sign multi-signers tx",
190+
txfAmino, txb, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
191+
{"amino: should append a second signature and not overwrite",
192+
txfAmino, txb, from2, false, []cryptotypes.PubKey{pubKey1, pubKey2}, []int{0, 0}},
193+
{"amino: should overwrite a signature",
194+
txfAmino, txb, from2, true, []cryptotypes.PubKey{pubKey2}, []int{1, 0}},
195+
196+
/**** test double sign Direct mode
197+
signing transaction with more than 2 signers should fail in DIRECT mode ****/
198+
{"direct: should fail to append a signature with different mode",
199+
txfDirect, txb, from1, false, []cryptotypes.PubKey{}, nil},
200+
{"direct: should fail to sign multi-signers tx",
201+
txfDirect, txb2, from1, false, []cryptotypes.PubKey{}, nil},
202+
{"direct: should fail to overwrite multi-signers tx",
203+
txfDirect, txb2, from1, true, []cryptotypes.PubKey{}, nil},
204+
}
205+
var prevSigs []signingtypes.SignatureV2
206+
for _, tc := range testCases {
207+
t.Run(tc.name, func(t *testing.T) {
208+
err = tx.Sign(tc.txf, tc.from, tc.txb, tc.overwrite)
209+
if len(tc.expectedPKs) == 0 {
210+
requireT.Error(err)
211+
} else {
212+
requireT.NoError(err)
213+
sigs := testSigners(requireT, tc.txb.GetTx(), tc.expectedPKs...)
214+
if tc.matchingSigs != nil {
215+
requireT.Equal(prevSigs[tc.matchingSigs[0]], sigs[tc.matchingSigs[1]])
216+
}
217+
prevSigs = sigs
218+
}
219+
})
220+
}
221+
}
165222

166-
t.Log("should fail for non existing key")
167-
err = tx.Sign(txf, "non_existing_key", txn)
168-
require.Error(t, err)
223+
func testSigners(require *require.Assertions, tr signing.Tx, pks ...cryptotypes.PubKey) []signingtypes.SignatureV2 {
224+
sigs, err := tr.GetSignaturesV2()
225+
require.Len(sigs, len(pks))
226+
require.NoError(err)
227+
require.Len(sigs, len(pks))
228+
for i := range pks {
229+
require.True(sigs[i].PubKey.Equals(pks[i]), "Signature is signed with a wrong pubkey. Got: %s, expected: %s", sigs[i].PubKey, pks[i])
230+
}
231+
return sigs
169232
}

codec/types/any.go

+13
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ func NewAnyWithValue(value proto.Message) (*Any, error) {
6969
return any, nil
7070
}
7171

72+
// NewAnyWithCustomTypeURL same as NewAnyWithValue, but sets a custom type url, instead
73+
// using the one from proto.Message.
74+
// NOTE: This functions should be only used for types with additional logic bundled
75+
// into the protobuf Any serialization. For simple marshaling you should use NewAnyWithValue.
76+
func NewAnyWithCustomTypeURL(v proto.Message, typeURL string) (*Any, error) {
77+
bz, err := proto.Marshal(v)
78+
return &Any{
79+
TypeUrl: typeURL,
80+
Value: bz,
81+
cachedValue: v,
82+
}, err
83+
}
84+
7285
// Pack packs the value x in the Any or returns an error. This also caches
7386
// the packed value so that it can be retrieved from GetCachedValue without
7487
// unmarshaling

server/grpc/server_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() {
154154
WithSignMode(signing.SignMode_SIGN_MODE_DIRECT)
155155

156156
// Sign Tx.
157-
err := authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false)
157+
err := authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false, true)
158158
s.Require().NoError(err)
159159

160160
txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())

simapp/simd/cmd/testnet.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func InitTestnet(
230230
WithKeybase(kb).
231231
WithTxConfig(clientCtx.TxConfig)
232232

233-
if err := tx.Sign(txFactory, nodeDirName, txBuilder); err != nil {
233+
if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil {
234234
return err
235235
}
236236

testutil/network/network.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ func New(t *testing.T, cfg Config) *Network {
323323
WithKeybase(kb).
324324
WithTxConfig(cfg.TxConfig)
325325

326-
err = tx.Sign(txFactory, nodeDirName, txBuilder)
326+
err = tx.Sign(txFactory, nodeDirName, txBuilder, true)
327327
require.NoError(t, err)
328328

329329
txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())

types/errors/errors.go

+4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ var (
131131
// the same resource and one of them fails.
132132
ErrConflict = Register(RootCodespace, 36, "conflict")
133133

134+
// ErrNotSupported is returned when we call a branch of a code which is currently not
135+
// supported.
136+
ErrNotSupported = Register(RootCodespace, 37, "feature not supported")
137+
134138
// ErrPanic is only set when we recover from a panic, so we know to
135139
// redact potentially sensitive system info
136140
ErrPanic = Register(UndefinedCodespace, 111222, "panic")

types/tx/types.go

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func (t *Tx) GetMsgs() []sdk.Msg {
2828
for i, any := range anys {
2929
var msg sdk.Msg
3030
if isServiceMsg(any.TypeUrl) {
31+
req := any.GetCachedValue()
32+
if req == nil {
33+
panic("Any cached value is nil. Transaction messages must be correctly packed Any values.")
34+
}
3135
msg = sdk.ServiceMsg{
3236
MethodName: any.TypeUrl,
3337
Request: any.GetCachedValue().(sdk.MsgRequest),

x/auth/client/cli/broadcast.go

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ $ <appd> tx broadcast ./mytxn.json
2626
Args: cobra.ExactArgs(1),
2727
RunE: func(cmd *cobra.Command, args []string) error {
2828
clientCtx := client.GetClientContextFromCmd(cmd)
29+
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
30+
if err != nil {
31+
return err
32+
}
2933

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

0 commit comments

Comments
 (0)