github.com/cosmos/cosmos-sdk@v0.50.10/x/auth/client/cli/tx_sign.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/spf13/cobra"
     8  
     9  	"github.com/cosmos/cosmos-sdk/client"
    10  	"github.com/cosmos/cosmos-sdk/client/flags"
    11  	"github.com/cosmos/cosmos-sdk/client/tx"
    12  	kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
    13  	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    14  	sdk "github.com/cosmos/cosmos-sdk/types"
    15  	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
    16  )
    17  
    18  const (
    19  	flagMultisig                  = "multisig"
    20  	flagOverwrite                 = "overwrite"
    21  	flagSigOnly                   = "signature-only"
    22  	flagSkipSignatureVerification = "skip-signature-verification"
    23  	flagNoAutoIncrement           = "no-auto-increment"
    24  	flagAppend                    = "append"
    25  )
    26  
    27  // GetSignBatchCommand returns the transaction sign-batch command.
    28  func GetSignBatchCommand() *cobra.Command {
    29  	cmd := &cobra.Command{
    30  		Use:   "sign-batch [file] ([file2]...)",
    31  		Short: "Sign transaction batch files",
    32  		Long: `Sign batch files of transactions generated with --generate-only.
    33  The command processes list of transactions from a file (one StdTx each line), or multiple files.
    34  Then generates signed transactions or signatures and print their JSON encoding, delimited by '\n'.
    35  As the signatures are generated, the command updates the account and sequence number accordingly.
    36  
    37  If the --signature-only flag is set, it will output the signature parts only.
    38  
    39  The --offline flag makes sure that the client will not reach out to full node.
    40  As a result, the account and the sequence number queries will not be performed and
    41  it is required to set such parameters manually. Note, invalid values will cause
    42  the transaction to fail. The sequence will be incremented automatically for each
    43  transaction that is signed.
    44  
    45  If --account-number or --sequence flag is used when offline=false, they are ignored and 
    46  overwritten by the default flag values.
    47  
    48  The --multisig=<multisig_key> flag generates a signature on behalf of a multisig
    49  account key. It implies --signature-only.
    50  `,
    51  		PreRun: preSignCmd,
    52  		RunE:   makeSignBatchCmd(),
    53  		Args:   cobra.MinimumNArgs(1),
    54  	}
    55  
    56  	cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
    57  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
    58  	cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
    59  	cmd.Flags().Bool(flagAppend, false, "Combine all message and generate single signed transaction for broadcast.")
    60  
    61  	flags.AddTxFlagsToCmd(cmd)
    62  
    63  	cmd.MarkFlagRequired(flags.FlagFrom)
    64  
    65  	return cmd
    66  }
    67  
    68  func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
    69  	return func(cmd *cobra.Command, args []string) error {
    70  		clientCtx, err := client.GetClientTxContext(cmd)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
    75  		if err != nil {
    76  			return err
    77  		}
    78  		txCfg := clientCtx.TxConfig
    79  		printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly)
    80  
    81  		ms, err := cmd.Flags().GetString(flagMultisig)
    82  		if err != nil {
    83  			return err
    84  		}
    85  
    86  		// prepare output document
    87  		closeFunc, err := setOutputFile(cmd)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		defer closeFunc()
    92  		clientCtx.WithOutput(cmd.OutOrStdout())
    93  
    94  		// reads tx from args
    95  		scanner, err := authclient.ReadTxsFromInput(txCfg, args...)
    96  		if err != nil {
    97  			return err
    98  		}
    99  
   100  		if !clientCtx.Offline {
   101  			if ms == "" {
   102  				from, err := cmd.Flags().GetString(flags.FlagFrom)
   103  				if err != nil {
   104  					return err
   105  				}
   106  
   107  				addr, _, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), from)
   108  				if err != nil {
   109  					return err
   110  				}
   111  
   112  				acc, err := txFactory.AccountRetriever().GetAccount(clientCtx, addr)
   113  				if err != nil {
   114  					return err
   115  				}
   116  
   117  				txFactory = txFactory.WithAccountNumber(acc.GetAccountNumber()).WithSequence(acc.GetSequence())
   118  			} else {
   119  				txFactory = txFactory.WithAccountNumber(0).WithSequence(0)
   120  			}
   121  		}
   122  
   123  		appendMessagesToSingleTx, _ := cmd.Flags().GetBool(flagAppend)
   124  		// Combines all tx msgs and create single signed transaction
   125  		if appendMessagesToSingleTx {
   126  			var totalFees sdk.Coins
   127  			txBuilder := txCfg.NewTxBuilder()
   128  			msgs := make([]sdk.Msg, 0)
   129  			newGasLimit := uint64(0)
   130  
   131  			for scanner.Scan() {
   132  				unsignedStdTx := scanner.Tx()
   133  				fe, err := clientCtx.TxConfig.WrapTxBuilder(unsignedStdTx)
   134  				if err != nil {
   135  					return err
   136  				}
   137  				// increment the gas
   138  				newGasLimit += fe.GetTx().GetGas()
   139  				// Individual fee values from each transaction need to be
   140  				// aggregated to calculate the total fee for the batch of transactions.
   141  				// https://github.com/cosmos/cosmos-sdk/issues/18064
   142  				unmergedFees := fe.GetTx().GetFee()
   143  				for _, fee := range unmergedFees {
   144  					totalFees = totalFees.Add(fee)
   145  				}
   146  				// append messages
   147  				msgs = append(msgs, unsignedStdTx.GetMsgs()...)
   148  			}
   149  			// set the new appened msgs into builder
   150  			txBuilder.SetMsgs(msgs...)
   151  
   152  			// set the memo,fees,feeGranter,feePayer from cmd flags
   153  			txBuilder.SetMemo(txFactory.Memo())
   154  			txBuilder.SetFeeGranter(clientCtx.FeeGranter)
   155  			txBuilder.SetFeePayer(clientCtx.FeePayer)
   156  
   157  			// set the gasLimit
   158  			txBuilder.SetGasLimit(newGasLimit)
   159  
   160  			// set the feeAmount
   161  			txBuilder.SetFeeAmount(totalFees)
   162  
   163  			// sign the txs
   164  			if ms == "" {
   165  				from, _ := cmd.Flags().GetString(flags.FlagFrom)
   166  				if err := sign(clientCtx, txBuilder, txFactory, from); err != nil {
   167  					return err
   168  				}
   169  			} else {
   170  				if err := multisigSign(clientCtx, txBuilder, txFactory, ms); err != nil {
   171  					return err
   172  				}
   173  			}
   174  
   175  			json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   176  			if err != nil {
   177  				return err
   178  			}
   179  
   180  			cmd.Printf("%s\n", json)
   181  		} else {
   182  			// It will generate signed tx for each tx
   183  			for sequence := txFactory.Sequence(); scanner.Scan(); sequence++ {
   184  				unsignedStdTx := scanner.Tx()
   185  				txFactory = txFactory.WithSequence(sequence)
   186  				txBuilder, err := txCfg.WrapTxBuilder(unsignedStdTx)
   187  				if err != nil {
   188  					return err
   189  				}
   190  
   191  				// sign the txs
   192  				if ms == "" {
   193  					from, _ := cmd.Flags().GetString(flags.FlagFrom)
   194  					if err := sign(clientCtx, txBuilder, txFactory, from); err != nil {
   195  						return err
   196  					}
   197  				} else {
   198  					if err := multisigSign(clientCtx, txBuilder, txFactory, ms); err != nil {
   199  						return err
   200  					}
   201  				}
   202  
   203  				json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   204  				if err != nil {
   205  					return err
   206  				}
   207  				cmd.Printf("%s\n", json)
   208  			}
   209  		}
   210  
   211  		if err := scanner.UnmarshalErr(); err != nil {
   212  			return err
   213  		}
   214  
   215  		return scanner.UnmarshalErr()
   216  	}
   217  }
   218  
   219  func sign(clientCtx client.Context, txBuilder client.TxBuilder, txFactory tx.Factory, from string) error {
   220  	_, fromName, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), from)
   221  	if err != nil {
   222  		return fmt.Errorf("error getting account from keybase: %w", err)
   223  	}
   224  
   225  	if err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true); err != nil {
   226  		return err
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  func multisigSign(clientCtx client.Context, txBuilder client.TxBuilder, txFactory tx.Factory, multisig string) error {
   233  	multisigAddr, multisigName, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), multisig)
   234  	if err != nil {
   235  		return fmt.Errorf("error getting account from keybase: %w", err)
   236  	}
   237  
   238  	fromRecord, err := clientCtx.Keyring.Key(clientCtx.FromName)
   239  	if err != nil {
   240  		return fmt.Errorf("error getting account from keybase: %w", err)
   241  	}
   242  
   243  	fromPubKey, err := fromRecord.GetPubKey()
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	multisigkey, err := clientCtx.Keyring.Key(multisigName)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	multisigPubKey, err := multisigkey.GetPubKey()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	isSigner, err := isMultisigSigner(clientCtx, multisigPubKey, fromPubKey)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	if !isSigner {
   264  		return fmt.Errorf("signing key is not a part of multisig key")
   265  	}
   266  
   267  	if err = authclient.SignTxWithSignerAddress(
   268  		txFactory,
   269  		clientCtx,
   270  		multisigAddr,
   271  		clientCtx.GetFromName(),
   272  		txBuilder,
   273  		clientCtx.Offline,
   274  		true,
   275  	); err != nil {
   276  		return err
   277  	}
   278  
   279  	return nil
   280  }
   281  
   282  // isMultisigSigner checks if the given pubkey is a signer in the multisig or in
   283  // any of the nested multisig signers.
   284  func isMultisigSigner(clientCtx client.Context, multisigPubKey, fromPubKey cryptotypes.PubKey) (bool, error) {
   285  	multisigLegacyPub := multisigPubKey.(*kmultisig.LegacyAminoPubKey)
   286  
   287  	var found bool
   288  	for _, pubkey := range multisigLegacyPub.GetPubKeys() {
   289  		if pubkey.Equals(fromPubKey) {
   290  			found = true
   291  			break
   292  		}
   293  
   294  		if nestedMultisig, ok := pubkey.(*kmultisig.LegacyAminoPubKey); ok {
   295  			var err error
   296  			found, err = isMultisigSigner(clientCtx, nestedMultisig, fromPubKey)
   297  			if err != nil {
   298  				return false, err
   299  			}
   300  			if found {
   301  				break
   302  			}
   303  		}
   304  	}
   305  
   306  	return found, nil
   307  }
   308  
   309  func setOutputFile(cmd *cobra.Command) (func(), error) {
   310  	outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   311  	if outputDoc == "" {
   312  		return func() {}, nil
   313  	}
   314  
   315  	fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   316  	if err != nil {
   317  		return func() {}, err
   318  	}
   319  
   320  	cmd.SetOut(fp)
   321  
   322  	return func() { fp.Close() }, nil
   323  }
   324  
   325  // GetSignCommand returns the transaction sign command.
   326  func GetSignCommand() *cobra.Command {
   327  	cmd := &cobra.Command{
   328  		Use:   "sign [file]",
   329  		Short: "Sign a transaction generated offline",
   330  		Long: `Sign a transaction created with the --generate-only flag.
   331  It will read a transaction from [file], sign it, and print its JSON encoding.
   332  
   333  If the --signature-only flag is set, it will output the signature parts only.
   334  
   335  The --offline flag makes sure that the client will not reach out to full node.
   336  As a result, the account and sequence number queries will not be performed and
   337  it is required to set such parameters manually. Note, invalid values will cause
   338  the transaction to fail.
   339  
   340  The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account
   341  key. It implies --signature-only. Full multisig signed transactions may eventually
   342  be generated via the 'multisign' command.
   343  `,
   344  		PreRun: preSignCmd,
   345  		RunE:   makeSignCmd(),
   346  		Args:   cobra.ExactArgs(1),
   347  	}
   348  
   349  	cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
   350  	cmd.Flags().Bool(flagOverwrite, false, "Overwrite existing signatures with a new one. If disabled, new signature will be appended")
   351  	cmd.Flags().Bool(flagSigOnly, false, "Print only the signatures")
   352  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
   353  	flags.AddTxFlagsToCmd(cmd)
   354  
   355  	cmd.MarkFlagRequired(flags.FlagFrom)
   356  
   357  	return cmd
   358  }
   359  
   360  func preSignCmd(cmd *cobra.Command, _ []string) {
   361  	// Conditionally mark the account and sequence numbers required as no RPC
   362  	// query will be done.
   363  	if offline, _ := cmd.Flags().GetBool(flags.FlagOffline); offline {
   364  		cmd.MarkFlagRequired(flags.FlagAccountNumber)
   365  		cmd.MarkFlagRequired(flags.FlagSequence)
   366  	}
   367  }
   368  
   369  func makeSignCmd() func(cmd *cobra.Command, args []string) error {
   370  	return func(cmd *cobra.Command, args []string) (err error) {
   371  		var clientCtx client.Context
   372  
   373  		clientCtx, err = client.GetClientTxContext(cmd)
   374  		if err != nil {
   375  			return err
   376  		}
   377  
   378  		clientCtx, txF, newTx, err := readTxAndInitContexts(clientCtx, cmd, args[0])
   379  		if err != nil {
   380  			return err
   381  		}
   382  
   383  		return signTx(cmd, clientCtx, txF, newTx)
   384  	}
   385  }
   386  
   387  func signTx(cmd *cobra.Command, clientCtx client.Context, txF tx.Factory, newTx sdk.Tx) error {
   388  	f := cmd.Flags()
   389  	txCfg := clientCtx.TxConfig
   390  	txBuilder, err := txCfg.WrapTxBuilder(newTx)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	printSignatureOnly, err := cmd.Flags().GetBool(flagSigOnly)
   396  	if err != nil {
   397  		return err
   398  	}
   399  
   400  	multisig, err := cmd.Flags().GetString(flagMultisig)
   401  	if err != nil {
   402  		return err
   403  	}
   404  
   405  	from, err := cmd.Flags().GetString(flags.FlagFrom)
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	_, fromName, _, err := client.GetFromFields(clientCtx, txF.Keybase(), from)
   411  	if err != nil {
   412  		return fmt.Errorf("error getting account from keybase: %w", err)
   413  	}
   414  
   415  	overwrite, err := f.GetBool(flagOverwrite)
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	if multisig != "" {
   421  		// Bech32 decode error, maybe it's a name, we try to fetch from keyring
   422  		multisigAddr, multisigName, _, err := client.GetFromFields(clientCtx, txF.Keybase(), multisig)
   423  		if err != nil {
   424  			return fmt.Errorf("error getting account from keybase: %w", err)
   425  		}
   426  		multisigkey, err := getMultisigRecord(clientCtx, multisigName)
   427  		if err != nil {
   428  			return err
   429  		}
   430  		multisigPubKey, err := multisigkey.GetPubKey()
   431  		if err != nil {
   432  			return err
   433  		}
   434  
   435  		fromRecord, err := clientCtx.Keyring.Key(fromName)
   436  		if err != nil {
   437  			return fmt.Errorf("error getting account from keybase: %w", err)
   438  		}
   439  		fromPubKey, err := fromRecord.GetPubKey()
   440  		if err != nil {
   441  			return err
   442  		}
   443  
   444  		isSigner, err := isMultisigSigner(clientCtx, multisigPubKey, fromPubKey)
   445  		if err != nil {
   446  			return err
   447  		}
   448  		if !isSigner {
   449  			return fmt.Errorf("signing key is not a part of multisig key")
   450  		}
   451  
   452  		err = authclient.SignTxWithSignerAddress(
   453  			txF, clientCtx, multisigAddr, fromName, txBuilder, clientCtx.Offline, overwrite)
   454  		if err != nil {
   455  			return err
   456  		}
   457  		printSignatureOnly = true
   458  	} else {
   459  		err = authclient.SignTx(txF, clientCtx, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, overwrite)
   460  	}
   461  	if err != nil {
   462  		return err
   463  	}
   464  
   465  	// set output
   466  	closeFunc, err := setOutputFile(cmd)
   467  	if err != nil {
   468  		return err
   469  	}
   470  
   471  	defer closeFunc()
   472  	clientCtx.WithOutput(cmd.OutOrStdout())
   473  
   474  	var json []byte
   475  	json, err = marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	cmd.Printf("%s\n", json)
   481  
   482  	return err
   483  }
   484  
   485  func marshalSignatureJSON(txConfig client.TxConfig, txBldr client.TxBuilder, signatureOnly bool) ([]byte, error) {
   486  	parsedTx := txBldr.GetTx()
   487  	if signatureOnly {
   488  		sigs, err := parsedTx.GetSignaturesV2()
   489  		if err != nil {
   490  			return nil, err
   491  		}
   492  		return txConfig.MarshalSignatureJSON(sigs)
   493  	}
   494  
   495  	return txConfig.TxJSONEncoder()(parsedTx)
   496  }