Merge PR #2995: Fully verify the signature in gaiacli tx sign

* Implement auxiliary methods/functions for just building a tx sig bytes

* Undo auxiliary methods/functions

* Validate signature

* Remove redundant comment

* Add pending log entry

* Minor cleanup

* Update sign cli doc

* Update cli sign offline flag doc

* Update printAndValidateSigs

* Implement TestGaiaCLIValidateSignatures

* Minor cleanup

* Fix linting

* Update x/auth/client/cli/sign.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Minor reformatting
This commit is contained in:
Alexander Bezobchuk
2018-12-06 02:02:04 -05:00
committed by frog power 4000
parent cfab1ad6ee
commit 5b42e83bc2
4 changed files with 141 additions and 21 deletions
+54 -13
View File
@@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"io/ioutil"
"os"
@@ -11,7 +12,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-amino"
@@ -37,10 +37,12 @@ If the flag --signature-only flag is on, it outputs a JSON representation
of the generated signature only.
If the flag --validate-signatures is on, then the command would check whether all required
signers have signed the transactions and whether the signatures were collected in the right
order.
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 provided, signature validation over the transaction will be not be
performed as that will require communication with a full node.
The --offline flag makes sure that the client will not reach out to the local cache.
The --offline flag makes sure that the client will not reach out to an external node.
Thus account number or sequence number lookups will not be performed and it is
recommended to set such parameters manually.`,
RunE: makeSignCmd(codec),
@@ -52,7 +54,7 @@ recommended to set such parameters manually.`,
cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit.")
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(flagOffline, false, "Offline mode. Do not query local cache.")
cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query a full node.")
cmd.Flags().String(flagOutfile, "",
"The document will be written to the given file instead of STDOUT")
@@ -67,10 +69,15 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error
return
}
offline := viper.GetBool(flagOffline)
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
txBldr := authtxb.NewTxBuilderFromCLI()
if viper.GetBool(flagValidateSigs) {
if !printSignatures(stdTx) {
if !printAndValidateSigs(cliCtx, txBldr.ChainID, stdTx, offline) {
return fmt.Errorf("signatures validation failed")
}
return nil
}
@@ -78,13 +85,11 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error
if name == "" {
return errors.New("required flag \"name\" has not been set")
}
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
txBldr := authtxb.NewTxBuilderFromCLI()
// if --signature-only is on, then override --append
generateSignatureOnly := viper.GetBool(flagSigOnly)
appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly
newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, appendSig, viper.GetBool(flagOffline))
newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, appendSig, offline)
if err != nil {
return err
}
@@ -107,6 +112,7 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error
json, err = cdc.MarshalJSON(newTx)
}
}
if err != nil {
return err
}
@@ -122,35 +128,70 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error
if err != nil {
return err
}
defer fp.Close()
fmt.Fprintf(fp, "%s\n", json)
return
}
}
func printSignatures(stdTx auth.StdTx) bool {
// 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 auth.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.GetSignatures()
fmt.Println("")
fmt.Println("Signatures:")
success := true
if len(sigs) != len(signers) {
success = false
}
for i, sig := range stdTx.GetSignatures() {
for i, sig := range sigs {
sigAddr := sdk.AccAddress(sig.Address())
sigSanity := "OK"
if i >= len(signers) || !sigAddr.Equals(signers[i]) {
sigSanity = fmt.Sprintf("ERROR: signature %d does not match its respective signer", 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 := cliCtx.GetAccount(sigAddr)
if err != nil {
fmt.Printf("failed to get account: %s\n", sigAddr)
return false
}
sigBytes := auth.StdSignBytes(
chainID, acc.GetAccountNumber(), acc.GetSequence(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
)
if ok := sig.VerifyBytes(sigBytes, sig.Signature); !ok {
sigSanity = "ERROR: signature invalid"
success = false
}
}
fmt.Printf(" %v: %v\t[%s]\n", i, sigAddr.String(), sigSanity)
}
fmt.Println("")
return success
}