Add support for github.com/99designs/keyring-backed keybases (#5029)

Introduce new Keybase implementation that can leverage
operating systems' built-in functionalities to securely store
secrets.

This chunk is extracted from @poldsam's original PR:
- #4754

Thanks: @alexanderbez
This commit is contained in:
Alessio Treglia 2019-09-21 09:54:14 -07:00 committed by GitHub
parent ab81c798c6
commit 2c96bbbaa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1545 additions and 192 deletions

View File

@ -71,6 +71,12 @@ and tx hash will be returned for specific Tendermint errors:
* `CodeMempoolIsFull` * `CodeMempoolIsFull`
* `CodeTxTooLarge` * `CodeTxTooLarge`
* [\#3872](https://github.com/cosmos/cosmos-sdk/issues/3872) Implement a RESTful endpoint and cli command to decode transactions. * [\#3872](https://github.com/cosmos/cosmos-sdk/issues/3872) Implement a RESTful endpoint and cli command to decode transactions.
* (keys) [\#4754](https://github.com/cosmos/cosmos-sdk/pull/4754) Introduce new Keybase implementation that can
leverage operating systems' built-in functionalities to securely store secrets. MacOS users may encounter
the following [issue](https://github.com/keybase/go-keychain/issues/47) with the `go-keychain` library. If
you encounter this issue, you must upgrade your xcode command line tools to version >= `10.2`. You can
upgrade via: `sudo rm -rf /Library/Developer/CommandLineTools; xcode-select --install`. Verify the
correct version via: `pkgutil --pkg-info=com.apple.pkg.CLTools_Executables`.
### Improvements ### Improvements

View File

@ -1,26 +1,19 @@
package keys package keys
import ( import (
"bufio"
"fmt" "fmt"
"os"
"reflect" "reflect"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types"
bip39 "github.com/cosmos/go-bip39"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/crypto/secp256k1"
dbm "github.com/tendermint/tm-db"
) )
var _ Keybase = dbKeybase{} var _ Keybase = dbKeybase{}
@ -71,139 +64,98 @@ var (
ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported") ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported")
) )
// dbKeybase combines encryption and storage implementation to provide // dbKeybase combines encryption and storage implementation to provide a
// a full-featured key manager // full-featured key manager.
//
// NOTE: dbKeybase will be deprecated in favor of keyringKeybase.
type dbKeybase struct { type dbKeybase struct {
db dbm.DB db dbm.DB
base baseKeybase
} }
// newDbKeybase creates a new keybase instance using the passed DB for reading and writing keys. // newDBKeybase creates a new dbKeybase instance using the provided DB for
func newDbKeybase(db dbm.DB) Keybase { // reading and writing keys.
func newDBKeybase(db dbm.DB) Keybase {
return dbKeybase{ return dbKeybase{
db: db, db: db,
base: baseKeybase{},
} }
} }
// NewInMemory creates a transient keybase on top of in-memory storage // NewInMemory creates a transient keybase on top of in-memory storage
// instance useful for testing purposes and on-the-fly key generation. // instance useful for testing purposes and on-the-fly key generation.
func NewInMemory() Keybase { return dbKeybase{dbm.NewMemDB()} } func NewInMemory() Keybase { return newDBKeybase(dbm.NewMemDB()) }
// CreateMnemonic generates a new key and persists it to storage, encrypted // CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. // using the provided password. It returns the generated mnemonic and the key Info.
// It returns the generated mnemonic and the key Info. // It returns an error if it fails to generate a key for the given key algorithm
// It returns an error if it fails to // type, or if another key is already stored under the same name.
// generate a key for the given algo type, or if another key is func (kb dbKeybase) CreateMnemonic(
// already stored under the same name. name string, language Language, passwd string, algo SigningAlgo,
func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { ) (info Info, mnemonic string, err error) {
if language != English {
return nil, "", ErrUnsupportedLanguage
}
if algo != Secp256k1 {
err = ErrUnsupportedSigningAlgo
return
}
// default number of words (24): return kb.base.CreateMnemonic(kb, name, language, passwd, algo)
// 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. // CreateAccount converts a mnemonic to a private key and persists it, encrypted
func (kb dbKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) { // with the given password.
coinType := types.GetConfig().GetCoinType() func (kb dbKeybase) CreateAccount(
hdPath := hd.NewFundraiserParams(account, coinType, index) name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32,
return kb.Derive(name, mnemonic, bip39Passwd, encryptPasswd, *hdPath) ) (Info, error) {
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, account, index)
} }
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) { // Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) func (kb dbKeybase) Derive(
if err != nil { name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
return ) (Info, error) {
}
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String()) return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, encryptPasswd, params)
return
} }
// CreateLedger creates a new locally-stored reference to a Ledger keypair // 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 // 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(
if algo != Secp256k1 { name string, algo SigningAlgo, hrp string, account, index uint32,
return nil, ErrUnsupportedSigningAlgo ) (Info, error) {
}
coinType := types.GetConfig().GetCoinType() return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
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 // CreateOffline creates a new reference to an offline keypair. It returns the
// created key info. // created key info.
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) { func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.writeOfflineKey(name, pub), nil return kb.base.writeOfflineKey(kb, name, pub), nil
} }
// CreateMulti creates a new reference to a multisig (offline) keypair. It // CreateMulti creates a new reference to a multisig (offline) keypair. It
// returns the created key info. // 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) (Info, error) {
return kb.writeMultisigKey(name, pub), nil return kb.base.writeMultisigKey(kb, name, pub), 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 != "" {
info = kb.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
} else {
pubk := secp256k1.PrivKeySecp256k1(derivedPriv).PubKey()
info = kb.writeOfflineKey(name, pubk)
}
return
} }
// List returns the keys from storage in alphabetical order. // List returns the keys from storage in alphabetical order.
func (kb dbKeybase) List() ([]Info, error) { func (kb dbKeybase) List() ([]Info, error) {
var res []Info var res []Info
iter := kb.db.Iterator(nil, nil) iter := kb.db.Iterator(nil, nil)
defer iter.Close() defer iter.Close()
for ; iter.Valid(); iter.Next() { for ; iter.Valid(); iter.Next() {
key := string(iter.Key()) key := string(iter.Key())
// need to include only keys in storage that have an info suffix // need to include only keys in storage that have an info suffix
if strings.HasSuffix(key, infoSuffix) { if strings.HasSuffix(key, infoSuffix) {
info, err := readInfo(iter.Value()) info, err := unmarshalInfo(iter.Value())
if err != nil { if err != nil {
return nil, err return nil, err
} }
res = append(res, info) res = append(res, info)
} }
} }
return res, nil return res, nil
} }
@ -213,20 +165,24 @@ func (kb dbKeybase) Get(name string) (Info, error) {
if len(bs) == 0 { if len(bs) == 0 {
return nil, keyerror.NewErrKeyNotFound(name) return nil, keyerror.NewErrKeyNotFound(name)
} }
return readInfo(bs)
return unmarshalInfo(bs)
} }
// GetByAddress returns Info based on a provided AccAddress. An error is returned
// if the address does not exist.
func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) {
ik := kb.db.Get(addrKey(address)) ik := kb.db.Get(addrKey(address))
if len(ik) == 0 { if len(ik) == 0 {
return nil, fmt.Errorf("key with address %s not found", address) return nil, fmt.Errorf("key with address %s not found", address)
} }
bs := kb.db.Get(ik) bs := kb.db.Get(ik)
return readInfo(bs) return unmarshalInfo(bs)
} }
// Sign signs the msg with the named key. // Sign signs the msg with the named key. It returns an error if the key doesn't
// It returns an error if the key doesn't exist or the decryption fails. // exist or the decryption fails.
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
info, err := kb.Get(name) info, err := kb.Get(name)
if err != nil { if err != nil {
@ -248,34 +204,10 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
} }
case ledgerInfo: case ledgerInfo:
priv, err = crypto.NewPrivKeyLedgerSecp256k1Unsafe(i.Path) return kb.base.SignWithLedger(info, msg)
if err != nil {
return
}
case offlineInfo, multiInfo: case offlineInfo, multiInfo:
_, err := fmt.Fprintf(os.Stderr, "Message to sign:\n\n%s\n", msg) return kb.base.DecodeSignature(info, 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) sig, err = priv.Sign(msg)
@ -283,10 +215,12 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
return nil, nil, err return nil, nil, err
} }
pub = priv.PubKey() return sig, priv.PubKey(), nil
return sig, pub, nil
} }
// ExportPrivateKeyObject returns a PrivKey object given the key name and
// passphrase. An error is returned if the key does not exist or if the Info for
// the key is invalid.
func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) { func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
info, err := kb.Get(name) info, err := kb.Get(name)
if err != nil { if err != nil {
@ -302,6 +236,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
err = fmt.Errorf("private key not available") err = fmt.Errorf("private key not available")
return nil, err return nil, err
} }
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
if err != nil { if err != nil {
return nil, err return nil, err
@ -319,26 +254,29 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
if bz == nil { if bz == nil {
return "", fmt.Errorf("no key to export with name %s", name) return "", fmt.Errorf("no key to export with name %s", name)
} }
return mintkey.ArmorInfoBytes(bz), nil return mintkey.ArmorInfoBytes(bz), nil
} }
// ExportPubKey returns public keys in ASCII armored format. // ExportPubKey returns public keys in ASCII armored format. It retrieves a Info
// Retrieve a Info object by its name and return the public key in // object by its name and return the public key in a portable format.
// a portable format.
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
bz := kb.db.Get(infoKey(name)) bz := kb.db.Get(infoKey(name))
if bz == nil { if bz == nil {
return "", fmt.Errorf("no key to export with name %s", name) return "", fmt.Errorf("no key to export with name %s", name)
} }
info, err := readInfo(bz)
info, err := unmarshalInfo(bz)
if err != nil { if err != nil {
return return
} }
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
} }
// ExportPrivKey returns a private key in ASCII armored format. // 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. // 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, func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
encryptPassphrase string) (armor string, err error) { encryptPassphrase string) (armor string, err error) {
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase) priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
@ -349,8 +287,8 @@ func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
} }
// ImportPrivKey imports a private key in ASCII armor format. // ImportPrivKey imports a private key in ASCII armor format. It returns an
// It returns an error if a key with the same name exists or a wrong encryption passphrase is // error if a key with the same name exists or a wrong encryption passphrase is
// supplied. // supplied.
func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error { func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
if _, err := kb.Get(name); err == nil { if _, err := kb.Get(name); err == nil {
@ -371,39 +309,42 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
if len(bz) > 0 { if len(bz) > 0 {
return errors.New("Cannot overwrite data for name " + name) return errors.New("Cannot overwrite data for name " + name)
} }
infoBytes, err := mintkey.UnarmorInfoBytes(armor) infoBytes, err := mintkey.UnarmorInfoBytes(armor)
if err != nil { if err != nil {
return return
} }
kb.db.Set(infoKey(name), infoBytes) kb.db.Set(infoKey(name), infoBytes)
return nil return nil
} }
// ImportPubKey imports ASCII-armored public keys. // ImportPubKey imports ASCII-armored public keys. Store a new Info object holding
// Store a new Info object holding a public key only, i.e. it will // a public key only, i.e. it will not be possible to sign with it as it lacks the
// not be possible to sign with it as it lacks the secret key. // secret key.
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
bz := kb.db.Get(infoKey(name)) bz := kb.db.Get(infoKey(name))
if len(bz) > 0 { if len(bz) > 0 {
return errors.New("Cannot overwrite data for name " + name) return errors.New("Cannot overwrite data for name " + name)
} }
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor) pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
if err != nil { if err != nil {
return return
} }
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
if err != nil { if err != nil {
return return
} }
kb.writeOfflineKey(name, pubKey)
kb.base.writeOfflineKey(kb, name, pubKey)
return return
} }
// Delete removes key forever, but we must present the // Delete removes key forever, but we must present the proper passphrase before
// proper passphrase before deleting it (for security). // deleting it (for security). It returns an error if the key doesn't exist or
// It returns an error if the key doesn't exist or // passphrases don't match. Passphrase is ignored when deleting references to
// passphrases don't match.
// Passphrase is ignored when deleting references to
// offline and Ledger / HW wallet keys. // offline and Ledger / HW wallet keys.
func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error { func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error {
// verify we have the proper password before deleting // verify we have the proper password before deleting
@ -411,13 +352,16 @@ func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error {
if err != nil { if err != nil {
return err return err
} }
if linfo, ok := info.(localInfo); ok && !skipPass { if linfo, ok := info.(localInfo); ok && !skipPass {
if _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil { if _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil {
return err return err
} }
} }
kb.db.DeleteSync(addrKey(info.GetAddress())) kb.db.DeleteSync(addrKey(info.GetAddress()))
kb.db.DeleteSync(infoKey(name)) kb.db.DeleteSync(infoKey(name))
return nil return nil
} }
@ -432,19 +376,24 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
if err != nil { if err != nil {
return err return err
} }
switch i := info.(type) { switch i := info.(type) {
case localInfo: case localInfo:
linfo := i linfo := i
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
if err != nil { if err != nil {
return err return err
} }
newpass, err := getNewpass() newpass, err := getNewpass()
if err != nil { if err != nil {
return err return err
} }
kb.writeLocalKey(name, key, newpass) kb.writeLocalKey(name, key, newpass)
return nil return nil
default: default:
return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String()) return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String())
} }
@ -458,27 +407,11 @@ func (kb dbKeybase) CloseDB() {
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info { func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
// encrypt private key using passphrase // encrypt private key using passphrase
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
// make Info // make Info
pub := priv.PubKey() pub := priv.PubKey()
info := newLocalInfo(name, pub, privArmor) info := newLocalInfo(name, pub, privArmor)
kb.writeInfo(name, info)
return info
}
func (kb dbKeybase) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params) Info {
info := newLedgerInfo(name, pub, path)
kb.writeInfo(name, info)
return info
}
func (kb dbKeybase) writeOfflineKey(name string, pub tmcrypto.PubKey) Info {
info := newOfflineInfo(name, pub)
kb.writeInfo(name, info)
return info
}
func (kb dbKeybase) writeMultisigKey(name string, pub tmcrypto.PubKey) Info {
info := NewMultiInfo(name, pub)
kb.writeInfo(name, info) kb.writeInfo(name, info)
return info return info
} }
@ -486,8 +419,10 @@ func (kb dbKeybase) writeMultisigKey(name string, pub tmcrypto.PubKey) Info {
func (kb dbKeybase) writeInfo(name string, info Info) { func (kb dbKeybase) writeInfo(name string, info Info) {
// write the info by key // write the info by key
key := infoKey(name) key := infoKey(name)
serializedInfo := writeInfo(info) serializedInfo := marshalInfo(info)
kb.db.SetSync(key, serializedInfo) kb.db.SetSync(key, serializedInfo)
// store a pointer to the infokey by address for fast lookup // store a pointer to the infokey by address for fast lookup
kb.db.SetSync(addrKey(info.GetAddress()), key) kb.db.SetSync(addrKey(info.GetAddress()), key)
} }

215
crypto/keys/keybase_base.go Normal file
View File

@ -0,0 +1,215 @@
package keys
import (
"bufio"
"fmt"
"os"
"github.com/cosmos/go-bip39"
"github.com/pkg/errors"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/types"
)
type (
// baseKeybase is an auxiliary type that groups Keybase storage agnostic features
// together.
baseKeybase struct{}
keyWriter interface {
writeLocalKeyer
infoWriter
}
writeLocalKeyer interface {
writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info
}
infoWriter interface {
writeInfo(name string, info Info)
}
)
// SignWithLedger signs a binary message with the ledger device referenced by an Info object
// and returns the signed bytes and the public key. It returns an error if the device could
// not be queried or it returned an error.
func (kb baseKeybase) SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
i := info.(ledgerInfo)
priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(i.Path)
if err != nil {
return
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}
// DecodeSignature decodes a an length-prefixed binary signature from standard input
// and return it as a byte slice.
func (kb baseKeybase) DecodeSignature(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
_, 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
}
// CreateAccount creates an account Info object.
func (kb baseKeybase) CreateAccount(
keyWriter keyWriter, name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
hdPath := CreateHDPath(account, index)
return kb.Derive(keyWriter, name, mnemonic, bip39Passwd, encryptPasswd, *hdPath)
}
func (kb baseKeybase) persistDerivedKey(
keyWriter keyWriter, seed []byte, passwd, name, fullHdPath string,
) (Info, error) {
// create master key and derive first key for keyring
derivedPriv, err := ComputeDerivedKey(seed, fullHdPath)
if err != nil {
return nil, err
}
var info Info
if passwd != "" {
info = keyWriter.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
} else {
info = kb.writeOfflineKey(keyWriter, name, secp256k1.PrivKeySecp256k1(derivedPriv).PubKey())
}
return info, nil
}
// CreateLedger creates a new reference to a Ledger key pair. It returns a public
// key and a derivation path. It returns an error if the device could not be queried.
func (kb baseKeybase) CreateLedger(
w infoWriter, name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
if !IsAlgoSupported(algo) {
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
}
return kb.writeLedgerKey(w, name, priv.PubKey(), *hdPath), nil
}
// CreateMnemonic generates a new key with the given algorithm and language pair.
func (kb baseKeybase) CreateMnemonic(
keyWriter keyWriter, name string, language Language, passwd string, algo SigningAlgo,
) (info Info, mnemonic string, err error) {
if language != English {
return nil, "", ErrUnsupportedLanguage
}
if !IsAlgoSupported(algo) {
return nil, "", ErrUnsupportedSigningAlgo
}
// 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 nil, "", err
}
mnemonic, err = bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", err
}
info, err = kb.persistDerivedKey(
keyWriter,
bip39.NewSeed(mnemonic, DefaultBIP39Passphrase), passwd,
name, types.GetConfig().GetFullFundraiserPath(),
)
return info, mnemonic, err
}
// Derive computes a BIP39 seed from the mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (kb baseKeybase) Derive(
keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
) (Info, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return nil, err
}
return kb.persistDerivedKey(keyWriter, seed, encryptPasswd, name, params.String())
}
func (kb baseKeybase) writeLedgerKey(w infoWriter, name string, pub tmcrypto.PubKey, path hd.BIP44Params) Info {
info := newLedgerInfo(name, pub, path)
w.writeInfo(name, info)
return info
}
func (kb baseKeybase) writeOfflineKey(w infoWriter, name string, pub tmcrypto.PubKey) Info {
info := newOfflineInfo(name, pub)
w.writeInfo(name, info)
return info
}
func (kb baseKeybase) writeMultisigKey(w infoWriter, name string, pub tmcrypto.PubKey) Info {
info := NewMultiInfo(name, pub)
w.writeInfo(name, info)
return info
}
// ComputeDerivedKey derives and returns the private key for the given seed and HD path.
func ComputeDerivedKey(seed []byte, fullHdPath string) ([32]byte, error) {
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
return hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
}
// CreateHDPath returns BIP 44 object from account and index parameters.
func CreateHDPath(account uint32, index uint32) *hd.BIP44Params {
return hd.NewFundraiserParams(account, types.GetConfig().GetCoinType(), index)
}
// IsAlgoSupported returns whether the signing algorithm is supported.
//
// TODO: Refactor this to be configurable to support interchangeable key signing
// and addressing.
// Ref: https://github.com/cosmos/cosmos-sdk/issues/4941
func IsAlgoSupported(algo SigningAlgo) bool { return algo == Secp256k1 }

