github.com/Finschia/finschia-sdk@v0.48.1/client/keys/add.go (about) 1 package keys 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "math" 9 "sort" 10 11 "github.com/cosmos/go-bip39" 12 "github.com/spf13/cobra" 13 14 "github.com/Finschia/finschia-sdk/client" 15 "github.com/Finschia/finschia-sdk/client/flags" 16 "github.com/Finschia/finschia-sdk/client/input" 17 "github.com/Finschia/finschia-sdk/crypto/hd" 18 "github.com/Finschia/finschia-sdk/crypto/keyring" 19 "github.com/Finschia/finschia-sdk/crypto/keys/multisig" 20 cryptotypes "github.com/Finschia/finschia-sdk/crypto/types" 21 sdk "github.com/Finschia/finschia-sdk/types" 22 ) 23 24 const ( 25 flagInteractive = "interactive" 26 flagRecover = "recover" 27 flagNoBackup = "no-backup" 28 flagCoinType = "coin-type" 29 flagAccount = "account" 30 flagIndex = "index" 31 flagMultisig = "multisig" 32 flagNoSort = "nosort" 33 flagHDPath = "hd-path" 34 35 // CoinTypeNotAssigned means a coin type not assigned 36 // finschia-sdk cannot support the coin type MaxUint32 37 CoinTypeNotAssigned = math.MaxUint32 38 ) 39 40 // AddKeyCommand defines a keys command to add a generated or recovered private key to keybase. 41 func AddKeyCommand() *cobra.Command { 42 cmd := &cobra.Command{ 43 Use: "add <name>", 44 Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to <name> file", 45 Long: `Derive a new private key and encrypt to disk. 46 Optionally specify a BIP39 mnemonic, a BIP39 passphrase to further secure the mnemonic, 47 and a bip32 HD path to derive a specific account. The key will be stored under the given name 48 and encrypted with the given password. The only input that is required is the encryption password. 49 50 If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase. 51 The flag --recover allows one to recover a key from a seed passphrase. 52 If run with --dry-run, a key would be generated (or recovered) but not stored to the 53 local keystore. 54 Use the --pubkey flag to add arbitrary public keys to the keystore for constructing 55 multisig transactions. 56 57 You can create and store a multisig key by passing the list of key names stored in a keyring 58 and the minimum number of signatures required through --multisig-threshold. The keys are 59 sorted by address, unless the flag --nosort is set. 60 Example: 61 62 keys add mymultisig --multisig "keyname1,keyname2,keyname3" --multisig-threshold 2 63 `, 64 Args: cobra.ExactArgs(1), 65 RunE: runAddCmdPrepare, 66 } 67 f := cmd.Flags() 68 f.StringSlice(flagMultisig, nil, "List of key names stored in keyring to construct a public legacy multisig key") 69 f.Int(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") 70 f.Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") 71 f.String(FlagPublicKey, "", "Parse a public key in JSON format and saves key info to <name> file.") 72 f.BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic") 73 f.Bool(flags.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") 74 f.Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating") 75 f.Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)") 76 f.Bool(flags.FlagDryRun, false, "Perform action, but don't add key to local keystore") 77 f.String(flagHDPath, "", "Manual HD Path derivation (overrides BIP44 config)") 78 f.Uint32(flagCoinType, sdk.GetConfig().GetCoinType(), "coin type number for HD derivation") 79 f.Uint32(flagAccount, 0, "Account number for HD derivation") 80 f.Uint32(flagIndex, 0, "Address index number for HD derivation") 81 f.String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") 82 83 return cmd 84 } 85 86 func runAddCmdPrepare(cmd *cobra.Command, args []string) error { 87 clientCtx, err := client.GetClientQueryContext(cmd) 88 if err != nil { 89 return err 90 } 91 92 buf := bufio.NewReader(clientCtx.Input) 93 return runAddCmd(clientCtx, cmd, args, buf) 94 } 95 96 /* 97 input 98 - bip39 mnemonic 99 - bip39 passphrase 100 - bip44 path 101 - local encryption password 102 103 output 104 - armor encrypted private key (saved to file) 105 */ 106 func runAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error { 107 var err error 108 var multisigThreshold int 109 110 name := args[0] 111 interactive, _ := cmd.Flags().GetBool(flagInteractive) 112 noBackup, _ := cmd.Flags().GetBool(flagNoBackup) 113 showMnemonic := !noBackup 114 kb := ctx.Keyring 115 outputFormat := ctx.OutputFormat 116 117 keyringAlgos, _ := kb.SupportedAlgorithms() 118 algoStr, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm) 119 algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) 120 if err != nil { 121 return err 122 } 123 multisigKeys, _ := cmd.Flags().GetStringSlice(flagMultisig) 124 if len(multisigKeys) != 0 { 125 multisigThreshold, _ = cmd.Flags().GetInt(flagMultiSigThreshold) 126 if err = validateMultisigThreshold(multisigThreshold, len(multisigKeys)); err != nil { 127 return err 128 } 129 130 err = verifyMultisigTarget(kb, multisigKeys, name) 131 if err != nil { 132 return err 133 } 134 } 135 136 if dryRun, _ := cmd.Flags().GetBool(flags.FlagDryRun); dryRun { 137 // use in memory keybase 138 kb = keyring.NewInMemory() 139 } else { 140 _, err = kb.Key(name) 141 if err == nil { 142 // account exists, ask for user confirmation 143 response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf, cmd.ErrOrStderr()) 144 if err2 != nil { 145 return err2 146 } 147 148 if !response { 149 return errors.New("aborted") 150 } 151 152 err2 = kb.Delete(name) 153 if err2 != nil { 154 return err2 155 } 156 } 157 158 if len(multisigKeys) != 0 { 159 pks := make([]cryptotypes.PubKey, len(multisigKeys)) 160 161 for i, keyname := range multisigKeys { 162 k, err := kb.Key(keyname) 163 if err != nil { 164 return err 165 } 166 167 pks[i] = k.GetPubKey() 168 } 169 170 if noSort, _ := cmd.Flags().GetBool(flagNoSort); !noSort { 171 sort.Slice(pks, func(i, j int) bool { 172 return bytes.Compare(pks[i].Address(), pks[j].Address()) < 0 173 }) 174 } 175 176 pk := multisig.NewLegacyAminoPubKey(multisigThreshold, pks) 177 info, err := kb.SaveMultisig(name, pk) 178 if err != nil { 179 return err 180 } 181 182 return printCreate(cmd, info, false, "", outputFormat) 183 } 184 } 185 186 pubKey, _ := cmd.Flags().GetString(FlagPublicKey) 187 if pubKey != "" { 188 var pk cryptotypes.PubKey 189 err = ctx.Codec.UnmarshalInterfaceJSON([]byte(pubKey), &pk) 190 if err != nil { 191 return err 192 } 193 194 info, err := kb.SavePubKey(name, pk, algo.Name()) 195 if err != nil { 196 return err 197 } 198 199 return printCreate(cmd, info, false, "", outputFormat) 200 } 201 202 coinType, _ := cmd.Flags().GetUint32(flagCoinType) 203 if coinType == CoinTypeNotAssigned { 204 coinType = sdk.GetConfig().GetCoinType() 205 } 206 account, _ := cmd.Flags().GetUint32(flagAccount) 207 index, _ := cmd.Flags().GetUint32(flagIndex) 208 hdPath, _ := cmd.Flags().GetString(flagHDPath) 209 useLedger, _ := cmd.Flags().GetBool(flags.FlagUseLedger) 210 211 if len(hdPath) == 0 { 212 hdPath = hd.CreateHDPath(coinType, account, index).String() 213 } else if useLedger { 214 return errors.New("cannot set custom bip32 path with ledger") 215 } 216 217 // If we're using ledger, only thing we need is the path and the bech32 prefix. 218 if useLedger { 219 bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() 220 221 info, err := kb.SaveLedgerKey(name, algo, bech32PrefixAccAddr, coinType, account, index) 222 if err != nil { 223 return err 224 } 225 226 return printCreate(cmd, info, false, "", outputFormat) 227 } 228 229 // Get bip39 mnemonic 230 var mnemonic, bip39Passphrase string 231 232 recover, _ := cmd.Flags().GetBool(flagRecover) 233 if recover { 234 mnemonic, err = input.GetString("Enter your bip39 mnemonic", inBuf) 235 if err != nil { 236 return err 237 } 238 239 if !bip39.IsMnemonicValid(mnemonic) { 240 return errors.New("invalid mnemonic") 241 } 242 } else if interactive { 243 mnemonic, err = input.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", inBuf) 244 if err != nil { 245 return err 246 } 247 248 if !bip39.IsMnemonicValid(mnemonic) && mnemonic != "" { 249 return errors.New("invalid mnemonic") 250 } 251 } 252 253 if len(mnemonic) == 0 { 254 // read entropy seed straight from occrypto.Rand and convert to mnemonic 255 entropySeed, err := bip39.NewEntropy(mnemonicEntropySize) 256 if err != nil { 257 return err 258 } 259 260 mnemonic, err = bip39.NewMnemonic(entropySeed) 261 if err != nil { 262 return err 263 } 264 } 265 266 // override bip39 passphrase 267 if interactive { 268 bip39Passphrase, err = input.GetString( 269 "Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+ 270 "Most users should just hit enter to use the default, \"\"", inBuf) 271 if err != nil { 272 return err 273 } 274 275 // if they use one, make them re-enter it 276 if len(bip39Passphrase) != 0 { 277 p2, err := input.GetString("Repeat the passphrase:", inBuf) 278 if err != nil { 279 return err 280 } 281 282 if bip39Passphrase != p2 { 283 return errors.New("passphrases don't match") 284 } 285 } 286 } 287 288 info, err := kb.NewAccount(name, mnemonic, bip39Passphrase, hdPath, algo) 289 if err != nil { 290 return err 291 } 292 293 // Recover key from seed passphrase 294 if recover { 295 // Hide mnemonic from output 296 showMnemonic = false 297 mnemonic = "" 298 } 299 300 return printCreate(cmd, info, showMnemonic, mnemonic, outputFormat) 301 } 302 303 func printCreate(cmd *cobra.Command, info keyring.Info, showMnemonic bool, mnemonic string, outputFormat string) error { 304 switch outputFormat { 305 case OutputFormatText: 306 cmd.PrintErrln() 307 printKeyInfo(cmd.OutOrStdout(), info, keyring.MkAccKeyOutput, outputFormat) 308 309 // print mnemonic unless requested not to. 310 if showMnemonic { 311 fmt.Fprintln(cmd.ErrOrStderr(), "\n**Important** write this mnemonic phrase in a safe place.") 312 fmt.Fprintln(cmd.ErrOrStderr(), "It is the only way to recover your account if you ever forget your password.") 313 fmt.Fprintln(cmd.ErrOrStderr(), "") 314 fmt.Fprintln(cmd.ErrOrStderr(), mnemonic) 315 } 316 case OutputFormatJSON: 317 out, err := keyring.MkAccKeyOutput(info) 318 if err != nil { 319 return err 320 } 321 322 if showMnemonic { 323 out.Mnemonic = mnemonic 324 } 325 326 jsonString, err := KeysCdc.MarshalJSON(out) 327 if err != nil { 328 return err 329 } 330 331 cmd.Println(string(jsonString)) 332 333 default: 334 return fmt.Errorf("invalid output format %s", outputFormat) 335 } 336 337 return nil 338 } 339 340 func verifyMultisigTarget(kb keyring.Keyring, multisigKeys []string, newkey string) error { 341 if _, err := kb.Key(newkey); err == nil { 342 return errors.New("you cannot specify a new key as one of the names of the keys that make up a multisig") 343 } 344 345 for _, k := range multisigKeys { 346 if _, err := kb.Key(k); err != nil { 347 return errors.New("part of the multisig target key does not exist") 348 } 349 } 350 351 return nil 352 }