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 }