View File

@ -0,0 +1,438 @@
package keys
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/99designs/keyring"
"github.com/pkg/errors"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"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"
)
var _ Keybase = keyringKeybase{}
// keyringKeybase implements the Keybase interface by using the Keyring library
// for account key persistence.
//
// TODO: There is no need for keyringKeybase to implement the Keybase interface
// or even have any of its members be public as it currently cannot be used by
// the outside world. Any client uses this implementation through the
// lazyKeybaseKeyring implementation. Consider either:
//
// 1. Remove lazyKeybaseKeyring and make keyringKeybase useable by the outside
// world.
// 2. Mark all keyringKeybase methods private and remove the compile time
// Keybase interface assertion.
type keyringKeybase struct {
db keyring.Keyring
base baseKeybase
}
func newKeyringKeybase(db keyring.Keyring) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
}
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. It returns the generated mnemonic and the key Info.
// An error is returned 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 keyringKeybase) CreateMnemonic(
name string, language Language, passwd string, algo SigningAlgo,
) (info Info, mnemonic string, err error) {
return kb.base.CreateMnemonic(kb, name, language, passwd, algo)
}
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (kb keyringKeybase) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
// Derive computes a BIP39 seed from th mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (kb keyringKeybase) Derive(
name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
) (info Info, err error) {
return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, encryptPasswd, params)
}
// 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 keyringKeybase) CreateLedger(
name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
}
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub), nil
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
// returns the created key Info object.
func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeMultisigKey(kb, name, pub), nil
}
// List returns the keys from storage in alphabetical order.
func (kb keyringKeybase) List() ([]Info, error) {
var res []Info
keys, err := kb.db.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
for _, key := range keys {
if strings.HasSuffix(key, infoSuffix) {
rawInfo, err := kb.db.Get(key)
if err != nil {
return nil, err
}
if len(rawInfo.Data) == 0 {
return nil, keyerror.NewErrKeyNotFound(key)
}
info, err := unmarshalInfo(rawInfo.Data)
if err != nil {
return nil, err
}
res = append(res, info)
}
}
return res, nil
}
// Get returns the public information about one key.
func (kb keyringKeybase) Get(name string) (Info, error) {
key := infoKey(name)
bs, err := kb.db.Get(string(key))
if err != nil {
return nil, err
}
if len(bs.Data) == 0 {
return nil, keyerror.NewErrKeyNotFound(name)
}
return unmarshalInfo(bs.Data)
}
// GetByAddress fetches a key by address and returns its public information.
func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) {
ik, err := kb.db.Get(string(addrKey(address)))
if err != nil {
return nil, err
}
if len(ik.Data) == 0 {
return nil, fmt.Errorf("key with address %s not found", address)
}
bs, err := kb.db.Get(string(ik.Data))
if err != nil {
return nil, err
}
return unmarshalInfo(bs.Data)
}
// Sign signs an arbitrary set of bytes with the named key. It returns an error
// if the key doesn't exist or the decryption fails.
func (kb keyringKeybase) 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 i := info.(type) {
case localInfo:
if i.PrivKeyArmor == "" {
return nil, nil, fmt.Errorf("private key not available")
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor))
if err != nil {
return nil, nil, err
}
case ledgerInfo:
return kb.base.SignWithLedger(info, msg)
case offlineInfo, multiInfo:
return kb.base.DecodeSignature(info, msg)
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}
// ExportPrivateKeyObject exports an armored private key object.
func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
info, err := kb.Get(name)
if err != nil {
return nil, err
}
var priv tmcrypto.PrivKey
switch linfo := info.(type) {
case localInfo:
if linfo.PrivKeyArmor == "" {
err = fmt.Errorf("private key not available")
return nil, err
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor))
if err != nil {
return nil, err
}
case ledgerInfo, offlineInfo, multiInfo:
return nil, errors.New("only works on local private keys")
}
return priv, nil
}
// Export exports armored private key to the caller.
func (kb keyringKeybase) Export(name string) (armor string, err error) {
bz, err := kb.db.Get(string(infoKey(name)))
if err != nil {
return "", err
}
if bz.Data == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return mintkey.ArmorInfoBytes(bz.Data), nil
}
// ExportPubKey returns public keys in ASCII armored format. It retrieves an Info
// object by its name and return the public key in a portable format.
func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) {
bz, err := kb.Get(name)
if err != nil {
return "", err
}
if bz == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes()), nil
}
// Import imports armored private key.
func (kb keyringKeybase) Import(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
}
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
if err != nil {
return err
}
info, err := unmarshalInfo(infoBytes)
if err != nil {
return err
}
kb.writeInfo(name, info)
err = kb.db.Set(keyring.Item{
Key: string(addrKey(info.GetAddress())),
Data: infoKey(name),
})
if err != nil {
return err
}
return nil
}
// ExportPrivKey returns a private key in ASCII armored format. An error is returned
// if the key does not exist or a wrong encryption passphrase is supplied.
func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, 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. An error is returned
// if a key with the same name exists or a wrong encryption passphrase is
// supplied.
func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
if kb.HasKey(name) {
return fmt.Errorf("cannot overwrite key: %s", name)
}
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
if err != nil {
return errors.Wrap(err, "failed to import private key")
}
kb.writeLocalKey(name, privKey, passphrase)
return nil
}
// HasKey returns whether the key exists in the keyring.
func (kb keyringKeybase) HasKey(name string) bool {
bz, _ := kb.Get(name)
return bz != nil
}
// ImportPubKey imports an ASCII-armored public key. It will 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 keyringKeybase) ImportPubKey(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
}
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
if err != nil {
return err
}
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
if err != nil {
return err
}
kb.base.writeOfflineKey(kb, name, pubKey)
return nil
}
// 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. The passphrase is ignored when deleting references to
// offline and Ledger / HW wallet keys.
func (kb keyringKeybase) 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
}
err = kb.db.Remove(string(addrKey(info.GetAddress())))
if err != nil {
return err
}
err = kb.db.Remove(string(infoKey(name)))
if err != nil {
return err
}
return nil
}
// Update changes the passphrase with which an already stored key is encrypted.
// The 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 keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
info, err := kb.Get(name)
if err != nil {
return err
}
switch linfo := info.(type) {
case 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 keyringKeybase) CloseDB() {}
func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
// encrypt private key using keyring
pub := priv.PubKey()
info := newLocalInfo(name, pub, string(priv.Bytes()))
kb.writeInfo(name, info)
return info
}
func (kb keyringKeybase) writeInfo(name string, info Info) {
// write the info by key
key := infoKey(name)
serializedInfo := marshalInfo(info)
err := kb.db.Set(keyring.Item{
Key: string(key),
Data: serializedInfo,
})
if err != nil {
panic(err)
}
err = kb.db.Set(keyring.Item{
Key: string(addrKey(info.GetAddress())),
Data: key,
})
if err != nil {
panic(err)
}
}

