2020-03-25 08:20:36 -07:00
|
|
|
package keyring
|
2019-09-21 09:54:14 -07:00
|
|
|
|
|
|
|
import (
|
2019-09-30 09:16:53 -07:00
|
|
|
"bufio"
|
2020-03-30 11:30:50 -07:00
|
|
|
"encoding/hex"
|
2019-09-21 09:54:14 -07:00
|
|
|
"fmt"
|
2019-09-30 09:16:53 -07:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-09-21 09:54:14 -07:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/99designs/keyring"
|
2020-04-08 02:38:28 -07:00
|
|
|
"github.com/cosmos/go-bip39"
|
2019-09-21 09:54:14 -07:00
|
|
|
"github.com/pkg/errors"
|
2020-06-04 03:38:24 -07:00
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
"github.com/tendermint/crypto/bcrypt"
|
2019-09-21 09:54:14 -07:00
|
|
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/client/input"
|
2020-03-27 02:24:19 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/crypto"
|
2020-06-04 03:38:24 -07:00
|
|
|
cryptoAmino "github.com/cosmos/cosmos-sdk/crypto/codec"
|
2020-04-08 02:38:28 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2020-03-25 08:20:36 -07:00
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2019-09-21 09:54:14 -07:00
|
|
|
)
|
|
|
|
|
2019-12-11 01:45:26 -08:00
|
|
|
const (
|
2020-01-23 08:48:00 -08:00
|
|
|
BackendFile = "file"
|
|
|
|
BackendOS = "os"
|
|
|
|
BackendKWallet = "kwallet"
|
|
|
|
BackendPass = "pass"
|
|
|
|
BackendTest = "test"
|
2020-03-30 11:30:50 -07:00
|
|
|
BackendMemory = "memory"
|
2020-01-22 09:54:56 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-04-08 02:38:28 -07:00
|
|
|
keyringFileDirName = "keyring-file"
|
|
|
|
keyringTestDirName = "keyring-test"
|
|
|
|
passKeyringPrefix = "keyring-%s"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ Keyring = &keystore{}
|
|
|
|
maxPassphraseEntryAttempts = 3
|
2019-12-11 01:45:26 -08:00
|
|
|
)
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// Keyring exposes operations over a backend supported by github.com/99designs/keyring.
|
|
|
|
type Keyring interface {
|
|
|
|
// List all keys.
|
|
|
|
List() ([]Info, error)
|
|
|
|
|
|
|
|
// Key and KeyByAddress return keys by uid and address respectively.
|
|
|
|
Key(uid string) (Info, error)
|
|
|
|
KeyByAddress(address sdk.Address) (Info, error)
|
|
|
|
|
|
|
|
// Delete and DeleteByAddress remove keys from the keyring.
|
|
|
|
Delete(uid string) error
|
|
|
|
DeleteByAddress(address sdk.Address) error
|
|
|
|
|
|
|
|
// NewMnemonic generates a new mnemonic, derives a hierarchical deterministic
|
|
|
|
// key from that, and persists it to the storage. 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.
|
|
|
|
NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error)
|
|
|
|
|
|
|
|
// NewAccount converts a mnemonic to a private key and BIP-39 HD Path and persists it.
|
|
|
|
NewAccount(uid, mnemonic, bip39Passwd, hdPath string, algo SignatureAlgo) (Info, error)
|
|
|
|
|
|
|
|
// SaveLedgerKey retrieves a public key reference from a Ledger device and persists it.
|
|
|
|
SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error)
|
|
|
|
|
|
|
|
// SavePubKey stores a public key and returns the persisted Info structure.
|
|
|
|
SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error)
|
|
|
|
|
|
|
|
// SaveMultisig stores and returns a new multsig (offline) key reference.
|
|
|
|
SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error)
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
Signer
|
|
|
|
|
|
|
|
Importer
|
|
|
|
Exporter
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// Signer is implemented by key stores that want to provide signing capabilities.
|
|
|
|
type Signer interface {
|
|
|
|
// Sign sign byte messages with a user key.
|
|
|
|
Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error)
|
2019-09-30 09:16:53 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// SignByAddress sign byte messages with a user key providing the address.
|
|
|
|
SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Importer is implemented by key stores that support import of public and private keys.
|
|
|
|
type Importer interface {
|
|
|
|
// ImportPrivKey imports ASCII armored passphrase-encrypted private keys.
|
|
|
|
ImportPrivKey(uid, armor, passphrase string) error
|
|
|
|
// ImportPubKey imports ASCII armored public keys.
|
|
|
|
ImportPubKey(uid string, armor string) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exporter is implemented by key stores that support export of public and private keys.
|
|
|
|
type Exporter interface {
|
|
|
|
// Export public key
|
|
|
|
ExportPubKeyArmor(uid string) (string, error)
|
|
|
|
ExportPubKeyArmorByAddress(address sdk.Address) (string, 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.
|
|
|
|
ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error)
|
|
|
|
ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Option overrides keyring configuration options.
|
|
|
|
type Option func(options *Options)
|
|
|
|
|
|
|
|
//Options define the options of the Keyring
|
|
|
|
type Options struct {
|
|
|
|
SupportedAlgos SigningAlgoList
|
|
|
|
SupportedAlgosLedger SigningAlgoList
|
2020-01-15 09:29:36 -08:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// NewInMemory creates a transient keyring useful for testing
|
|
|
|
// purposes and on-the-fly key generation.
|
|
|
|
// Keybase options can be applied when generating this new Keybase.
|
|
|
|
func NewInMemory(opts ...Option) Keyring {
|
|
|
|
return newKeystore(keyring.NewArrayKeyring(nil), opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewKeyring creates a new instance of a keyring.
|
|
|
|
// Keyring ptions can be applied when generating the new instance.
|
|
|
|
// Available backends are "os", "file", "kwallet", "memory", "pass", "test".
|
|
|
|
func New(
|
|
|
|
appName, backend, rootDir string, userInput io.Reader, opts ...Option,
|
|
|
|
) (Keyring, error) {
|
2020-05-02 12:26:59 -07:00
|
|
|
var (
|
|
|
|
db keyring.Keyring
|
|
|
|
err error
|
|
|
|
)
|
2019-09-30 09:16:53 -07:00
|
|
|
|
2020-01-22 09:54:56 -08:00
|
|
|
switch backend {
|
2020-03-30 11:30:50 -07:00
|
|
|
case BackendMemory:
|
2020-04-08 02:38:28 -07:00
|
|
|
return NewInMemory(opts...), err
|
2020-01-22 09:54:56 -08:00
|
|
|
case BackendTest:
|
2020-04-08 02:38:28 -07:00
|
|
|
db, err = keyring.Open(newTestBackendKeyringConfig(appName, rootDir))
|
2020-01-22 09:54:56 -08:00
|
|
|
case BackendFile:
|
2020-01-23 10:47:11 -08:00
|
|
|
db, err = keyring.Open(newFileBackendKeyringConfig(appName, rootDir, userInput))
|
2020-01-22 09:54:56 -08:00
|
|
|
case BackendOS:
|
2020-04-08 02:38:28 -07:00
|
|
|
db, err = keyring.Open(newOSBackendKeyringConfig(appName, rootDir, userInput))
|
2020-01-23 08:48:00 -08:00
|
|
|
case BackendKWallet:
|
2020-01-23 10:47:11 -08:00
|
|
|
db, err = keyring.Open(newKWalletBackendKeyringConfig(appName, rootDir, userInput))
|
2020-01-23 08:48:00 -08:00
|
|
|
case BackendPass:
|
2020-01-23 10:47:11 -08:00
|
|
|
db, err = keyring.Open(newPassBackendKeyringConfig(appName, rootDir, userInput))
|
2020-01-22 09:54:56 -08:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown keyring backend %v", backend)
|
2019-12-11 01:45:26 -08:00
|
|
|
}
|
2020-04-08 02:38:28 -07:00
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
if err != nil {
|
2019-10-09 11:53:54 -07:00
|
|
|
return nil, err
|
2019-09-30 09:16:53 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return newKeystore(db, opts...), nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
type keystore struct {
|
|
|
|
db keyring.Keyring
|
|
|
|
options Options
|
2020-03-30 11:30:50 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func newKeystore(kr keyring.Keyring, opts ...Option) keystore {
|
|
|
|
// Default options for keybase
|
|
|
|
options := Options{
|
|
|
|
SupportedAlgos: SigningAlgoList{hd.Secp256k1},
|
|
|
|
SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1},
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
for _, optionFn := range opts {
|
|
|
|
optionFn(&options)
|
|
|
|
}
|
|
|
|
|
|
|
|
return keystore{kr, options}
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) ExportPubKeyArmor(uid string) (string, error) {
|
|
|
|
bz, err := ks.Key(uid)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
if bz == nil {
|
|
|
|
return "", fmt.Errorf("no key to export with name: %s", uid)
|
|
|
|
}
|
|
|
|
|
|
|
|
return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) ExportPubKeyArmorByAddress(address sdk.Address) (string, error) {
|
|
|
|
info, err := ks.KeyByAddress(address)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return ks.ExportPubKeyArmor(info.GetName())
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) {
|
|
|
|
priv, err := ks.ExportPrivateKeyObject(uid)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
info, err := ks.Key(uid)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// ExportPrivateKeyObject exports an armored private key object.
|
|
|
|
func (ks keystore) ExportPrivateKeyObject(uid string) (tmcrypto.PrivKey, error) {
|
|
|
|
info, err := ks.Key(uid)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
var priv tmcrypto.PrivKey
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
switch linfo := info.(type) {
|
|
|
|
case localInfo:
|
|
|
|
if linfo.PrivKeyArmor == "" {
|
|
|
|
err = fmt.Errorf("private key not available")
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
case ledgerInfo, offlineInfo, multiInfo:
|
|
|
|
return nil, errors.New("only works on local private keys")
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return priv, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error) {
|
|
|
|
byAddress, err := ks.KeyByAddress(address)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return ks.ExportPrivKeyArmor(byAddress.GetName(), encryptPassphrase)
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
|
|
|
|
if _, err := ks.Key(uid); err == nil {
|
|
|
|
return fmt.Errorf("cannot overwrite key: %s", uid)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return errors.Wrap(err, "failed to decrypt private key")
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
_, err = ks.writeLocalKey(uid, privKey, hd.PubKeyType(algo))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) ImportPubKey(uid string, armor string) error {
|
|
|
|
if _, err := ks.Key(uid); err == nil {
|
|
|
|
return fmt.Errorf("cannot overwrite key: %s", uid)
|
|
|
|
}
|
|
|
|
|
|
|
|
pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
_, err = ks.writeOfflineKey(uid, pubKey, hd.PubKeyType(algo))
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error) {
|
|
|
|
info, err := ks.Key(uid)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2020-03-01 09:16:22 -08:00
|
|
|
return SignWithLedger(info, msg)
|
2019-09-21 09:54:14 -07:00
|
|
|
|
|
|
|
case offlineInfo, multiInfo:
|
2020-03-20 08:00:12 -07:00
|
|
|
return nil, info.GetPubKey(), errors.New("cannot sign with offline keys")
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
sig, err := priv.Sign(msg)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sig, priv.PubKey(), nil
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) SignByAddress(address sdk.Address, msg []byte) ([]byte, tmcrypto.PubKey, error) {
|
|
|
|
key, err := ks.KeyByAddress(address)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return ks.Sign(key.GetName(), msg)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error) {
|
|
|
|
if !ks.options.SupportedAlgosLedger.Contains(algo) {
|
|
|
|
return nil, ErrUnsupportedSigningAlgo
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
hdPath := hd.NewFundraiserParams(account, coinType, index)
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
priv, _, err := crypto.NewPrivKeyLedgerSecp256k1(*hdPath, hrp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return ks.writeLedgerKey(uid, priv.PubKey(), *hdPath, algo.Name())
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo hd.PubKeyType) (Info, error) {
|
|
|
|
info := newLedgerInfo(name, pub, path, algo)
|
2020-05-02 12:26:59 -07:00
|
|
|
if err := ks.writeInfo(info); err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) {
|
|
|
|
return ks.writeMultisigKey(uid, pubkey)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) SavePubKey(uid string, pubkey tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) {
|
|
|
|
return ks.writeOfflineKey(uid, pubkey, algo)
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) DeleteByAddress(address sdk.Address) error {
|
|
|
|
info, err := ks.KeyByAddress(address)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
err = ks.Delete(info.GetName())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) Delete(uid string) error {
|
|
|
|
info, err := ks.Key(uid)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
err = ks.db.Remove(addrHexKeyAsString(info.GetAddress()))
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
err = ks.db.Remove(string(infoKey(uid)))
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) KeyByAddress(address sdk.Address) (Info, error) {
|
|
|
|
ik, err := ks.db.Get(addrHexKeyAsString(address))
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
if len(ik.Data) == 0 {
|
|
|
|
return nil, fmt.Errorf("key with address %s not found", address)
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, err := ks.db.Get(string(ik.Data))
|
2020-01-14 07:40:10 -08:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
2020-01-14 07:40:10 -08:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return unmarshalInfo(bs.Data)
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) List() ([]Info, error) {
|
|
|
|
var res []Info
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
keys, err := ks.db.Keys()
|
2020-05-08 01:46:12 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
for _, key := range keys {
|
|
|
|
if strings.HasSuffix(key, infoSuffix) {
|
|
|
|
rawInfo, err := ks.db.Get(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(rawInfo.Data) == 0 {
|
|
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := unmarshalInfo(rawInfo.Data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
res = append(res, info)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return res, nil
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) NewMnemonic(uid string, language Language, hdPath string, algo SignatureAlgo) (Info, string, error) {
|
|
|
|
if language != English {
|
|
|
|
return nil, "", ErrUnsupportedLanguage
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
if !ks.isSupportedSigningAlgo(algo) {
|
|
|
|
return nil, "", ErrUnsupportedSigningAlgo
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// 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
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
mnemonic, err := bip39.NewMnemonic(entropy)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, "", err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
info, err := ks.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, hdPath, algo)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, "", err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return info, mnemonic, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) NewAccount(uid string, mnemonic string, bip39Passphrase string, hdPath string, algo SignatureAlgo) (Info, error) {
|
|
|
|
if !ks.isSupportedSigningAlgo(algo) {
|
|
|
|
return nil, ErrUnsupportedSigningAlgo
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// create master key and derive first key for keyring
|
|
|
|
derivedPriv, err := algo.Derive()(mnemonic, bip39Passphrase, hdPath)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
privKey := algo.Generate()(derivedPriv)
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return ks.writeLocalKey(uid, privKey, algo.Name())
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) isSupportedSigningAlgo(algo SignatureAlgo) bool {
|
|
|
|
return ks.options.SupportedAlgos.Contains(algo)
|
2020-01-14 07:40:10 -08:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) Key(uid string) (Info, error) {
|
|
|
|
key := infoKey(uid)
|
2020-01-14 07:40:10 -08:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
bs, err := ks.db.Get(string(key))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
if len(bs.Data) == 0 {
|
|
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, uid)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return unmarshalInfo(bs.Data)
|
|
|
|
}
|
2019-09-21 09:54:14 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
// 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 SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
|
|
|
|
switch info.(type) {
|
|
|
|
case *ledgerInfo, ledgerInfo:
|
|
|
|
default:
|
|
|
|
return nil, nil, errors.New("not a ledger object")
|
|
|
|
}
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2020-05-08 01:46:12 -07:00
|
|
|
path, err := info.GetPath()
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path)
|
2019-09-21 09:54:14 -07:00
|
|
|
if err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return
|
2019-09-21 09:54:14 -07:00
|
|
|
}
|
2019-09-30 09:16:53 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
sig, err = priv.Sign(msg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2019-09-30 09:16:53 -07:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
return sig, priv.PubKey(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newOSBackendKeyringConfig(appName, dir string, buf io.Reader) keyring.Config {
|
2019-12-11 01:45:26 -08:00
|
|
|
return keyring.Config{
|
2020-01-23 10:47:11 -08:00
|
|
|
ServiceName: appName,
|
2019-12-11 01:45:26 -08:00
|
|
|
FileDir: dir,
|
|
|
|
FilePasswordFunc: newRealPrompt(dir, buf),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func newTestBackendKeyringConfig(appName, dir string) keyring.Config {
|
|
|
|
return keyring.Config{
|
|
|
|
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
|
|
|
|
ServiceName: appName,
|
|
|
|
FileDir: filepath.Join(dir, keyringTestDirName),
|
|
|
|
FilePasswordFunc: func(_ string) (string, error) {
|
|
|
|
return "test", nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-23 10:47:11 -08:00
|
|
|
func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config {
|
2020-01-23 08:48:00 -08:00
|
|
|
return keyring.Config{
|
|
|
|
AllowedBackends: []keyring.BackendType{keyring.KWalletBackend},
|
|
|
|
ServiceName: "kdewallet",
|
2020-01-23 10:47:11 -08:00
|
|
|
KWalletAppID: appName,
|
2020-01-23 08:48:00 -08:00
|
|
|
KWalletFolder: "",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func newPassBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config {
|
2020-03-23 08:40:47 -07:00
|
|
|
prefix := fmt.Sprintf(passKeyringPrefix, appName)
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2020-01-23 08:48:00 -08:00
|
|
|
return keyring.Config{
|
|
|
|
AllowedBackends: []keyring.BackendType{keyring.PassBackend},
|
2020-01-23 10:47:11 -08:00
|
|
|
ServiceName: appName,
|
2020-01-23 08:48:00 -08:00
|
|
|
PassPrefix: prefix,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-11 01:45:26 -08:00
|
|
|
func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config {
|
2020-04-08 02:38:28 -07:00
|
|
|
fileDir := filepath.Join(dir, keyringFileDirName)
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2019-12-11 01:45:26 -08:00
|
|
|
return keyring.Config{
|
2020-01-23 08:48:00 -08:00
|
|
|
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
|
2019-12-11 01:45:26 -08:00
|
|
|
ServiceName: name,
|
|
|
|
FileDir: fileDir,
|
|
|
|
FilePasswordFunc: newRealPrompt(fileDir, buf),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
|
|
|
|
return func(prompt string) (string, error) {
|
2019-09-30 09:16:53 -07:00
|
|
|
keyhashStored := false
|
|
|
|
keyhashFilePath := filepath.Join(dir, "keyhash")
|
|
|
|
|
|
|
|
var keyhash []byte
|
|
|
|
|
|
|
|
_, err := os.Stat(keyhashFilePath)
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
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
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
for {
|
|
|
|
failureCounter++
|
|
|
|
if failureCounter > maxPassphraseEntryAttempts {
|
|
|
|
return "", fmt.Errorf("too many failed passphrase attempts")
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := bufio.NewReader(buf)
|
|
|
|
pass, err := input.GetPassword("Enter keyring passphrase:", buf)
|
2020-05-08 01:46:12 -07:00
|
|
|
if err != nil {
|
2020-03-03 05:32:52 -08:00
|
|
|
fmt.Fprintln(os.Stderr, err)
|
2019-09-30 09:16:53 -07:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if keyhashStored {
|
|
|
|
if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, "incorrect passphrase")
|
|
|
|
continue
|
|
|
|
}
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2019-09-30 09:16:53 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-18 05:59:08 -07:00
|
|
|
saltBytes := tmcrypto.CRandBytes(16)
|
2019-09-30 09:16:53 -07:00
|
|
|
passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
|
2020-05-08 01:46:12 -07:00
|
|
|
if err != nil {
|
2019-09-30 09:16:53 -07:00
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile(dir+"/keyhash", passwordHash, 0555); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pass, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 11:30:50 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
func (ks keystore) writeLocalKey(name string, priv tmcrypto.PrivKey, algo hd.PubKeyType) (Info, error) {
|
|
|
|
// encrypt private key using keyring
|
|
|
|
pub := priv.PubKey()
|
|
|
|
|
|
|
|
info := newLocalInfo(name, pub, string(priv.Bytes()), algo)
|
2020-05-02 12:26:59 -07:00
|
|
|
if err := ks.writeInfo(info); err != nil {
|
2020-04-08 02:38:28 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) writeInfo(info Info) error {
|
|
|
|
// write the info by key
|
|
|
|
key := infoKey(info.GetName())
|
|
|
|
serializedInfo := marshalInfo(info)
|
|
|
|
|
|
|
|
exists, err := ks.existsInDb(info)
|
|
|
|
if exists {
|
|
|
|
return fmt.Errorf("public key already exist in keybase")
|
|
|
|
}
|
2020-05-02 12:26:59 -07:00
|
|
|
|
2020-04-08 02:38:28 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ks.db.Set(keyring.Item{
|
|
|
|
Key: string(key),
|
|
|
|
Data: serializedInfo,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ks.db.Set(keyring.Item{
|
|
|
|
Key: addrHexKeyAsString(info.GetAddress()),
|
|
|
|
Data: key,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) existsInDb(info Info) (bool, error) {
|
|
|
|
if _, err := ks.db.Get(addrHexKeyAsString(info.GetAddress())); err == nil {
|
|
|
|
return true, nil // address lookup succeeds - info exists
|
|
|
|
} else if err != keyring.ErrKeyNotFound {
|
|
|
|
return false, err // received unexpected error - returns error
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := ks.db.Get(string(infoKey(info.GetName()))); err == nil {
|
|
|
|
return true, nil // uid lookup succeeds - info exists
|
|
|
|
} else if err != keyring.ErrKeyNotFound {
|
|
|
|
return false, err // received unexpected error - returns
|
|
|
|
}
|
|
|
|
|
|
|
|
// both lookups failed, info does not exist
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) writeOfflineKey(name string, pub tmcrypto.PubKey, algo hd.PubKeyType) (Info, error) {
|
|
|
|
info := newOfflineInfo(name, pub, algo)
|
|
|
|
err := ks.writeInfo(info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ks keystore) writeMultisigKey(name string, pub tmcrypto.PubKey) (Info, error) {
|
|
|
|
info := NewMultiInfo(name, pub)
|
|
|
|
err := ks.writeInfo(info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addrHexKeyAsString(address sdk.Address) string {
|
|
|
|
return fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix)
|
2020-03-30 11:30:50 -07:00
|
|
|
}
|