Ethermint key generation (#78)
* WIP setting up Ethereum key CLI commands * Functional key gen and showing Ethereum address * Cleaned up changes * WIP setting up Ethereum key CLI commands * Functional key gen and showing Ethereum address * Cleaned up changes * Changed address to cosmos specific address * Remove default bech32 prefixes and add basic add command test * Changed Private key type to slice of bytes for compatibility and storability * switch back to using cosmos crypto Keybase interfaces * Changed key output to ethereum addressing instead of bitcoin and key generation to allow seeding from mnemonic and bip39 password * Updated show command and added test * Remove prefix requirement for showing keys and added existing keys commands to CLI temporarily * Removed unnecessary duplicate code * Readd prefixes for accounts temporarily * Fix linting issue * Remove TODO for setting PK to specific length of bytes (all functions use slice) * Cleaned up descriptions to remove multi-sigs
This commit is contained in:
parent
92dc7d9a59
commit
cfac906f92
@ -90,7 +90,7 @@ func newTestStdFee() auth.StdFee {
|
||||
// GenerateAddress generates an Ethereum address.
|
||||
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
|
||||
privkey, _ := crypto.GenerateKey()
|
||||
addr := ethcrypto.PubkeyToAddress(privkey.PublicKey)
|
||||
addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey)
|
||||
|
||||
return sdk.AccAddress(addr.Bytes()), privkey
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/cosmos/ethermint/rpc"
|
||||
"github.com/tendermint/go-amino"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/ethermint/rpc"
|
||||
"github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdkrpc "github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
emintkeys "github.com/cosmos/ethermint/keys"
|
||||
|
||||
emintapp "github.com/cosmos/ethermint/app"
|
||||
"github.com/spf13/cobra"
|
||||
@ -24,6 +26,7 @@ func main() {
|
||||
|
||||
// Read in the configuration file for the sdk
|
||||
config := sdk.GetConfig()
|
||||
// TODO: Remove or change prefix if usable to generate Ethereum address
|
||||
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
|
||||
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
|
||||
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
|
||||
@ -49,7 +52,9 @@ func main() {
|
||||
// TODO: Set up rest routes (if included, different from web3 api)
|
||||
rpc.Web3RpcCmd(cdc),
|
||||
client.LineBreak,
|
||||
// TODO: Remove these commands once ethermint keys and genesis set up
|
||||
keys.Commands(),
|
||||
emintkeys.Commands(),
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
|
@ -28,6 +28,7 @@ func main() {
|
||||
cdc := emintapp.MakeCodec()
|
||||
|
||||
config := sdk.GetConfig()
|
||||
// TODO: Remove or change prefix if usable to generate Ethereum address
|
||||
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
|
||||
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
|
||||
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
|
||||
|
@ -1,6 +1,8 @@
|
||||
package crypto
|
||||
|
||||
import "github.com/cosmos/cosmos-sdk/codec"
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
var cryptoCodec = codec.New()
|
||||
|
||||
|
25
crypto/keys/codec.go
Normal file
25
crypto/keys/codec.go
Normal file
@ -0,0 +1,25 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
emintCrypto "github.com/cosmos/ethermint/crypto"
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
var cdc *codec.Codec
|
||||
|
||||
func init() {
|
||||
cdc = codec.New()
|
||||
cryptoAmino.RegisterAmino(cdc)
|
||||
cdc.RegisterInterface((*cosmosKeys.Info)(nil), nil)
|
||||
emintCrypto.RegisterCodec(cdc)
|
||||
cdc.RegisterConcrete(hd.BIP44Params{}, "crypto/keys/hd/BIP44Params", nil)
|
||||
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
|
||||
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
|
||||
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
|
||||
// cdc.RegisterConcrete(multiInfo{}, "crypto/keys/multiInfo", nil)
|
||||
cdc.Seal()
|
||||
}
|
516
crypto/keys/keybase.go
Normal file
516
crypto/keys/keybase.go
Normal file
@ -0,0 +1,516 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
bip39 "github.com/cosmos/go-bip39"
|
||||
|
||||
emintCrypto "github.com/cosmos/ethermint/crypto"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
var _ cosmosKeys.Keybase = dbKeybase{}
|
||||
|
||||
// Language is a language to create the BIP 39 mnemonic in.
|
||||
// Currently, only english is supported though.
|
||||
// Find a list of all supported languages in the BIP 39 spec (word lists).
|
||||
type Language int
|
||||
|
||||
//noinspection ALL
|
||||
const (
|
||||
// English is the default language to create a mnemonic.
|
||||
// It is the only supported language by this package.
|
||||
English Language = iota + 1
|
||||
// Japanese is currently not supported.
|
||||
Japanese
|
||||
// Korean is currently not supported.
|
||||
Korean
|
||||
// Spanish is currently not supported.
|
||||
Spanish
|
||||
// ChineseSimplified is currently not supported.
|
||||
ChineseSimplified
|
||||
// ChineseTraditional is currently not supported.
|
||||
ChineseTraditional
|
||||
// French is currently not supported.
|
||||
French
|
||||
// Italian is currently not supported.
|
||||
Italian
|
||||
addressSuffix = "address"
|
||||
infoSuffix = "info"
|
||||
)
|
||||
|
||||
const (
|
||||
// used for deriving seed from mnemonic
|
||||
DefaultBIP39Passphrase = ""
|
||||
|
||||
// bits of entropy to draw when creating a mnemonic
|
||||
defaultEntropySize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
|
||||
// different signing scheme than secp256k1.
|
||||
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported")
|
||||
|
||||
// ErrUnsupportedLanguage is raised when the caller tries to use a
|
||||
// different language than english for creating a mnemonic sentence.
|
||||
ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported")
|
||||
)
|
||||
|
||||
// dbKeybase combines encryption and storage implementation to provide
|
||||
// a full-featured key manager
|
||||
type dbKeybase struct {
|
||||
db dbm.DB
|
||||
}
|
||||
|
||||
// newDbKeybase creates a new keybase instance using the passed DB for reading and writing keys.
|
||||
func newDbKeybase(db dbm.DB) cosmosKeys.Keybase {
|
||||
return dbKeybase{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInMemory creates a transient keybase on top of in-memory storage
|
||||
// instance useful for testing purposes and on-the-fly key generation.
|
||||
func NewInMemory() cosmosKeys.Keybase { return dbKeybase{dbm.NewMemDB()} }
|
||||
|
||||
// CreateMnemonic generates a new key and persists it to storage, encrypted
|
||||
// using the provided password.
|
||||
// It returns the generated mnemonic and the key Info.
|
||||
// It returns an error if it fails to
|
||||
// generate a key for the given algo type, or if another key is
|
||||
// already stored under the same name.
|
||||
func (kb dbKeybase) CreateMnemonic(name string, language cosmosKeys.Language, passwd string, algo cosmosKeys.SigningAlgo) (info cosmosKeys.Info, mnemonic string, err error) {
|
||||
if language != cosmosKeys.English {
|
||||
return nil, "", ErrUnsupportedLanguage
|
||||
}
|
||||
if algo != Secp256k1 {
|
||||
err = ErrUnsupportedSigningAlgo
|
||||
return
|
||||
}
|
||||
|
||||
// default number of words (24):
|
||||
// this generates a mnemonic directly from the number of words by reading system entropy.
|
||||
entropy, err := bip39.NewEntropy(defaultEntropySize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
seed := bip39.NewSeed(mnemonic, DefaultBIP39Passphrase)
|
||||
fullFundraiserPath := types.GetConfig().GetFullFundraiserPath()
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, fullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateAccount converts a mnemonic to a private key and persists it, encrypted with the given password.
|
||||
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (cosmosKeys.Info, error) {
|
||||
coinType := types.GetConfig().GetCoinType()
|
||||
hdPath := hd.NewFundraiserParams(account, coinType, index)
|
||||
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info cosmosKeys.Info, err error) {
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||
return info, err
|
||||
}
|
||||
|
||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair
|
||||
// It returns the created key info and an error if the Ledger could not be queried
|
||||
func (kb dbKeybase) CreateLedger(name string, algo cosmosKeys.SigningAlgo, hrp string, account, index uint32) (cosmosKeys.Info, error) {
|
||||
if algo != Secp256k1 {
|
||||
return nil, ErrUnsupportedSigningAlgo
|
||||
}
|
||||
|
||||
coinType := types.GetConfig().GetCoinType()
|
||||
hdPath := hd.NewFundraiserParams(account, coinType, index)
|
||||
priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := priv.PubKey()
|
||||
|
||||
// Note: Once Cosmos App v1.3.1 is compulsory, it could be possible to check that pubkey and addr match
|
||||
return kb.writeLedgerKey(name, pub, *hdPath), nil
|
||||
}
|
||||
|
||||
// CreateOffline creates a new reference to an offline keypair. It returns the
|
||||
// created key info.
|
||||
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (cosmosKeys.Info, error) {
|
||||
return kb.writeOfflineKey(name, pub), nil
|
||||
}
|
||||
|
||||
// CreateMulti creates a new reference to a multisig (offline) keypair. It
|
||||
// returns the created key info.
|
||||
func (kb dbKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (cosmosKeys.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info cosmosKeys.Info, err error) {
|
||||
// create master key and derive first key:
|
||||
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
|
||||
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if we have a password, use it to encrypt the private key and store it
|
||||
// else store the public key only
|
||||
if passwd != "" {
|
||||
info = kb.writeLocalKey(name, emintCrypto.PrivKeySecp256k1(derivedPriv[:]), passwd)
|
||||
} else {
|
||||
pubk := emintCrypto.PrivKeySecp256k1(derivedPriv[:]).PubKey()
|
||||
info = kb.writeOfflineKey(name, pubk)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
func (kb dbKeybase) List() ([]cosmosKeys.Info, error) {
|
||||
var res []cosmosKeys.Info
|
||||
iter := kb.db.Iterator(nil, nil)
|
||||
defer iter.Close()
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
key := string(iter.Key())
|
||||
|
||||
// need to include only keys in storage that have an info suffix
|
||||
if strings.HasSuffix(key, infoSuffix) {
|
||||
info, err := readInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, info)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get returns the public information about one key.
|
||||
func (kb dbKeybase) Get(name string) (cosmosKeys.Info, error) {
|
||||
bs := kb.db.Get(infoKey(name))
|
||||
if len(bs) == 0 {
|
||||
return nil, keyerror.NewErrKeyNotFound(name)
|
||||
}
|
||||
return readInfo(bs)
|
||||
}
|
||||
|
||||
func (kb dbKeybase) GetByAddress(address types.AccAddress) (cosmosKeys.Info, error) {
|
||||
ik := kb.db.Get(addrKey(address))
|
||||
if len(ik) == 0 {
|
||||
return nil, fmt.Errorf("key with address %s not found", address)
|
||||
}
|
||||
bs := kb.db.Get(ik)
|
||||
return readInfo(bs)
|
||||
}
|
||||
|
||||
// Sign signs the msg with the named key.
|
||||
// It returns an error if the key doesn't exist or the decryption fails.
|
||||
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var priv tmcrypto.PrivKey
|
||||
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
case ledgerInfo:
|
||||
linfo := info.(ledgerInfo)
|
||||
priv, err = crypto.NewPrivKeyLedgerSecp256k1Unsafe(linfo.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// case offlineInfo, multiInfo:
|
||||
case offlineInfo:
|
||||
_, err := fmt.Fprintf(os.Stderr, "Message to sign:\n\n%s\n", msg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
_, err = fmt.Fprintf(os.Stderr, "\nEnter Amino-encoded signature:\n")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Will block until user inputs the signature
|
||||
signed, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := cdc.UnmarshalBinaryLengthPrefixed([]byte(signed), sig); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to decode signature")
|
||||
}
|
||||
|
||||
return sig, info.GetPubKey(), nil
|
||||
}
|
||||
|
||||
sig, err = priv.Sign(msg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pub = priv.PubKey()
|
||||
return sig, pub, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var priv tmcrypto.PrivKey
|
||||
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return nil, err
|
||||
}
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// case ledgerInfo, offlineInfo, multiInfo:
|
||||
case ledgerInfo, offlineInfo:
|
||||
return nil, errors.New("only works on local private keys")
|
||||
}
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
return mintkey.ArmorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format.
|
||||
// Retrieve a Info object by its name and return the public key in
|
||||
// a portable format.
|
||||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
info, err := readInfo(bz)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
}
|
||||
|
||||
// ExportPrivKey returns a private key in ASCII armored format.
|
||||
// It returns an error if the key does not exist or a wrong encryption passphrase is supplied.
|
||||
func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||
encryptPassphrase string) (armor string, err error) {
|
||||
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
|
||||
}
|
||||
|
||||
// ImportPrivKey imports a private key in ASCII armor format.
|
||||
// It returns an error if a key with the same name exists or a wrong encryption passphrase is
|
||||
// supplied.
|
||||
func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||
if _, err := kb.Get(name); err == nil {
|
||||
return errors.New("Cannot overwrite key " + name)
|
||||
}
|
||||
|
||||
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "couldn't import private key")
|
||||
}
|
||||
|
||||
kb.writeLocalKey(name, privKey, passphrase)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.db.Set(infoKey(name), infoBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportPubKey imports ASCII-armored public keys.
|
||||
// Store a new Info object holding a public key only, i.e. it will
|
||||
// not be possible to sign with it as it lacks the secret key.
|
||||
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.writeOfflineKey(name, pubKey)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key forever, but we must present the
|
||||
// proper passphrase before deleting it (for security).
|
||||
// It returns an error if the key doesn't exist or
|
||||
// passphrases don't match.
|
||||
// Passphrase is ignored when deleting references to
|
||||
// offline and Ledger / HW wallet keys.
|
||||
func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error {
|
||||
// verify we have the proper password before deleting
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if linfo, ok := info.(localInfo); ok && !skipPass {
|
||||
if _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
kb.db.DeleteSync(addrKey(info.GetAddress()))
|
||||
kb.db.DeleteSync(infoKey(name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update changes the passphrase with which an already stored key is
|
||||
// encrypted.
|
||||
//
|
||||
// oldpass must be the current passphrase used for encryption,
|
||||
// getNewpass is a function to get the passphrase to permanently replace
|
||||
// the current passphrase
|
||||
func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newpass, err := getNewpass()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb.writeLocalKey(name, key, newpass)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String())
|
||||
}
|
||||
}
|
||||
|
||||
// CloseDB releases the lock and closes the storage backend.
|
||||
func (kb dbKeybase) CloseDB() {
|
||||
kb.db.Close()
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) cosmosKeys.Info {
|
||||
privkey, ok := priv.(emintCrypto.PrivKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid private key type: %T", priv))
|
||||
}
|
||||
// encrypt private key using passphrase
|
||||
privArmor := mintkey.EncryptArmorPrivKey(privkey, passphrase)
|
||||
// make Info
|
||||
pub := privkey.PubKey()
|
||||
pubkey, ok := pub.(emintCrypto.PubKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid public key type: %T", pub))
|
||||
}
|
||||
info := newLocalInfo(name, pubkey, privArmor)
|
||||
kb.writeInfo(name, info)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params) cosmosKeys.Info {
|
||||
pubkey, ok := pub.(emintCrypto.PubKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid public key type: %T", pub))
|
||||
}
|
||||
info := newLedgerInfo(name, pubkey, path)
|
||||
kb.writeInfo(name, info)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) cosmosKeys.Info {
|
||||
pubkey, ok := pub.(emintCrypto.PubKeySecp256k1)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid public key type: %T", pub))
|
||||
}
|
||||
info := newOfflineInfo(name, pubkey)
|
||||
kb.writeInfo(name, info)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeInfo(name string, info cosmosKeys.Info) {
|
||||
// write the info by key
|
||||
key := infoKey(name)
|
||||
serializedInfo := writeInfo(info)
|
||||
kb.db.SetSync(key, serializedInfo)
|
||||
// store a pointer to the infokey by address for fast lookup
|
||||
kb.db.SetSync(addrKey(info.GetAddress()), key)
|
||||
}
|
||||
|
||||
func addrKey(address types.AccAddress) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix))
|
||||
}
|
||||
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", name, infoSuffix))
|
||||
}
|
13
crypto/keys/keys.go
Normal file
13
crypto/keys/keys.go
Normal file
@ -0,0 +1,13 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing.
|
||||
type SigningAlgo string
|
||||
|
||||
const (
|
||||
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters.
|
||||
Secp256k1 = cosmosKeys.SigningAlgo("emintsecp256k1")
|
||||
)
|
222
crypto/keys/lazy_keybase.go
Normal file
222
crypto/keys/lazy_keybase.go
Normal file
@ -0,0 +1,222 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var _ cosmosKeys.Keybase = lazyKeybase{}
|
||||
|
||||
type lazyKeybase struct {
|
||||
name string
|
||||
dir string
|
||||
}
|
||||
|
||||
// New creates a new instance of a lazy keybase.
|
||||
func New(name, dir string) cosmosKeys.Keybase {
|
||||
if err := cmn.EnsureDir(dir, 0700); err != nil {
|
||||
panic(fmt.Sprintf("failed to create Keybase directory: %s", err))
|
||||
}
|
||||
|
||||
return lazyKeybase{name: name, dir: dir}
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) List() ([]cosmosKeys.Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).List()
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Get(name string) (cosmosKeys.Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Get(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (cosmosKeys.Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).GetByAddress(address)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Delete(name, passphrase, skipPass)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Sign(name, passphrase, msg)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateMnemonic(name string, language cosmosKeys.Language, passwd string, algo cosmosKeys.SigningAlgo) (info cosmosKeys.Info, seed string, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).CreateMnemonic(name, language, passwd, algo)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (cosmosKeys.Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (cosmosKeys.Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateLedger(name string, algo cosmosKeys.SigningAlgo, hrp string, account, index uint32) (info cosmosKeys.Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).CreateLedger(name, algo, hrp, account, index)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info cosmosKeys.Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).CreateOffline(name, pubkey)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info cosmosKeys.Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).CreateMulti(name, pubkey)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Update(name, oldpass, getNewpass)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Import(name string, armor string) (err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Import(name, armor)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).ImportPrivKey(name, armor, passphrase)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).ImportPubKey(name, armor)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Export(name string) (armor string, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).Export(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).ExportPubKey(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||
encryptPassphrase string) (armor string, err error) {
|
||||
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDbKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CloseDB() {}
|
85
crypto/keys/output.go
Normal file
85
crypto/keys/output.go
Normal file
@ -0,0 +1,85 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
// KeyOutput defines a structure wrapping around an Info object used for output
|
||||
// functionality.
|
||||
type KeyOutput struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
PubKey string `json:"pubkey"`
|
||||
Mnemonic string `json:"mnemonic,omitempty"`
|
||||
Threshold uint `json:"threshold,omitempty"`
|
||||
}
|
||||
|
||||
// Bech32KeysOutput returns a slice of KeyOutput objects, each with the "acc"
|
||||
// Bech32 prefixes, given a slice of Info objects. It returns an error if any
|
||||
// call to Bech32KeyOutput fails.
|
||||
func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) {
|
||||
kos := make([]cosmosKeys.KeyOutput, len(infos))
|
||||
for i, info := range infos {
|
||||
ko, err := Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kos[i] = ko
|
||||
}
|
||||
|
||||
return kos, nil
|
||||
}
|
||||
|
||||
// Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes.
|
||||
func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
||||
consAddr := keyInfo.GetPubKey().Address()
|
||||
bytes := keyInfo.GetPubKey().Bytes()
|
||||
|
||||
// bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey())
|
||||
// if err != nil {
|
||||
// return KeyOutput{}, err
|
||||
// }
|
||||
|
||||
return cosmosKeys.KeyOutput{
|
||||
Name: keyInfo.GetName(),
|
||||
Type: keyInfo.GetType().String(),
|
||||
Address: consAddr.String(),
|
||||
PubKey: hex.EncodeToString(bytes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes.
|
||||
func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
||||
valAddr := keyInfo.GetPubKey().Address()
|
||||
bytes := keyInfo.GetPubKey().Bytes()
|
||||
|
||||
// bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey())
|
||||
// if err != nil {
|
||||
// return KeyOutput{}, err
|
||||
// }
|
||||
|
||||
return cosmosKeys.KeyOutput{
|
||||
Name: keyInfo.GetName(),
|
||||
Type: keyInfo.GetType().String(),
|
||||
Address: valAddr.String(),
|
||||
PubKey: hex.EncodeToString(bytes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes.
|
||||
func Bech32KeyOutput(info cosmosKeys.Info) (cosmosKeys.KeyOutput, error) {
|
||||
accAddr := info.GetPubKey().Address()
|
||||
bytes := info.GetPubKey().Bytes()
|
||||
|
||||
ko := cosmosKeys.KeyOutput{
|
||||
Name: info.GetName(),
|
||||
Type: info.GetType().String(),
|
||||
Address: accAddr.String(),
|
||||
PubKey: hex.EncodeToString(bytes),
|
||||
}
|
||||
|
||||
return ko, nil
|
||||
}
|
156
crypto/keys/types.go
Normal file
156
crypto/keys/types.go
Normal file
@ -0,0 +1,156 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
emintCrypto "github.com/cosmos/ethermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// KeyType reflects a human-readable type for key listing.
|
||||
type KeyType uint
|
||||
|
||||
// Info KeyTypes
|
||||
const (
|
||||
TypeLocal KeyType = 0
|
||||
TypeLedger KeyType = 1
|
||||
TypeOffline KeyType = 2
|
||||
TypeMulti KeyType = 3
|
||||
)
|
||||
|
||||
var keyTypes = map[KeyType]string{
|
||||
TypeLocal: "local",
|
||||
TypeLedger: "ledger",
|
||||
TypeOffline: "offline",
|
||||
TypeMulti: "multi",
|
||||
}
|
||||
|
||||
// String implements the stringer interface for KeyType.
|
||||
func (kt KeyType) String() string {
|
||||
return keyTypes[kt]
|
||||
}
|
||||
|
||||
var (
|
||||
_ cosmosKeys.Info = &localInfo{}
|
||||
_ cosmosKeys.Info = &ledgerInfo{}
|
||||
_ cosmosKeys.Info = &offlineInfo{}
|
||||
)
|
||||
|
||||
// localInfo is the public information about a locally stored key
|
||||
type localInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey emintCrypto.PubKeySecp256k1 `json:"pubkey"`
|
||||
PrivKeyArmor string `json:"privkey.armor"`
|
||||
}
|
||||
|
||||
func newLocalInfo(name string, pub emintCrypto.PubKeySecp256k1, privArmor string) cosmosKeys.Info {
|
||||
return &localInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
PrivKeyArmor: privArmor,
|
||||
}
|
||||
}
|
||||
|
||||
func (i localInfo) GetType() cosmosKeys.KeyType {
|
||||
return cosmosKeys.TypeLocal
|
||||
}
|
||||
|
||||
func (i localInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i localInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
func (i localInfo) GetAddress() types.AccAddress {
|
||||
return i.PubKey.Address().Bytes()
|
||||
}
|
||||
|
||||
func (i localInfo) GetPath() (*hd.BIP44Params, error) {
|
||||
return nil, fmt.Errorf("BIP44 Paths are not available for this type")
|
||||
}
|
||||
|
||||
// ledgerInfo is the public information about a Ledger key
|
||||
type ledgerInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey emintCrypto.PubKeySecp256k1 `json:"pubkey"`
|
||||
Path hd.BIP44Params `json:"path"`
|
||||
}
|
||||
|
||||
func newLedgerInfo(name string, pub emintCrypto.PubKeySecp256k1, path hd.BIP44Params) cosmosKeys.Info {
|
||||
return &ledgerInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetType() cosmosKeys.KeyType {
|
||||
return cosmosKeys.TypeLedger
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetAddress() types.AccAddress {
|
||||
return i.PubKey.Address().Bytes()
|
||||
}
|
||||
|
||||
func (i ledgerInfo) GetPath() (*hd.BIP44Params, error) {
|
||||
tmp := i.Path
|
||||
return &tmp, nil
|
||||
}
|
||||
|
||||
// offlineInfo is the public information about an offline key
|
||||
type offlineInfo struct {
|
||||
Name string `json:"name"`
|
||||
PubKey emintCrypto.PubKeySecp256k1 `json:"pubkey"`
|
||||
}
|
||||
|
||||
func newOfflineInfo(name string, pub emintCrypto.PubKeySecp256k1) cosmosKeys.Info {
|
||||
return &offlineInfo{
|
||||
Name: name,
|
||||
PubKey: pub,
|
||||
}
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetType() cosmosKeys.KeyType {
|
||||
return cosmosKeys.TypeOffline
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetName() string {
|
||||
return i.Name
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetPubKey() crypto.PubKey {
|
||||
return i.PubKey
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetAddress() types.AccAddress {
|
||||
return i.PubKey.Address().Bytes()
|
||||
}
|
||||
|
||||
func (i offlineInfo) GetPath() (*hd.BIP44Params, error) {
|
||||
return nil, fmt.Errorf("BIP44 Paths are not available for this type")
|
||||
}
|
||||
|
||||
// encoding info
|
||||
func writeInfo(i cosmosKeys.Info) []byte {
|
||||
return cdc.MustMarshalBinaryLengthPrefixed(i)
|
||||
}
|
||||
|
||||
// decoding info
|
||||
func readInfo(bz []byte) (info cosmosKeys.Info, err error) {
|
||||
err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info)
|
||||
return
|
||||
}
|
46
crypto/keys/types_test.go
Normal file
46
crypto/keys/types_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/ethermint/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteReadInfo(t *testing.T) {
|
||||
tmpKey, err := crypto.GenerateKey()
|
||||
assert.NoError(t, err)
|
||||
pkey := tmpKey.PubKey()
|
||||
pubkey, ok := pkey.(crypto.PubKeySecp256k1)
|
||||
assert.True(t, ok)
|
||||
|
||||
info := newOfflineInfo("offline", pubkey)
|
||||
bytes := writeInfo(info)
|
||||
assert.NotNil(t, bytes)
|
||||
|
||||
regeneratedKey, err := readInfo(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info.GetPubKey(), regeneratedKey.GetPubKey())
|
||||
assert.Equal(t, info.GetName(), regeneratedKey.GetName())
|
||||
|
||||
info = newLocalInfo("local", pubkey, "testarmor")
|
||||
bytes = writeInfo(info)
|
||||
assert.NotNil(t, bytes)
|
||||
|
||||
regeneratedKey, err = readInfo(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info.GetPubKey(), regeneratedKey.GetPubKey())
|
||||
assert.Equal(t, info.GetName(), regeneratedKey.GetName())
|
||||
|
||||
info = newLedgerInfo("ledger", pubkey,
|
||||
hd.BIP44Params{Purpose: 1, CoinType: 1, Account: 1, Change: false, AddressIndex: 1})
|
||||
bytes = writeInfo(info)
|
||||
assert.NotNil(t, bytes)
|
||||
|
||||
regeneratedKey, err = readInfo(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info.GetPubKey(), regeneratedKey.GetPubKey())
|
||||
assert.Equal(t, info.GetName(), regeneratedKey.GetName())
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ var _ tmcrypto.PrivKey = PrivKeySecp256k1{}
|
||||
|
||||
// PrivKeySecp256k1 defines a type alias for an ecdsa.PrivateKey that implements
|
||||
// Tendermint's PrivateKey interface.
|
||||
type PrivKeySecp256k1 ecdsa.PrivateKey
|
||||
type PrivKeySecp256k1 []byte
|
||||
|
||||
// GenerateKey generates a new random private key. It returns an error upon
|
||||
// failure.
|
||||
@ -27,17 +27,18 @@ func GenerateKey() (PrivKeySecp256k1, error) {
|
||||
return PrivKeySecp256k1{}, err
|
||||
}
|
||||
|
||||
return PrivKeySecp256k1(*priv), nil
|
||||
return PrivKeySecp256k1(ethcrypto.FromECDSA(priv)), nil
|
||||
}
|
||||
|
||||
// PubKey returns the ECDSA private key's public key.
|
||||
func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey {
|
||||
return PubKeySecp256k1{privkey.PublicKey}
|
||||
ecdsaPKey := privkey.ToECDSA()
|
||||
return PubKeySecp256k1(ethcrypto.FromECDSAPub(&ecdsaPKey.PublicKey))
|
||||
}
|
||||
|
||||
// Bytes returns the raw ECDSA private key bytes.
|
||||
func (privkey PrivKeySecp256k1) Bytes() []byte {
|
||||
return ethcrypto.FromECDSA(privkey.ToECDSA())
|
||||
return privkey
|
||||
}
|
||||
|
||||
// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the
|
||||
@ -58,7 +59,8 @@ func (privkey PrivKeySecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
||||
|
||||
// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
|
||||
func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey {
|
||||
return (*ecdsa.PrivateKey)(&privkey)
|
||||
key, _ := ethcrypto.ToECDSA(privkey.Bytes())
|
||||
return key
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -68,18 +70,17 @@ var _ tmcrypto.PubKey = (*PubKeySecp256k1)(nil)
|
||||
|
||||
// PubKeySecp256k1 defines a type alias for an ecdsa.PublicKey that implements
|
||||
// Tendermint's PubKey interface.
|
||||
type PubKeySecp256k1 struct {
|
||||
pubkey ecdsa.PublicKey
|
||||
}
|
||||
type PubKeySecp256k1 []byte
|
||||
|
||||
// Address returns the address of the ECDSA public key.
|
||||
func (key PubKeySecp256k1) Address() tmcrypto.Address {
|
||||
return tmcrypto.Address(ethcrypto.PubkeyToAddress(key.pubkey).Bytes())
|
||||
pubk, _ := ethcrypto.UnmarshalPubkey(key)
|
||||
return tmcrypto.Address(ethcrypto.PubkeyToAddress(*pubk).Bytes())
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes of the ECDSA public key.
|
||||
func (key PubKeySecp256k1) Bytes() []byte {
|
||||
return ethcrypto.FromECDSAPub(&key.pubkey)
|
||||
return key
|
||||
}
|
||||
|
||||
// VerifyBytes verifies that the ECDSA public key created a given signature over
|
||||
|
@ -24,7 +24,7 @@ func TestPrivKeySecp256k1PrivKey(t *testing.T) {
|
||||
|
||||
// validate Ethereum address equality
|
||||
addr := privKey.PubKey().Address()
|
||||
expectedAddr := ethcrypto.PubkeyToAddress(privKey.PublicKey)
|
||||
expectedAddr := ethcrypto.PubkeyToAddress(privKey.ToECDSA().PublicKey)
|
||||
require.Equal(t, expectedAddr.Bytes(), addr.Bytes())
|
||||
|
||||
// validate we can sign some bytes
|
||||
|
3
go.mod
3
go.mod
@ -8,7 +8,7 @@ require (
|
||||
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 // indirect
|
||||
github.com/cespare/cp v1.1.1 // indirect
|
||||
github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f
|
||||
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect
|
||||
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d
|
||||
github.com/deckarep/golang-set v1.7.1 // indirect
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
|
||||
github.com/elastic/gosigar v0.10.3 // indirect
|
||||
@ -58,4 +58,5 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect
|
||||
google.golang.org/grpc v1.22.0 // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
258
keys/add.go
Normal file
258
keys/add.go
Normal file
@ -0,0 +1,258 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/input"
|
||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/ethermint/crypto/keys"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
flagInteractive = "interactive"
|
||||
flagRecover = "recover"
|
||||
flagNoBackup = "no-backup"
|
||||
// flagDryRun = "dry-run"
|
||||
flagAccount = "account"
|
||||
flagIndex = "index"
|
||||
// flagNoSort = "nosort"
|
||||
|
||||
// DefaultKeyPass contains the default key password for genesis transactions
|
||||
DefaultKeyPass = "12345678"
|
||||
|
||||
mnemonicEntropySize = 256
|
||||
)
|
||||
|
||||
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.
|
||||
`,
|
||||
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(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(flags.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(flags.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(cmd *cobra.Command, args []string) error {
|
||||
var kb cosmosKeys.Keybase
|
||||
var err error
|
||||
var encryptPassword string
|
||||
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
name := args[0]
|
||||
|
||||
interactive := viper.GetBool(flagInteractive)
|
||||
showMnemonic := !viper.GetBool(flagNoBackup)
|
||||
|
||||
kb, err = clientkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = kb.Get(name)
|
||||
if err == nil {
|
||||
// account exists, ask for user confirmation
|
||||
response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", name), inBuf)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
if !response {
|
||||
return errors.New("aborted")
|
||||
}
|
||||
}
|
||||
|
||||
// ask for a password when generating a local key
|
||||
if viper.GetString(FlagPublicKey) == "" && !viper.GetBool(flags.FlagUseLedger) {
|
||||
encryptPassword, err = input.GetCheckPassword(
|
||||
"Enter a passphrase to encrypt your key to disk:",
|
||||
"Repeat the passphrase:", inBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
if viper.GetString(FlagPublicKey) != "" {
|
||||
pk, err := sdk.GetAccPubKeyBech32(viper.GetString(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(flags.FlagUseLedger) {
|
||||
// bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix()
|
||||
// info, err := kb.CreateLedger(name, keys.Secp256k1, bech32PrefixAccAddr, account, index)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return printCreate(cmd, 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 = input.GetString(bip39Message, inBuf)
|
||||
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 = 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info, err := kb.CreateAccount(name, mnemonic, bip39Passphrase, encryptPassword, account, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recover key from seed passphrase
|
||||
if viper.GetBool(flagRecover) {
|
||||
// Hide mnemonic from output
|
||||
showMnemonic = false
|
||||
mnemonic = ""
|
||||
}
|
||||
|
||||
return printCreate(cmd, info, showMnemonic, mnemonic)
|
||||
}
|
||||
|
||||
func printCreate(cmd *cobra.Command, info cosmosKeys.Info, showMnemonic bool, mnemonic string) error {
|
||||
output := viper.Get(cli.OutputFlag)
|
||||
|
||||
switch output {
|
||||
case clientkeys.OutputFormatText:
|
||||
cmd.PrintErrln()
|
||||
printKeyInfo(info, keys.Bech32KeyOutput)
|
||||
|
||||
// print mnemonic unless requested not to.
|
||||
if showMnemonic {
|
||||
cmd.PrintErrln("\n**Important** write this mnemonic phrase in a safe place.")
|
||||
cmd.PrintErrln("It is the only way to recover your account if you ever forget your password.")
|
||||
cmd.PrintErrln("")
|
||||
cmd.PrintErrln(mnemonic)
|
||||
}
|
||||
case clientkeys.OutputFormatJSON:
|
||||
out, err := keys.Bech32KeyOutput(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if showMnemonic {
|
||||
out.Mnemonic = mnemonic
|
||||
}
|
||||
|
||||
var jsonString []byte
|
||||
if viper.GetBool(flags.FlagIndentResponse) {
|
||||
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
|
||||
} else {
|
||||
jsonString, err = cdc.MarshalJSON(out)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.PrintErrln(string(jsonString))
|
||||
default:
|
||||
return fmt.Errorf("I can't speak: %s", output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
48
keys/add_test.go
Normal file
48
keys/add_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
)
|
||||
|
||||
func TestAddCommandBasic(t *testing.T) {
|
||||
cmd := addKeyCommand()
|
||||
assert.NotNil(t, cmd)
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmd)
|
||||
|
||||
kbHome, kbCleanUp := tests.NewTestCaseDir(t)
|
||||
assert.NotNil(t, kbHome)
|
||||
defer kbCleanUp()
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
|
||||
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||
|
||||
mockIn.Reset("test1234\ntest1234\n")
|
||||
err := runAddCmd(cmd, []string{"keyname1"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||
|
||||
mockIn.Reset("test1234\ntest1234\n")
|
||||
err = runAddCmd(cmd, []string{"keyname1"})
|
||||
assert.Error(t, err)
|
||||
|
||||
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||
|
||||
mockIn.Reset("y\ntest1234\ntest1234\n")
|
||||
err = runAddCmd(cmd, []string{"keyname1"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
viper.Set(cli.OutputFlag, OutputFormatJSON)
|
||||
|
||||
mockIn.Reset("test1234\ntest1234\n")
|
||||
err = runAddCmd(cmd, []string{"keyname2"})
|
||||
assert.NoError(t, err)
|
||||
}
|
23
keys/codec.go
Normal file
23
keys/codec.go
Normal file
@ -0,0 +1,23 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
var cdc *codec.Codec
|
||||
|
||||
func init() {
|
||||
cdc = codec.New()
|
||||
codec.RegisterCrypto(cdc)
|
||||
cdc.Seal()
|
||||
}
|
||||
|
||||
// marshal keys
|
||||
func MarshalJSON(o interface{}) ([]byte, error) {
|
||||
return cdc.MarshalJSON(o)
|
||||
}
|
||||
|
||||
// unmarshal json
|
||||
func UnmarshalJSON(bz []byte, ptr interface{}) error {
|
||||
return cdc.UnmarshalJSON(bz, ptr)
|
||||
}
|
34
keys/root.go
Normal file
34
keys/root.go
Normal file
@ -0,0 +1,34 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
)
|
||||
|
||||
// Commands registers a sub-tree of commands to interact with
|
||||
// local private key storage.
|
||||
func Commands() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "emintkeys",
|
||||
Short: "Add or view local private keys",
|
||||
Long: `Keys allows you to manage your local keystore for tendermint.
|
||||
|
||||
These keys may be in any format supported by go-crypto and can be
|
||||
used by light-clients, full nodes, or any other application that
|
||||
needs to sign with a private key.`,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
// mnemonicKeyCommand(),
|
||||
addKeyCommand(),
|
||||
// exportKeyCommand(),
|
||||
// importKeyCommand(),
|
||||
// listKeysCmd(),
|
||||
showKeysCmd(),
|
||||
flags.LineBreak,
|
||||
// deleteKeyCommand(),
|
||||
// updateKeyCommand(),
|
||||
// parseKeyStringCommand(),
|
||||
)
|
||||
return cmd
|
||||
}
|
124
keys/show.go
Normal file
124
keys/show.go
Normal file
@ -0,0 +1,124 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/ethermint/crypto/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
// FlagAddress is the flag for the user's address on the command line.
|
||||
FlagAddress = "address"
|
||||
// FlagPublicKey represents the user's public key on the command line.
|
||||
FlagPublicKey = "pubkey"
|
||||
// FlagBechPrefix defines a desired Bech32 prefix encoding for a key.
|
||||
FlagBechPrefix = "bech"
|
||||
// FlagDevice indicates that the information should be shown in the device
|
||||
FlagDevice = "device"
|
||||
)
|
||||
|
||||
func showKeysCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show <name>",
|
||||
Short: "Show key info for the given name",
|
||||
Long: `Return public details of a single local key.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: runShowCmd,
|
||||
}
|
||||
|
||||
cmd.Flags().String(FlagBechPrefix, sdk.PrefixAccount, "The Bech32 prefix encoding for a key (acc|val|cons)")
|
||||
cmd.Flags().BoolP(FlagAddress, "a", false, "Output the address only (overrides --output)")
|
||||
cmd.Flags().BoolP(FlagPublicKey, "p", false, "Output the public key only (overrides --output)")
|
||||
cmd.Flags().BoolP(FlagDevice, "d", false, "Output the address in a ledger device")
|
||||
cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runShowCmd(cmd *cobra.Command, args []string) (err error) {
|
||||
var info cosmosKeys.Info
|
||||
|
||||
if len(args) == 1 {
|
||||
info, err = GetKeyInfo(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("Must provide only one name")
|
||||
}
|
||||
|
||||
isShowAddr := viper.GetBool(FlagAddress)
|
||||
isShowPubKey := viper.GetBool(FlagPublicKey)
|
||||
isShowDevice := viper.GetBool(FlagDevice)
|
||||
|
||||
isOutputSet := false
|
||||
tmp := cmd.Flag(cli.OutputFlag)
|
||||
if tmp != nil {
|
||||
isOutputSet = tmp.Changed
|
||||
}
|
||||
|
||||
if isShowAddr && isShowPubKey {
|
||||
return errors.New("cannot use both --address and --pubkey at once")
|
||||
}
|
||||
|
||||
if isOutputSet && (isShowAddr || isShowPubKey) {
|
||||
return errors.New("cannot use --output with --address or --pubkey")
|
||||
}
|
||||
|
||||
keyOutputFunction := keys.Bech32KeyOutput
|
||||
|
||||
switch {
|
||||
case isShowAddr:
|
||||
printKeyAddress(info, keyOutputFunction)
|
||||
case isShowPubKey:
|
||||
printPubKey(info, keyOutputFunction)
|
||||
default:
|
||||
printKeyInfo(info, keyOutputFunction)
|
||||
}
|
||||
|
||||
if isShowDevice {
|
||||
if isShowPubKey {
|
||||
return fmt.Errorf("the device flag (-d) can only be used for addresses not pubkeys")
|
||||
}
|
||||
if viper.GetString(FlagBechPrefix) != "acc" {
|
||||
return fmt.Errorf("the device flag (-d) can only be used for accounts")
|
||||
}
|
||||
// Override and show in the device
|
||||
if info.GetType() != cosmosKeys.TypeLedger {
|
||||
return fmt.Errorf("the device flag (-d) can only be used for accounts stored in devices")
|
||||
}
|
||||
|
||||
hdpath, err := info.GetPath()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return crypto.LedgerShowAddress(*hdpath, info.GetPubKey())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Determine if the different prefixes are necessary for ethermint
|
||||
// func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) {
|
||||
// switch bechPrefix {
|
||||
// case sdk.PrefixAccount:
|
||||
// return keys.Bech32KeyOutput, nil
|
||||
// case sdk.PrefixValidator:
|
||||
// return keys.Bech32ValKeyOutput, nil
|
||||
// case sdk.PrefixConsensus:
|
||||
// return keys.Bech32ConsKeyOutput, nil
|
||||
// }
|
||||
|
||||
// return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix)
|
||||
// }
|
52
keys/show_test.go
Normal file
52
keys/show_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
)
|
||||
|
||||
func TestShowKeysCmd(t *testing.T) {
|
||||
cmd := showKeysCmd()
|
||||
assert.NotNil(t, cmd)
|
||||
assert.Equal(t, "false", cmd.Flag(FlagAddress).DefValue)
|
||||
assert.Equal(t, "false", cmd.Flag(FlagPublicKey).DefValue)
|
||||
}
|
||||
|
||||
func TestRunShowCmd(t *testing.T) {
|
||||
cmd := showKeysCmd()
|
||||
|
||||
err := runShowCmd(cmd, []string{"invalid"})
|
||||
assert.EqualError(t, err, "Key invalid not found")
|
||||
|
||||
// Prepare a key base
|
||||
// Now add a temporary keybase
|
||||
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||
defer cleanUp()
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
|
||||
fakeKeyName1 := "runShowCmd_Key1"
|
||||
fakeKeyName2 := "runShowCmd_Key2"
|
||||
kb, err := clientkeys.NewKeyBaseFromHomeFlag()
|
||||
assert.NoError(t, err)
|
||||
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0)
|
||||
assert.NoError(t, err)
|
||||
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", 0, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// // Now try single key
|
||||
// err = runShowCmd(cmd, []string{fakeKeyName1})
|
||||
// assert.EqualError(t, err, "invalid Bech32 prefix encoding provided: ")
|
||||
|
||||
// // Now try single key - set bech to acc
|
||||
// viper.Set(FlagBechPrefix, sdk.PrefixAccount)
|
||||
err = runShowCmd(cmd, []string{fakeKeyName1})
|
||||
assert.NoError(t, err)
|
||||
err = runShowCmd(cmd, []string{fakeKeyName2})
|
||||
assert.NoError(t, err)
|
||||
}
|
112
keys/utils.go
Normal file
112
keys/utils.go
Normal file
@ -0,0 +1,112 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
)
|
||||
|
||||
// available output formats.
|
||||
const (
|
||||
OutputFormatText = "text"
|
||||
OutputFormatJSON = "json"
|
||||
)
|
||||
|
||||
type bechKeyOutFn func(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error)
|
||||
|
||||
// GetKeyInfo returns key info for a given name. An error is returned if the
|
||||
// keybase cannot be retrieved or getting the info fails.
|
||||
func GetKeyInfo(name string) (cosmosKeys.Info, error) {
|
||||
keybase, err := clientkeys.NewKeyBaseFromHomeFlag()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keybase.Get(name)
|
||||
}
|
||||
|
||||
func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(keyInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case OutputFormatText:
|
||||
printTextInfos([]cosmosKeys.KeyOutput{ko})
|
||||
|
||||
case OutputFormatJSON:
|
||||
var out []byte
|
||||
var err error
|
||||
if viper.GetBool(flags.FlagIndentResponse) {
|
||||
out, err = cdc.MarshalJSONIndent(ko, "", " ")
|
||||
} else {
|
||||
out, err = cdc.MarshalJSON(ko)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
}
|
||||
|
||||
// func printInfos(infos []keys.Info) {
|
||||
// kos, err := keys.Bech32KeysOutput(infos)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// switch viper.Get(cli.OutputFlag) {
|
||||
// case OutputFormatText:
|
||||
// printTextInfos(kos)
|
||||
|
||||
// case OutputFormatJSON:
|
||||
// var out []byte
|
||||
// var err error
|
||||
|
||||
// if viper.GetBool(flags.FlagIndentResponse) {
|
||||
// out, err = cdc.MarshalJSONIndent(kos, "", " ")
|
||||
// } else {
|
||||
// out, err = cdc.MarshalJSON(kos)
|
||||
// }
|
||||
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// fmt.Printf("%s", out)
|
||||
// }
|
||||
// }
|
||||
|
||||
func printTextInfos(kos []cosmosKeys.KeyOutput) {
|
||||
out, err := yaml.Marshal(&kos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
func printKeyAddress(info cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(ko.Address)
|
||||
}
|
||||
|
||||
func printPubKey(info cosmosKeys.Info, bechKeyOut bechKeyOutFn) {
|
||||
ko, err := bechKeyOut(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(ko.PubKey)
|
||||
}
|
@ -19,7 +19,7 @@ func GenerateEthAddress() ethcmn.Address {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ethcrypto.PubkeyToAddress(priv.PublicKey)
|
||||
return ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
|
||||
}
|
||||
|
||||
// ValidateSigner attempts to validate a signer for a given slice of bytes over
|
||||
|
Loading…
Reference in New Issue
Block a user