View File

@ -1,3 +1,4 @@
//nolint: goconst
package keys package keys
import ( import (

View File

@ -12,6 +12,7 @@ import (
var _ Keybase = lazyKeybase{} var _ Keybase = lazyKeybase{}
// NOTE: lazyKeybase will be deprecated in favor of lazyKeybaseKeyring.
type lazyKeybase struct { type lazyKeybase struct {
name string name string
dir string dir string
@ -33,7 +34,7 @@ func (lkb lazyKeybase) List() ([]Info, error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).List() return newDBKeybase(db).List()
} }
func (lkb lazyKeybase) Get(name string) (Info, error) { func (lkb lazyKeybase) Get(name string) (Info, error) {
@ -43,7 +44,7 @@ func (lkb lazyKeybase) Get(name string) (Info, error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Get(name) return newDBKeybase(db).Get(name)
} }
func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) { func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) {
@ -53,7 +54,7 @@ func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).GetByAddress(address) return newDBKeybase(db).GetByAddress(address)
} }
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error { func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
@ -63,7 +64,7 @@ func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Delete(name, passphrase, skipPass) return newDBKeybase(db).Delete(name, passphrase, skipPass)
} }
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) { func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
@ -73,7 +74,7 @@ func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Sign(name, passphrase, msg) 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 Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) {
@ -83,7 +84,7 @@ func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd str
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).CreateMnemonic(name, language, passwd, algo) 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) (Info, error) {
@ -93,7 +94,7 @@ func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index) 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) (Info, error) {
@ -103,7 +104,7 @@ func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string,
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params) 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 SigningAlgo, hrp string, account, index uint32) (info Info, err error) {
@ -113,7 +114,7 @@ func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, a
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).CreateLedger(name, algo, hrp, account, index) 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 Info, err error) {
@ -123,7 +124,7 @@ func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info In
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).CreateOffline(name, pubkey) 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 Info, err error) {
@ -133,7 +134,7 @@ func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).CreateMulti(name, pubkey) return newDBKeybase(db).CreateMulti(name, pubkey)
} }
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
@ -143,7 +144,7 @@ func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, e
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Update(name, oldpass, getNewpass) return newDBKeybase(db).Update(name, oldpass, getNewpass)
} }
func (lkb lazyKeybase) Import(name string, armor string) (err error) { func (lkb lazyKeybase) Import(name string, armor string) (err error) {
@ -153,7 +154,7 @@ func (lkb lazyKeybase) Import(name string, armor string) (err error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Import(name, armor) return newDBKeybase(db).Import(name, armor)
} }
func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error { func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
@ -163,7 +164,7 @@ func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase strin
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).ImportPrivKey(name, armor, passphrase) return newDBKeybase(db).ImportPrivKey(name, armor, passphrase)
} }
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) { func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
@ -173,7 +174,7 @@ func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).ImportPubKey(name, armor) return newDBKeybase(db).ImportPubKey(name, armor)
} }
func (lkb lazyKeybase) Export(name string) (armor string, err error) { func (lkb lazyKeybase) Export(name string) (armor string, err error) {
@ -183,7 +184,7 @@ func (lkb lazyKeybase) Export(name string) (armor string, err error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).Export(name) return newDBKeybase(db).Export(name)
} }
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) { func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
@ -193,7 +194,7 @@ func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).ExportPubKey(name) return newDBKeybase(db).ExportPubKey(name)
} }
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) { func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
@ -203,7 +204,7 @@ func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (c
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase) return newDBKeybase(db).ExportPrivateKeyObject(name, passphrase)
} }
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string, func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
@ -215,7 +216,7 @@ func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
} }
defer db.Close() defer db.Close()
return newDbKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase) return newDBKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
} }
func (lkb lazyKeybase) CloseDB() {} func (lkb lazyKeybase) CloseDB() {}

