Skip to content

Commit

Permalink
Remove inconsistent gaiacli keys new/add dualism
Browse files Browse the repository at this point in the history
- Incorporate --recover in the new command.
- Code clean up

Closes: #2595
  • Loading branch information
Alessio Treglia committed Nov 29, 2018
1 parent b773636 commit 63e67a4
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 234 deletions.
208 changes: 163 additions & 45 deletions client/keys/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,45 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"

"github.com/cosmos/cosmos-sdk/client"
"github.com/bartekn/go-bip39"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"

"github.com/cosmos/cosmos-sdk/client"
ccrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys"

"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
)

const (
flagType = "type"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagDryRun = "dry-run"
flagAccount = "account"
flagIndex = "index"
flagNewDefault = "default"
flagBIP44Path = "bip44-path"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagDryRun = "dry-run"
flagAccount = "account"
flagIndex = "index"
)

func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
Long: `Add a public/private key pair to the key store.
If you select --seed/-s you can recover a key from the seed
phrase, otherwise, a new key will be generated.`,
Use: "add",
Short: "Interactive command to derive a new private key, encrypt it, and save to disk",
Long: `Derive a new private key using an interactive command that will prompt you for each input.
Optionally specify a bip39 mnemonic, a bip39 passphrase to further secure the mnemonic,
and a bip32 HD path to derive a specific account. The key will be stored under the given name
and encrypted with the given password. The only input that is required is the encryption password.`,
Args: cobra.ExactArgs(1),
RunE: runAddCmd,
}
cmd.Flags().StringP(flagType, "t", "secp256k1", "Type of private key (secp256k1|ed25519)")
cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything")
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key")
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
Expand All @@ -45,24 +52,30 @@ phrase, otherwise, a new key will be generated.`,
return cmd
}

// TODO remove the above when addressing #1446
/*
input
- bip39 mnemonic
- bip39 passphrase
- bip44 path
- local encryption password
output
- armor encrypted private key (saved to file)
*/
func runAddCmd(cmd *cobra.Command, args []string) error {
var kb keys.Keybase
var err error
var name, pass string
var (
kb keys.Keybase
err error
name, pass, mnemonic string
)

buf := client.BufferStdin()
name = args[0]
if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly
kb = client.MockKeyBase()
pass = "throwing-this-key-away"
name = "inmemorykey"
} else {
if len(args) != 1 || len(args[0]) == 0 {
return errMissingName()
}
name = args[0]
kb, err = GetKeyBaseWithWritePerm()
if err != nil {
return err
Expand All @@ -88,17 +101,32 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
}
}

flags := cmd.Flags()
useDefaults, _ := flags.GetBool(flagNewDefault)
bipFlag := flags.Lookup(flagBIP44Path)

bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults)
if err != nil {
return err
}

// If we're using ledger, only thing we need is the path. So generate key and
// we're done.
if viper.GetBool(client.FlagUseLedger) {
account := uint32(viper.GetInt(flagAccount))
index := uint32(viper.GetInt(flagIndex))
path := ccrypto.DerivationPath{44, 118, account, 0, index}
algo := keys.SigningAlgo(viper.GetString(flagType))
info, err := kb.CreateLedger(name, path, algo)
info, err := kb.CreateLedger(name, path, keys.Secp256k1)
if err != nil {
return err
}

printCreate(info, "")
} else if viper.GetBool(flagRecover) {
return nil
}

// Recover key from seed passphrase
if viper.GetBool(flagRecover) {
seed, err := client.GetSeed(
"Enter your recovery seed phrase:", buf)
if err != nil {
Expand All @@ -111,17 +139,99 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
// print out results without the seed phrase
viper.Set(flagNoBackup, true)
printCreate(info, "")
} else {
algo := keys.SigningAlgo(viper.GetString(flagType))
info, seed, err := kb.CreateMnemonic(name, keys.English, pass, algo)
return nil
}

if !useDefaults {
mnemonic, err = client.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", buf)
if err != nil {
return err
}
}

if len(mnemonic) == 0 {
// read entropy seed straight from crypto.Rand and convert to mnemonic
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
if err != nil {
return err
}
printCreate(info, seed)

mnemonic, err = bip39.NewMnemonic(entropySeed[:])
if err != nil {
return err
}
}

// get bip39 passphrase
var bip39Passphrase string
if !useDefaults {
printStep()
printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed")

bip39Passphrase, err = client.GetString("Most users should just hit enter to use the default, \"\"", buf)
if err != nil {
return err
}

// if they use one, make them re-enter it
if len(bip39Passphrase) != 0 {
p2, err := client.GetString("Repeat the passphrase:", buf)
if err != nil {
return err
}

if bip39Passphrase != p2 {
return errors.New("passphrases don't match")
}
}
}

printStep()

// get the encryption password
encryptPassword, err := client.GetCheckPassword(
"> Enter a passphrase to encrypt your key to disk:",
"> Repeat the passphrase:", buf)
if err != nil {
return err
}

info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
if err != nil {
return err
}
printCreate(info, mnemonic)
return nil
}

func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
buf := client.BufferStdin()
bip44Path := path

// if it wasn't set in the flag, give it a chance to overide interactively
if !flagSet {
var err error

printStep()

bip44Path, err = client.GetString(fmt.Sprintf("Enter your bip44 path. Default is %s\n", path), buf)
if err != nil {
return nil, err
}

if len(bip44Path) == 0 {
bip44Path = path
}
}

bip44params, err := hd.NewParamsFromPath(bip44Path)
if err != nil {
return nil, err
}

return bip44params, nil
}

func printCreate(info keys.Info, seed string) {
output := viper.Get(cli.OutputFlag)
switch output {
Expand All @@ -130,10 +240,10 @@ func printCreate(info keys.Info, seed string) {

// print seed unless requested not to.
if !viper.GetBool(client.FlagUseLedger) && !viper.GetBool(flagNoBackup) {
fmt.Println("**Important** write this seed phrase in a safe place.")
fmt.Println("It is the only way to recover your account if you ever forget your password.")
fmt.Println()
fmt.Println(seed)
fmt.Fprintln(os.Stderr, "**Important** write this seed phrase in a safe place.")
fmt.Fprintln(os.Stderr, "It is the only way to recover your account if you ever forget your password.")
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, seed)
}
case "json":
out, err := Bech32KeyOutput(info)
Expand All @@ -152,12 +262,29 @@ func printCreate(info keys.Info, seed string) {
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(jsonString))
fmt.Fprintln(os.Stderr, string(jsonString))
default:
panic(fmt.Sprintf("I can't speak: %s", output))
}
}

// function to just a new seed to display in the UI before actually persisting it in the keybase
func getSeed(algo keys.SigningAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}

func printPrefixed(msg string) {
fmt.Fprintf(os.Stderr, "> %s\n", msg)
}

func printStep() {
printPrefixed("-------------------------------------")
}

/////////////////////////////
// REST

Expand Down Expand Up @@ -242,15 +369,6 @@ func AddNewKeyRequestHandler(indent bool) http.HandlerFunc {
}
}

// function to just a new seed to display in the UI before actually persisting it in the keybase
func getSeed(algo keys.SigningAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.CreateMnemonic(name, keys.English, pass, algo)
return seed
}

// Seed REST request handler
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
Expand Down
Loading

0 comments on commit 63e67a4

Please sign in to comment.