-
Notifications
You must be signed in to change notification settings - Fork 287
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add chain id check to update endblocker only for columbus-2 * move chain-id check to update module * upgrade $terracli keys add to support old mnemonic support * add address selection when interactive mode is enabled * remove tmp keys folder * change log update * gitignore update * add old-hd-path option to give option for recover
- Loading branch information
Showing
27 changed files
with
1,995 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
keys/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
package keys | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"sort" | ||
|
||
"github.com/terra-project/core/types/util" | ||
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
sdkkeys "github.com/cosmos/cosmos-sdk/client/keys" | ||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app" | ||
"github.com/cosmos/cosmos-sdk/crypto/keys" | ||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
|
||
bip39 "github.com/cosmos/go-bip39" | ||
|
||
"github.com/tendermint/tendermint/crypto" | ||
"github.com/tendermint/tendermint/crypto/multisig" | ||
"github.com/tendermint/tendermint/libs/cli" | ||
) | ||
|
||
const ( | ||
flagInteractive = "interactive" | ||
flagRecover = "recover" | ||
flagOldHdPath = "old-hd-path" | ||
flagNoBackup = "no-backup" | ||
flagDryRun = "dry-run" | ||
flagAccount = "account" | ||
flagIndex = "index" | ||
flagMultisig = "multisig" | ||
flagNoSort = "nosort" | ||
) | ||
|
||
func addKeyCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "add <name>", | ||
Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk", | ||
Long: `Derive a new private key and encrypt to disk. | ||
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. | ||
If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase. | ||
The flag --recover allows one to recover a key from a seed passphrase. | ||
If run with --dry-run, a key would be generated (or recovered) but not stored to the | ||
local keystore. | ||
Use the --pubkey flag to add arbitrary public keys to the keystore for constructing | ||
multisig transactions. | ||
You can add a multisig key by passing the list of key names you want the public | ||
key to be composed of to the --multisig flag and the minimum number of signatures | ||
required through --multisig-threshold. The keys are sorted by address, unless | ||
the flag --nosort is set. | ||
`, | ||
Args: cobra.ExactArgs(1), | ||
RunE: runAddCmd, | ||
} | ||
cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)") | ||
cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") | ||
cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") | ||
cmd.Flags().String(sdkkeys.FlagPublicKey, "", "Parse a public key in bech32 format and save it to disk") | ||
cmd.Flags().BoolP(flagInteractive, "i", false, "Interactively prompt user for BIP39 passphrase and mnemonic") | ||
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") | ||
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") | ||
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation") | ||
cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation") | ||
cmd.Flags().Bool(flagOldHdPath, false, "Recover key with old hd path") | ||
cmd.Flags().Bool(client.FlagIndentResponse, false, "Add indent to JSON response") | ||
return cmd | ||
} | ||
|
||
/* | ||
input | ||
- bip39 mnemonic | ||
- bip39 passphrase | ||
- bip44 path | ||
- local encryption password | ||
output | ||
- armor encrypted private key (saved to file) | ||
*/ | ||
func runAddCmd(_ *cobra.Command, args []string) error { | ||
var kb keys.Keybase | ||
var err error | ||
var encryptPassword string | ||
|
||
buf := client.BufferStdin() | ||
name := args[0] | ||
|
||
interactive := viper.GetBool(flagInteractive) | ||
showMnemonic := !viper.GetBool(flagNoBackup) | ||
|
||
if viper.GetBool(flagDryRun) { | ||
// we throw this away, so don't enforce args, | ||
// we want to get a new random seed phrase quickly | ||
kb = keys.NewInMemory() | ||
encryptPassword = app.DefaultKeyPass | ||
} else { | ||
kb, err = sdkkeys.NewKeyBaseFromHomeFlag() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = kb.Get(name) | ||
if err == nil { | ||
// account exists, ask for user confirmation | ||
if response, err2 := client.GetConfirmation( | ||
fmt.Sprintf("override the existing name %s", name), buf); err2 != nil || !response { | ||
return err2 | ||
} | ||
} | ||
|
||
multisigKeys := viper.GetStringSlice(flagMultisig) | ||
if len(multisigKeys) != 0 { | ||
var pks []crypto.PubKey | ||
|
||
multisigThreshold := viper.GetInt(flagMultiSigThreshold) | ||
if err := validateMultisigThreshold(multisigThreshold, len(multisigKeys)); err != nil { | ||
return err | ||
} | ||
|
||
for _, keyname := range multisigKeys { | ||
k, err := kb.Get(keyname) | ||
if err != nil { | ||
return err | ||
} | ||
pks = append(pks, k.GetPubKey()) | ||
} | ||
|
||
// Handle --nosort | ||
if !viper.GetBool(flagNoSort) { | ||
sort.Slice(pks, func(i, j int) bool { | ||
return bytes.Compare(pks[i].Address(), pks[j].Address()) < 0 | ||
}) | ||
} | ||
|
||
pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks) | ||
if _, err := kb.CreateMulti(name, pk); err != nil { | ||
return err | ||
} | ||
|
||
fmt.Fprintf(os.Stderr, "Key %q saved to disk.\n", name) | ||
return nil | ||
} | ||
|
||
// ask for a password when generating a local key | ||
if viper.GetString(sdkkeys.FlagPublicKey) == "" && !viper.GetBool(client.FlagUseLedger) { | ||
encryptPassword, err = client.GetCheckPassword( | ||
"Enter a passphrase to encrypt your key to disk:", | ||
"Repeat the passphrase:", buf) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
if viper.GetString(sdkkeys.FlagPublicKey) != "" { | ||
pk, err := sdk.GetAccPubKeyBech32(viper.GetString(sdkkeys.FlagPublicKey)) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = kb.CreateOffline(name, pk) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
account := uint32(viper.GetInt(flagAccount)) | ||
index := uint32(viper.GetInt(flagIndex)) | ||
|
||
// If we're using ledger, only thing we need is the path and the bech32 prefix. | ||
if viper.GetBool(client.FlagUseLedger) { | ||
bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() | ||
info, err := kb.CreateLedger(name, keys.Secp256k1, bech32PrefixAccAddr, account, index) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return printCreate(info, false, "") | ||
} | ||
|
||
// Get bip39 mnemonic | ||
var mnemonic string | ||
var bip39Passphrase string | ||
|
||
if interactive || viper.GetBool(flagRecover) { | ||
bip39Message := "Enter your bip39 mnemonic" | ||
if !viper.GetBool(flagRecover) { | ||
bip39Message = "Enter your bip39 mnemonic, or hit enter to generate one." | ||
} | ||
|
||
mnemonic, err = client.GetString(bip39Message, buf) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !bip39.IsMnemonicValid(mnemonic) { | ||
return errors.New("invalid mnemonic") | ||
} | ||
} | ||
|
||
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 | ||
} | ||
|
||
mnemonic, err = bip39.NewMnemonic(entropySeed[:]) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// override bip39 passphrase | ||
if interactive { | ||
bip39Passphrase, err = client.GetString( | ||
"Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed. "+ | ||
"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") | ||
} | ||
} | ||
} | ||
|
||
coinType := util.CoinType | ||
if (viper.GetBool(flagRecover) || viper.GetBool(flagInteractive)) && viper.GetBool(flagOldHdPath) { | ||
coinType = sdk.CoinType | ||
} | ||
|
||
hdParams := hd.NewFundraiserParams(account, coinType, index) | ||
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *hdParams) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Recover key from seed passphrase | ||
if viper.GetBool(flagRecover) { | ||
// Hide mnemonic from output | ||
showMnemonic = false | ||
mnemonic = "" | ||
} | ||
|
||
return printCreate(info, showMnemonic, mnemonic) | ||
} | ||
|
||
func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error { | ||
output := viper.Get(cli.OutputFlag) | ||
|
||
switch output { | ||
case sdkkeys.OutputFormatText: | ||
fmt.Fprintln(os.Stderr) | ||
printKeyInfo(info, keys.Bech32KeyOutput) | ||
|
||
// print mnemonic unless requested not to. | ||
if showMnemonic { | ||
fmt.Fprintln(os.Stderr, "\n**Important** write this mnemonic 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, mnemonic) | ||
} | ||
case sdkkeys.OutputFormatJSON: | ||
out, err := keys.Bech32KeyOutput(info) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if showMnemonic { | ||
out.Mnemonic = mnemonic | ||
} | ||
|
||
var jsonString []byte | ||
if viper.GetBool(client.FlagIndentResponse) { | ||
jsonString, err = cdc.MarshalJSONIndent(out, "", " ") | ||
} else { | ||
jsonString, err = cdc.MarshalJSON(out) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
fmt.Fprintln(os.Stderr, string(jsonString)) | ||
default: | ||
return fmt.Errorf("I can't speak: %s", output) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.