View File

@ -0,0 +1,362 @@
package keys
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/99designs/keyring"
"github.com/tendermint/crypto/bcrypt"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var (
_ Keybase = lazyKeybaseKeyring{}
maxPassphraseEntryAttempts = 3
)
// lazyKeybaseKeyring implements a public wrapper around the keyringKeybase type
// and implements the Keybase interface.
type lazyKeybaseKeyring struct {
name string
dir string
test bool
userInput io.Reader
}
// NewKeybaseKeyring creates a new instance of a lazy keybase using a Keyring as
// the persistence layer.
func NewKeybaseKeyring(name string, dir string, userInput io.Reader, test bool) Keybase {
_, err := keyring.Open(keyring.Config{
ServiceName: name,
})
if err != nil {
panic(err)
}
return lazyKeybaseKeyring{name: name, dir: dir, userInput: userInput, test: test}
}
func (lkb lazyKeybaseKeyring) lkbToKeyringConfig() keyring.Config {
if lkb.test {
return keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: lkb.name,
FileDir: lkb.dir,
FilePasswordFunc: fakePrompt,
}
}
realPrompt := func(prompt string) (string, error) {
keyhashStored := false
keyhashFilePath := filepath.Join(lkb.dir, "keyhash")
var keyhash []byte
_, err := os.Stat(keyhashFilePath)
switch {
case err == nil:
keyhash, err = ioutil.ReadFile(keyhashFilePath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
}
keyhashStored = true
case os.IsNotExist(err):
keyhashStored = false
default:
return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
}
failureCounter := 0
for {
failureCounter++
if failureCounter > maxPassphraseEntryAttempts {
return "", fmt.Errorf("too many failed passphrase attempts")
}
buf := bufio.NewReader(lkb.userInput)
pass, err := input.GetPassword("Enter keyring passphrase:", buf)
if err != nil {
continue
}
if keyhashStored {
if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
fmt.Fprintln(os.Stderr, "incorrect passphrase")
continue
}
return pass, nil
}
reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if pass != reEnteredPass {
fmt.Fprintln(os.Stderr, "passphrase do not match")
continue
}
saltBytes := crypto.CRandBytes(16)
passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if err := ioutil.WriteFile(lkb.dir+"/keyhash", passwordHash, 0555); err != nil {
return "", err
}
return pass, nil
}
}
return keyring.Config{
ServiceName: lkb.name,
FileDir: lkb.dir,
FilePasswordFunc: realPrompt,
}
}
func fakePrompt(prompt string) (string, error) {
fmt.Fprintln(os.Stderr, "Fake Prompt for passphase. Testing only")
return "test", nil
}
// List returns the keys from storage in alphabetical order.
func (lkb lazyKeybaseKeyring) List() ([]Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).List()
}
// Get returns the public information about one key.
func (lkb lazyKeybaseKeyring) Get(name string) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).Get(name)
}
// GetByAddress fetches a key by address and returns its public information.
func (lkb lazyKeybaseKeyring) GetByAddress(address sdk.AccAddress) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).GetByAddress(address)
}
// 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. The passphrase is ignored when deleting references to
// offline and Ledger / HW wallet keys.
func (lkb lazyKeybaseKeyring) Delete(name, passphrase string, skipPass bool) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Delete(name, passphrase, skipPass)
}
// Sign signs an arbitrary set of bytes with the named key. It returns an error
// if the key doesn't exist or the decryption fails.
func (lkb lazyKeybaseKeyring) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, nil, err
}
return newKeyringKeybase(db).Sign(name, passphrase, msg)
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. It returns the generated mnemonic and the key Info.
// An error is returned if it fails to generate a key for the given algo type,
// or if another key is already stored under the same name.
func (lkb lazyKeybaseKeyring) CreateMnemonic(
name string, language Language, passwd string, algo SigningAlgo,
) (info Info, seed string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, "", err
}
return newKeyringKeybase(db).CreateMnemonic(name, language, passwd, algo)
}
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (lkb lazyKeybaseKeyring) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
// Derive computes a BIP39 seed from th mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (lkb lazyKeybaseKeyring) Derive(
name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
}
// 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 (lkb lazyKeybaseKeyring) CreateLedger(
name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateLedger(name, algo, hrp, account, index)
}
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (lkb lazyKeybaseKeyring) CreateOffline(name string, pubkey crypto.PubKey) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateOffline(name, pubkey)
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
// returns the created key Info object.
func (lkb lazyKeybaseKeyring) CreateMulti(name string, pubkey crypto.PubKey) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateMulti(name, pubkey)
}
// Update changes the passphrase with which an already stored key is encrypted.
// The oldpass must be the current passphrase used for encryption, getNewpass is
// a function to get the passphrase to permanently replace the current passphrase.
func (lkb lazyKeybaseKeyring) Update(name, oldpass string, getNewpass func() (string, error)) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Update(name, oldpass, getNewpass)
}
// Import imports armored private key.
func (lkb lazyKeybaseKeyring) Import(name, armor string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Import(name, armor)
}
// ImportPrivKey imports a private key in ASCII armor format. An error is returned
// if a key with the same name exists or a wrong encryption passphrase is
// supplied.
func (lkb lazyKeybaseKeyring) ImportPrivKey(name, armor, passphrase string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).ImportPrivKey(name, armor, passphrase)
}
// ImportPubKey imports an ASCII-armored public key. It will 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 (lkb lazyKeybaseKeyring) ImportPubKey(name, armor string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).ImportPubKey(name, armor)
}
// Export exports armored a private key for the given name.
func (lkb lazyKeybaseKeyring) Export(name string) (armor string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).Export(name)
}
// ExportPubKey returns public keys in ASCII armored format. It retrieves an Info
// object by its name and return the public key in a portable format.
func (lkb lazyKeybaseKeyring) ExportPubKey(name string) (armor string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).ExportPubKey(name)
}
// ExportPrivateKeyObject exports an armored private key object.
func (lkb lazyKeybaseKeyring) ExportPrivateKeyObject(name, passphrase string) (crypto.PrivKey, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).ExportPrivateKeyObject(name, passphrase)
}
// ExportPrivKey returns a private key in ASCII armored format. An error is returned
// if the key does not exist or a wrong encryption passphrase is supplied.
func (lkb lazyKeybaseKeyring) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (string, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
}
func (lkb lazyKeybaseKeyring) CloseDB() {}

