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`
* `CodeTxTooLarge`
* [\#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

View File

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

View File

@ -12,6 +12,7 @@ import (
var _ Keybase = lazyKeybase{}
// NOTE: lazyKeybase will be deprecated in favor of lazyKeybaseKeyring.
type lazyKeybase struct {
name string
dir string
@ -33,7 +34,7 @@ func (lkb lazyKeybase) List() ([]Info, error) {
}
defer db.Close()
return newDbKeybase(db).List()
return newDBKeybase(db).List()
}
func (lkb lazyKeybase) Get(name string) (Info, error) {
@ -43,7 +44,7 @@ func (lkb lazyKeybase) Get(name string) (Info, error) {
}
defer db.Close()
return newDbKeybase(db).Get(name)
return newDBKeybase(db).Get(name)
}
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()
return newDbKeybase(db).GetByAddress(address)
return newDBKeybase(db).GetByAddress(address)
}
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()
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) {
@ -73,7 +74,7 @@ func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto
}
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) {
@ -83,7 +84,7 @@ func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd str
}
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) {
@ -93,7 +94,7 @@ func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd
}
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) {
@ -103,7 +104,7 @@ func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string,
}
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) {
@ -113,7 +114,7 @@ func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, a
}
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) {
@ -123,7 +124,7 @@ func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info In
}
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) {
@ -133,7 +134,7 @@ func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info
}
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 {
@ -143,7 +144,7 @@ func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, e
}
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) {
@ -153,7 +154,7 @@ func (lkb lazyKeybase) Import(name string, armor string) (err error) {
}
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 {
@ -163,7 +164,7 @@ func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase strin
}
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) {
@ -173,7 +174,7 @@ func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
}
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) {
@ -183,7 +184,7 @@ func (lkb lazyKeybase) Export(name string) (armor string, err error) {
}
defer db.Close()
return newDbKeybase(db).Export(name)
return newDBKeybase(db).Export(name)
}
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()
return newDbKeybase(db).ExportPubKey(name)
return newDBKeybase(db).ExportPubKey(name)
}
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()
return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase)
return newDBKeybase(db).ExportPrivateKeyObject(name, passphrase)
}
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
@ -215,7 +216,7 @@ func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
}
defer db.Close()
return newDbKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
return newDBKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
}
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 {
// CRUD on the keystore
List() ([]Info, error)
// Get returns the public information about one key.
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)
// Delete removes a key.
Delete(name, passphrase string, skipPass bool) error
// Sign some bytes, looking up the private key to use
// Sign bytes, looking up the private key to use.
Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error)
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic
// key from that.
// CreateMnemonic generates a new mnemonic, derives a hierarchical deterministic
// 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)
// 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)
// 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
Update(name, oldpass string, getNewpass func() (string, error)) error
// Import imports ASCII armored Info objects.
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
// 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)
// Export exports an Info object in ASCII armored format.
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)
// 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)
// 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
func writeInfo(i Info) []byte {
func marshalInfo(i Info) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(i)
}
// decoding info
func readInfo(bz []byte) (info Info, err error) {
func unmarshalInfo(bz []byte) (info Info, err error) {
err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info)
return
}

View File

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

3
go.mod
View File

@ -1,6 +1,7 @@
module github.com/cosmos/cosmos-sdk
require (
github.com/99designs/keyring v1.1.2
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/bgentry/speakeasy v0.1.0
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
@ -22,13 +23,13 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper 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/crypto v0.0.0-20190823183015-45b1026d81ae
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/iavl v0.12.4
github.com/tendermint/tendermint v0.32.3
github.com/tendermint/tm-db v0.2.0
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
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=
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI=
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 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
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.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
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-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
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-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/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/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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/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/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.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/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/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/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
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-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-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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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-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-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-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
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-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-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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190425150028-36563e24a262/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=