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