View File

@ -0,0 +1,342 @@
// nolint: goconst
package keys
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/99designs/keyring"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// New creates a new instance of a lazy keybase.
func newTestKeybaseKeyring(name string, dir string) Keybase {
if _, err := keyring.Open(keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: name,
FileDir: dir,
}); err != nil {
panic(err)
}
return lazyKeybaseKeyring{name: name, dir: dir, test: true}
}
func TestNewTestKeybaseKeyring(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
lazykb, ok := kb.(lazyKeybaseKeyring)
require.True(t, ok)
require.Equal(t, lazykb.name, "keybasename")
}
func TestLazyKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
algo := Secp256k1
n1, n2, n3 := "personal", "business", "other"
p1, p2 := "1234", "really-secure!@#$"
// Check empty state
l, err := kb.List()
require.Nil(t, err)
assert.Empty(t, l)
_, _, err = kb.CreateMnemonic(n1, English, p1, Ed25519)
require.Error(t, err, "ed25519 keys are currently not supported by keybase")
// create some keys
_, err = kb.Get(n1)
require.Error(t, err)
i, _, err := kb.CreateMnemonic(n1, English, p1, algo)
require.NoError(t, err)
require.Equal(t, n1, i.GetName())
_, _, err = kb.CreateMnemonic(n2, English, p2, algo)
require.NoError(t, err)
// we can get these keys
i2, err := kb.Get(n2)
require.NoError(t, err)
_, err = kb.Get(n3)
require.NotNil(t, err)
_, err = kb.GetByAddress(accAddr(i2))
require.NoError(t, err)
addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
require.NoError(t, err)
_, err = kb.GetByAddress(addr)
require.NotNil(t, err)
// list shows them in order
keyS, err := kb.List()
require.NoError(t, err)
require.Equal(t, 2, len(keyS))
// note these are in alphabetical order
require.Equal(t, n2, keyS[0].GetName())
require.Equal(t, n1, keyS[1].GetName())
require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
// deleting a key removes it
err = kb.Delete("bad name", "foo", false)
require.NotNil(t, err)
err = kb.Delete(n1, p1, false)
require.NoError(t, err)
keyS, err = kb.List()
require.NoError(t, err)
require.Equal(t, 1, len(keyS))
_, err = kb.Get(n1)
require.Error(t, err)
// create an offline key
o1 := "offline"
priv1 := ed25519.GenPrivKey()
pub1 := priv1.PubKey()
i, err = kb.CreateOffline(o1, pub1)
require.Nil(t, err)
require.Equal(t, pub1, i.GetPubKey())
require.Equal(t, o1, i.GetName())
keyS, err = kb.List()
require.NoError(t, err)
require.Equal(t, 2, len(keyS))
// delete the offline key
err = kb.Delete(o1, "", false)
require.NoError(t, err)
keyS, err = kb.List()
require.NoError(t, err)
require.Equal(t, 1, len(keyS))
// addr cache gets nuked - and test skip flag
err = kb.Delete(n2, "", true)
require.NoError(t, err)
}
func TestLazySignVerifyKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
algo := Secp256k1
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
p1, p2, p3 := "1234", "foobar", "foobar"
// create two users and get their info
i1, _, err := kb.CreateMnemonic(n1, English, p1, algo)
require.Nil(t, err)
i2, _, err := kb.CreateMnemonic(n2, English, p2, algo)
require.Nil(t, err)
// Import a public key
armor, err := kb.ExportPubKey(n2)
require.Nil(t, err)
kb.ImportPubKey(n3, armor)
i3, err := kb.Get(n3)
require.NoError(t, err)
require.Equal(t, i3.GetName(), n3)
// let's try to sign some messages
d1 := []byte("my first message")
d2 := []byte("some other important info!")
d3 := []byte("feels like I forgot something...")
// try signing both data with both ..
s11, pub1, err := kb.Sign(n1, p1, d1)
require.Nil(t, err)
require.Equal(t, i1.GetPubKey(), pub1)
s12, pub1, err := kb.Sign(n1, p1, d2)
require.Nil(t, err)
require.Equal(t, i1.GetPubKey(), pub1)
s21, pub2, err := kb.Sign(n2, p2, d1)
require.Nil(t, err)
require.Equal(t, i2.GetPubKey(), pub2)
s22, pub2, err := kb.Sign(n2, p2, d2)
require.Nil(t, err)
require.Equal(t, i2.GetPubKey(), pub2)
// let's try to validate and make sure it only works when everything is proper
cases := []struct {
key crypto.PubKey
data []byte
sig []byte
valid bool
}{
// proper matches
{i1.GetPubKey(), d1, s11, true},
// change data, pubkey, or signature leads to fail
{i1.GetPubKey(), d2, s11, false},
{i2.GetPubKey(), d1, s11, false},
{i1.GetPubKey(), d1, s21, false},
// make sure other successes
{i1.GetPubKey(), d2, s12, true},
{i2.GetPubKey(), d1, s21, true},
{i2.GetPubKey(), d2, s22, true},
}
for i, tc := range cases {
valid := tc.key.VerifyBytes(tc.data, tc.sig)
require.Equal(t, tc.valid, valid, "%d", i)
}
// Now try to sign data with a secret-less key
_, _, err = kb.Sign(n3, p3, d3)
require.NotNil(t, err)
}
func TestLazyExportImportKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
require.NoError(t, err)
require.Equal(t, info.GetName(), "john")
john, err := kb.Get("john")
require.NoError(t, err)
require.Equal(t, info.GetName(), "john")
johnAddr := info.GetPubKey().Address()
armor, err := kb.Export("john")
require.NoError(t, err)
err = kb.Import("john2", armor)
require.NoError(t, err)
john2, err := kb.Get("john2")
require.NoError(t, err)
require.Equal(t, john.GetPubKey().Address(), johnAddr)
require.Equal(t, john.GetName(), "john")
require.Equal(t, john, john2)
}
func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
// CreateMnemonic a private-public key pair and ensure consistency
notPasswd := "n9y25ah7"
info, _, err := kb.CreateMnemonic("john", English, notPasswd, Secp256k1)
require.Nil(t, err)
require.NotEqual(t, info, "")
require.Equal(t, info.GetName(), "john")
addr := info.GetPubKey().Address()
john, err := kb.Get("john")
require.NoError(t, err)
require.Equal(t, john.GetName(), "john")
require.Equal(t, john.GetPubKey().Address(), addr)
// Export the public key only
armor, err := kb.ExportPubKey("john")
require.NoError(t, err)
// Import it under a different name
err = kb.ImportPubKey("john-pubkey-only", armor)
require.NoError(t, err)
// Ensure consistency
john2, err := kb.Get("john-pubkey-only")
require.NoError(t, err)
// Compare the public keys
require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
// Ensure the original key hasn't changed
john, err = kb.Get("john")
require.NoError(t, err)
require.Equal(t, john.GetPubKey().Address(), addr)
require.Equal(t, john.GetName(), "john")
// Ensure keys cannot be overwritten
err = kb.ImportPubKey("john-pubkey-only", armor)
require.NotNil(t, err)
}
func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
require.NoError(t, err)
require.Equal(t, info.GetName(), "john")
// export private key object
exported, err := kb.ExportPrivateKeyObject("john", "secretcpw")
require.Nil(t, err, "%+v", err)
require.True(t, exported.PubKey().Equals(info.GetPubKey()))
}
func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
algo := Secp256k1
n1, n2 := "old-name", "new name"
p1 := "1234"
// make sure key works with initial password
_, _, err := kb.CreateMnemonic(n1, English, p1, algo)
require.Nil(t, err, "%+v", err)
_, err = kb.Export(n1 + ".notreal")
require.NotNil(t, err)
_, err = kb.Export(" " + n1)
require.NotNil(t, err)
_, err = kb.Export(n1 + " ")
require.NotNil(t, err)
_, err = kb.Export("")
require.NotNil(t, err)
exported, err := kb.Export(n1)
require.Nil(t, err, "%+v", err)
// import succeeds
err = kb.Import(n2, exported)
require.NoError(t, err)
// second import fails
err = kb.Import(n2, exported)
require.NotNil(t, err)
}
func TestLazySeedPhraseKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
algo := Secp256k1
n1, n2 := "lost-key", "found-again"
p1, p2 := "1234", "foobar"
// make sure key works with initial password
info, mnemonic, err := kb.CreateMnemonic(n1, English, p1, algo)
require.Nil(t, err, "%+v", err)
require.Equal(t, n1, info.GetName())
assert.NotEmpty(t, mnemonic)
// now, let us delete this key
err = kb.Delete(n1, p1, false)
require.Nil(t, err, "%+v", err)
_, err = kb.Get(n1)
require.NotNil(t, err)
// let us re-create it from the mnemonic-phrase
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
newInfo, err := kb.Derive(n2, mnemonic, DefaultBIP39Passphrase, p2, params)
require.NoError(t, err)
require.Equal(t, n2, newInfo.GetName())
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
}

