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:
parent
ab81c798c6
commit
2c96bbbaa8
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
//nolint: goconst
|
||||
package keys
|
||||
|
||||
import (
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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() {}
|
|
@ -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())
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
3
go.mod
|
@ -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
30
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in New Issue