github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/client/keys/add.go (about)

     1  package keys
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sort"
    10  	"strings"
    11  
    12  	bip39 "github.com/bartekn/go-bip39"
    13  
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/flags"
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/input"
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys"
    17  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    18  
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/viper"
    21  
    22  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
    23  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/multisig"
    24  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/cli"
    25  )
    26  
    27  const (
    28  	flagInteractive = "interactive"
    29  	flagRecover     = "recover"
    30  	flagNoBackup    = "no-backup"
    31  	flagDryRun      = "dry-run"
    32  	flagAccount     = "account"
    33  	flagIndex       = "index"
    34  	flagCointype    = "coin-type"
    35  	flagMultisig    = "multisig"
    36  	flagNoSort      = "nosort"
    37  	flagHDPath      = "hd-path"
    38  	flagKeyAlgo     = "algo"
    39  
    40  	// DefaultKeyPass contains the default key password for genesis transactions
    41  	DefaultKeyPass = "12345678"
    42  	flagMnemonic   = "mnemonic"
    43  )
    44  
    45  // AddKeyCommand defines a keys command to add a generated or recovered private key to keybase.
    46  func AddKeyCommand() *cobra.Command {
    47  	cmd := &cobra.Command{
    48  		Use:   "add <name>",
    49  		Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk",
    50  		Long: `Derive a new private key and encrypt to disk.
    51  Optionally specify a BIP39 mnemonic, a BIP39 passphrase to further secure the mnemonic,
    52  and a bip32 HD path to derive a specific account. The key will be stored under the given name
    53  and encrypted with the given password. The only input that is required is the encryption password.
    54  
    55  If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase.
    56  The flag --recover allows one to recover a key from a seed passphrase.
    57  If run with --dry-run, a key would be generated (or recovered) but not stored to the
    58  local keystore.
    59  Use the --pubkey flag to add arbitrary public keys to the keystore for constructing
    60  multisig transactions.
    61  
    62  You can add a multisig key by passing the list of key names you want the public
    63  key to be composed of to the --multisig flag and the minimum number of signatures
    64  required through --multisig-threshold. The keys are sorted by address, unless
    65  the flag --nosort is set.
    66  `,
    67  		Args: cobra.ExactArgs(1),
    68  		RunE: runAddCmd,
    69  	}
    70  	cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)")
    71  	cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig")
    72  	cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied")
    73  	cmd.Flags().String(FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk")
    74  	cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic")
    75  	cmd.Flags().Bool(flags.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
    76  	cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
    77  	cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
    78  	cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
    79  	cmd.Flags().String(flagHDPath, "", "Manual HD Path derivation (overrides BIP44 config)")
    80  	cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
    81  	cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation")
    82  	cmd.Flags().Uint32(flagCointype, 60, "Coin type for HD derivation")
    83  	cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response")
    84  	cmd.Flags().String(flagKeyAlgo, string(keys.Secp256k1), "Key signing algorithm to generate keys for")
    85  
    86  	cmd.Flags().BoolP(flagYes, "y", false, "Overwrite the existing account without confirmation")
    87  	cmd.Flags().StringP(flagMnemonic, "m", "", "Mnemonic words or private key")
    88  	return cmd
    89  }
    90  
    91  func getKeybase(transient bool, buf io.Reader) (keys.Keybase, error) {
    92  	if transient {
    93  		return keys.NewInMemory(), nil
    94  	}
    95  
    96  	return keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
    97  }
    98  
    99  func runAddCmd(cmd *cobra.Command, args []string) error {
   100  	inBuf := bufio.NewReader(cmd.InOrStdin())
   101  	kb, err := getKeybase(viper.GetBool(flagDryRun), inBuf)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	return RunAddCmd(cmd, args, kb, inBuf)
   107  }
   108  
   109  /*
   110  input
   111    - bip39 mnemonic
   112    - bip39 passphrase
   113    - bip44 path
   114    - local encryption password
   115  
   116  output
   117    - armor encrypted private key (saved to file)
   118  */
   119  func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.Reader) error {
   120  	var err error
   121  
   122  	name := args[0]
   123  
   124  	interactive := viper.GetBool(flagInteractive)
   125  	showMnemonic := !viper.GetBool(flagNoBackup)
   126  
   127  	algo := keys.SigningAlgo(viper.GetString(flagKeyAlgo))
   128  	if algo == keys.SigningAlgo("") {
   129  		algo = keys.Secp256k1
   130  	}
   131  	if !keys.IsSupportedAlgorithm(kb.SupportedAlgos(), algo) {
   132  		return keys.ErrUnsupportedSigningAlgo
   133  	}
   134  
   135  	if !viper.GetBool(flagDryRun) {
   136  		ask := !viper.GetBool(flagYes)
   137  		_, err = kb.Get(name)
   138  		if err == nil && ask {
   139  			// account exists, ask for user confirmation
   140  			response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf)
   141  			if err2 != nil {
   142  				return err2
   143  			}
   144  			if !response {
   145  				return errors.New("aborted")
   146  			}
   147  		}
   148  
   149  		multisigKeys := viper.GetStringSlice(flagMultisig)
   150  		if len(multisigKeys) != 0 {
   151  			var pks []crypto.PubKey
   152  
   153  			multisigThreshold := viper.GetInt(flagMultiSigThreshold)
   154  			if err := validateMultisigThreshold(multisigThreshold, len(multisigKeys)); err != nil {
   155  				return err
   156  			}
   157  
   158  			for _, keyname := range multisigKeys {
   159  				k, err := kb.Get(keyname)
   160  				if err != nil {
   161  					return err
   162  				}
   163  				pks = append(pks, k.GetPubKey())
   164  			}
   165  
   166  			// Handle --nosort
   167  			if !viper.GetBool(flagNoSort) {
   168  				sort.Slice(pks, func(i, j int) bool {
   169  					return bytes.Compare(pks[i].Address(), pks[j].Address()) < 0
   170  				})
   171  			}
   172  
   173  			pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks)
   174  			if _, err := kb.CreateMulti(name, pk); err != nil {
   175  				return err
   176  			}
   177  
   178  			cmd.PrintErrf("Key %q saved to disk.\n", name)
   179  			return nil
   180  		}
   181  	}
   182  
   183  	if viper.GetString(FlagPublicKey) != "" {
   184  		pk, err := sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeAccPub, viper.GetString(FlagPublicKey))
   185  		if err != nil {
   186  			return err
   187  		}
   188  		_, err = kb.CreateOffline(name, pk, algo)
   189  		if err != nil {
   190  			return err
   191  		}
   192  		return nil
   193  	}
   194  
   195  	account := uint32(viper.GetInt(flagAccount))
   196  	index := uint32(viper.GetInt(flagIndex))
   197  	cointype := uint32(viper.GetInt(flagCointype))
   198  
   199  	useBIP44 := !viper.IsSet(flagHDPath)
   200  	var hdPath string
   201  
   202  	if useBIP44 {
   203  		hdPath = keys.CreateHDPathEx(cointype, account, index).String()
   204  	} else {
   205  		hdPath = viper.GetString(flagHDPath)
   206  	}
   207  
   208  	// If we're using ledger, only thing we need is the path and the bech32 prefix.
   209  	if viper.GetBool(flags.FlagUseLedger) {
   210  
   211  		if !useBIP44 {
   212  			return errors.New("cannot set custom bip32 path with ledger")
   213  		}
   214  
   215  		if !keys.IsSupportedAlgorithm(kb.SupportedAlgosLedger(), algo) {
   216  			return keys.ErrUnsupportedSigningAlgo
   217  		}
   218  
   219  		bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix()
   220  		info, err := kb.CreateLedger(name, keys.Secp256k1, bech32PrefixAccAddr, account, index)
   221  		if err != nil {
   222  			return err
   223  		}
   224  
   225  		return printCreate(cmd, info, false, "")
   226  	}
   227  
   228  	// Get bip39 mnemonic
   229  	var mnemonic, bip39Passphrase string
   230  
   231  	recover, _ := cmd.Flags().GetBool(flagRecover)
   232  	if recover {
   233  		mnemonic = viper.GetString(flagMnemonic)
   234  
   235  		if len(mnemonic) == 0 {
   236  			return errors.New("mnemonic is needed")
   237  		}
   238  		if strings.Contains(mnemonic, " ") && !bip39.IsMnemonicValid(mnemonic) {
   239  			return errors.New("invalid mnemonic")
   240  		}
   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 crypto.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.CreateAccount(name, mnemonic, bip39Passphrase, DefaultKeyPass, hdPath, algo)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	// Recover key from seed passphrase
   294  	if viper.GetBool(flagRecover) {
   295  		// Hide mnemonic from output
   296  		showMnemonic = false
   297  		mnemonic = ""
   298  	}
   299  	return printCreate(cmd, info, showMnemonic, mnemonic)
   300  }
   301  
   302  func printCreate(cmd *cobra.Command, info keys.Info, showMnemonic bool, mnemonic string) error {
   303  	output := viper.Get(cli.OutputFlag)
   304  	switch output {
   305  	case OutputFormatText:
   306  		cmd.PrintErrln()
   307  		printKeyInfo(info, keys.Bech32KeyOutput)
   308  
   309  		// print mnemonic unless requested not to.
   310  		if showMnemonic {
   311  			cmd.PrintErrln("\n**Important** write this mnemonic phrase in a safe place.")
   312  			cmd.PrintErrln("It is the only way to recover your account if you ever forget your password.")
   313  			cmd.PrintErrln("")
   314  			cmd.PrintErrln(mnemonic)
   315  		}
   316  	case OutputFormatJSON:
   317  		out, err := keys.Bech32KeyOutput(info)
   318  		if err != nil {
   319  			return err
   320  		}
   321  
   322  		if showMnemonic {
   323  			out.Mnemonic = mnemonic
   324  		}
   325  
   326  		var jsonString []byte
   327  		if viper.GetBool(flags.FlagIndentResponse) {
   328  			jsonString, err = KeysCdc.MarshalJSONIndent(out, "", "  ")
   329  		} else {
   330  			jsonString, err = KeysCdc.MarshalJSON(out)
   331  		}
   332  
   333  		if err != nil {
   334  			return err
   335  		}
   336  		cmd.PrintErrln(string(jsonString))
   337  	default:
   338  		return fmt.Errorf("invalid output format %s", output)
   339  	}
   340  
   341  	return nil
   342  }