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  }