View File

@ -14,18 +14,25 @@ import (
type Keybase interface { type Keybase interface {
// CRUD on the keystore // CRUD on the keystore
List() ([]Info, error) List() ([]Info, error)
// Get returns the public information about one key.
Get(name string) (Info, error) Get(name string) (Info, error)
// Get performs a by-address lookup and returns the public
// information about one key if there's any.
GetByAddress(address types.AccAddress) (Info, error) GetByAddress(address types.AccAddress) (Info, error)
// Delete removes a key.
Delete(name, passphrase string, skipPass bool) error Delete(name, passphrase string, skipPass bool) error
// Sign bytes, looking up the private key to use.
// Sign some bytes, looking up the private key to use
Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error)
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic // CreateMnemonic generates a new mnemonic, derives a hierarchical deterministic
// key from that. // key from that. 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.
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) 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 converts a mnemonic to a private key using a BIP44 path 44'/118'/{account}'/0/{index}
// and persists it, encrypted with the given password.
CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error)
// Derive computes a BIP39 seed from th mnemonic and bip39Passwd. // Derive computes a BIP39 seed from th mnemonic and bip39Passwd.
@ -45,11 +52,30 @@ type Keybase interface {
// The following operations will *only* work on locally-stored keys // The following operations will *only* work on locally-stored keys
Update(name, oldpass string, getNewpass func() (string, error)) error Update(name, oldpass string, getNewpass func() (string, error)) error
// Import imports ASCII armored Info objects.
Import(name string, armor string) (err error) Import(name string, armor string) (err error)
// 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.
ImportPrivKey(name, armor, passphrase string) error ImportPrivKey(name, armor, passphrase string) error
// 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.
ImportPubKey(name string, armor string) (err error) ImportPubKey(name string, armor string) (err error)
// Export exports an Info object in ASCII armored format.
Export(name string) (armor string, err error) Export(name string) (armor string, err error)
// ExportPubKey returns public keys in ASCII armored format.
// Retrieve a Info object by its name and return the public key in
// a portable format.
ExportPubKey(name string) (armor string, err error) ExportPubKey(name string) (armor string, err error)
// 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.
ExportPrivKey(name, decryptPassphrase, encryptPassphrase 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 *only* works on locally-stored keys. Temporary method until we redo the exporting API
@ -279,12 +305,12 @@ func (i multiInfo) GetPath() (*hd.BIP44Params, error) {
} }
// encoding info // encoding info
func writeInfo(i Info) []byte { func marshalInfo(i Info) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(i) return cdc.MustMarshalBinaryLengthPrefixed(i)
} }
// decoding info // decoding info
func readInfo(bz []byte) (info Info, err error) { func unmarshalInfo(bz []byte) (info Info, err error) {
err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info) err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info)
return return
} }

