From 615dcfee38cb953a82e4b25f949533f3a0c23922 Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 25 Jul 2019 16:10:05 -0400 Subject: [PATCH 01/18] WIP setting up Ethereum key CLI commands --- cmd/emintcli/main.go | 4 +- crypto/codec.go | 4 +- crypto/keys/codec.go | 24 ++ crypto/keys/keybase.go | 523 ++++++++++++++++++++++++++++++++++++ crypto/keys/keys.go | 9 + crypto/keys/lazy_keybase.go | 221 +++++++++++++++ crypto/keys/output.go | 89 ++++++ crypto/keys/types.go | 218 +++++++++++++++ crypto/secp256k1.go | 10 +- go.mod | 4 +- go.sum | 10 - keys/add.go | 261 ++++++++++++++++++ keys/codec.go | 23 ++ keys/mnemonic.go | 64 +++++ keys/root.go | 34 +++ keys/show.go | 133 +++++++++ keys/utils.go | 169 ++++++++++++ 17 files changed, 1782 insertions(+), 18 deletions(-) create mode 100644 crypto/keys/codec.go create mode 100644 crypto/keys/keybase.go create mode 100644 crypto/keys/keys.go create mode 100644 crypto/keys/lazy_keybase.go create mode 100644 crypto/keys/output.go create mode 100644 crypto/keys/types.go create mode 100644 keys/add.go create mode 100644 keys/codec.go create mode 100644 keys/mnemonic.go create mode 100644 keys/root.go create mode 100644 keys/show.go create mode 100644 keys/utils.go diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index b2963b1c5..acd72285b 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -5,9 +5,9 @@ import ( "path" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" 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/cosmos/ethermint/rpc" @@ -48,7 +48,7 @@ func main() { // TODO: Set up rest routes (if included, different from web3 api) rpc.Web3RpcCmd(cdc), client.LineBreak, - keys.Commands(), + emintkeys.Commands(), client.LineBreak, ) diff --git a/crypto/codec.go b/crypto/codec.go index 3c827a6ba..219585e27 100644 --- a/crypto/codec.go +++ b/crypto/codec.go @@ -1,6 +1,8 @@ package crypto -import "github.com/cosmos/cosmos-sdk/codec" +import ( + "github.com/cosmos/cosmos-sdk/codec" +) var cryptoCodec = codec.New() diff --git a/crypto/keys/codec.go b/crypto/keys/codec.go new file mode 100644 index 000000000..fa382daab --- /dev/null +++ b/crypto/keys/codec.go @@ -0,0 +1,24 @@ +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" +) + +var cdc *codec.Codec + +func init() { + cdc = codec.New() + cryptoAmino.RegisterAmino(cdc) + cdc.RegisterInterface((*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() +} diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go new file mode 100644 index 000000000..1cd9a580b --- /dev/null +++ b/crypto/keys/keybase.go @@ -0,0 +1,523 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + "reflect" + "strings" + + "github.com/pkg/errors" + + "github.com/cosmos/cosmos-sdk/crypto" + "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 _ 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) 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() 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 Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { + if language != 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) (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 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 SigningAlgo, hrp string, account, index uint32) (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) (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) (Info, error) { + return nil, nil +} + +func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info 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 != "" { + key, err := emintCrypto.GenerateKey() + if err != nil { + return nil, err + } + info = kb.writeLocalKey(name, key, passwd) + } else { + key, err := emintCrypto.GenerateKey() + if err != nil { + return nil, err + } + pubk := key.PubKey() + info = kb.writeOfflineKey(name, pubk) + } + return info, nil +} + +// List returns the keys from storage in alphabetical order. +func (kb dbKeybase) List() ([]Info, error) { + var res []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) (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) (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) 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) 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) 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 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)) +} diff --git a/crypto/keys/keys.go b/crypto/keys/keys.go new file mode 100644 index 000000000..c48840b97 --- /dev/null +++ b/crypto/keys/keys.go @@ -0,0 +1,9 @@ +package 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 = SigningAlgo("emintsecp256k1") +) diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go new file mode 100644 index 000000000..d1e855fe6 --- /dev/null +++ b/crypto/keys/lazy_keybase.go @@ -0,0 +1,221 @@ +package keys + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ Keybase = lazyKeybase{} + +type lazyKeybase struct { + name string + dir string +} + +// New creates a new instance of a lazy keybase. +func New(name, dir string) 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() ([]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) (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) (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 Language, passwd string, algo SigningAlgo) (info 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) (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) (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 SigningAlgo, hrp string, account, index uint32) (info 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 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 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() {} diff --git a/crypto/keys/output.go b/crypto/keys/output.go new file mode 100644 index 000000000..7dfbb6326 --- /dev/null +++ b/crypto/keys/output.go @@ -0,0 +1,89 @@ +package keys + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// 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 []byte `json:"address"` + PubKey []byte `json:"pubkey"` + Mnemonic string `json:"mnemonic,omitempty"` + Threshold uint `json:"threshold,omitempty"` + // PubKeys []multisigPubKeyOutput `json:"pubkeys,omitempty"` +} + +// type multisigPubKeyOutput struct { +// Address string `json:"address"` +// PubKey string `json:"pubkey"` +// Weight uint `json:"weight"` +// } + +// 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 []Info) ([]KeyOutput, error) { + kos := make([]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 Info) (KeyOutput, error) { + consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + + // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) + // if err != nil { + // return KeyOutput{}, err + // } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: consAddr.Bytes(), + PubKey: keyInfo.GetPubKey().Bytes(), + }, nil +} + +// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. +func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { + valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + + // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) + // if err != nil { + // return KeyOutput{}, err + // } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: valAddr.Bytes(), + PubKey: keyInfo.GetPubKey().Bytes(), + }, nil +} + +// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. If the +// public key is a multisig public key, then the threshold and constituent +// public keys will be added. +func Bech32KeyOutput(info Info) (KeyOutput, error) { + accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + + ko := KeyOutput{ + Name: info.GetName(), + Type: info.GetType().String(), + Address: accAddr.Bytes(), + PubKey: info.GetPubKey().Bytes(), + } + + return ko, nil +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go new file mode 100644 index 000000000..f08462588 --- /dev/null +++ b/crypto/keys/types.go @@ -0,0 +1,218 @@ +package keys + +import ( + "fmt" + + 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" +) + +// Keybase exposes operations on a generic keystore +type Keybase interface { + // CRUD on the keystore + List() ([]Info, error) + Get(name string) (Info, error) + GetByAddress(address types.AccAddress) (Info, error) + Delete(name, passphrase string, skipPass bool) error + + // Sign some bytes, looking up the private key to use + Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) + + // CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic + // key from that. + CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) + + // CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index} + CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) + + // Derive computes a BIP39 seed from th mnemonic and bip39Passwd. + // Derive private key from the seed using the BIP44 params. + // Encrypt the key to disk using encryptPasswd. + // See https://github.com/cosmos/cosmos-sdk/issues/2095 + Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) + + // CreateLedger creates, stores, and returns a new Ledger key reference + CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) + + // CreateOffline creates, stores, and returns a new offline key reference + CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) + + // CreateMulti creates, stores, and returns a new multsig (offline) key reference + CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) + + // The following operations will *only* work on locally-stored keys + Update(name, oldpass string, getNewpass func() (string, error)) error + Import(name string, armor string) (err error) + ImportPrivKey(name, armor, passphrase string) error + ImportPubKey(name string, armor string) (err error) + Export(name string) (armor string, err error) + ExportPubKey(name string) (armor string, err error) + ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) + + // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API + ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) + + // CloseDB closes the database. + CloseDB() +} + +// 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] +} + +// Info is the publicly exposed information about a keypair +type Info interface { + // Human-readable type for key listing + GetType() KeyType + // Name of the key + GetName() string + // Public key + GetPubKey() emintCrypto.PubKeySecp256k1 + // Address + GetAddress() types.AccAddress + // Bip44 Path + GetPath() (*hd.BIP44Params, error) +} + +var ( + _ Info = &localInfo{} + _ Info = &ledgerInfo{} + _ 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) Info { + return &localInfo{ + Name: name, + PubKey: pub, + PrivKeyArmor: privArmor, + } +} + +func (i localInfo) GetType() KeyType { + return TypeLocal +} + +func (i localInfo) GetName() string { + return i.Name +} + +func (i localInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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) Info { + return &ledgerInfo{ + Name: name, + PubKey: pub, + Path: path, + } +} + +func (i ledgerInfo) GetType() KeyType { + return TypeLedger +} + +func (i ledgerInfo) GetName() string { + return i.Name +} + +func (i ledgerInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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) Info { + return &offlineInfo{ + Name: name, + PubKey: pub, + } +} + +func (i offlineInfo) GetType() KeyType { + return TypeOffline +} + +func (i offlineInfo) GetName() string { + return i.Name +} + +func (i offlineInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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 Info) []byte { + return cdc.MustMarshalBinaryLengthPrefixed(i) +} + +// decoding info +func readInfo(bz []byte) (info Info, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) + return +} diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index f57b1400c..2966038d6 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -32,7 +32,8 @@ func GenerateKey() (PrivKeySecp256k1, error) { // PubKey returns the ECDSA private key's public key. func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey { - return PubKeySecp256k1{privkey.PublicKey} + ethcrypto.FromECDSAPub(&privkey.PublicKey) + return PubKeySecp256k1{ethcrypto.FromECDSAPub(&privkey.PublicKey)} } // Bytes returns the raw ECDSA private key bytes. @@ -69,17 +70,18 @@ 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 + pubkey []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.pubkey) + 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.pubkey } // VerifyBytes verifies that the ECDSA public key created a given signature over diff --git a/go.mod b/go.mod index 554a1f527..cf0bcbda1 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,11 @@ go 1.12 require ( github.com/allegro/bigcache v1.2.1 // indirect github.com/aristanetworks/goarista v0.0.0-20181101003910-5bb443fba8e0 // indirect + github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d 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 @@ -57,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 ) diff --git a/go.sum b/go.sum index 3171f7f78..6bee951b8 100644 --- a/go.sum +++ b/go.sum @@ -39,15 +39,11 @@ github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cosmos/cosmos-sdk v0.28.2-0.20190709220430-3f519832a7a5 h1:gakqjbZrqlUB1/rx8r/s86SVcRatOafVDfJF99yBcng= -github.com/cosmos/cosmos-sdk v0.28.2-0.20190709220430-3f519832a7a5/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f h1:jmVM19bsHZRVVe8rugzfILuL3VPgCj5b6941I20Naw0= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q= -github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= @@ -124,8 +120,6 @@ github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -146,7 +140,6 @@ github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -212,7 +205,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -301,7 +293,6 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+m github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -340,7 +331,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7 h1:bit1t3mgdR35yN0cX0G8orgLtOuyL9Wqxa1mccLB0ig= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/keys/add.go b/keys/add.go new file mode 100644 index 000000000..7c5e61e8e --- /dev/null +++ b/keys/add.go @@ -0,0 +1,261 @@ +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" + + 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" + // flagMultisig = "multisig" + flagNoSort = "nosort" + + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" +) + +func addKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "add ", + Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk", + Long: `Derive a new private key and encrypt to disk. +Optionally specify a BIP39 mnemonic, a BIP39 passphrase to further secure the mnemonic, +and a bip32 HD path to derive a specific account. The key will be stored under the given name +and encrypted with the given password. The only input that is required is the encryption password. + +If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase. +The flag --recover allows one to recover a key from a seed passphrase. +If run with --dry-run, a key would be generated (or recovered) but not stored to the +local keystore. +Use the --pubkey flag to add arbitrary public keys to the keystore for constructing +multisig transactions. + +You can add a multisig key by passing the list of key names you want the public +key to be composed of to the --multisig flag and the minimum number of signatures +required through --multisig-threshold. The keys are sorted by address, unless +the flag --nosort is set. +`, + Args: cobra.ExactArgs(1), + RunE: runAddCmd, + } + // cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)") + // cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") + cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") + cmd.Flags().String(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 keys.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 = 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 keys.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 +} diff --git a/keys/codec.go b/keys/codec.go new file mode 100644 index 000000000..eae45446e --- /dev/null +++ b/keys/codec.go @@ -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) +} diff --git a/keys/mnemonic.go b/keys/mnemonic.go new file mode 100644 index 000000000..35b54eccd --- /dev/null +++ b/keys/mnemonic.go @@ -0,0 +1,64 @@ +package keys + +const ( + // flagUserEntropy = "unsafe-entropy" + + mnemonicEntropySize = 256 +) + +// func mnemonicKeyCommand() *cobra.Command { +// cmd := &cobra.Command{ +// Use: "mnemonic", +// Short: "Compute the bip39 mnemonic for some input entropy", +// Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", +// RunE: runMnemonicCmd, +// } +// cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") +// return cmd +// } + +// func runMnemonicCmd(cmd *cobra.Command, args []string) error { +// flags := cmd.Flags() + +// userEntropy, _ := flags.GetBool(flagUserEntropy) + +// var entropySeed []byte + +// if userEntropy { +// // prompt the user to enter some entropy +// buf := bufio.NewReader(cmd.InOrStdin()) +// inputEntropy, err := input.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) +// if err != nil { +// return err +// } +// if len(inputEntropy) < 43 { +// return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) +// } +// conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf) +// if err != nil { +// return err +// } +// if !conf { +// return nil +// } + +// // hash input entropy to get entropy seed +// hashedEntropy := sha256.Sum256([]byte(inputEntropy)) +// entropySeed = hashedEntropy[:] +// } else { +// // read entropy seed straight from crypto.Rand +// var err error +// entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) +// if err != nil { +// return err +// } +// } + +// mnemonic, err := bip39.NewMnemonic(entropySeed[:]) +// if err != nil { +// return err +// } +// cmd.Println(mnemonic) + +// return nil +// } diff --git a/keys/root.go b/keys/root.go new file mode 100644 index 000000000..d18ef4682 --- /dev/null +++ b/keys/root.go @@ -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: "keys", + 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 +} diff --git a/keys/show.go b/keys/show.go new file mode 100644 index 000000000..3b3f1ab70 --- /dev/null +++ b/keys/show.go @@ -0,0 +1,133 @@ +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" + 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" + + // flagMultiSigThreshold = "multisig-threshold" + + // defaultMultiSigKeyName = "multi" +) + +func showKeysCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "show [name [name...]]", + Short: "Show key info for the given name", + Long: `Return public details of a single local key. If multiple names are +provided, then an ephemeral multisig key will be created under the name "multi" +consisting of all the keys provided by name and multisig threshold.`, + 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().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") + cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response") + + return cmd +} + +func runShowCmd(cmd *cobra.Command, args []string) (err error) { + var info keys.Info + + if len(args) == 1 { + info, err = GetKeyInfo(args[0]) + fmt.Printf("%+v\n\n", info) + 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") + } + + bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) + if err != nil { + return err + } + + switch { + case isShowAddr: + printKeyAddress(info, bechKeyOut) + case isShowPubKey: + printPubKey(info, bechKeyOut) + default: + printKeyInfo(info, bechKeyOut) + } + + 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() != keys.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 +} + +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) +} diff --git a/keys/utils.go b/keys/utils.go new file mode 100644 index 000000000..6cd088264 --- /dev/null +++ b/keys/utils.go @@ -0,0 +1,169 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" + "gopkg.in/yaml.v2" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/ethermint/crypto/keys" +) + +// available output formats. +const ( + OutputFormatText = "text" + OutputFormatJSON = "json" + + // defaultKeyDBName is the client's subdirectory where keys are stored. + defaultKeyDBName = "keys" +) + +type bechKeyOutFn func(keyInfo keys.Info) (keys.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) (keys.Info, error) { + keybase, err := NewKeyBaseFromHomeFlag() + if err != nil { + return nil, err + } + + return keybase.Get(name) +} + +// GetPassphrase returns a passphrase for a given name. It will first retrieve +// the key info for that name if the type is local, it'll fetch input from +// STDIN. Otherwise, an empty passphrase is returned. An error is returned if +// the key info cannot be fetched or reading from STDIN fails. +func GetPassphrase(name string) (string, error) { + var passphrase string + + keyInfo, err := GetKeyInfo(name) + if err != nil { + return passphrase, err + } + + // we only need a passphrase for locally stored keys + // TODO: (ref: #864) address security concerns + if keyInfo.GetType() == keys.TypeLocal { + passphrase, err = ReadPassphraseFromStdin(name) + if err != nil { + return passphrase, err + } + } + + return passphrase, nil +} + +// ReadPassphraseFromStdin attempts to read a passphrase from STDIN return an +// error upon failure. +func ReadPassphraseFromStdin(name string) (string, error) { + buf := bufio.NewReader(os.Stdin) + prompt := fmt.Sprintf("Password to sign with '%s':", name) + + passphrase, err := input.GetPassword(prompt, buf) + if err != nil { + return passphrase, fmt.Errorf("Error reading passphrase: %v", err) + } + + return passphrase, nil +} + +// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. +func NewKeyBaseFromHomeFlag() (keys.Keybase, error) { + rootDir := viper.GetString(flags.FlagHome) + return NewKeyBaseFromDir(rootDir) +} + +// NewKeyBaseFromDir initializes a keybase at a particular dir. +func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) { + return getLazyKeyBaseFromDir(rootDir) +} + +// NewInMemoryKeyBase returns a storage-less keybase. +func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() } + +func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) { + return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil +} + +func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { + ko, err := bechKeyOut(keyInfo) + if err != nil { + panic(err) + } + + switch viper.Get(cli.OutputFlag) { + case OutputFormatText: + printTextInfos([]keys.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 []keys.KeyOutput) { + out, err := yaml.Marshal(&kos) + if err != nil { + panic(err) + } + fmt.Println(string(out)) +} + +func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { + + fmt.Println(info.GetAddress().Bytes()) +} + +func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { + ko, err := bechKeyOut(info) + if err != nil { + panic(err) + } + + fmt.Println(ko.PubKey) +} From ca0e58787095587864b57ccda0a8ccb98a487823 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 26 Jul 2019 14:59:00 -0400 Subject: [PATCH 02/18] Functional key gen and showing Ethereum address --- crypto/keys/output.go | 34 +++++++++++++---------------- crypto/keys/types_test.go | 46 +++++++++++++++++++++++++++++++++++++++ crypto/secp256k1.go | 11 ++++------ keys/root.go | 2 +- keys/show.go | 6 ----- keys/utils.go | 4 ++-- 6 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 crypto/keys/types_test.go diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 7dfbb6326..d1072fe65 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -1,7 +1,7 @@ package keys import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "encoding/hex" ) // KeyOutput defines a structure wrapping around an Info object used for output @@ -9,19 +9,12 @@ import ( type KeyOutput struct { Name string `json:"name"` Type string `json:"type"` - Address []byte `json:"address"` - PubKey []byte `json:"pubkey"` + Address string `json:"address"` + PubKey string `json:"pubkey"` Mnemonic string `json:"mnemonic,omitempty"` Threshold uint `json:"threshold,omitempty"` - // PubKeys []multisigPubKeyOutput `json:"pubkeys,omitempty"` } -// type multisigPubKeyOutput struct { -// Address string `json:"address"` -// PubKey string `json:"pubkey"` -// Weight uint `json:"weight"` -// } - // 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. @@ -40,7 +33,8 @@ func Bech32KeysOutput(infos []Info) ([]KeyOutput, error) { // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { - consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + // consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) // if err != nil { @@ -50,14 +44,15 @@ func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: consAddr.Bytes(), - PubKey: keyInfo.GetPubKey().Bytes(), + Address: keyInfo.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), }, nil } // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { - valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + // valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) // if err != nil { @@ -67,8 +62,8 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: valAddr.Bytes(), - PubKey: keyInfo.GetPubKey().Bytes(), + Address: keyInfo.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), }, nil } @@ -76,13 +71,14 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { // public key is a multisig public key, then the threshold and constituent // public keys will be added. func Bech32KeyOutput(info Info) (KeyOutput, error) { - accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + // accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + bytes := info.GetPubKey().Bytes() ko := KeyOutput{ Name: info.GetName(), Type: info.GetType().String(), - Address: accAddr.Bytes(), - PubKey: info.GetPubKey().Bytes(), + Address: info.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), } return ko, nil diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go new file mode 100644 index 000000000..f4e9cfba5 --- /dev/null +++ b/crypto/keys/types_test.go @@ -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()) + +} diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 2966038d6..8d9b5d51c 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -32,8 +32,7 @@ func GenerateKey() (PrivKeySecp256k1, error) { // PubKey returns the ECDSA private key's public key. func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey { - ethcrypto.FromECDSAPub(&privkey.PublicKey) - return PubKeySecp256k1{ethcrypto.FromECDSAPub(&privkey.PublicKey)} + return PubKeySecp256k1(ethcrypto.FromECDSAPub(&privkey.PublicKey)) } // Bytes returns the raw ECDSA private key bytes. @@ -69,19 +68,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 []byte -} +type PubKeySecp256k1 []byte // Address returns the address of the ECDSA public key. func (key PubKeySecp256k1) Address() tmcrypto.Address { - pubk, _ := ethcrypto.UnmarshalPubkey(key.pubkey) + 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 key.pubkey + return key } // VerifyBytes verifies that the ECDSA public key created a given signature over diff --git a/keys/root.go b/keys/root.go index d18ef4682..1fc55ea6d 100644 --- a/keys/root.go +++ b/keys/root.go @@ -10,7 +10,7 @@ import ( // local private key storage. func Commands() *cobra.Command { cmd := &cobra.Command{ - Use: "keys", + Use: "emintkeys", Short: "Add or view local private keys", Long: `Keys allows you to manage your local keystore for tendermint. diff --git a/keys/show.go b/keys/show.go index 3b3f1ab70..4ccdd8ae7 100644 --- a/keys/show.go +++ b/keys/show.go @@ -24,10 +24,6 @@ const ( FlagBechPrefix = "bech" // FlagDevice indicates that the information should be shown in the device FlagDevice = "device" - - // flagMultiSigThreshold = "multisig-threshold" - - // defaultMultiSigKeyName = "multi" ) func showKeysCmd() *cobra.Command { @@ -45,7 +41,6 @@ consisting of all the keys provided by name and multisig threshold.`, 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().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response") return cmd @@ -56,7 +51,6 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { if len(args) == 1 { info, err = GetKeyInfo(args[0]) - fmt.Printf("%+v\n\n", info) if err != nil { return err } diff --git a/keys/utils.go b/keys/utils.go index 6cd088264..9f00be3ec 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -2,6 +2,7 @@ package keys import ( "bufio" + "encoding/hex" "fmt" "os" "path/filepath" @@ -155,8 +156,7 @@ func printTextInfos(kos []keys.KeyOutput) { } func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { - - fmt.Println(info.GetAddress().Bytes()) + fmt.Println(hex.EncodeToString(info.GetAddress().Bytes())) } func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { From 752834f215d6250d89a75439fb56723eb91cb999 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 26 Jul 2019 15:24:51 -0400 Subject: [PATCH 03/18] Cleaned up changes --- keys/add.go | 5 ++-- keys/mnemonic.go | 64 ------------------------------------------------ 2 files changed, 3 insertions(+), 66 deletions(-) delete mode 100644 keys/mnemonic.go diff --git a/keys/add.go b/keys/add.go index 7c5e61e8e..4cd5d4554 100644 --- a/keys/add.go +++ b/keys/add.go @@ -27,11 +27,12 @@ const ( // flagDryRun = "dry-run" flagAccount = "account" flagIndex = "index" - // flagMultisig = "multisig" - flagNoSort = "nosort" + flagNoSort = "nosort" // DefaultKeyPass contains the default key password for genesis transactions DefaultKeyPass = "12345678" + + mnemonicEntropySize = 256 ) func addKeyCommand() *cobra.Command { diff --git a/keys/mnemonic.go b/keys/mnemonic.go deleted file mode 100644 index 35b54eccd..000000000 --- a/keys/mnemonic.go +++ /dev/null @@ -1,64 +0,0 @@ -package keys - -const ( - // flagUserEntropy = "unsafe-entropy" - - mnemonicEntropySize = 256 -) - -// func mnemonicKeyCommand() *cobra.Command { -// cmd := &cobra.Command{ -// Use: "mnemonic", -// Short: "Compute the bip39 mnemonic for some input entropy", -// Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", -// RunE: runMnemonicCmd, -// } -// cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") -// return cmd -// } - -// func runMnemonicCmd(cmd *cobra.Command, args []string) error { -// flags := cmd.Flags() - -// userEntropy, _ := flags.GetBool(flagUserEntropy) - -// var entropySeed []byte - -// if userEntropy { -// // prompt the user to enter some entropy -// buf := bufio.NewReader(cmd.InOrStdin()) -// inputEntropy, err := input.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) -// if err != nil { -// return err -// } -// if len(inputEntropy) < 43 { -// return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) -// } -// conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf) -// if err != nil { -// return err -// } -// if !conf { -// return nil -// } - -// // hash input entropy to get entropy seed -// hashedEntropy := sha256.Sum256([]byte(inputEntropy)) -// entropySeed = hashedEntropy[:] -// } else { -// // read entropy seed straight from crypto.Rand -// var err error -// entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) -// if err != nil { -// return err -// } -// } - -// mnemonic, err := bip39.NewMnemonic(entropySeed[:]) -// if err != nil { -// return err -// } -// cmd.Println(mnemonic) - -// return nil -// } From 97d0259841ec1f996431c44b01ef1d24a2f0ac98 Mon Sep 17 00:00:00 2001 From: austinabell Date: Thu, 25 Jul 2019 16:10:05 -0400 Subject: [PATCH 04/18] WIP setting up Ethereum key CLI commands --- cmd/emintcli/main.go | 4 +- crypto/codec.go | 4 +- crypto/keys/codec.go | 24 ++ crypto/keys/keybase.go | 523 ++++++++++++++++++++++++++++++++++++ crypto/keys/keys.go | 9 + crypto/keys/lazy_keybase.go | 221 +++++++++++++++ crypto/keys/output.go | 89 ++++++ crypto/keys/types.go | 218 +++++++++++++++ crypto/secp256k1.go | 10 +- go.mod | 4 +- keys/add.go | 261 ++++++++++++++++++ keys/codec.go | 23 ++ keys/mnemonic.go | 64 +++++ keys/root.go | 34 +++ keys/show.go | 133 +++++++++ keys/utils.go | 169 ++++++++++++ 16 files changed, 1782 insertions(+), 8 deletions(-) create mode 100644 crypto/keys/codec.go create mode 100644 crypto/keys/keybase.go create mode 100644 crypto/keys/keys.go create mode 100644 crypto/keys/lazy_keybase.go create mode 100644 crypto/keys/output.go create mode 100644 crypto/keys/types.go create mode 100644 keys/add.go create mode 100644 keys/codec.go create mode 100644 keys/mnemonic.go create mode 100644 keys/root.go create mode 100644 keys/show.go create mode 100644 keys/utils.go diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index abac4fecb..19d5b8dd4 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -7,9 +7,9 @@ import ( "path" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" 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" @@ -49,7 +49,7 @@ func main() { // TODO: Set up rest routes (if included, different from web3 api) rpc.Web3RpcCmd(cdc), client.LineBreak, - keys.Commands(), + emintkeys.Commands(), client.LineBreak, ) diff --git a/crypto/codec.go b/crypto/codec.go index 3c827a6ba..219585e27 100644 --- a/crypto/codec.go +++ b/crypto/codec.go @@ -1,6 +1,8 @@ package crypto -import "github.com/cosmos/cosmos-sdk/codec" +import ( + "github.com/cosmos/cosmos-sdk/codec" +) var cryptoCodec = codec.New() diff --git a/crypto/keys/codec.go b/crypto/keys/codec.go new file mode 100644 index 000000000..fa382daab --- /dev/null +++ b/crypto/keys/codec.go @@ -0,0 +1,24 @@ +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" +) + +var cdc *codec.Codec + +func init() { + cdc = codec.New() + cryptoAmino.RegisterAmino(cdc) + cdc.RegisterInterface((*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() +} diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go new file mode 100644 index 000000000..1cd9a580b --- /dev/null +++ b/crypto/keys/keybase.go @@ -0,0 +1,523 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + "reflect" + "strings" + + "github.com/pkg/errors" + + "github.com/cosmos/cosmos-sdk/crypto" + "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 _ 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) 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() 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 Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { + if language != 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) (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 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 SigningAlgo, hrp string, account, index uint32) (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) (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) (Info, error) { + return nil, nil +} + +func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info 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 != "" { + key, err := emintCrypto.GenerateKey() + if err != nil { + return nil, err + } + info = kb.writeLocalKey(name, key, passwd) + } else { + key, err := emintCrypto.GenerateKey() + if err != nil { + return nil, err + } + pubk := key.PubKey() + info = kb.writeOfflineKey(name, pubk) + } + return info, nil +} + +// List returns the keys from storage in alphabetical order. +func (kb dbKeybase) List() ([]Info, error) { + var res []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) (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) (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) 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) 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) 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 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)) +} diff --git a/crypto/keys/keys.go b/crypto/keys/keys.go new file mode 100644 index 000000000..c48840b97 --- /dev/null +++ b/crypto/keys/keys.go @@ -0,0 +1,9 @@ +package 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 = SigningAlgo("emintsecp256k1") +) diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go new file mode 100644 index 000000000..d1e855fe6 --- /dev/null +++ b/crypto/keys/lazy_keybase.go @@ -0,0 +1,221 @@ +package keys + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ Keybase = lazyKeybase{} + +type lazyKeybase struct { + name string + dir string +} + +// New creates a new instance of a lazy keybase. +func New(name, dir string) 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() ([]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) (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) (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 Language, passwd string, algo SigningAlgo) (info 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) (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) (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 SigningAlgo, hrp string, account, index uint32) (info 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 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 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() {} diff --git a/crypto/keys/output.go b/crypto/keys/output.go new file mode 100644 index 000000000..7dfbb6326 --- /dev/null +++ b/crypto/keys/output.go @@ -0,0 +1,89 @@ +package keys + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// 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 []byte `json:"address"` + PubKey []byte `json:"pubkey"` + Mnemonic string `json:"mnemonic,omitempty"` + Threshold uint `json:"threshold,omitempty"` + // PubKeys []multisigPubKeyOutput `json:"pubkeys,omitempty"` +} + +// type multisigPubKeyOutput struct { +// Address string `json:"address"` +// PubKey string `json:"pubkey"` +// Weight uint `json:"weight"` +// } + +// 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 []Info) ([]KeyOutput, error) { + kos := make([]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 Info) (KeyOutput, error) { + consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + + // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) + // if err != nil { + // return KeyOutput{}, err + // } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: consAddr.Bytes(), + PubKey: keyInfo.GetPubKey().Bytes(), + }, nil +} + +// Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. +func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { + valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + + // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) + // if err != nil { + // return KeyOutput{}, err + // } + + return KeyOutput{ + Name: keyInfo.GetName(), + Type: keyInfo.GetType().String(), + Address: valAddr.Bytes(), + PubKey: keyInfo.GetPubKey().Bytes(), + }, nil +} + +// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. If the +// public key is a multisig public key, then the threshold and constituent +// public keys will be added. +func Bech32KeyOutput(info Info) (KeyOutput, error) { + accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + + ko := KeyOutput{ + Name: info.GetName(), + Type: info.GetType().String(), + Address: accAddr.Bytes(), + PubKey: info.GetPubKey().Bytes(), + } + + return ko, nil +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go new file mode 100644 index 000000000..f08462588 --- /dev/null +++ b/crypto/keys/types.go @@ -0,0 +1,218 @@ +package keys + +import ( + "fmt" + + 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" +) + +// Keybase exposes operations on a generic keystore +type Keybase interface { + // CRUD on the keystore + List() ([]Info, error) + Get(name string) (Info, error) + GetByAddress(address types.AccAddress) (Info, error) + Delete(name, passphrase string, skipPass bool) error + + // Sign some bytes, looking up the private key to use + Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) + + // CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic + // key from that. + CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) + + // CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index} + CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) + + // Derive computes a BIP39 seed from th mnemonic and bip39Passwd. + // Derive private key from the seed using the BIP44 params. + // Encrypt the key to disk using encryptPasswd. + // See https://github.com/cosmos/cosmos-sdk/issues/2095 + Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) + + // CreateLedger creates, stores, and returns a new Ledger key reference + CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) + + // CreateOffline creates, stores, and returns a new offline key reference + CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) + + // CreateMulti creates, stores, and returns a new multsig (offline) key reference + CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) + + // The following operations will *only* work on locally-stored keys + Update(name, oldpass string, getNewpass func() (string, error)) error + Import(name string, armor string) (err error) + ImportPrivKey(name, armor, passphrase string) error + ImportPubKey(name string, armor string) (err error) + Export(name string) (armor string, err error) + ExportPubKey(name string) (armor string, err error) + ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) + + // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API + ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) + + // CloseDB closes the database. + CloseDB() +} + +// 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] +} + +// Info is the publicly exposed information about a keypair +type Info interface { + // Human-readable type for key listing + GetType() KeyType + // Name of the key + GetName() string + // Public key + GetPubKey() emintCrypto.PubKeySecp256k1 + // Address + GetAddress() types.AccAddress + // Bip44 Path + GetPath() (*hd.BIP44Params, error) +} + +var ( + _ Info = &localInfo{} + _ Info = &ledgerInfo{} + _ 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) Info { + return &localInfo{ + Name: name, + PubKey: pub, + PrivKeyArmor: privArmor, + } +} + +func (i localInfo) GetType() KeyType { + return TypeLocal +} + +func (i localInfo) GetName() string { + return i.Name +} + +func (i localInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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) Info { + return &ledgerInfo{ + Name: name, + PubKey: pub, + Path: path, + } +} + +func (i ledgerInfo) GetType() KeyType { + return TypeLedger +} + +func (i ledgerInfo) GetName() string { + return i.Name +} + +func (i ledgerInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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) Info { + return &offlineInfo{ + Name: name, + PubKey: pub, + } +} + +func (i offlineInfo) GetType() KeyType { + return TypeOffline +} + +func (i offlineInfo) GetName() string { + return i.Name +} + +func (i offlineInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { + 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 Info) []byte { + return cdc.MustMarshalBinaryLengthPrefixed(i) +} + +// decoding info +func readInfo(bz []byte) (info Info, err error) { + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) + return +} diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index f57b1400c..2966038d6 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -32,7 +32,8 @@ func GenerateKey() (PrivKeySecp256k1, error) { // PubKey returns the ECDSA private key's public key. func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey { - return PubKeySecp256k1{privkey.PublicKey} + ethcrypto.FromECDSAPub(&privkey.PublicKey) + return PubKeySecp256k1{ethcrypto.FromECDSAPub(&privkey.PublicKey)} } // Bytes returns the raw ECDSA private key bytes. @@ -69,17 +70,18 @@ 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 + pubkey []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.pubkey) + 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.pubkey } // VerifyBytes verifies that the ECDSA public key created a given signature over diff --git a/go.mod b/go.mod index 83b84c11a..7f1222b1a 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,11 @@ go 1.12 require ( github.com/allegro/bigcache v1.2.1 // indirect github.com/aristanetworks/goarista v0.0.0-20181101003910-5bb443fba8e0 // indirect + github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d 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 +59,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 ) diff --git a/keys/add.go b/keys/add.go new file mode 100644 index 000000000..7c5e61e8e --- /dev/null +++ b/keys/add.go @@ -0,0 +1,261 @@ +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" + + 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" + // flagMultisig = "multisig" + flagNoSort = "nosort" + + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" +) + +func addKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "add ", + Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk", + Long: `Derive a new private key and encrypt to disk. +Optionally specify a BIP39 mnemonic, a BIP39 passphrase to further secure the mnemonic, +and a bip32 HD path to derive a specific account. The key will be stored under the given name +and encrypted with the given password. The only input that is required is the encryption password. + +If run with -i, it will prompt the user for BIP44 path, BIP39 mnemonic, and passphrase. +The flag --recover allows one to recover a key from a seed passphrase. +If run with --dry-run, a key would be generated (or recovered) but not stored to the +local keystore. +Use the --pubkey flag to add arbitrary public keys to the keystore for constructing +multisig transactions. + +You can add a multisig key by passing the list of key names you want the public +key to be composed of to the --multisig flag and the minimum number of signatures +required through --multisig-threshold. The keys are sorted by address, unless +the flag --nosort is set. +`, + Args: cobra.ExactArgs(1), + RunE: runAddCmd, + } + // cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)") + // cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") + cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") + cmd.Flags().String(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 keys.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 = 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 keys.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 +} diff --git a/keys/codec.go b/keys/codec.go new file mode 100644 index 000000000..eae45446e --- /dev/null +++ b/keys/codec.go @@ -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) +} diff --git a/keys/mnemonic.go b/keys/mnemonic.go new file mode 100644 index 000000000..35b54eccd --- /dev/null +++ b/keys/mnemonic.go @@ -0,0 +1,64 @@ +package keys + +const ( + // flagUserEntropy = "unsafe-entropy" + + mnemonicEntropySize = 256 +) + +// func mnemonicKeyCommand() *cobra.Command { +// cmd := &cobra.Command{ +// Use: "mnemonic", +// Short: "Compute the bip39 mnemonic for some input entropy", +// Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", +// RunE: runMnemonicCmd, +// } +// cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") +// return cmd +// } + +// func runMnemonicCmd(cmd *cobra.Command, args []string) error { +// flags := cmd.Flags() + +// userEntropy, _ := flags.GetBool(flagUserEntropy) + +// var entropySeed []byte + +// if userEntropy { +// // prompt the user to enter some entropy +// buf := bufio.NewReader(cmd.InOrStdin()) +// inputEntropy, err := input.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) +// if err != nil { +// return err +// } +// if len(inputEntropy) < 43 { +// return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) +// } +// conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf) +// if err != nil { +// return err +// } +// if !conf { +// return nil +// } + +// // hash input entropy to get entropy seed +// hashedEntropy := sha256.Sum256([]byte(inputEntropy)) +// entropySeed = hashedEntropy[:] +// } else { +// // read entropy seed straight from crypto.Rand +// var err error +// entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) +// if err != nil { +// return err +// } +// } + +// mnemonic, err := bip39.NewMnemonic(entropySeed[:]) +// if err != nil { +// return err +// } +// cmd.Println(mnemonic) + +// return nil +// } diff --git a/keys/root.go b/keys/root.go new file mode 100644 index 000000000..d18ef4682 --- /dev/null +++ b/keys/root.go @@ -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: "keys", + 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 +} diff --git a/keys/show.go b/keys/show.go new file mode 100644 index 000000000..3b3f1ab70 --- /dev/null +++ b/keys/show.go @@ -0,0 +1,133 @@ +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" + 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" + + // flagMultiSigThreshold = "multisig-threshold" + + // defaultMultiSigKeyName = "multi" +) + +func showKeysCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "show [name [name...]]", + Short: "Show key info for the given name", + Long: `Return public details of a single local key. If multiple names are +provided, then an ephemeral multisig key will be created under the name "multi" +consisting of all the keys provided by name and multisig threshold.`, + 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().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") + cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response") + + return cmd +} + +func runShowCmd(cmd *cobra.Command, args []string) (err error) { + var info keys.Info + + if len(args) == 1 { + info, err = GetKeyInfo(args[0]) + fmt.Printf("%+v\n\n", info) + 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") + } + + bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) + if err != nil { + return err + } + + switch { + case isShowAddr: + printKeyAddress(info, bechKeyOut) + case isShowPubKey: + printPubKey(info, bechKeyOut) + default: + printKeyInfo(info, bechKeyOut) + } + + 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() != keys.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 +} + +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) +} diff --git a/keys/utils.go b/keys/utils.go new file mode 100644 index 000000000..6cd088264 --- /dev/null +++ b/keys/utils.go @@ -0,0 +1,169 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" + "gopkg.in/yaml.v2" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/ethermint/crypto/keys" +) + +// available output formats. +const ( + OutputFormatText = "text" + OutputFormatJSON = "json" + + // defaultKeyDBName is the client's subdirectory where keys are stored. + defaultKeyDBName = "keys" +) + +type bechKeyOutFn func(keyInfo keys.Info) (keys.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) (keys.Info, error) { + keybase, err := NewKeyBaseFromHomeFlag() + if err != nil { + return nil, err + } + + return keybase.Get(name) +} + +// GetPassphrase returns a passphrase for a given name. It will first retrieve +// the key info for that name if the type is local, it'll fetch input from +// STDIN. Otherwise, an empty passphrase is returned. An error is returned if +// the key info cannot be fetched or reading from STDIN fails. +func GetPassphrase(name string) (string, error) { + var passphrase string + + keyInfo, err := GetKeyInfo(name) + if err != nil { + return passphrase, err + } + + // we only need a passphrase for locally stored keys + // TODO: (ref: #864) address security concerns + if keyInfo.GetType() == keys.TypeLocal { + passphrase, err = ReadPassphraseFromStdin(name) + if err != nil { + return passphrase, err + } + } + + return passphrase, nil +} + +// ReadPassphraseFromStdin attempts to read a passphrase from STDIN return an +// error upon failure. +func ReadPassphraseFromStdin(name string) (string, error) { + buf := bufio.NewReader(os.Stdin) + prompt := fmt.Sprintf("Password to sign with '%s':", name) + + passphrase, err := input.GetPassword(prompt, buf) + if err != nil { + return passphrase, fmt.Errorf("Error reading passphrase: %v", err) + } + + return passphrase, nil +} + +// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. +func NewKeyBaseFromHomeFlag() (keys.Keybase, error) { + rootDir := viper.GetString(flags.FlagHome) + return NewKeyBaseFromDir(rootDir) +} + +// NewKeyBaseFromDir initializes a keybase at a particular dir. +func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) { + return getLazyKeyBaseFromDir(rootDir) +} + +// NewInMemoryKeyBase returns a storage-less keybase. +func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() } + +func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) { + return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil +} + +func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { + ko, err := bechKeyOut(keyInfo) + if err != nil { + panic(err) + } + + switch viper.Get(cli.OutputFlag) { + case OutputFormatText: + printTextInfos([]keys.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 []keys.KeyOutput) { + out, err := yaml.Marshal(&kos) + if err != nil { + panic(err) + } + fmt.Println(string(out)) +} + +func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { + + fmt.Println(info.GetAddress().Bytes()) +} + +func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { + ko, err := bechKeyOut(info) + if err != nil { + panic(err) + } + + fmt.Println(ko.PubKey) +} From 288ae3e50f602469530acf91d5b8a59754ffd210 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 26 Jul 2019 14:59:00 -0400 Subject: [PATCH 05/18] Functional key gen and showing Ethereum address --- crypto/keys/output.go | 34 +++++++++++++---------------- crypto/keys/types_test.go | 46 +++++++++++++++++++++++++++++++++++++++ crypto/secp256k1.go | 11 ++++------ keys/root.go | 2 +- keys/show.go | 6 ----- keys/utils.go | 4 ++-- 6 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 crypto/keys/types_test.go diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 7dfbb6326..d1072fe65 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -1,7 +1,7 @@ package keys import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "encoding/hex" ) // KeyOutput defines a structure wrapping around an Info object used for output @@ -9,19 +9,12 @@ import ( type KeyOutput struct { Name string `json:"name"` Type string `json:"type"` - Address []byte `json:"address"` - PubKey []byte `json:"pubkey"` + Address string `json:"address"` + PubKey string `json:"pubkey"` Mnemonic string `json:"mnemonic,omitempty"` Threshold uint `json:"threshold,omitempty"` - // PubKeys []multisigPubKeyOutput `json:"pubkeys,omitempty"` } -// type multisigPubKeyOutput struct { -// Address string `json:"address"` -// PubKey string `json:"pubkey"` -// Weight uint `json:"weight"` -// } - // 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. @@ -40,7 +33,8 @@ func Bech32KeysOutput(infos []Info) ([]KeyOutput, error) { // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { - consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + // consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) // if err != nil { @@ -50,14 +44,15 @@ func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: consAddr.Bytes(), - PubKey: keyInfo.GetPubKey().Bytes(), + Address: keyInfo.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), }, nil } // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { - valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + // valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) // if err != nil { @@ -67,8 +62,8 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: valAddr.Bytes(), - PubKey: keyInfo.GetPubKey().Bytes(), + Address: keyInfo.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), }, nil } @@ -76,13 +71,14 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { // public key is a multisig public key, then the threshold and constituent // public keys will be added. func Bech32KeyOutput(info Info) (KeyOutput, error) { - accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + // accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + bytes := info.GetPubKey().Bytes() ko := KeyOutput{ Name: info.GetName(), Type: info.GetType().String(), - Address: accAddr.Bytes(), - PubKey: info.GetPubKey().Bytes(), + Address: info.GetPubKey().Address().String(), + PubKey: hex.EncodeToString(bytes), } return ko, nil diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go new file mode 100644 index 000000000..f4e9cfba5 --- /dev/null +++ b/crypto/keys/types_test.go @@ -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()) + +} diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 2966038d6..8d9b5d51c 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -32,8 +32,7 @@ func GenerateKey() (PrivKeySecp256k1, error) { // PubKey returns the ECDSA private key's public key. func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey { - ethcrypto.FromECDSAPub(&privkey.PublicKey) - return PubKeySecp256k1{ethcrypto.FromECDSAPub(&privkey.PublicKey)} + return PubKeySecp256k1(ethcrypto.FromECDSAPub(&privkey.PublicKey)) } // Bytes returns the raw ECDSA private key bytes. @@ -69,19 +68,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 []byte -} +type PubKeySecp256k1 []byte // Address returns the address of the ECDSA public key. func (key PubKeySecp256k1) Address() tmcrypto.Address { - pubk, _ := ethcrypto.UnmarshalPubkey(key.pubkey) + 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 key.pubkey + return key } // VerifyBytes verifies that the ECDSA public key created a given signature over diff --git a/keys/root.go b/keys/root.go index d18ef4682..1fc55ea6d 100644 --- a/keys/root.go +++ b/keys/root.go @@ -10,7 +10,7 @@ import ( // local private key storage. func Commands() *cobra.Command { cmd := &cobra.Command{ - Use: "keys", + Use: "emintkeys", Short: "Add or view local private keys", Long: `Keys allows you to manage your local keystore for tendermint. diff --git a/keys/show.go b/keys/show.go index 3b3f1ab70..4ccdd8ae7 100644 --- a/keys/show.go +++ b/keys/show.go @@ -24,10 +24,6 @@ const ( FlagBechPrefix = "bech" // FlagDevice indicates that the information should be shown in the device FlagDevice = "device" - - // flagMultiSigThreshold = "multisig-threshold" - - // defaultMultiSigKeyName = "multi" ) func showKeysCmd() *cobra.Command { @@ -45,7 +41,6 @@ consisting of all the keys provided by name and multisig threshold.`, 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().Uint(flagMultiSigThreshold, 1, "K out of N required signatures") cmd.Flags().Bool(flags.FlagIndentResponse, false, "Add indent to JSON response") return cmd @@ -56,7 +51,6 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { if len(args) == 1 { info, err = GetKeyInfo(args[0]) - fmt.Printf("%+v\n\n", info) if err != nil { return err } diff --git a/keys/utils.go b/keys/utils.go index 6cd088264..9f00be3ec 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -2,6 +2,7 @@ package keys import ( "bufio" + "encoding/hex" "fmt" "os" "path/filepath" @@ -155,8 +156,7 @@ func printTextInfos(kos []keys.KeyOutput) { } func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { - - fmt.Println(info.GetAddress().Bytes()) + fmt.Println(hex.EncodeToString(info.GetAddress().Bytes())) } func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { From e5211ad83bbbdd2bc609951d3e86edf4faf0a189 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 26 Jul 2019 15:24:51 -0400 Subject: [PATCH 06/18] Cleaned up changes --- keys/add.go | 5 ++-- keys/mnemonic.go | 64 ------------------------------------------------ 2 files changed, 3 insertions(+), 66 deletions(-) delete mode 100644 keys/mnemonic.go diff --git a/keys/add.go b/keys/add.go index 7c5e61e8e..4cd5d4554 100644 --- a/keys/add.go +++ b/keys/add.go @@ -27,11 +27,12 @@ const ( // flagDryRun = "dry-run" flagAccount = "account" flagIndex = "index" - // flagMultisig = "multisig" - flagNoSort = "nosort" + flagNoSort = "nosort" // DefaultKeyPass contains the default key password for genesis transactions DefaultKeyPass = "12345678" + + mnemonicEntropySize = 256 ) func addKeyCommand() *cobra.Command { diff --git a/keys/mnemonic.go b/keys/mnemonic.go deleted file mode 100644 index 35b54eccd..000000000 --- a/keys/mnemonic.go +++ /dev/null @@ -1,64 +0,0 @@ -package keys - -const ( - // flagUserEntropy = "unsafe-entropy" - - mnemonicEntropySize = 256 -) - -// func mnemonicKeyCommand() *cobra.Command { -// cmd := &cobra.Command{ -// Use: "mnemonic", -// Short: "Compute the bip39 mnemonic for some input entropy", -// Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", -// RunE: runMnemonicCmd, -// } -// cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") -// return cmd -// } - -// func runMnemonicCmd(cmd *cobra.Command, args []string) error { -// flags := cmd.Flags() - -// userEntropy, _ := flags.GetBool(flagUserEntropy) - -// var entropySeed []byte - -// if userEntropy { -// // prompt the user to enter some entropy -// buf := bufio.NewReader(cmd.InOrStdin()) -// inputEntropy, err := input.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) -// if err != nil { -// return err -// } -// if len(inputEntropy) < 43 { -// return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) -// } -// conf, err := input.GetConfirmation(fmt.Sprintf("> Input length: %d", len(inputEntropy)), buf) -// if err != nil { -// return err -// } -// if !conf { -// return nil -// } - -// // hash input entropy to get entropy seed -// hashedEntropy := sha256.Sum256([]byte(inputEntropy)) -// entropySeed = hashedEntropy[:] -// } else { -// // read entropy seed straight from crypto.Rand -// var err error -// entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) -// if err != nil { -// return err -// } -// } - -// mnemonic, err := bip39.NewMnemonic(entropySeed[:]) -// if err != nil { -// return err -// } -// cmd.Println(mnemonic) - -// return nil -// } From 102a218cb199695ce4f859c5c3c503680e4cdf9f Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 26 Jul 2019 16:44:03 -0400 Subject: [PATCH 07/18] Changed address to cosmos specific address --- crypto/keys/output.go | 14 ++++++++------ keys/utils.go | 9 +++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crypto/keys/output.go b/crypto/keys/output.go index d1072fe65..dff156ebf 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -2,6 +2,8 @@ package keys import ( "encoding/hex" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // KeyOutput defines a structure wrapping around an Info object used for output @@ -33,7 +35,7 @@ func Bech32KeysOutput(infos []Info) ([]KeyOutput, error) { // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { - // consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) @@ -44,14 +46,14 @@ func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: keyInfo.GetPubKey().Address().String(), + Address: consAddr.String(), PubKey: hex.EncodeToString(bytes), }, nil } // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { - // valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) @@ -62,7 +64,7 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { return KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), - Address: keyInfo.GetPubKey().Address().String(), + Address: valAddr.String(), PubKey: hex.EncodeToString(bytes), }, nil } @@ -71,13 +73,13 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { // public key is a multisig public key, then the threshold and constituent // public keys will be added. func Bech32KeyOutput(info Info) (KeyOutput, error) { - // accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) + accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) bytes := info.GetPubKey().Bytes() ko := KeyOutput{ Name: info.GetName(), Type: info.GetType().String(), - Address: info.GetPubKey().Address().String(), + Address: accAddr.String(), PubKey: hex.EncodeToString(bytes), } diff --git a/keys/utils.go b/keys/utils.go index 9f00be3ec..17c8028c5 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -2,7 +2,6 @@ package keys import ( "bufio" - "encoding/hex" "fmt" "os" "path/filepath" @@ -152,11 +151,17 @@ func printTextInfos(kos []keys.KeyOutput) { if err != nil { panic(err) } + fmt.Println(string(out)) } func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { - fmt.Println(hex.EncodeToString(info.GetAddress().Bytes())) + ko, err := bechKeyOut(info) + if err != nil { + panic(err) + } + + fmt.Println(ko.Address) } func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { From 7e156d7a8c5d41cfd18a7620ffe4799998eb432f Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 14:47:33 -0400 Subject: [PATCH 08/18] Remove default bech32 prefixes and add basic add command test --- cmd/emintcli/main.go | 12 ++++++----- keys/add_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 keys/add_test.go diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index 19d5b8dd4..a4008b503 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -1,11 +1,12 @@ package main import ( - "github.com/cosmos/ethermint/rpc" - "github.com/tendermint/go-amino" "os" "path" + "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" @@ -24,9 +25,10 @@ func main() { // Read in the configuration file for the sdk config := sdk.GetConfig() - config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) - config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) - config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + // TODO: Remove or change prefix if usable to generate Ethereum address + config.SetBech32PrefixForAccount("", "") + config.SetBech32PrefixForValidator("", "") + config.SetBech32PrefixForConsensusNode("", "") config.Seal() rootCmd := &cobra.Command{ diff --git a/keys/add_test.go b/keys/add_test.go new file mode 100644 index 000000000..e158cf046 --- /dev/null +++ b/keys/add_test.go @@ -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) +} From 7e809a93c021b30e23dd604625d05cb55b7136f8 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 15:08:56 -0400 Subject: [PATCH 09/18] Changed Private key type to slice of bytes for compatibility and storability --- app/test_utils.go | 2 +- crypto/secp256k1.go | 13 ++++++++----- crypto/secp256k1_test.go | 2 +- x/evm/types/utils.go | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/test_utils.go b/app/test_utils.go index 31eb4a8dd..4a6304927 100644 --- a/app/test_utils.go +++ b/app/test_utils.go @@ -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 } diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 8d9b5d51c..03be95be0 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -17,7 +17,8 @@ var _ tmcrypto.PrivKey = PrivKeySecp256k1{} // PrivKeySecp256k1 defines a type alias for an ecdsa.PrivateKey that implements // Tendermint's PrivateKey interface. -type PrivKeySecp256k1 ecdsa.PrivateKey +// TODO: Change to array of 32 bytes after GenerateKey is changed +type PrivKeySecp256k1 []byte // GenerateKey generates a new random private key. It returns an error upon // failure. @@ -27,17 +28,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(ethcrypto.FromECDSAPub(&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 +60,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 } // ---------------------------------------------------------------------------- diff --git a/crypto/secp256k1_test.go b/crypto/secp256k1_test.go index 5c80cdd45..1e16dec2d 100644 --- a/crypto/secp256k1_test.go +++ b/crypto/secp256k1_test.go @@ -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 diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 8c9e45993..de7973baf 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -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 From 1cfde9433089237d6f068d42ccac4aa76150015c Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 15:49:24 -0400 Subject: [PATCH 10/18] switch back to using cosmos crypto Keybase interfaces --- crypto/keys/codec.go | 3 +- crypto/keys/keybase.go | 39 ++++++++------- crypto/keys/keys.go | 6 ++- crypto/keys/lazy_keybase.go | 23 ++++----- crypto/keys/output.go | 21 ++++---- crypto/keys/types.go | 98 +++++++------------------------------ keys/add.go | 5 +- keys/show.go | 12 ++--- keys/utils.go | 26 +++++----- 9 files changed, 91 insertions(+), 142 deletions(-) diff --git a/crypto/keys/codec.go b/crypto/keys/codec.go index fa382daab..ed71ff515 100644 --- a/crypto/keys/codec.go +++ b/crypto/keys/codec.go @@ -6,6 +6,7 @@ import ( "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 @@ -13,7 +14,7 @@ var cdc *codec.Codec func init() { cdc = codec.New() cryptoAmino.RegisterAmino(cdc) - cdc.RegisterInterface((*Info)(nil), nil) + 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) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 1cd9a580b..f5f43c613 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -10,6 +10,7 @@ import ( "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" @@ -23,7 +24,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" ) -var _ Keybase = dbKeybase{} +var _ cosmosKeys.Keybase = dbKeybase{} // Language is a language to create the BIP 39 mnemonic in. // Currently, only english is supported though. @@ -78,7 +79,7 @@ type dbKeybase struct { } // newDbKeybase creates a new keybase instance using the passed DB for reading and writing keys. -func newDbKeybase(db dbm.DB) Keybase { +func newDbKeybase(db dbm.DB) cosmosKeys.Keybase { return dbKeybase{ db: db, } @@ -86,7 +87,7 @@ func newDbKeybase(db dbm.DB) Keybase { // NewInMemory creates a transient keybase on top of in-memory storage // instance useful for testing purposes and on-the-fly key generation. -func NewInMemory() Keybase { return dbKeybase{dbm.NewMemDB()} } +func NewInMemory() cosmosKeys.Keybase { return dbKeybase{dbm.NewMemDB()} } // CreateMnemonic generates a new key and persists it to storage, encrypted // using the provided password. @@ -94,8 +95,8 @@ func NewInMemory() Keybase { return dbKeybase{dbm.NewMemDB()} } // 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 Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { - if language != English { +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 { @@ -121,13 +122,13 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string } // 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) (Info, error) { +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 Info, err error) { +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 @@ -139,7 +140,7 @@ func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string // 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 SigningAlgo, hrp string, account, index uint32) (Info, error) { +func (kb dbKeybase) CreateLedger(name string, algo cosmosKeys.SigningAlgo, hrp string, account, index uint32) (cosmosKeys.Info, error) { if algo != Secp256k1 { return nil, ErrUnsupportedSigningAlgo } @@ -158,17 +159,17 @@ func (kb dbKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, acco // CreateOffline creates a new reference to an offline keypair. It returns the // created key info. -func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) { +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) (Info, error) { +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 Info, err error) { +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) @@ -196,8 +197,8 @@ func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath str } // List returns the keys from storage in alphabetical order. -func (kb dbKeybase) List() ([]Info, error) { - var res []Info +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() { @@ -216,7 +217,7 @@ func (kb dbKeybase) List() ([]Info, error) { } // Get returns the public information about one key. -func (kb dbKeybase) Get(name string) (Info, error) { +func (kb dbKeybase) Get(name string) (cosmosKeys.Info, error) { bs := kb.db.Get(infoKey(name)) if len(bs) == 0 { return nil, keyerror.NewErrKeyNotFound(name) @@ -224,7 +225,7 @@ func (kb dbKeybase) Get(name string) (Info, error) { return readInfo(bs) } -func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { +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) @@ -467,7 +468,7 @@ func (kb dbKeybase) CloseDB() { kb.db.Close() } -func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info { +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)) @@ -485,7 +486,7 @@ func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase return info } -func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params) 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)) @@ -495,7 +496,7 @@ func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP return info } -func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) 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)) @@ -505,7 +506,7 @@ func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info { return info } -func (kb dbKeybase) writeInfo(name string, info Info) { +func (kb dbKeybase) writeInfo(name string, info cosmosKeys.Info) { // write the info by key key := infoKey(name) serializedInfo := writeInfo(info) diff --git a/crypto/keys/keys.go b/crypto/keys/keys.go index c48840b97..72c14ca1b 100644 --- a/crypto/keys/keys.go +++ b/crypto/keys/keys.go @@ -1,9 +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 = SigningAlgo("emintsecp256k1") + Secp256k1 = cosmosKeys.SigningAlgo("emintsecp256k1") ) diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go index d1e855fe6..a38805b6c 100644 --- a/crypto/keys/lazy_keybase.go +++ b/crypto/keys/lazy_keybase.go @@ -6,11 +6,12 @@ import ( "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 _ Keybase = lazyKeybase{} +var _ cosmosKeys.Keybase = lazyKeybase{} type lazyKeybase struct { name string @@ -18,7 +19,7 @@ type lazyKeybase struct { } // New creates a new instance of a lazy keybase. -func New(name, dir string) 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)) } @@ -26,7 +27,7 @@ func New(name, dir string) Keybase { return lazyKeybase{name: name, dir: dir} } -func (lkb lazyKeybase) List() ([]Info, error) { +func (lkb lazyKeybase) List() ([]cosmosKeys.Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err @@ -36,7 +37,7 @@ func (lkb lazyKeybase) List() ([]Info, error) { return newDbKeybase(db).List() } -func (lkb lazyKeybase) Get(name string) (Info, error) { +func (lkb lazyKeybase) Get(name string) (cosmosKeys.Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err @@ -46,7 +47,7 @@ func (lkb lazyKeybase) Get(name string) (Info, error) { return newDbKeybase(db).Get(name) } -func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) { +func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (cosmosKeys.Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err @@ -76,7 +77,7 @@ func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto return newDbKeybase(db).Sign(name, passphrase, msg) } -func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) { +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 @@ -86,7 +87,7 @@ func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd str return newDbKeybase(db).CreateMnemonic(name, language, passwd, algo) } -func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) { +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 @@ -96,7 +97,7 @@ func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd return newDbKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index) } -func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) { +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 @@ -106,7 +107,7 @@ func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, return newDbKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params) } -func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) { +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 @@ -116,7 +117,7 @@ func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, a return newDbKeybase(db).CreateLedger(name, algo, hrp, account, index) } -func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) { +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 @@ -126,7 +127,7 @@ func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info In return newDbKeybase(db).CreateOffline(name, pubkey) } -func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) { +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 diff --git a/crypto/keys/output.go b/crypto/keys/output.go index dff156ebf..93930f453 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -3,6 +3,7 @@ package keys import ( "encoding/hex" + cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -20,8 +21,8 @@ type KeyOutput struct { // 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 []Info) ([]KeyOutput, error) { - kos := make([]KeyOutput, len(infos)) +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 { @@ -34,7 +35,7 @@ func Bech32KeysOutput(infos []Info) ([]KeyOutput, error) { } // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. -func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { +func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) bytes := keyInfo.GetPubKey().Bytes() @@ -43,7 +44,7 @@ func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { // return KeyOutput{}, err // } - return KeyOutput{ + return cosmosKeys.KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), Address: consAddr.String(), @@ -52,7 +53,7 @@ func Bech32ConsKeyOutput(keyInfo Info) (KeyOutput, error) { } // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. -func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { +func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) bytes := keyInfo.GetPubKey().Bytes() @@ -61,7 +62,7 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { // return KeyOutput{}, err // } - return KeyOutput{ + return cosmosKeys.KeyOutput{ Name: keyInfo.GetName(), Type: keyInfo.GetType().String(), Address: valAddr.String(), @@ -72,14 +73,14 @@ func Bech32ValKeyOutput(keyInfo Info) (KeyOutput, error) { // Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. If the // public key is a multisig public key, then the threshold and constituent // public keys will be added. -func Bech32KeyOutput(info Info) (KeyOutput, error) { - accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes()) +func Bech32KeyOutput(info cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { + accAddr := info.GetPubKey().Address().String() bytes := info.GetPubKey().Bytes() - ko := KeyOutput{ + ko := cosmosKeys.KeyOutput{ Name: info.GetName(), Type: info.GetType().String(), - Address: accAddr.String(), + Address: accAddr, PubKey: hex.EncodeToString(bytes), } diff --git a/crypto/keys/types.go b/crypto/keys/types.go index f08462588..69fc63b1f 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -3,6 +3,7 @@ package keys import ( "fmt" + cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" emintCrypto "github.com/cosmos/ethermint/crypto" "github.com/tendermint/tendermint/crypto" @@ -10,55 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" ) -// Keybase exposes operations on a generic keystore -type Keybase interface { - // CRUD on the keystore - List() ([]Info, error) - Get(name string) (Info, error) - GetByAddress(address types.AccAddress) (Info, error) - Delete(name, passphrase string, skipPass bool) error - - // Sign some bytes, looking up the private key to use - Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) - - // CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic - // key from that. - CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) - - // CreateAccount creates an account based using the BIP44 path (44'/118'/{account}'/0/{index} - CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) - - // Derive computes a BIP39 seed from th mnemonic and bip39Passwd. - // Derive private key from the seed using the BIP44 params. - // Encrypt the key to disk using encryptPasswd. - // See https://github.com/cosmos/cosmos-sdk/issues/2095 - Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) - - // CreateLedger creates, stores, and returns a new Ledger key reference - CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) - - // CreateOffline creates, stores, and returns a new offline key reference - CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) - - // CreateMulti creates, stores, and returns a new multsig (offline) key reference - CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) - - // The following operations will *only* work on locally-stored keys - Update(name, oldpass string, getNewpass func() (string, error)) error - Import(name string, armor string) (err error) - ImportPrivKey(name, armor, passphrase string) error - ImportPubKey(name string, armor string) (err error) - Export(name string) (armor string, err error) - ExportPubKey(name string) (armor string, err error) - ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) - - // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API - ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) - - // CloseDB closes the database. - CloseDB() -} - // KeyType reflects a human-readable type for key listing. type KeyType uint @@ -82,24 +34,10 @@ func (kt KeyType) String() string { return keyTypes[kt] } -// Info is the publicly exposed information about a keypair -type Info interface { - // Human-readable type for key listing - GetType() KeyType - // Name of the key - GetName() string - // Public key - GetPubKey() emintCrypto.PubKeySecp256k1 - // Address - GetAddress() types.AccAddress - // Bip44 Path - GetPath() (*hd.BIP44Params, error) -} - var ( - _ Info = &localInfo{} - _ Info = &ledgerInfo{} - _ Info = &offlineInfo{} + _ cosmosKeys.Info = &localInfo{} + _ cosmosKeys.Info = &ledgerInfo{} + _ cosmosKeys.Info = &offlineInfo{} ) // localInfo is the public information about a locally stored key @@ -109,7 +47,7 @@ type localInfo struct { PrivKeyArmor string `json:"privkey.armor"` } -func newLocalInfo(name string, pub emintCrypto.PubKeySecp256k1, privArmor string) Info { +func newLocalInfo(name string, pub emintCrypto.PubKeySecp256k1, privArmor string) cosmosKeys.Info { return &localInfo{ Name: name, PubKey: pub, @@ -117,15 +55,15 @@ func newLocalInfo(name string, pub emintCrypto.PubKeySecp256k1, privArmor string } } -func (i localInfo) GetType() KeyType { - return TypeLocal +func (i localInfo) GetType() cosmosKeys.KeyType { + return cosmosKeys.TypeLocal } func (i localInfo) GetName() string { return i.Name } -func (i localInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { +func (i localInfo) GetPubKey() crypto.PubKey { return i.PubKey } @@ -144,7 +82,7 @@ type ledgerInfo struct { Path hd.BIP44Params `json:"path"` } -func newLedgerInfo(name string, pub emintCrypto.PubKeySecp256k1, path hd.BIP44Params) Info { +func newLedgerInfo(name string, pub emintCrypto.PubKeySecp256k1, path hd.BIP44Params) cosmosKeys.Info { return &ledgerInfo{ Name: name, PubKey: pub, @@ -152,15 +90,15 @@ func newLedgerInfo(name string, pub emintCrypto.PubKeySecp256k1, path hd.BIP44Pa } } -func (i ledgerInfo) GetType() KeyType { - return TypeLedger +func (i ledgerInfo) GetType() cosmosKeys.KeyType { + return cosmosKeys.TypeLedger } func (i ledgerInfo) GetName() string { return i.Name } -func (i ledgerInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { +func (i ledgerInfo) GetPubKey() crypto.PubKey { return i.PubKey } @@ -179,22 +117,22 @@ type offlineInfo struct { PubKey emintCrypto.PubKeySecp256k1 `json:"pubkey"` } -func newOfflineInfo(name string, pub emintCrypto.PubKeySecp256k1) Info { +func newOfflineInfo(name string, pub emintCrypto.PubKeySecp256k1) cosmosKeys.Info { return &offlineInfo{ Name: name, PubKey: pub, } } -func (i offlineInfo) GetType() KeyType { - return TypeOffline +func (i offlineInfo) GetType() cosmosKeys.KeyType { + return cosmosKeys.TypeOffline } func (i offlineInfo) GetName() string { return i.Name } -func (i offlineInfo) GetPubKey() emintCrypto.PubKeySecp256k1 { +func (i offlineInfo) GetPubKey() crypto.PubKey { return i.PubKey } @@ -207,12 +145,12 @@ func (i offlineInfo) GetPath() (*hd.BIP44Params, error) { } // encoding info -func writeInfo(i Info) []byte { +func writeInfo(i cosmosKeys.Info) []byte { return cdc.MustMarshalBinaryLengthPrefixed(i) } // decoding info -func readInfo(bz []byte) (info Info, err error) { +func readInfo(bz []byte) (info cosmosKeys.Info, err error) { err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) return } diff --git a/keys/add.go b/keys/add.go index 4cd5d4554..a5c7eaf1e 100644 --- a/keys/add.go +++ b/keys/add.go @@ -8,6 +8,7 @@ import ( "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" @@ -84,7 +85,7 @@ output - armor encrypted private key (saved to file) */ func runAddCmd(cmd *cobra.Command, args []string) error { - var kb keys.Keybase + var kb cosmosKeys.Keybase var err error var encryptPassword string @@ -218,7 +219,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error { return printCreate(cmd, info, showMnemonic, mnemonic) } -func printCreate(cmd *cobra.Command, info keys.Info, showMnemonic bool, mnemonic string) error { +func printCreate(cmd *cobra.Command, info cosmosKeys.Info, showMnemonic bool, mnemonic string) error { output := viper.Get(cli.OutputFlag) switch output { diff --git a/keys/show.go b/keys/show.go index 4ccdd8ae7..69e8a85b7 100644 --- a/keys/show.go +++ b/keys/show.go @@ -11,8 +11,8 @@ import ( "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 ( @@ -47,7 +47,7 @@ consisting of all the keys provided by name and multisig threshold.`, } func runShowCmd(cmd *cobra.Command, args []string) (err error) { - var info keys.Info + var info cosmosKeys.Info if len(args) == 1 { info, err = GetKeyInfo(args[0]) @@ -98,7 +98,7 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return fmt.Errorf("the device flag (-d) can only be used for accounts") } // Override and show in the device - if info.GetType() != keys.TypeLedger { + if info.GetType() != cosmosKeys.TypeLedger { return fmt.Errorf("the device flag (-d) can only be used for accounts stored in devices") } @@ -116,11 +116,11 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { case sdk.PrefixAccount: - return keys.Bech32KeyOutput, nil + return cosmosKeys.Bech32KeyOutput, nil case sdk.PrefixValidator: - return keys.Bech32ValKeyOutput, nil + return cosmosKeys.Bech32ValKeyOutput, nil case sdk.PrefixConsensus: - return keys.Bech32ConsKeyOutput, nil + return cosmosKeys.Bech32ConsKeyOutput, nil } return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix) diff --git a/keys/utils.go b/keys/utils.go index 17c8028c5..b02bdf928 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -12,6 +12,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" + cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/ethermint/crypto/keys" ) @@ -24,11 +26,11 @@ const ( defaultKeyDBName = "keys" ) -type bechKeyOutFn func(keyInfo keys.Info) (keys.KeyOutput, error) +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) (keys.Info, error) { +func GetKeyInfo(name string) (cosmosKeys.Info, error) { keybase, err := NewKeyBaseFromHomeFlag() if err != nil { return nil, err @@ -51,7 +53,7 @@ func GetPassphrase(name string) (string, error) { // we only need a passphrase for locally stored keys // TODO: (ref: #864) address security concerns - if keyInfo.GetType() == keys.TypeLocal { + if keyInfo.GetType() == cosmosKeys.TypeLocal { passphrase, err = ReadPassphraseFromStdin(name) if err != nil { return passphrase, err @@ -76,24 +78,24 @@ func ReadPassphraseFromStdin(name string) (string, error) { } // NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. -func NewKeyBaseFromHomeFlag() (keys.Keybase, error) { +func NewKeyBaseFromHomeFlag() (cosmosKeys.Keybase, error) { rootDir := viper.GetString(flags.FlagHome) return NewKeyBaseFromDir(rootDir) } // NewKeyBaseFromDir initializes a keybase at a particular dir. -func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) { +func NewKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { return getLazyKeyBaseFromDir(rootDir) } // NewInMemoryKeyBase returns a storage-less keybase. -func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() } +func NewInMemoryKeyBase() cosmosKeys.Keybase { return keys.NewInMemory() } -func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) { +func getLazyKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil } -func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { +func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(keyInfo) if err != nil { panic(err) @@ -101,7 +103,7 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { switch viper.Get(cli.OutputFlag) { case OutputFormatText: - printTextInfos([]keys.KeyOutput{ko}) + printTextInfos([]cosmosKeys.KeyOutput{ko}) case OutputFormatJSON: var out []byte @@ -146,7 +148,7 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) { // } // } -func printTextInfos(kos []keys.KeyOutput) { +func printTextInfos(kos []cosmosKeys.KeyOutput) { out, err := yaml.Marshal(&kos) if err != nil { panic(err) @@ -155,7 +157,7 @@ func printTextInfos(kos []keys.KeyOutput) { fmt.Println(string(out)) } -func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { +func printKeyAddress(info cosmosKeys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(info) if err != nil { panic(err) @@ -164,7 +166,7 @@ func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) { fmt.Println(ko.Address) } -func printPubKey(info keys.Info, bechKeyOut bechKeyOutFn) { +func printPubKey(info cosmosKeys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(info) if err != nil { panic(err) From 5eada04779ca0d8ab8f8723794675a260ea13ead Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 16:11:25 -0400 Subject: [PATCH 11/18] Changed key output to ethereum addressing instead of bitcoin and key generation to allow seeding from mnemonic and bip39 password --- crypto/keys/keybase.go | 22 +++++++--------------- crypto/keys/output.go | 6 +++--- keys/show.go | 7 ++++--- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index f5f43c613..c9c0e4303 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -171,26 +171,18 @@ func (kb dbKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (cosmosKeys.In 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 - // } + 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 != "" { - key, err := emintCrypto.GenerateKey() - if err != nil { - return nil, err - } - info = kb.writeLocalKey(name, key, passwd) + info = kb.writeLocalKey(name, emintCrypto.PrivKeySecp256k1(derivedPriv[:]), passwd) } else { - key, err := emintCrypto.GenerateKey() - if err != nil { - return nil, err - } - pubk := key.PubKey() + pubk := emintCrypto.PrivKeySecp256k1(derivedPriv[:]).PubKey() info = kb.writeOfflineKey(name, pubk) } return info, nil diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 93930f453..22948fe98 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -36,7 +36,7 @@ func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) { // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes()) + consAddr := keyInfo.GetPubKey().Address() bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) @@ -74,13 +74,13 @@ func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { // public key is a multisig public key, then the threshold and constituent // public keys will be added. func Bech32KeyOutput(info cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - accAddr := info.GetPubKey().Address().String() + accAddr := info.GetPubKey().Address() bytes := info.GetPubKey().Bytes() ko := cosmosKeys.KeyOutput{ Name: info.GetName(), Type: info.GetType().String(), - Address: accAddr, + Address: accAddr.String(), PubKey: hex.EncodeToString(bytes), } diff --git a/keys/show.go b/keys/show.go index 69e8a85b7..e2cc1dce7 100644 --- a/keys/show.go +++ b/keys/show.go @@ -13,6 +13,7 @@ import ( "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 ( @@ -116,11 +117,11 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) { switch bechPrefix { case sdk.PrefixAccount: - return cosmosKeys.Bech32KeyOutput, nil + return keys.Bech32KeyOutput, nil case sdk.PrefixValidator: - return cosmosKeys.Bech32ValKeyOutput, nil + return keys.Bech32ValKeyOutput, nil case sdk.PrefixConsensus: - return cosmosKeys.Bech32ConsKeyOutput, nil + return keys.Bech32ConsKeyOutput, nil } return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix) From b857febeb366b699fdced33ae647d23985f556d8 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 16:36:13 -0400 Subject: [PATCH 12/18] Updated show command and added test --- crypto/keys/output.go | 3 +-- keys/show.go | 36 ++++++++++++++---------------- keys/show_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 keys/show_test.go diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 22948fe98..774da860d 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -4,7 +4,6 @@ import ( "encoding/hex" cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" - sdk "github.com/cosmos/cosmos-sdk/types" ) // KeyOutput defines a structure wrapping around an Info object used for output @@ -54,7 +53,7 @@ func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes()) + valAddr := keyInfo.GetPubKey().Address() bytes := keyInfo.GetPubKey().Bytes() // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) diff --git a/keys/show.go b/keys/show.go index e2cc1dce7..42c7f86bb 100644 --- a/keys/show.go +++ b/keys/show.go @@ -77,18 +77,15 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return errors.New("cannot use --output with --address or --pubkey") } - bechKeyOut, err := getBechKeyOut(viper.GetString(FlagBechPrefix)) - if err != nil { - return err - } + keyOutputFunction := keys.Bech32KeyOutput switch { case isShowAddr: - printKeyAddress(info, bechKeyOut) + printKeyAddress(info, keyOutputFunction) case isShowPubKey: - printPubKey(info, bechKeyOut) + printPubKey(info, keyOutputFunction) default: - printKeyInfo(info, bechKeyOut) + printKeyInfo(info, keyOutputFunction) } if isShowDevice { @@ -114,15 +111,16 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return nil } -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) -} +// 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) +// } diff --git a/keys/show_test.go b/keys/show_test.go new file mode 100644 index 000000000..b7cf1cd54 --- /dev/null +++ b/keys/show_test.go @@ -0,0 +1,52 @@ +package keys + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func Test_showKeysCmd(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 Test_runShowCmd(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 := 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) +} From 77a9c8ff7b44c40544fec2d7779617d6118df224 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 16:42:50 -0400 Subject: [PATCH 13/18] Remove prefix requirement for showing keys and added existing keys commands to CLI temporarily --- cmd/emintcli/main.go | 3 +++ go.sum | 1 + keys/show_test.go | 11 +++++------ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index a4008b503..9752b8fbc 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -4,6 +4,7 @@ import ( "os" "path" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/ethermint/rpc" "github.com/tendermint/go-amino" @@ -51,6 +52,8 @@ 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, ) diff --git a/go.sum b/go.sum index 6bee951b8..3c3c1d55d 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f h1:jmVM19bsHZRVVe8rugzfILuL3VPgCj5b6941I20Naw0= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q= +github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= diff --git a/keys/show_test.go b/keys/show_test.go index b7cf1cd54..611c09abb 100644 --- a/keys/show_test.go +++ b/keys/show_test.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/tests" - sdk "github.com/cosmos/cosmos-sdk/types" ) func Test_showKeysCmd(t *testing.T) { @@ -39,12 +38,12 @@ func Test_runShowCmd(t *testing.T) { _, 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 + // 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) + // // 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}) From f2ce82c10d165b80499e9a0e2337b2d4bae34862 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 16:55:12 -0400 Subject: [PATCH 14/18] Removed unnecessary duplicate code --- keys/add.go | 2 +- keys/show_test.go | 3 ++- keys/utils.go | 65 ++--------------------------------------------- 3 files changed, 5 insertions(+), 65 deletions(-) diff --git a/keys/add.go b/keys/add.go index a5c7eaf1e..5e07e772e 100644 --- a/keys/add.go +++ b/keys/add.go @@ -95,7 +95,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error { interactive := viper.GetBool(flagInteractive) showMnemonic := !viper.GetBool(flagNoBackup) - kb, err = NewKeyBaseFromHomeFlag() + kb, err = clientkeys.NewKeyBaseFromHomeFlag() if err != nil { return err } diff --git a/keys/show_test.go b/keys/show_test.go index 611c09abb..da08eccd0 100644 --- a/keys/show_test.go +++ b/keys/show_test.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -31,7 +32,7 @@ func Test_runShowCmd(t *testing.T) { fakeKeyName1 := "runShowCmd_Key1" fakeKeyName2 := "runShowCmd_Key2" - kb, err := NewKeyBaseFromHomeFlag() + kb, err := clientkeys.NewKeyBaseFromHomeFlag() assert.NoError(t, err) _, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0) assert.NoError(t, err) diff --git a/keys/utils.go b/keys/utils.go index b02bdf928..5ceaefc82 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -1,20 +1,15 @@ package keys import ( - "bufio" "fmt" - "os" - "path/filepath" "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" "gopkg.in/yaml.v2" "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" - - "github.com/cosmos/ethermint/crypto/keys" ) // available output formats. @@ -31,7 +26,7 @@ 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 := NewKeyBaseFromHomeFlag() + keybase, err := clientkeys.NewKeyBaseFromHomeFlag() if err != nil { return nil, err } @@ -39,62 +34,6 @@ func GetKeyInfo(name string) (cosmosKeys.Info, error) { return keybase.Get(name) } -// GetPassphrase returns a passphrase for a given name. It will first retrieve -// the key info for that name if the type is local, it'll fetch input from -// STDIN. Otherwise, an empty passphrase is returned. An error is returned if -// the key info cannot be fetched or reading from STDIN fails. -func GetPassphrase(name string) (string, error) { - var passphrase string - - keyInfo, err := GetKeyInfo(name) - if err != nil { - return passphrase, err - } - - // we only need a passphrase for locally stored keys - // TODO: (ref: #864) address security concerns - if keyInfo.GetType() == cosmosKeys.TypeLocal { - passphrase, err = ReadPassphraseFromStdin(name) - if err != nil { - return passphrase, err - } - } - - return passphrase, nil -} - -// ReadPassphraseFromStdin attempts to read a passphrase from STDIN return an -// error upon failure. -func ReadPassphraseFromStdin(name string) (string, error) { - buf := bufio.NewReader(os.Stdin) - prompt := fmt.Sprintf("Password to sign with '%s':", name) - - passphrase, err := input.GetPassword(prompt, buf) - if err != nil { - return passphrase, fmt.Errorf("Error reading passphrase: %v", err) - } - - return passphrase, nil -} - -// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. -func NewKeyBaseFromHomeFlag() (cosmosKeys.Keybase, error) { - rootDir := viper.GetString(flags.FlagHome) - return NewKeyBaseFromDir(rootDir) -} - -// NewKeyBaseFromDir initializes a keybase at a particular dir. -func NewKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { - return getLazyKeyBaseFromDir(rootDir) -} - -// NewInMemoryKeyBase returns a storage-less keybase. -func NewInMemoryKeyBase() cosmosKeys.Keybase { return keys.NewInMemory() } - -func getLazyKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { - return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil -} - func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(keyInfo) if err != nil { From 5d9b7bd117edffd258963dc51e1adb2cf06eab22 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 16:58:58 -0400 Subject: [PATCH 15/18] Readd prefixes for accounts temporarily --- cmd/emintcli/main.go | 6 +++--- cmd/emintd/main.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index 9752b8fbc..a09ee935b 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -27,9 +27,9 @@ 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("", "") - config.SetBech32PrefixForValidator("", "") - config.SetBech32PrefixForConsensusNode("", "") + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) config.Seal() rootCmd := &cobra.Command{ diff --git a/cmd/emintd/main.go b/cmd/emintd/main.go index acb9ad313..83626d5c7 100644 --- a/cmd/emintd/main.go +++ b/cmd/emintd/main.go @@ -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) From 80893be6282559dd4cd5a088bcb2ba243988a653 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 17:01:01 -0400 Subject: [PATCH 16/18] Fix linting issue --- keys/utils.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/keys/utils.go b/keys/utils.go index 5ceaefc82..3cc3df21c 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -16,9 +16,6 @@ import ( const ( OutputFormatText = "text" OutputFormatJSON = "json" - - // defaultKeyDBName is the client's subdirectory where keys are stored. - defaultKeyDBName = "keys" ) type bechKeyOutFn func(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) From 32f27f4a080b24d800b03f0a2cbe1f92a431afe2 Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 17:29:35 -0400 Subject: [PATCH 17/18] Remove TODO for setting PK to specific length of bytes (all functions use slice) --- crypto/secp256k1.go | 1 - keys/show_test.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 03be95be0..6087d9222 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -17,7 +17,6 @@ var _ tmcrypto.PrivKey = PrivKeySecp256k1{} // PrivKeySecp256k1 defines a type alias for an ecdsa.PrivateKey that implements // Tendermint's PrivateKey interface. -// TODO: Change to array of 32 bytes after GenerateKey is changed type PrivKeySecp256k1 []byte // GenerateKey generates a new random private key. It returns an error upon diff --git a/keys/show_test.go b/keys/show_test.go index da08eccd0..6732006c3 100644 --- a/keys/show_test.go +++ b/keys/show_test.go @@ -11,14 +11,14 @@ import ( "github.com/cosmos/cosmos-sdk/tests" ) -func Test_showKeysCmd(t *testing.T) { +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 Test_runShowCmd(t *testing.T) { +func TestRunShowCmd(t *testing.T) { cmd := showKeysCmd() err := runShowCmd(cmd, []string{"invalid"}) From e6ac4f99e409aeafd56ad09b4bb8bd19de45d5bd Mon Sep 17 00:00:00 2001 From: austinabell Date: Fri, 9 Aug 2019 17:39:58 -0400 Subject: [PATCH 18/18] Cleaned up descriptions to remove multi-sigs --- crypto/keys/output.go | 4 +--- go.mod | 1 - go.sum | 1 - keys/add.go | 9 ++------- keys/show.go | 10 ++++------ 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 774da860d..30e7a3024 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -69,9 +69,7 @@ func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { }, nil } -// Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. If the -// public key is a multisig public key, then the threshold and constituent -// public keys will be added. +// 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() diff --git a/go.mod b/go.mod index 7f1222b1a..92dc635d7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.12 require ( github.com/allegro/bigcache v1.2.1 // indirect github.com/aristanetworks/goarista v0.0.0-20181101003910-5bb443fba8e0 // indirect - github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d 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 diff --git a/go.sum b/go.sum index 3c3c1d55d..6bee951b8 100644 --- a/go.sum +++ b/go.sum @@ -44,7 +44,6 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f h1:jmVM19bsHZRVVe8rugzfILuL3VPgCj5b6941I20Naw0= github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q= -github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= diff --git a/keys/add.go b/keys/add.go index 5e07e772e..ec69d783f 100644 --- a/keys/add.go +++ b/keys/add.go @@ -28,7 +28,7 @@ const ( // flagDryRun = "dry-run" flagAccount = "account" flagIndex = "index" - flagNoSort = "nosort" + // flagNoSort = "nosort" // DefaultKeyPass contains the default key password for genesis transactions DefaultKeyPass = "12345678" @@ -51,18 +51,13 @@ If run with --dry-run, a key would be generated (or recovered) but not stored to local keystore. Use the --pubkey flag to add arbitrary public keys to the keystore for constructing multisig transactions. - -You can add a multisig key by passing the list of key names you want the public -key to be composed of to the --multisig flag and the minimum number of signatures -required through --multisig-threshold. The keys are sorted by address, unless -the flag --nosort is set. `, Args: cobra.ExactArgs(1), RunE: runAddCmd, } // cmd.Flags().StringSlice(flagMultisig, nil, "Construct and store a multisig public key (implies --pubkey)") // cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures. For use in conjunction with --multisig") - cmd.Flags().Bool(flagNoSort, false, "Keys passed to --multisig are taken in the order they're supplied") + // cmd.Flags().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") diff --git a/keys/show.go b/keys/show.go index 42c7f86bb..ddccf56b1 100644 --- a/keys/show.go +++ b/keys/show.go @@ -29,13 +29,11 @@ const ( func showKeysCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "show [name [name...]]", + Use: "show ", Short: "Show key info for the given name", - Long: `Return public details of a single local key. If multiple names are -provided, then an ephemeral multisig key will be created under the name "multi" -consisting of all the keys provided by name and multisig threshold.`, - Args: cobra.MinimumNArgs(1), - RunE: runShowCmd, + 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)")