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