View File

@ -27,8 +27,8 @@ func Test_writeReadLedgerInfo(t *testing.T) {
types.MustBech32ifyAccPub(lInfo.GetPubKey())) types.MustBech32ifyAccPub(lInfo.GetPubKey()))
// Serialize and restore // Serialize and restore
serialized := writeInfo(lInfo) serialized := marshalInfo(lInfo)
restoredInfo, err := readInfo(serialized) restoredInfo, err := unmarshalInfo(serialized)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, restoredInfo) assert.NotNil(t, restoredInfo)

3
go.mod
View File

@ -1,6 +1,7 @@
module github.com/cosmos/cosmos-sdk module github.com/cosmos/cosmos-sdk
require ( require (
github.com/99designs/keyring v1.1.2
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/bgentry/speakeasy v0.1.0 github.com/bgentry/speakeasy v0.1.0
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
@ -22,13 +23,13 @@ require (
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.4.0 github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/stumble/gorocksdb v0.0.3 // indirect
github.com/tendermint/btcd v0.1.1 github.com/tendermint/btcd v0.1.1
github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae
github.com/tendermint/go-amino v0.15.0 github.com/tendermint/go-amino v0.15.0
github.com/tendermint/iavl v0.12.4 github.com/tendermint/iavl v0.12.4
github.com/tendermint/tendermint v0.32.3 github.com/tendermint/tendermint v0.32.3
github.com/tendermint/tm-db v0.2.0 github.com/tendermint/tm-db v0.2.0
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
) )

30
go.sum
View File

@ -1,4 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/keyring v1.1.2 h1:JJauROcU6x6Nh9uZb+8JgXFvyo0GUESLo1ixhpA0Kmw=
github.com/99designs/keyring v1.1.2/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@ -41,15 +43,25 @@ github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0W
github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI=
github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
@ -65,6 +77,8 @@ github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80n
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
@ -96,6 +110,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -110,6 +126,8 @@ github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 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/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -130,6 +148,7 @@ github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -196,10 +215,14 @@ github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stumble/gorocksdb v0.0.3 h1:9UU+QA1pqFYJuf9+5p7z1IqdE5k0mma4UAeu2wmX8kA=
github.com/stumble/gorocksdb v0.0.3/go.mod h1:v6IHdFBXk5DJ1K4FZ0xi+eY737quiiBxYtSWXadLybY=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s=
@ -238,6 +261,8 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -245,6 +270,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -261,6 +287,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@ -272,8 +300,6 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=