github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/auth/client/cli/tx_sign.go (about)

     1  package cli
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/multisig"
    10  	"github.com/spf13/cobra"
    11  	"github.com/spf13/viper"
    12  
    13  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context"
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/flags"
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec"
    16  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    17  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/client/utils"
    18  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/types"
    19  )
    20  
    21  const (
    22  	flagMultisig     = "multisig"
    23  	flagAppend       = "append"
    24  	flagValidateSigs = "validate-signatures"
    25  	flagOffline      = "offline"
    26  	flagSigOnly      = "signature-only"
    27  	flagOutfile      = "output-document"
    28  )
    29  
    30  // GetSignCommand returns the transaction sign command.
    31  func GetSignCommand(codec *codec.Codec) *cobra.Command {
    32  	cmd := &cobra.Command{
    33  		Use:   "sign [file]",
    34  		Short: "Sign transactions generated offline",
    35  		Long: `Sign transactions created with the --generate-only flag.
    36  It will read a transaction from [file], sign it, and print its JSON encoding.
    37  
    38  If the flag --signature-only flag is set, it will output a JSON representation
    39  of the generated signature only.
    40  
    41  If the flag --validate-signatures is set, then the command would check whether all required
    42  signers have signed the transactions, whether the signatures were collected in the right
    43  order, and if the signature is valid over the given transaction. If the --offline
    44  flag is also set, signature validation over the transaction will be not be
    45  performed as that will require RPC communication with a full node.
    46  
    47  The --offline flag makes sure that the client will not reach out to full node.
    48  As a result, the account and sequence number queries will not be performed and
    49  it is required to set such parameters manually. Note, invalid values will cause
    50  the transaction to fail.
    51  
    52  The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account
    53  key. It implies --signature-only. Full multisig signed transactions may eventually
    54  be generated via the 'multisign' command.
    55  `,
    56  		PreRun: preSignCmd,
    57  		RunE:   makeSignCmd(codec),
    58  		Args:   cobra.ExactArgs(1),
    59  	}
    60  
    61  	cmd.Flags().String(
    62  		flagMultisig, "",
    63  		"Address of the multisig account on behalf of which the transaction shall be signed",
    64  	)
    65  	cmd.Flags().Bool(
    66  		flagAppend, true,
    67  		"Append the signature to the existing ones. If disabled, old signatures would be overwritten. Ignored if --multisig is on",
    68  	)
    69  	cmd.Flags().Bool(
    70  		flagValidateSigs, false,
    71  		"Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order",
    72  	)
    73  	cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
    74  	cmd.Flags().Bool(
    75  		flagOffline, false,
    76  		"Offline mode; Do not query a full node. --account and --sequence options would be required if offline is set",
    77  	)
    78  	cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT")
    79  
    80  	cmd = flags.PostCommands(cmd)[0]
    81  	cmd.MarkFlagRequired(flags.FlagFrom)
    82  
    83  	return cmd
    84  }
    85  
    86  func preSignCmd(cmd *cobra.Command, _ []string) {
    87  	// Conditionally mark the account and sequence numbers required as no RPC
    88  	// query will be done.
    89  	if viper.GetBool(flagOffline) {
    90  		cmd.MarkFlagRequired(flags.FlagAccountNumber)
    91  		cmd.MarkFlagRequired(flags.FlagSequence)
    92  	}
    93  }
    94  
    95  func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error {
    96  	return func(cmd *cobra.Command, args []string) error {
    97  		stdTx, err := utils.ReadStdTxFromFile(cdc, args[0])
    98  		if err != nil {
    99  			return err
   100  		}
   101  
   102  		inBuf := bufio.NewReader(cmd.InOrStdin())
   103  		offline := viper.GetBool(flagOffline)
   104  		cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
   105  		txBldr := types.NewTxBuilderFromCLI(inBuf)
   106  
   107  		if viper.GetBool(flagValidateSigs) {
   108  			if !printAndValidateSigs(cliCtx, txBldr.ChainID(), stdTx, offline) {
   109  				return fmt.Errorf("signatures validation failed")
   110  			}
   111  
   112  			return nil
   113  		}
   114  
   115  		// if --signature-only is on, then override --append
   116  		var newTx *types.StdTx
   117  		generateSignatureOnly := viper.GetBool(flagSigOnly)
   118  		multisigAddrStr := viper.GetString(flagMultisig)
   119  
   120  		if multisigAddrStr != "" {
   121  			var multisigAddr sdk.AccAddress
   122  
   123  			multisigAddr, err = sdk.AccAddressFromBech32(multisigAddrStr)
   124  			if err != nil {
   125  				return err
   126  			}
   127  
   128  			newTx, err = utils.SignStdTxWithSignerAddress(
   129  				txBldr, cliCtx, multisigAddr, cliCtx.GetFromName(), stdTx, offline,
   130  			)
   131  			generateSignatureOnly = true
   132  		} else {
   133  			appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly
   134  			newTx, err = utils.SignStdTx(txBldr, cliCtx, cliCtx.GetFromName(), stdTx, appendSig, offline)
   135  		}
   136  
   137  		if err != nil {
   138  			return err
   139  		}
   140  
   141  		json, err := getSignatureJSON(cdc, newTx, cliCtx.Indent, generateSignatureOnly)
   142  		if err != nil {
   143  			return err
   144  		}
   145  
   146  		if viper.GetString(flagOutfile) == "" {
   147  			fmt.Printf("%s\n", json)
   148  			return nil
   149  		}
   150  
   151  		fp, err := os.OpenFile(
   152  			viper.GetString(flagOutfile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644,
   153  		)
   154  		if err != nil {
   155  			return err
   156  		}
   157  
   158  		defer fp.Close()
   159  		fmt.Fprintf(fp, "%s\n", json)
   160  
   161  		return nil
   162  	}
   163  }
   164  
   165  func getSignatureJSON(cdc *codec.Codec, newTx *types.StdTx, indent, generateSignatureOnly bool) ([]byte, error) {
   166  	switch generateSignatureOnly {
   167  	case true:
   168  		switch indent {
   169  		case true:
   170  			return cdc.MarshalJSONIndent(newTx.Signatures[0], "", "  ")
   171  
   172  		default:
   173  			return cdc.MarshalJSON(newTx.Signatures[0])
   174  		}
   175  	default:
   176  		switch indent {
   177  		case true:
   178  			return cdc.MarshalJSONIndent(newTx, "", "  ")
   179  
   180  		default:
   181  			return cdc.MarshalJSON(newTx)
   182  		}
   183  	}
   184  }
   185  
   186  // printAndValidateSigs will validate the signatures of a given transaction over
   187  // its expected signers. In addition, if offline has not been supplied, the
   188  // signature is verified over the transaction sign bytes.
   189  func printAndValidateSigs(
   190  	cliCtx context.CLIContext, chainID string, stdTx *types.StdTx, offline bool,
   191  ) bool {
   192  
   193  	fmt.Println("Signers:")
   194  
   195  	signers := stdTx.GetSigners()
   196  	for i, signer := range signers {
   197  		fmt.Printf("  %v: %v\n", i, signer.String())
   198  	}
   199  
   200  	success := true
   201  	sigs := stdTx.Signatures
   202  
   203  	fmt.Println("")
   204  	fmt.Println("Signatures:")
   205  
   206  	if len(sigs) != len(signers) {
   207  		success = false
   208  	}
   209  
   210  	for i, sig := range sigs {
   211  		sigAddr := sdk.AccAddress(sig.Address())
   212  		sigSanity := "OK"
   213  
   214  		var (
   215  			multiSigHeader string
   216  			multiSigMsg    string
   217  		)
   218  
   219  		if i >= len(signers) || !sigAddr.Equals(signers[i]) {
   220  			sigSanity = "ERROR: signature does not match its respective signer"
   221  			success = false
   222  		}
   223  
   224  		// Validate the actual signature over the transaction bytes since we can
   225  		// reach out to a full node to query accounts.
   226  		if !offline && success {
   227  			acc, err := types.NewAccountRetriever(cliCtx).GetAccount(sigAddr)
   228  			if err != nil {
   229  				fmt.Printf("failed to get account: %s\n", sigAddr)
   230  				return false
   231  			}
   232  
   233  			sigBytes := types.StdSignBytes(
   234  				chainID, acc.GetAccountNumber(), acc.GetSequence(),
   235  				stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
   236  			)
   237  
   238  			if ok := sig.VerifyBytes(sigBytes, sig.Signature); !ok {
   239  				sigSanity = "ERROR: signature invalid"
   240  				success = false
   241  			}
   242  		}
   243  
   244  		multiPK, ok := sig.PubKey.(multisig.PubKeyMultisigThreshold)
   245  		if ok {
   246  			var multiSig multisig.Multisignature
   247  			cliCtx.Codec.MustUnmarshalBinaryBare(sig.Signature, &multiSig)
   248  
   249  			var b strings.Builder
   250  			b.WriteString("\n  MultiSig Signatures:\n")
   251  
   252  			for i := 0; i < multiSig.BitArray.Size(); i++ {
   253  				if multiSig.BitArray.GetIndex(i) {
   254  					addr := sdk.AccAddress(multiPK.PubKeys[i].Address().Bytes())
   255  					b.WriteString(fmt.Sprintf("    %d: %s (weight: %d)\n", i, addr, 1))
   256  				}
   257  			}
   258  
   259  			multiSigHeader = fmt.Sprintf(" [multisig threshold: %d/%d]", multiPK.K, len(multiPK.PubKeys))
   260  			multiSigMsg = b.String()
   261  		}
   262  
   263  		fmt.Printf("  %d: %s\t\t\t[%s]%s%s\n", i, sigAddr.String(), sigSanity, multiSigHeader, multiSigMsg)
   264  	}
   265  
   266  	fmt.Println("")
   267  	return success
   268  }