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 }