diff --git a/CHANGELOG.md b/CHANGELOG.md index 3072e5367..c5b25f03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 79840f8b2..b00965778 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -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) } diff --git a/crypto/keys/keybase_base.go b/crypto/keys/keybase_base.go new file mode 100644 index 000000000..6d88a0054 --- /dev/null +++ b/crypto/keys/keybase_base.go @@ -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 } diff --git a/crypto/keys/keybase_keyring.go b/crypto/keys/keybase_keyring.go new file mode 100644 index 000000000..b89671b0b --- /dev/null +++ b/crypto/keys/keybase_keyring.go @@ -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) + } +} diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 559dccbf9..2d4752fe3 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -1,3 +1,4 @@ +//nolint: goconst package keys import ( diff --git a/crypto/keys/lazy_keybase.go b/crypto/keys/lazy_keybase.go index d1e855fe6..f362c9a1a 100644 --- a/crypto/keys/lazy_keybase.go +++ b/crypto/keys/lazy_keybase.go @@ -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() {} diff --git a/crypto/keys/lazy_keybase_keyring.go b/crypto/keys/lazy_keybase_keyring.go new file mode 100644 index 000000000..311ed2951 --- /dev/null +++ b/crypto/keys/lazy_keybase_keyring.go @@ -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() {} diff --git a/crypto/keys/lazy_keybase_keyring_test.go b/crypto/keys/lazy_keybase_keyring_test.go new file mode 100644 index 000000000..e2866141b --- /dev/null +++ b/crypto/keys/lazy_keybase_keyring_test.go @@ -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()) +} diff --git a/crypto/keys/types.go b/crypto/keys/types.go index c8424b969..d968dce59 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -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 } diff --git a/crypto/keys/types_test.go b/crypto/keys/types_test.go index 3b368aa2e..5c2d28c3b 100644 --- a/crypto/keys/types_test.go +++ b/crypto/keys/types_test.go @@ -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) diff --git a/go.mod b/go.mod index 1f9bd6e48..4d9e5b735 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 5d9dcef10..4f60c00bc 100644 --- a/go.sum +++ b/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=