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

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/spf13/cobra"
     9  	"github.com/spf13/viper"
    10  	"google.golang.org/protobuf/types/known/anypb"
    11  
    12  	errorsmod "cosmossdk.io/errors"
    13  	txsigning "cosmossdk.io/x/tx/signing"
    14  
    15  	"github.com/cosmos/cosmos-sdk/client"
    16  	"github.com/cosmos/cosmos-sdk/client/flags"
    17  	"github.com/cosmos/cosmos-sdk/client/tx"
    18  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    19  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    20  	kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
    21  	"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
    22  	sdk "github.com/cosmos/cosmos-sdk/types"
    23  	signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
    24  	"github.com/cosmos/cosmos-sdk/version"
    25  	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
    26  	"github.com/cosmos/cosmos-sdk/x/auth/signing"
    27  )
    28  
    29  // GetMultiSignCommand returns the multi-sign command
    30  func GetMultiSignCommand() *cobra.Command {
    31  	cmd := &cobra.Command{
    32  		Use:     "multi-sign [file] [name] [[signature]...]",
    33  		Aliases: []string{"multisign"},
    34  		Short:   "Generate multisig signatures for transactions generated offline",
    35  		Long: strings.TrimSpace(
    36  			fmt.Sprintf(`Sign transactions created with the --generate-only flag that require multisig signatures.
    37  
    38  Read one or more signatures from one or more [signature] file, generate a multisig signature compliant to the
    39  multisig key [name], and attach the key name to the transaction read from [file].
    40  
    41  Example:
    42  $ %s tx multisign transaction.json k1k2k3 k1sig.json k2sig.json k3sig.json
    43  
    44  If --signature-only flag is on, output a JSON representation
    45  of only the generated signature.
    46  
    47  If the --offline flag is on, the client will not reach out to an external node.
    48  Account number or sequence number lookups are not performed so you must
    49  set these parameters manually.
    50  
    51  If the --skip-signature-verification flag is on, the command will not verify the
    52  signatures in the provided signature files. This is useful when the multisig
    53  account is a signer in a nested multisig scenario.
    54  
    55  The current multisig implementation defaults to amino-json sign mode.
    56  The SIGN_MODE_DIRECT sign mode is not supported.'
    57  `,
    58  				version.AppName,
    59  			),
    60  		),
    61  		RunE: makeMultiSignCmd(),
    62  		Args: cobra.MinimumNArgs(3),
    63  	}
    64  
    65  	cmd.Flags().Bool(flagSkipSignatureVerification, false, "Skip signature verification")
    66  	cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
    67  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document is written to the given file instead of STDOUT")
    68  	flags.AddTxFlagsToCmd(cmd)
    69  	_ = cmd.Flags().MarkHidden(flags.FlagOutput)
    70  
    71  	return cmd
    72  }
    73  
    74  func makeMultiSignCmd() func(cmd *cobra.Command, args []string) (err error) {
    75  	return func(cmd *cobra.Command, args []string) (err error) {
    76  		clientCtx, err := client.GetClientTxContext(cmd)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		parsedTx, err := authclient.ReadTxFromFile(clientCtx, args[0])
    81  		if err != nil {
    82  			return
    83  		}
    84  
    85  		txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
    86  		if err != nil {
    87  			return err
    88  		}
    89  		if txFactory.SignMode() == signingtypes.SignMode_SIGN_MODE_UNSPECIFIED {
    90  			txFactory = txFactory.WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
    91  		}
    92  
    93  		txCfg := clientCtx.TxConfig
    94  		txBuilder, err := txCfg.WrapTxBuilder(parsedTx)
    95  		if err != nil {
    96  			return err
    97  		}
    98  
    99  		k, err := getMultisigRecord(clientCtx, args[1])
   100  		if err != nil {
   101  			return err
   102  		}
   103  		pubKey, err := k.GetPubKey()
   104  		if err != nil {
   105  			return err
   106  		}
   107  
   108  		addr, err := k.GetAddress()
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		// avoid signature verification if the sender of the tx is different than
   114  		// the multisig key (useful for nested multisigs).
   115  		skipSigVerify, _ := cmd.Flags().GetBool(flagSkipSignatureVerification)
   116  
   117  		multisigPub := pubKey.(*kmultisig.LegacyAminoPubKey)
   118  		multisigSig := multisig.NewMultisig(len(multisigPub.PubKeys))
   119  		if !clientCtx.Offline {
   120  			accnum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, addr)
   121  			if err != nil {
   122  				return err
   123  			}
   124  
   125  			txFactory = txFactory.WithAccountNumber(accnum).WithSequence(seq)
   126  		}
   127  
   128  		// read each signature and add it to the multisig if valid
   129  		for i := 2; i < len(args); i++ {
   130  			sigs, err := unmarshalSignatureJSON(clientCtx, args[i])
   131  			if err != nil {
   132  				return err
   133  			}
   134  
   135  			if txFactory.ChainID() == "" {
   136  				return fmt.Errorf("set the chain id with either the --chain-id flag or config file")
   137  			}
   138  
   139  			for _, sig := range sigs {
   140  				anyPk, err := codectypes.NewAnyWithValue(sig.PubKey)
   141  				if err != nil {
   142  					return err
   143  				}
   144  				txSignerData := txsigning.SignerData{
   145  					ChainID:       txFactory.ChainID(),
   146  					AccountNumber: txFactory.AccountNumber(),
   147  					Sequence:      txFactory.Sequence(),
   148  					Address:       sdk.AccAddress(sig.PubKey.Address()).String(),
   149  					PubKey: &anypb.Any{
   150  						TypeUrl: anyPk.TypeUrl,
   151  						Value:   anyPk.Value,
   152  					},
   153  				}
   154  				builtTx := txBuilder.GetTx()
   155  				adaptableTx, ok := builtTx.(signing.V2AdaptableTx)
   156  				if !ok {
   157  					return fmt.Errorf("expected Tx to be signing.V2AdaptableTx, got %T", builtTx)
   158  				}
   159  				txData := adaptableTx.GetSigningTxData()
   160  
   161  				if !skipSigVerify {
   162  					err = signing.VerifySignature(cmd.Context(), sig.PubKey, txSignerData, sig.Data,
   163  						txCfg.SignModeHandler(), txData)
   164  					if err != nil {
   165  						addr, _ := sdk.AccAddressFromHexUnsafe(sig.PubKey.Address().String())
   166  						return fmt.Errorf("couldn't verify signature for address %s %w", addr, err)
   167  					}
   168  				}
   169  
   170  				if err := multisig.AddSignatureV2(multisigSig, sig, multisigPub.GetPubKeys()); err != nil {
   171  					return err
   172  				}
   173  			}
   174  		}
   175  
   176  		sigV2 := signingtypes.SignatureV2{
   177  			PubKey:   multisigPub,
   178  			Data:     multisigSig,
   179  			Sequence: txFactory.Sequence(),
   180  		}
   181  
   182  		err = txBuilder.SetSignatures(sigV2)
   183  		if err != nil {
   184  			return err
   185  		}
   186  
   187  		sigOnly, _ := cmd.Flags().GetBool(flagSigOnly)
   188  
   189  		var json []byte
   190  		json, err = marshalSignatureJSON(txCfg, txBuilder, sigOnly)
   191  		if err != nil {
   192  			return err
   193  		}
   194  
   195  		closeFunc, err := setOutputFile(cmd)
   196  		if err != nil {
   197  			return err
   198  		}
   199  
   200  		defer closeFunc()
   201  
   202  		cmd.Printf("%s\n", json)
   203  		return nil
   204  	}
   205  }
   206  
   207  func GetMultiSignBatchCmd() *cobra.Command {
   208  	cmd := &cobra.Command{
   209  		Use:     "multisign-batch [file] [name] [[signature-file]...]",
   210  		Aliases: []string{"multi-sign-batch"},
   211  		Short:   "Assemble multisig transactions in batch from batch signatures",
   212  		Long: strings.TrimSpace(
   213  			fmt.Sprintf(`Assemble a batch of multisig transactions generated by batch sign command.
   214  
   215  Read one or more signatures from one or more [signature] file, generate a multisig signature compliant to the
   216  multisig key [name], and attach the key name to the transaction read from [file].
   217  
   218  Example:
   219  $ %s tx multisign-batch transactions.json multisigk1k2k3 k1sigs.json k2sigs.json k3sig.json
   220  
   221  The current multisig implementation defaults to amino-json sign mode.
   222  The SIGN_MODE_DIRECT sign mode is not supported.'
   223  `, version.AppName,
   224  			),
   225  		),
   226  		PreRun: preSignCmd,
   227  		RunE:   makeBatchMultisignCmd(),
   228  		Args:   cobra.MinimumNArgs(3),
   229  	}
   230  
   231  	cmd.Flags().Bool(flagNoAutoIncrement, false, "disable sequence auto increment")
   232  	cmd.Flags().String(
   233  		flagMultisig, "",
   234  		"Address of the multisig account that the transaction signs on behalf of",
   235  	)
   236  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document is written to the given file instead of STDOUT")
   237  	flags.AddTxFlagsToCmd(cmd)
   238  	_ = cmd.Flags().MarkHidden(flags.FlagOutput) // signing makes sense to output only json
   239  
   240  	return cmd
   241  }
   242  
   243  func makeBatchMultisignCmd() func(cmd *cobra.Command, args []string) error {
   244  	return func(cmd *cobra.Command, args []string) (err error) {
   245  		var clientCtx client.Context
   246  
   247  		clientCtx, err = client.GetClientTxContext(cmd)
   248  		if err != nil {
   249  			return err
   250  		}
   251  
   252  		txCfg := clientCtx.TxConfig
   253  		txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
   254  		if err != nil {
   255  			return err
   256  		}
   257  		if txFactory.SignMode() == signingtypes.SignMode_SIGN_MODE_UNSPECIFIED {
   258  			txFactory = txFactory.WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
   259  		}
   260  
   261  		// reads tx from args[0]
   262  		scanner, err := authclient.ReadTxsFromInput(txCfg, args[0])
   263  		if err != nil {
   264  			return err
   265  		}
   266  
   267  		k, err := getMultisigRecord(clientCtx, args[1])
   268  		if err != nil {
   269  			return err
   270  		}
   271  
   272  		var signatureBatch [][]signingtypes.SignatureV2
   273  		for i := 2; i < len(args); i++ {
   274  			sigs, err := readSignaturesFromFile(clientCtx, args[i])
   275  			if err != nil {
   276  				return err
   277  			}
   278  
   279  			signatureBatch = append(signatureBatch, sigs)
   280  		}
   281  
   282  		addr, err := k.GetAddress()
   283  		if err != nil {
   284  			return err
   285  		}
   286  
   287  		if !clientCtx.Offline {
   288  			accnum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, addr)
   289  			if err != nil {
   290  				return err
   291  			}
   292  
   293  			txFactory = txFactory.WithAccountNumber(accnum).WithSequence(seq)
   294  		}
   295  
   296  		// prepare output document
   297  		closeFunc, err := setOutputFile(cmd)
   298  		if err != nil {
   299  			return err
   300  		}
   301  
   302  		defer closeFunc()
   303  		clientCtx.WithOutput(cmd.OutOrStdout())
   304  
   305  		for i := 0; scanner.Scan(); i++ {
   306  			txBldr, err := txCfg.WrapTxBuilder(scanner.Tx())
   307  			if err != nil {
   308  				return err
   309  			}
   310  			pubKey, err := k.GetPubKey()
   311  			if err != nil {
   312  				return err
   313  			}
   314  			multisigPub := pubKey.(*kmultisig.LegacyAminoPubKey)
   315  			multisigSig := multisig.NewMultisig(len(multisigPub.PubKeys))
   316  
   317  			anyPk, err := codectypes.NewAnyWithValue(multisigPub)
   318  			if err != nil {
   319  				return err
   320  			}
   321  			txSignerData := txsigning.SignerData{
   322  				ChainID:       txFactory.ChainID(),
   323  				AccountNumber: txFactory.AccountNumber(),
   324  				Sequence:      txFactory.Sequence(),
   325  				Address:       sdk.AccAddress(pubKey.Address()).String(),
   326  				PubKey: &anypb.Any{
   327  					TypeUrl: anyPk.TypeUrl,
   328  					Value:   anyPk.Value,
   329  				},
   330  			}
   331  
   332  			builtTx := txBldr.GetTx()
   333  			adaptableTx, ok := builtTx.(signing.V2AdaptableTx)
   334  			if !ok {
   335  				return fmt.Errorf("expected Tx to be signing.V2AdaptableTx, got %T", builtTx)
   336  			}
   337  			txData := adaptableTx.GetSigningTxData()
   338  
   339  			for _, sig := range signatureBatch {
   340  				err = signing.VerifySignature(cmd.Context(), sig[i].PubKey, txSignerData, sig[i].Data,
   341  					txCfg.SignModeHandler(), txData)
   342  				if err != nil {
   343  					return fmt.Errorf("couldn't verify signature: %w %v", err, sig)
   344  				}
   345  
   346  				if err := multisig.AddSignatureV2(multisigSig, sig[i], multisigPub.GetPubKeys()); err != nil {
   347  					return err
   348  				}
   349  			}
   350  
   351  			sigV2 := signingtypes.SignatureV2{
   352  				PubKey:   multisigPub,
   353  				Data:     multisigSig,
   354  				Sequence: txFactory.Sequence(),
   355  			}
   356  
   357  			err = txBldr.SetSignatures(sigV2)
   358  			if err != nil {
   359  				return err
   360  			}
   361  
   362  			sigOnly, _ := cmd.Flags().GetBool(flagSigOnly)
   363  			var json []byte
   364  			json, err = marshalSignatureJSON(txCfg, txBldr, sigOnly)
   365  			if err != nil {
   366  				return err
   367  			}
   368  
   369  			err = clientCtx.PrintString(fmt.Sprintf("%s\n", json))
   370  			if err != nil {
   371  				return err
   372  			}
   373  
   374  			if viper.GetBool(flagNoAutoIncrement) {
   375  				continue
   376  			}
   377  			sequence := txFactory.Sequence() + 1
   378  			txFactory = txFactory.WithSequence(sequence)
   379  		}
   380  
   381  		return scanner.UnmarshalErr()
   382  	}
   383  }
   384  
   385  func unmarshalSignatureJSON(clientCtx client.Context, filename string) (sigs []signingtypes.SignatureV2, err error) {
   386  	var bytes []byte
   387  	if bytes, err = os.ReadFile(filename); err != nil {
   388  		return
   389  	}
   390  	return clientCtx.TxConfig.UnmarshalSignatureJSON(bytes)
   391  }
   392  
   393  func readSignaturesFromFile(ctx client.Context, filename string) (sigs []signingtypes.SignatureV2, err error) {
   394  	bz, err := os.ReadFile(filename)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	newString := strings.TrimSuffix(string(bz), "\n")
   400  	lines := strings.Split(newString, "\n")
   401  
   402  	for _, bz := range lines {
   403  		sig, err := ctx.TxConfig.UnmarshalSignatureJSON([]byte(bz))
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  
   408  		sigs = append(sigs, sig...)
   409  	}
   410  	return sigs, nil
   411  }
   412  
   413  func getMultisigRecord(clientCtx client.Context, name string) (*keyring.Record, error) {
   414  	kb := clientCtx.Keyring
   415  	multisigRecord, err := kb.Key(name)
   416  	if err != nil {
   417  		return nil, errorsmod.Wrap(err, "error getting keybase multisig account")
   418  	}
   419  
   420  	return multisigRecord, nil
   421  }