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  }