Skip to content

Commit

Permalink
x/auth: turn sign --validate-sigantures into a standalone command
Browse files Browse the repository at this point in the history
--validate-signatures should not be a flag of the sign command
as the operation performed (transaction signatures verification)
is logically distinct.
  • Loading branch information
Alessio Treglia committed Apr 30, 2020
1 parent a8a455a commit e45f99e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 111 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ that parse log messages.
* (client) [\#5799](https://github.com/cosmos/cosmos-sdk/pull/5799) The `tx encode/decode` commands, due to change on encoding break compatibility with
older clients.
* (x/auth) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) `tx sign` command now returns an error when signing is attempted with offline/multisig keys.
* (x/auth) [\#6108](https://github.com/cosmos/cosmos-sdk/pull/6108) `tx sign` command's `--validate-signatures` flag is migrated into a `tx validate-signatures` standalone command.
* (client/keys) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Remove `keys update` command.
* (x/evidence) [\#5952](https://github.com/cosmos/cosmos-sdk/pull/5952) Remove CLI and REST handlers for querying `x/evidence` parameters.
* (server) [\#5982](https://github.com/cosmos/cosmos-sdk/pull/5982) `--pruning` now must be set to `custom` if you want to customise the granular options.
Expand Down
2 changes: 2 additions & 0 deletions client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
c.MarkFlagRequired(FlagChainID)

c.SetErr(c.ErrOrStderr())
c.SetOut(c.OutOrStdout())
}
return cmds
}
Expand Down Expand Up @@ -135,6 +136,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.MarkFlagRequired(FlagChainID)

c.SetErr(c.ErrOrStderr())
c.SetOut(c.OutOrStdout())
}
return cmds
}
Expand Down
1 change: 1 addition & 0 deletions x/auth/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
txCmd.AddCommand(
GetMultiSignCommand(cdc),
GetSignCommand(cdc),
GetValidateSignaturesCommand(cdc),
)
return txCmd
}
112 changes: 1 addition & 111 deletions x/auth/client/cli/tx_sign.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package cli

import (
"bufio"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/crypto/multisig"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -37,12 +33,6 @@ It will read a transaction from [file], sign it, and print its JSON encoding.
If the flag --signature-only flag is set, it will output a JSON representation
of the generated signature only.
If the flag --validate-signatures is set, then the command would check whether all required
signers have signed the transactions, whether the signatures were collected in the right
order, and if the signature is valid over the given transaction. If the --offline
flag is also set, signature validation over the transaction will be not be
performed as that will require RPC communication with a full node.
The --offline flag makes sure that the client will not reach out to full node.
As a result, the account and sequence number queries will not be performed and
it is required to set such parameters manually. Note, invalid values will cause
Expand All @@ -65,10 +55,6 @@ be generated via the 'multisign' command.
flagAppend, true,
"Append the signature to the existing ones. If disabled, old signatures would be overwritten. Ignored if --multisig is on",
)
cmd.Flags().Bool(
flagValidateSigs, false,
"Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order",
)
cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT")
cmd = flags.PostCommands(cmd)[0]
Expand All @@ -88,23 +74,11 @@ func preSignCmd(cmd *cobra.Command, _ []string) {

func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
stdTx, err := client.ReadStdTxFromFile(cdc, args[0])
cliCtx, txBldr, stdTx, err := readStdTxAndInitContexts(cdc, cmd, args[0])
if err != nil {
return err
}

inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
txBldr := types.NewTxBuilderFromCLI(inBuf)

if viper.GetBool(flagValidateSigs) {
if !printAndValidateSigs(cliCtx, txBldr.ChainID(), stdTx, cliCtx.Offline) {
return fmt.Errorf("signatures validation failed")
}

return nil
}

// if --signature-only is on, then override --append
var newTx types.StdTx
generateSignatureOnly := viper.GetBool(flagSigOnly)
Expand Down Expand Up @@ -174,87 +148,3 @@ func getSignatureJSON(cdc *codec.Codec, newTx types.StdTx, indent, generateSigna
}
}
}

