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:
Austin Abell 2019-08-11 10:42:46 -04:00 committed by GitHub
parent 92dc7d9a59
commit cfac906f92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1742 additions and 18 deletions

View File

@ -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
}

View File

@ -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,
)

View File

@ -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)

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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())
}

View File

@ -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

View File

@ -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
View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}

View File

@ -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