29d3abcf09
* Reuse cosmos-sdk client library to create keyring Extracted from https://github.com/evmos/ethermint/pull/1168 Cleanup cmd code for easier to migration to cosmos-sdk 0.46 * Update cosmos-sdk v0.46 prepare for implementing cosmos-sdk feemarket and tx prioritization changelog refactor cmd use sdkmath fix lint fix unit tests fix unit test genesis fix unit tests fix unit test env setup fix unit tests fix unit tests register PrivKey impl fix extension options fix lint fix unit tests make HandlerOption.Validate private gofumpt fix msg response decoding fix sim test bump cosmos-sdk version fix sim test sdk 46 fix unit test fix unit tests update ibc-go
286 lines
7.1 KiB
Go
286 lines
7.1 KiB
Go
package keys
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
|
|
etherminthd "github.com/evmos/ethermint/crypto/hd"
|
|
|
|
bip39 "github.com/cosmos/go-bip39"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
"github.com/cosmos/cosmos-sdk/client/input"
|
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
const (
|
|
flagInteractive = "interactive"
|
|
flagRecover = "recover"
|
|
flagNoBackup = "no-backup"
|
|
flagCoinType = "coin-type"
|
|
flagAccount = "account"
|
|
flagIndex = "index"
|
|
flagMultisig = "multisig"
|
|
flagMultiSigThreshold = "multisig-threshold"
|
|
flagNoSort = "nosort"
|
|
flagHDPath = "hd-path"
|
|
|
|
mnemonicEntropySize = 256
|
|
)
|
|
|
|
/* RunAddCmd
|
|
input
|
|
- bip39 mnemonic
|
|
- bip39 passphrase
|
|
- bip44 path
|
|
- local encryption password
|
|
output
|
|
- armor encrypted private key (saved to file)
|
|
*/
|
|
func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error {
|
|
var err error
|
|
|
|
name := args[0]
|
|
interactive, _ := cmd.Flags().GetBool(flagInteractive)
|
|
noBackup, _ := cmd.Flags().GetBool(flagNoBackup)
|
|
showMnemonic := !noBackup
|
|
kb := ctx.Keyring
|
|
outputFormat := ctx.OutputFormat
|
|
|
|
keyringAlgos, _ := kb.SupportedAlgorithms()
|
|
algoStr, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm)
|
|
algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if dryRun, _ := cmd.Flags().GetBool(flags.FlagDryRun); dryRun {
|
|
// use in memory keybase
|
|
kb = keyring.NewInMemory(ctx.Codec, etherminthd.EthSecp256k1Option())
|
|
} else {
|
|
_, err = kb.Key(name)
|
|
if err == nil {
|
|
// account exists, ask for user confirmation
|
|
response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf, cmd.ErrOrStderr())
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
|
|
if !response {
|
|
return errors.New("aborted")
|
|
}
|
|
|
|
err2 = kb.Delete(name)
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
}
|
|
|
|
multisigKeys, _ := cmd.Flags().GetStringSlice(flagMultisig)
|
|
if len(multisigKeys) != 0 {
|
|
pks := make([]cryptotypes.PubKey, len(multisigKeys))
|
|
multisigThreshold, _ := cmd.Flags().GetInt(flagMultiSigThreshold)
|
|
if err := validateMultisigThreshold(multisigThreshold, len(multisigKeys)); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, keyname := range multisigKeys {
|
|
k, err := kb.Key(keyname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key, err := k.GetPubKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pks[i] = key
|
|
}
|
|
|
|
if noSort, _ := cmd.Flags().GetBool(flagNoSort); !noSort {
|
|
sort.Slice(pks, func(i, j int) bool {
|
|
return bytes.Compare(pks[i].Address(), pks[j].Address()) < 0
|
|
})
|
|
}
|
|
|
|
pk := multisig.NewLegacyAminoPubKey(multisigThreshold, pks)
|
|
k, err := kb.SaveMultisig(name, pk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printCreate(cmd, k, false, "", outputFormat)
|
|
}
|
|
}
|
|
|
|
pubKey, _ := cmd.Flags().GetString(keys.FlagPublicKey)
|
|
if pubKey != "" {
|
|
var pk cryptotypes.PubKey
|
|
if err = ctx.Codec.UnmarshalInterfaceJSON([]byte(pubKey), &pk); err != nil {
|
|
return err
|
|
}
|
|
|
|
k, err := kb.SaveOfflineKey(name, pk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printCreate(cmd, k, false, "", outputFormat)
|
|
}
|
|
|
|
coinType, _ := cmd.Flags().GetUint32(flagCoinType)
|
|
account, _ := cmd.Flags().GetUint32(flagAccount)
|
|
index, _ := cmd.Flags().GetUint32(flagIndex)
|
|
hdPath, _ := cmd.Flags().GetString(flagHDPath)
|
|
useLedger, _ := cmd.Flags().GetBool(flags.FlagUseLedger)
|
|
|
|
if len(hdPath) == 0 {
|
|
hdPath = hd.CreateHDPath(coinType, account, index).String()
|
|
} else if useLedger {
|
|
return errors.New("cannot set custom bip32 path with ledger")
|
|
}
|
|
|
|
// If we're using ledger, only thing we need is the path and the bech32 prefix.
|
|
if useLedger {
|
|
bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix()
|
|
k, err := kb.SaveLedgerKey(name, hd.Secp256k1, bech32PrefixAccAddr, coinType, account, index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printCreate(cmd, k, false, "", outputFormat)
|
|
}
|
|
|
|
// Get bip39 mnemonic
|
|
var mnemonic, bip39Passphrase string
|
|
|
|
recover, _ := cmd.Flags().GetBool(flagRecover)
|
|
if recover {
|
|
mnemonic, err = input.GetString("Enter your bip39 mnemonic", inBuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bip39.IsMnemonicValid(mnemonic) {
|
|
return errors.New("invalid mnemonic")
|
|
}
|
|
} else if interactive {
|
|
mnemonic, err = input.GetString("Enter your bip39 mnemonic, or hit enter to generate one.", inBuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bip39.IsMnemonicValid(mnemonic) && mnemonic != "" {
|
|
return errors.New("invalid mnemonic")
|
|
}
|
|
}
|
|
|
|
if len(mnemonic) == 0 {
|
|
// read entropy seed straight from tmcrypto.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 = input.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, \"\"", inBuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if they use one, make them re-enter it
|
|
if len(bip39Passphrase) != 0 {
|
|
p2, err := input.GetString("Repeat the passphrase:", inBuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if bip39Passphrase != p2 {
|
|
return errors.New("passphrases don't match")
|
|
}
|
|
}
|
|
}
|
|
|
|
k, err := kb.NewAccount(name, mnemonic, bip39Passphrase, hdPath, algo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Recover key from seed passphrase
|
|
if recover {
|
|
// Hide mnemonic from output
|
|
showMnemonic = false
|
|
mnemonic = ""
|
|
}
|
|
|
|
return printCreate(cmd, k, showMnemonic, mnemonic, outputFormat)
|
|
}
|
|
|
|
func printCreate(cmd *cobra.Command, k *keyring.Record, showMnemonic bool, mnemonic, outputFormat string) error {
|
|
switch outputFormat {
|
|
case OutputFormatText:
|
|
cmd.PrintErrln()
|
|
if err := printKeyringRecord(cmd.OutOrStdout(), k, keyring.MkAccKeyOutput, outputFormat); err != nil {
|
|
return err
|
|
}
|
|
|
|
// print mnemonic unless requested not to.
|
|
if showMnemonic {
|
|
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\n", mnemonic); err != nil {
|
|
return fmt.Errorf("failed to print mnemonic: %v", err)
|
|
}
|
|
}
|
|
case OutputFormatJSON:
|
|
out, err := keyring.MkAccKeyOutput(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if showMnemonic {
|
|
out.Mnemonic = mnemonic
|
|
}
|
|
|
|
jsonString, err := keys.KeysCdc.MarshalJSON(out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd.Println(string(jsonString))
|
|
|
|
default:
|
|
return fmt.Errorf("invalid output format %s", outputFormat)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateMultisigThreshold(k, nKeys int) error {
|
|
if k <= 0 {
|
|
return fmt.Errorf("threshold must be a positive integer")
|
|
}
|
|
if nKeys < k {
|
|
return fmt.Errorf(
|
|
"threshold k of n multisignature: %d < %d", nKeys, k)
|
|
}
|
|
return nil
|
|
}
|