// printAndValidateSigs will validate the signatures of a given transaction over
// its expected signers. In addition, if offline has not been supplied, the
// signature is verified over the transaction sign bytes.
func printAndValidateSigs(
cliCtx context.CLIContext, chainID string, stdTx types.StdTx, offline bool,
) bool {

fmt.Println("Signers:")

signers := stdTx.GetSigners()
for i, signer := range signers {
fmt.Printf(" %v: %v\n", i, signer.String())
}

success := true
sigs := stdTx.Signatures

fmt.Println("")
fmt.Println("Signatures:")

if len(sigs) != len(signers) {
success = false
}

for i, sig := range sigs {
sigAddr := sdk.AccAddress(sig.GetPubKey().Address())
sigSanity := "OK"

var (
multiSigHeader string
multiSigMsg string
)

if i >= len(signers) || !sigAddr.Equals(signers[i]) {
sigSanity = "ERROR: signature does not match its respective signer"
success = false
}

// Validate the actual signature over the transaction bytes since we can
// reach out to a full node to query accounts.
if !offline && success {
acc, err := types.NewAccountRetriever(client.Codec, cliCtx).GetAccount(sigAddr)
if err != nil {
fmt.Printf("failed to get account: %s\n", sigAddr)
return false
}

sigBytes := types.StdSignBytes(
chainID, acc.GetAccountNumber(), acc.GetSequence(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
)

if ok := sig.GetPubKey().VerifyBytes(sigBytes, sig.Signature); !ok {
sigSanity = "ERROR: signature invalid"
success = false
}
}

multiPK, ok := sig.GetPubKey().(multisig.PubKeyMultisigThreshold)
if ok {
var multiSig multisig.Multisignature
cliCtx.Codec.MustUnmarshalBinaryBare(sig.Signature, &multiSig)

var b strings.Builder
b.WriteString("\n MultiSig Signatures:\n")

for i := 0; i < multiSig.BitArray.Size(); i++ {
if multiSig.BitArray.GetIndex(i) {
addr := sdk.AccAddress(multiPK.PubKeys[i].Address().Bytes())
b.WriteString(fmt.Sprintf(" %d: %s (weight: %d)\n", i, addr, 1))
}
}

multiSigHeader = fmt.Sprintf(" [multisig threshold: %d/%d]", multiPK.K, len(multiPK.PubKeys))
multiSigMsg = b.String()
}

fmt.Printf(" %d: %s\t\t\t[%s]%s%s\n", i, sigAddr.String(), sigSanity, multiSigHeader, multiSigMsg)
}

fmt.Println("")
return success
}
149 changes: 149 additions & 0 deletions x/auth/client/cli/validate_sigs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package cli

import (
"bufio"
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/tendermint/tendermint/crypto/multisig"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

func GetValidateSignaturesCommand(codec *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "validate-signatures [file]",
Short: "Validate transactions signatures",
Long: `Print the addresses that must sign the transaction, those who have already
signed it, and make sure that signatures are in the correct order.
The command would check whether all required signers have signed the transactions, whether
the signatures were collected in the right order, and if the signature is valid over the
given transaction. If the --offline flag is also set, signature validation over the
transaction will be not be performed as that will require RPC communication with a full node.
`,
PreRun: preSignCmd,
RunE: makeValidateSignaturesCmd(codec),
Args: cobra.ExactArgs(1),
}

return flags.PostCommands(cmd)[0]
}

func makeValidateSignaturesCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
cliCtx, txBldr, stdTx, err := readStdTxAndInitContexts(cdc, cmd, args[0])
if err != nil {
return err
}

if !printAndValidateSigs(cmd, cliCtx, txBldr.ChainID(), stdTx, cliCtx.Offline) {
return fmt.Errorf("signatures validation failed")
}

return nil
}
}

// printAndValidateSigs will validate the signatures of a given transaction over its
// expected signers. In addition, if offline has not been supplied, the signature is
// verified over the transaction sign bytes. Returns false if the validation fails.
func printAndValidateSigs(
cmd *cobra.Command, cliCtx context.CLIContext, chainID string, stdTx types.StdTx, offline bool,
) bool {
cmd.Println("Signers:")
signers := stdTx.GetSigners()

for i, signer := range signers {
cmd.Printf(" %v: %v\n", i, signer.String())
}

success := true
sigs := stdTx.Signatures
cmd.Println("")
cmd.Println("Signatures:")

if len(sigs) != len(signers) {
success = false
}

for i, sig := range sigs {
var (
multiSigHeader string
multiSigMsg string
sigAddr = sdk.AccAddress(sig.GetPubKey().Address())
sigSanity = "OK"
)

if i >= len(signers) || !sigAddr.Equals(signers[i]) {
sigSanity = "ERROR: signature does not match its respective signer"
success = false
}

// Validate the actual signature over the transaction bytes since we can
// reach out to a full node to query accounts.
if !offline && success {
acc, err := types.NewAccountRetriever(client.Codec, cliCtx).GetAccount(sigAddr)
if err != nil {
cmd.Printf("failed to get account: %s\n", sigAddr)
return false
}

sigBytes := types.StdSignBytes(
chainID, acc.GetAccountNumber(), acc.GetSequence(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
)

if ok := sig.GetPubKey().VerifyBytes(sigBytes, sig.Signature); !ok {
sigSanity = "ERROR: signature invalid"
success = false
}
}

multiPK, ok := sig.GetPubKey().(multisig.PubKeyMultisigThreshold)
if ok {
var multiSig multisig.Multisignature
cliCtx.Codec.MustUnmarshalBinaryBare(sig.Signature, &multiSig)

var b strings.Builder
b.WriteString("\n MultiSig Signatures:\n")

for i := 0; i < multiSig.BitArray.Size(); i++ {
if multiSig.BitArray.GetIndex(i) {
addr := sdk.AccAddress(multiPK.PubKeys[i].Address().Bytes())
b.WriteString(fmt.Sprintf(" %d: %s (weight: %d)\n", i, addr, 1))
}
}

multiSigHeader = fmt.Sprintf(" [multisig threshold: %d/%d]", multiPK.K, len(multiPK.PubKeys))
multiSigMsg = b.String()
}

cmd.Printf(" %d: %s\t\t\t[%s]%s%s\n", i, sigAddr.String(), sigSanity, multiSigHeader, multiSigMsg)
}

cmd.Println("")

return success
}

func readStdTxAndInitContexts(cdc *codec.Codec, cmd *cobra.Command, filename string) (
context.CLIContext, types.TxBuilder, types.StdTx, error,
) {
stdTx, err := client.ReadStdTxFromFile(cdc, filename)
if err != nil {
return context.CLIContext{}, types.TxBuilder{}, types.StdTx{}, err
}

inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
txBldr := types.NewTxBuilderFromCLI(inBuf)

return cliCtx, txBldr, stdTx, nil
}

0 comments on commit e45f99e

Please sign in to comment.