github.com/Finschia/finschia-sdk@v0.49.1/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/Finschia/finschia-sdk/client" 10 "github.com/Finschia/finschia-sdk/client/flags" 11 "github.com/Finschia/finschia-sdk/client/tx" 12 kmultisig "github.com/Finschia/finschia-sdk/crypto/keys/multisig" 13 authclient "github.com/Finschia/finschia-sdk/x/auth/client" 14 ) 15 16 const ( 17 flagMultisig = "multisig" 18 flagOverwrite = "overwrite" 19 flagSigOnly = "signature-only" 20 flagAmino = "amino" 21 flagNoAutoIncrement = "no-auto-increment" 22 ) 23 24 // GetSignBatchCommand returns the transaction sign-batch command. 25 func GetSignBatchCommand() *cobra.Command { 26 cmd := &cobra.Command{ 27 Use: "sign-batch [file]", 28 Short: "Sign transaction batch files", 29 Long: `Sign batch files of transactions generated with --generate-only. 30 The command processes list of transactions from file (one StdTx each line), generate 31 signed transactions or signatures and print their JSON encoding, delimited by '\n'. 32 As the signatures are generated, the command updates the account sequence number accordingly. 33 34 If the --signature-only flag is set, it will output the signature parts only. 35 36 The --offline flag makes sure that the client will not reach out to full node. 37 As a result, the account and the sequence number queries will not be performed and 38 it is required to set such parameters manually. Note, invalid values will cause 39 the transaction to fail. The sequence will be incremented automatically for each 40 transaction that is signed. 41 42 The --multisig=<multisig_key> flag generates a signature on behalf of a multisig 43 account key. It implies --signature-only. 44 `, 45 PreRun: preSignCmd, 46 RunE: makeSignBatchCmd(), 47 Args: cobra.ExactArgs(1), 48 } 49 50 cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed") 51 cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT") 52 cmd.Flags().Bool(flagSigOnly, true, "Print only the generated signature, then exit") 53 cmd.Flags().String(flags.FlagChainID, "", "network chain ID") 54 _ = cmd.MarkFlagRequired(flags.FlagFrom) 55 flags.AddTxFlagsToCmd(cmd) 56 57 return cmd 58 } 59 60 func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error { 61 return func(cmd *cobra.Command, args []string) error { 62 clientCtx, err := client.GetClientTxContext(cmd) 63 if err != nil { 64 return err 65 } 66 txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags()) 67 txCfg := clientCtx.TxConfig 68 printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly) 69 infile := os.Stdin 70 71 ms, err := cmd.Flags().GetString(flagMultisig) 72 if err != nil { 73 return err 74 } 75 76 // prepare output document 77 closeFunc, err := setOutputFile(cmd) 78 if err != nil { 79 return err 80 } 81 82 defer closeFunc() 83 clientCtx.WithOutput(cmd.OutOrStdout()) 84 85 if args[0] != "-" { 86 infile, err = os.Open(args[0]) 87 if err != nil { 88 return err 89 } 90 } 91 scanner := authclient.NewBatchScanner(txCfg, infile) 92 93 if !clientCtx.Offline { 94 if ms == "" { 95 from, err := cmd.Flags().GetString(flags.FlagFrom) 96 if err != nil { 97 return err 98 } 99 100 addr, _, _, err := client.GetFromFields(txFactory.Keybase(), from, clientCtx.GenerateOnly) 101 if err != nil { 102 return err 103 } 104 105 acc, err := txFactory.AccountRetriever().GetAccount(clientCtx, addr) 106 if err != nil { 107 return err 108 } 109 110 txFactory = txFactory.WithAccountNumber(acc.GetAccountNumber()).WithSequence(acc.GetSequence()) 111 } else { 112 txFactory = txFactory.WithAccountNumber(0).WithSequence(0) 113 } 114 } 115 116 for sequence := txFactory.Sequence(); scanner.Scan(); sequence++ { 117 unsignedStdTx := scanner.Tx() 118 txFactory = txFactory.WithSequence(sequence) 119 txBuilder, err := txCfg.WrapTxBuilder(unsignedStdTx) 120 if err != nil { 121 return err 122 } 123 if ms == "" { 124 from, _ := cmd.Flags().GetString(flags.FlagFrom) 125 _, fromName, _, err := client.GetFromFields(txFactory.Keybase(), from, clientCtx.GenerateOnly) 126 if err != nil { 127 return fmt.Errorf("error getting account from keybase: %w", err) 128 } 129 err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true) 130 if err != nil { 131 return err 132 } 133 } else { 134 multisigAddr, _, _, err := client.GetFromFields(txFactory.Keybase(), ms, clientCtx.GenerateOnly) 135 if err != nil { 136 return fmt.Errorf("error getting account from keybase: %w", err) 137 } 138 err = authclient.SignTxWithSignerAddress( 139 txFactory, clientCtx, multisigAddr, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, true) 140 if err != nil { 141 return err 142 } 143 } 144 145 if err != nil { 146 return err 147 } 148 149 json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly) 150 if err != nil { 151 return err 152 } 153 154 cmd.Printf("%s\n", json) 155 } 156 157 if err := scanner.UnmarshalErr(); err != nil { 158 return err 159 } 160 161 return scanner.UnmarshalErr() 162 } 163 } 164 165 func setOutputFile(cmd *cobra.Command) (func(), error) { 166 outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument) 167 if outputDoc == "" { 168 return func() {}, nil 169 } 170 171 fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) 172 if err != nil { 173 return func() {}, err 174 } 175 176 cmd.SetOut(fp) 177 178 return func() { fp.Close() }, nil 179 } 180 181 // GetSignCommand returns the transaction sign command. 182 func GetSignCommand() *cobra.Command { 183 cmd := &cobra.Command{ 184 Use: "sign [file]", 185 Short: "Sign a transaction generated offline", 186 Long: `Sign a transaction created with the --generate-only flag. 187 It will read a transaction from [file], sign it, and print its JSON encoding. 188 189 If the --signature-only flag is set, it will output the signature parts only. 190 191 The --offline flag makes sure that the client will not reach out to full node. 192 As a result, the account and sequence number queries will not be performed and 193 it is required to set such parameters manually. Note, invalid values will cause 194 the transaction to fail. 195 196 The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account 197 key. It implies --signature-only. Full multisig signed transactions may eventually 198 be generated via the 'multisign' command. 199 `, 200 PreRun: preSignCmd, 201 RunE: makeSignCmd(), 202 Args: cobra.ExactArgs(1), 203 } 204 205 cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed") 206 cmd.Flags().Bool(flagOverwrite, false, "Overwrite existing signatures with a new one. If disabled, new signature will be appended") 207 cmd.Flags().Bool(flagSigOnly, false, "Print only the signatures") 208 cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT") 209 cmd.Flags().String(flags.FlagChainID, "", "The network chain ID") 210 cmd.Flags().Bool(flagAmino, false, "Generate Amino encoded JSON suitable for submiting to the txs REST endpoint") 211 _ = cmd.MarkFlagRequired(flags.FlagFrom) 212 flags.AddTxFlagsToCmd(cmd) 213 214 return cmd 215 } 216 217 func preSignCmd(cmd *cobra.Command, _ []string) { 218 // Conditionally mark the account and sequence numbers required as no RPC 219 // query will be done. 220 if offline, _ := cmd.Flags().GetBool(flags.FlagOffline); offline { 221 _ = cmd.MarkFlagRequired(flags.FlagAccountNumber) 222 _ = cmd.MarkFlagRequired(flags.FlagSequence) 223 } 224 } 225 226 func makeSignCmd() func(cmd *cobra.Command, args []string) error { 227 return func(cmd *cobra.Command, args []string) (err error) { 228 var clientCtx client.Context 229 230 clientCtx, err = client.GetClientTxContext(cmd) 231 if err != nil { 232 return err 233 } 234 f := cmd.Flags() 235 236 clientCtx, txF, newTx, err := readTxAndInitContexts(clientCtx, cmd, args[0]) 237 if err != nil { 238 return err 239 } 240 241 txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags()) 242 txCfg := clientCtx.TxConfig 243 txBuilder, err := txCfg.WrapTxBuilder(newTx) 244 if err != nil { 245 return err 246 } 247 248 printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly) 249 multisig, _ := cmd.Flags().GetString(flagMultisig) 250 if err != nil { 251 return err 252 } 253 from, _ := cmd.Flags().GetString(flags.FlagFrom) 254 _, fromName, _, err := client.GetFromFields(txF.Keybase(), from, clientCtx.GenerateOnly) 255 if err != nil { 256 return fmt.Errorf("error getting account from keybase: %w", err) 257 } 258 259 overwrite, _ := f.GetBool(flagOverwrite) 260 if multisig != "" { 261 // Bech32 decode error, maybe it's a name, we try to fetch from keyring 262 multisigAddr, multisigName, _, err := client.GetFromFields(txFactory.Keybase(), multisig, clientCtx.GenerateOnly) 263 if err != nil { 264 return fmt.Errorf("error getting account from keybase: %w", err) 265 } 266 multisigkey, err := getMultisigRecord(clientCtx, multisigName) 267 if err != nil { 268 return err 269 } 270 multisigPubKey := multisigkey.GetPubKey() 271 multisigLegacyPub := multisigPubKey.(*kmultisig.LegacyAminoPubKey) 272 273 fromRecord, err := clientCtx.Keyring.Key(fromName) 274 if err != nil { 275 return fmt.Errorf("error getting account from keybase: %w", err) 276 } 277 fromPubKey := fromRecord.GetPubKey() 278 279 var found bool 280 for _, pubkey := range multisigLegacyPub.GetPubKeys() { 281 if pubkey.Equals(fromPubKey) { 282 found = true 283 } 284 } 285 if !found { 286 return fmt.Errorf("signing key is not a part of multisig key") 287 } 288 err = authclient.SignTxWithSignerAddress( 289 txF, clientCtx, multisigAddr, fromName, txBuilder, clientCtx.Offline, overwrite) 290 if err != nil { 291 return err 292 } 293 printSignatureOnly = true 294 } else { 295 err = authclient.SignTx(txF, clientCtx, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, overwrite) 296 } 297 if err != nil { 298 return err 299 } 300 301 aminoJSON, err := f.GetBool(flagAmino) 302 if err != nil { 303 return err 304 } 305 306 var json []byte 307 if aminoJSON { 308 stdTx, err := tx.ConvertTxToStdTx(clientCtx.LegacyAmino, txBuilder.GetTx()) 309 if err != nil { 310 return err 311 } 312 req := BroadcastReq{ 313 Tx: stdTx, 314 Mode: "block|sync|async", 315 } 316 json, err = clientCtx.LegacyAmino.MarshalJSON(req) 317 if err != nil { 318 return err 319 } 320 } else { 321 json, err = marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly) 322 if err != nil { 323 return err 324 } 325 } 326 327 outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument) 328 if outputDoc == "" { 329 cmd.Printf("%s\n", json) 330 return nil 331 } 332 333 fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) 334 if err != nil { 335 return err 336 } 337 defer func() { 338 err2 := fp.Close() 339 if err == nil { 340 err = err2 341 } 342 }() 343 344 _, err = fp.Write(append(json, '\n')) 345 return err 346 } 347 } 348 349 func marshalSignatureJSON(txConfig client.TxConfig, txBldr client.TxBuilder, signatureOnly bool) ([]byte, error) { 350 parsedTx := txBldr.GetTx() 351 if signatureOnly { 352 sigs, err := parsedTx.GetSignaturesV2() 353 if err != nil { 354 return nil, err 355 } 356 return txConfig.MarshalSignatureJSON(sigs) 357 } 358 359 return txConfig.TxJSONEncoder()(parsedTx) 360 }