diff --git a/crypto/keyring/alt_signing_algorithms.go b/crypto/keyring/alt_signing_algorithms.go new file mode 100644 index 000000000000..91a2f2e9d959 --- /dev/null +++ b/crypto/keyring/alt_signing_algorithms.go @@ -0,0 +1,44 @@ +package keyring + +import "github.com/tendermint/tendermint/crypto" + +type AltSigningAlgo interface { + Name() SigningAlgo + DeriveKey() AltDeriveKeyFunc + PrivKeyGen() AltPrivKeyGenFunc +} + +type AltDeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) +type AltPrivKeyGenFunc func(bz []byte) crypto.PrivKey + +type secp256k1Algo struct { +} + +func (s secp256k1Algo) Name() SigningAlgo { + return Secp256k1 +} + +func (s secp256k1Algo) DeriveKey() AltDeriveKeyFunc { + return SecpDeriveKey +} + +func (s secp256k1Algo) PrivKeyGen() AltPrivKeyGenFunc { + return SecpPrivKeyGen +} + +var ( + // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. + AltSecp256k1 = secp256k1Algo{} +) + +type AltSigningAlgoList []AltSigningAlgo + +func (l AltSigningAlgoList) Contains(algo AltSigningAlgo) bool { + for _, cAlgo := range l { + if cAlgo.Name() == algo.Name() { + return true + } + } + + return false +} diff --git a/crypto/keyring/alt_signing_algorithms_test.go b/crypto/keyring/alt_signing_algorithms_test.go new file mode 100644 index 000000000000..2db5545e2bcb --- /dev/null +++ b/crypto/keyring/alt_signing_algorithms_test.go @@ -0,0 +1,31 @@ +package keyring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAltSigningAlgoList_Contains(t *testing.T) { + list := AltSigningAlgoList{ + AltSecp256k1, + } + + assert.True(t, list.Contains(AltSecp256k1)) + assert.False(t, list.Contains(notSupportedAlgo{})) +} + +type notSupportedAlgo struct { +} + +func (n notSupportedAlgo) Name() SigningAlgo { + return "notSupported" +} + +func (n notSupportedAlgo) DeriveKey() AltDeriveKeyFunc { + return SecpDeriveKey +} + +func (n notSupportedAlgo) PrivKeyGen() AltPrivKeyGenFunc { + return SecpPrivKeyGen +} diff --git a/crypto/keyring/altkeyring.go b/crypto/keyring/altkeyring.go new file mode 100644 index 000000000000..7730628bbe78 --- /dev/null +++ b/crypto/keyring/altkeyring.go @@ -0,0 +1,549 @@ +package keyring + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/99designs/keyring" + "github.com/pkg/errors" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" + + "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/go-bip39" + tmcrypto "github.com/tendermint/tendermint/crypto" +) + +var ( + _ Keyring = &altKeyring{} +) + +// Keyring exposes operations on a generic keystore +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 types.Address) (Info, error) + + //// Delete and DeleteByAddress remove keys. + Delete(uid string) error + DeleteByAddress(address types.Address) error + + // NewMnemonic generates a new mnemonic, derives a hierarchical deterministic + // key from that, and persists it to 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, algo AltSigningAlgo) (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 AltSigningAlgo) (Info, error) + + // SaveLedgerKey retrieves a public key reference from a Ledger device and persists it. + SaveLedgerKey(uid string, algo AltSigningAlgo, hrp string, account, index uint32) (Info, error) + + // SavePubKey stores a public key and returns the persisted Info structure. + SavePubKey(uid string, pubkey tmcrypto.PubKey, algo AltSigningAlgo) (Info, error) + + // SaveMultisig stores, stores, and returns a new multsig (offline) key reference + SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) + + Signer + + Importer + Exporter +} + +// 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) + + // SignByAddress sign byte messages with a user key providing the address. + SignByAddress(address types.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(uid, armor, passphrase string) error + 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 types.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 types.Address, encryptPassphrase string) (armor string, err error) +} + +type AltKeyringOption func(options *altKrOptions) + +// NewKeyring creates a new instance of a keyring. Keybase +// options can be applied when generating this new Keybase. +// Available backends are "os", "file", "kwallet", "pass", "test". +func NewAltKeyring( + appName, backend, rootDir string, userInput io.Reader, opts ...AltKeyringOption, +) (Keyring, error) { + + var db keyring.Keyring + var err error + + switch backend { + case BackendTest: + db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, nil, true)) + case BackendFile: + db, err = keyring.Open(newFileBackendKeyringConfig(appName, rootDir, userInput)) + case BackendOS: + db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, userInput, false)) + case BackendKWallet: + db, err = keyring.Open(newKWalletBackendKeyringConfig(appName, rootDir, userInput)) + case BackendPass: + db, err = keyring.Open(newPassBackendKeyringConfig(appName, rootDir, userInput)) + default: + return nil, fmt.Errorf("unknown keyring backend %v", backend) + } + + if err != nil { + return nil, err + } + + // Default options for keybase + options := altKrOptions{ + supportedAlgos: AltSigningAlgoList{AltSecp256k1}, + supportedAlgosLedger: AltSigningAlgoList{AltSecp256k1}, + } + + for _, optionFn := range opts { + optionFn(&options) + } + + return altKeyring{ + db: db, + options: options, + }, nil +} + +type altKeyring struct { + db keyring.Keyring + options altKrOptions +} + +func (a altKeyring) ExportPubKeyArmor(uid string) (string, error) { + bz, err := a.Key(uid) + if err != nil { + return "", err + } + + if bz == nil { + return "", fmt.Errorf("no key to export with name: %s", uid) + } + + return crypto.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil +} + +func (a altKeyring) ExportPubKeyArmorByAddress(address types.Address) (string, error) { + info, err := a.KeyByAddress(address) + if err != nil { + return "", err + } + + return a.ExportPubKeyArmor(info.GetName()) +} + +func (a altKeyring) ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) { + priv, err := a.ExportPrivateKeyObject(uid) + if err != nil { + return "", err + } + + info, err := a.Key(uid) + if err != nil { + return "", err + } + + return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil +} + +// ExportPrivateKeyObject exports an armored private key object. +func (a altKeyring) ExportPrivateKeyObject(uid string) (tmcrypto.PrivKey, error) { + info, err := a.Key(uid) + 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 +} + +func (a altKeyring) ExportPrivKeyArmorByAddress(address types.Address, encryptPassphrase string) (armor string, err error) { + byAddress, err := a.KeyByAddress(address) + if err != nil { + return "", err + } + + return a.ExportPrivKeyArmor(byAddress.GetName(), encryptPassphrase) +} + +func (a altKeyring) ImportPrivKey(uid, armor, passphrase string) error { + if a.hasKey(uid) { + return fmt.Errorf("cannot overwrite key: %s", uid) + } + + privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase) + if err != nil { + return errors.Wrap(err, "failed to decrypt private key") + } + + _, err = a.writeLocalKey(uid, privKey, SigningAlgo(algo)) + if err != nil { + return err + } + + return nil +} + +// HasKey returns whether the key exists in the keyring. +func (a altKeyring) hasKey(name string) bool { + bz, _ := a.Key(name) + return bz != nil +} + +func (a altKeyring) ImportPubKey(uid string, armor string) error { + bz, _ := a.Key(uid) + if bz != nil { + pubkey := bz.GetPubKey() + + if len(pubkey.Bytes()) > 0 { + return fmt.Errorf("cannot overwrite data for name: %s", uid) + } + } + + pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor) + if err != nil { + return err + } + + pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) + if err != nil { + return err + } + + _, err = a.writeOfflineKey(uid, pubKey, SigningAlgo(algo)) + if err != nil { + return err + } + + return nil +} + +func (a altKeyring) Sign(uid string, msg []byte) ([]byte, tmcrypto.PubKey, error) { + info, err := a.Key(uid) + if err != nil { + return nil, nil, err + } + + 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 SignWithLedger(info, msg) + + case offlineInfo, multiInfo: + return nil, info.GetPubKey(), errors.New("cannot sign with offline keys") + } + + sig, err := priv.Sign(msg) + if err != nil { + return nil, nil, err + } + + return sig, priv.PubKey(), nil +} + +func (a altKeyring) SignByAddress(address types.Address, msg []byte) ([]byte, tmcrypto.PubKey, error) { + key, err := a.KeyByAddress(address) + if err != nil { + return nil, nil, err + } + + return a.Sign(key.GetName(), msg) +} + +func (a altKeyring) SaveLedgerKey(uid string, algo AltSigningAlgo, hrp string, account, index uint32) (Info, error) { + if !a.options.supportedAlgosLedger.Contains(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 a.writeLedgerKey(uid, priv.PubKey(), *hdPath, algo.Name()) +} + +func (a altKeyring) writeLedgerKey(name string, pub tmcrypto.PubKey, path hd.BIP44Params, algo SigningAlgo) (Info, error) { + info := newLedgerInfo(name, pub, path, algo) + err := a.writeInfo(name, info) + if err != nil { + return nil, err + } + + return info, nil +} + +type altKrOptions struct { + supportedAlgos AltSigningAlgoList + supportedAlgosLedger AltSigningAlgoList +} + +func (a altKeyring) SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) { + return a.writeMultisigKey(uid, pubkey) +} + +func (a altKeyring) SavePubKey(uid string, pubkey tmcrypto.PubKey, algo AltSigningAlgo) (Info, error) { + return a.writeOfflineKey(uid, pubkey, algo.Name()) +} + +func (a altKeyring) DeleteByAddress(address types.Address) error { + info, err := a.KeyByAddress(address) + if err != nil { + return err + } + + err = a.Delete(info.GetName()) + if err != nil { + return err + } + + return nil +} + +func (a altKeyring) Delete(uid string) error { + info, err := a.Key(uid) + if err != nil { + return err + } + + err = a.db.Remove(addrHexKeyAsString(info.GetAddress())) + if err != nil { + return err + } + + err = a.db.Remove(string(infoKey(uid))) + if err != nil { + return err + } + + return nil +} + +func (a altKeyring) KeyByAddress(address types.Address) (Info, error) { + ik, err := a.db.Get(addrHexKeyAsString(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 := a.db.Get(string(ik.Data)) + if err != nil { + return nil, err + } + + return unmarshalInfo(bs.Data) +} + +func (a altKeyring) List() ([]Info, error) { + var res []Info + keys, err := a.db.Keys() + if err != nil { + return nil, err + } + + sort.Strings(keys) + + for _, key := range keys { + if strings.HasSuffix(key, infoSuffix) { + rawInfo, err := a.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) + } + } + + return res, nil +} + +func (a altKeyring) NewMnemonic(uid string, language Language, algo AltSigningAlgo) (Info, string, error) { + if language != English { + return nil, "", ErrUnsupportedLanguage + } + + if !a.isSupportedSigningAlgo(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 := a.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, types.GetConfig().GetFullFundraiserPath(), algo) + if err != nil { + return nil, "", err + } + + return info, mnemonic, err +} + +func (a altKeyring) NewAccount(uid string, mnemonic string, bip39Passphrase string, hdPath string, algo AltSigningAlgo) (Info, error) { + if !a.isSupportedSigningAlgo(algo) { + return nil, ErrUnsupportedSigningAlgo + } + + // create master key and derive first key for keyring + derivedPriv, err := algo.DeriveKey()(mnemonic, bip39Passphrase, hdPath) + if err != nil { + return nil, err + } + + privKey := algo.PrivKeyGen()(derivedPriv) + + return a.writeLocalKey(uid, privKey, algo.Name()) +} + +func (a altKeyring) isSupportedSigningAlgo(algo AltSigningAlgo) bool { + return a.options.supportedAlgos.Contains(algo) +} + +func (a altKeyring) Key(uid string) (Info, error) { + key := infoKey(uid) + + bs, err := a.db.Get(string(key)) + if err != nil { + return nil, err + } + + if len(bs.Data) == 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, uid) + } + + return unmarshalInfo(bs.Data) +} + +func (a altKeyring) writeLocalKey(name string, priv tmcrypto.PrivKey, algo SigningAlgo) (Info, error) { + // encrypt private key using keyring + pub := priv.PubKey() + + info := newLocalInfo(name, pub, string(priv.Bytes()), algo) + err := a.writeInfo(name, info) + if err != nil { + return nil, err + } + + return info, nil +} + +func (a altKeyring) writeInfo(name string, info Info) error { + // write the info by key + key := infoKey(name) + serializedInfo := marshalInfo(info) + + err := a.db.Set(keyring.Item{ + Key: string(key), + Data: serializedInfo, + }) + if err != nil { + return err + } + + err = a.db.Set(keyring.Item{ + Key: addrHexKeyAsString(info.GetAddress()), + Data: key, + }) + if err != nil { + return err + } + + return nil +} + +func (a altKeyring) writeOfflineKey(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) { + info := newOfflineInfo(name, pub, algo) + err := a.writeInfo(name, info) + if err != nil { + return nil, err + } + + return info, nil +} + +func (a altKeyring) writeMultisigKey(name string, pub tmcrypto.PubKey) (Info, error) { + info := NewMultiInfo(name, pub) + err := a.writeInfo(name, info) + if err != nil { + return nil, err + } + + return info, nil +} diff --git a/crypto/keyring/altkeyring_test.go b/crypto/keyring/altkeyring_test.go new file mode 100644 index 000000000000..c113f2f8da47 --- /dev/null +++ b/crypto/keyring/altkeyring_test.go @@ -0,0 +1,414 @@ +package keyring + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/multisig" + + "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/go-bip39" +) + +const ( + someKey = "theKey" + theID = "theID" + otherID = "otherID" +) + +func TestAltKeyring_List(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Empty(t, list) + + // Fails on creating unsupported SigningAlgo + _, _, err = keyring.NewMnemonic("failing", English, notSupportedAlgo{}) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + // Create 3 keys + uid1, uid2, uid3 := "Zkey", "Bkey", "Rkey" + _, _, err = keyring.NewMnemonic(uid1, English, AltSecp256k1) + require.NoError(t, err) + _, _, err = keyring.NewMnemonic(uid2, English, AltSecp256k1) + require.NoError(t, err) + _, _, err = keyring.NewMnemonic(uid3, English, AltSecp256k1) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Len(t, list, 3) + + // Check they are in alphabetical order + require.Equal(t, uid2, list[0].GetName()) + require.Equal(t, uid3, list[1].GetName()) + require.Equal(t, uid1, list[2].GetName()) +} + +func TestAltKeyring_NewAccount(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + entropy, err := bip39.NewEntropy(defaultEntropySize) + require.NoError(t, err) + + mnemonic, err := bip39.NewMnemonic(entropy) + require.NoError(t, err) + + uid := "newUid" + + // Fails on creating unsupported SigningAlgo + _, err = keyring.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, types.GetConfig().GetFullFundraiserPath(), notSupportedAlgo{}) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + info, err := keyring.NewAccount(uid, mnemonic, DefaultBIP39Passphrase, types.GetConfig().GetFullFundraiserPath(), AltSecp256k1) + require.NoError(t, err) + + require.Equal(t, uid, info.GetName()) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) +} + +func TestAltKeyring_SaveLedgerKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + // Test unsupported Algo + _, err = keyring.SaveLedgerKey("key", notSupportedAlgo{}, "cosmos", 0, 0) + require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + + info, err := keyring.SaveLedgerKey("key", AltSecp256k1, "cosmos", 0, 0) + if err != nil { + require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) + t.Skip("ledger nano S: support for ledger devices is not available in this executable") + return + } + require.Equal(t, "key", info.GetName()) +} + +func TestAltKeyring_Get(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + key, err := keyring.Key(uid) + require.NoError(t, err) + requireEqualInfo(t, mnemonic, key) +} + +func TestAltKeyring_KeyByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + key, err := keyring.KeyByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + requireEqualInfo(t, key, mnemonic) +} + +func TestAltKeyring_Delete(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + _, _, err = keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) + + err = keyring.Delete(uid) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Empty(t, list) +} + +func TestAltKeyring_DeleteByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := someKey + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 1) + + err = keyring.DeleteByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + + list, err = keyring.List() + require.NoError(t, err) + require.Empty(t, list) +} + +func TestAltKeyring_SavePubKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + list, err := keyring.List() + require.NoError(t, err) + require.Empty(t, list) + + key := someKey + priv := ed25519.GenPrivKey() + pub := priv.PubKey() + + info, err := keyring.SavePubKey(key, pub, AltSecp256k1) + require.Nil(t, err) + require.Equal(t, pub, info.GetPubKey()) + require.Equal(t, key, info.GetName()) + require.Equal(t, AltSecp256k1.Name(), info.GetAlgo()) + + list, err = keyring.List() + require.NoError(t, err) + require.Equal(t, 1, len(list)) +} + +func TestAltKeyring_SaveMultisig(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + mnemonic1, _, err := keyring.NewMnemonic("key1", English, AltSecp256k1) + require.NoError(t, err) + mnemonic2, _, err := keyring.NewMnemonic("key2", English, AltSecp256k1) + require.NoError(t, err) + + key := "multi" + pub := multisig.NewPubKeyMultisigThreshold(2, []crypto.PubKey{mnemonic1.GetPubKey(), mnemonic2.GetPubKey()}) + + info, err := keyring.SaveMultisig(key, pub) + require.Nil(t, err) + require.Equal(t, pub, info.GetPubKey()) + require.Equal(t, key, info.GetName()) + + list, err := keyring.List() + require.NoError(t, err) + require.Len(t, list, 3) +} + +func TestAltKeyring_Sign(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := "jack" + _, _, err = keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + msg := []byte("some message") + + sign, key, err := keyring.Sign(uid, msg) + require.NoError(t, err) + + require.True(t, key.VerifyBytes(msg, sign)) +} + +func TestAltKeyring_SignByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := "jack" + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + msg := []byte("some message") + + sign, key, err := keyring.SignByAddress(mnemonic.GetAddress(), msg) + require.NoError(t, err) + + require.True(t, key.VerifyBytes(msg, sign)) +} + +func TestAltKeyring_ImportExportPrivKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + _, _, err = keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + passphrase := "somePass" + armor, err := keyring.ExportPrivKeyArmor(uid, passphrase) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPrivKey(uid, armor, passphrase) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", uid)) + + newUID := otherID + // Should fail importing with wrong password + err = keyring.ImportPrivKey(newUID, armor, "wrongPass") + require.EqualError(t, err, "failed to decrypt private key: ciphertext decryption failed") + + err = keyring.ImportPrivKey(newUID, armor, passphrase) + assert.NoError(t, err) +} + +func TestAltKeyring_ImportExportPrivKey_ByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + passphrase := "somePass" + armor, err := keyring.ExportPrivKeyArmorByAddress(mnemonic.GetAddress(), passphrase) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPrivKey(uid, armor, passphrase) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", uid)) + + newUID := otherID + // Should fail importing with wrong password + err = keyring.ImportPrivKey(newUID, armor, "wrongPass") + require.EqualError(t, err, "failed to decrypt private key: ciphertext decryption failed") + + err = keyring.ImportPrivKey(newUID, armor, passphrase) + assert.NoError(t, err) +} + +func TestAltKeyring_ImportExportPubKey(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + _, _, err = keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + armor, err := keyring.ExportPubKeyArmor(uid) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPubKey(uid, armor) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite data for name: %s", uid)) + + newUID := otherID + err = keyring.ImportPubKey(newUID, armor) + assert.NoError(t, err) +} + +func TestAltKeyring_ImportExportPubKey_ByAddress(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + uid := theID + mnemonic, _, err := keyring.NewMnemonic(uid, English, AltSecp256k1) + require.NoError(t, err) + + armor, err := keyring.ExportPubKeyArmorByAddress(mnemonic.GetAddress()) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = keyring.ImportPubKey(uid, armor) + require.EqualError(t, err, fmt.Sprintf("cannot overwrite data for name: %s", uid)) + + newUID := otherID + err = keyring.ImportPubKey(newUID, armor) + assert.NoError(t, err) +} + +func TestAltKeyring_ConstructorSupportedAlgos(t *testing.T) { + dir, clean := tests.NewTestCaseDir(t) + t.Cleanup(clean) + + keyring, err := NewAltKeyring(t.Name(), BackendTest, dir, nil) + require.NoError(t, err) + + // should fail when using unsupported signing algorythm. + _, _, err = keyring.NewMnemonic("test", English, notSupportedAlgo{}) + require.EqualError(t, err, "unsupported signing algo") + + // but works with default signing algo. + _, _, err = keyring.NewMnemonic("test", English, AltSecp256k1) + require.NoError(t, err) + + // but we can create a new keybase with our provided algos. + dir2, clean2 := tests.NewTestCaseDir(t) + t.Cleanup(clean2) + + keyring2, err := NewAltKeyring(t.Name(), BackendTest, dir2, nil, func(options *altKrOptions) { + options.supportedAlgos = AltSigningAlgoList{ + notSupportedAlgo{}, + } + }) + require.NoError(t, err) + + // now this new keyring does not fail when signing with provided algo + _, _, err = keyring2.NewMnemonic("test", English, notSupportedAlgo{}) + require.NoError(t, err) +} + +func requireEqualInfo(t *testing.T, key Info, mnemonic Info) { + require.Equal(t, key.GetName(), mnemonic.GetName()) + require.Equal(t, key.GetAddress(), mnemonic.GetAddress()) + require.Equal(t, key.GetPubKey(), mnemonic.GetPubKey()) + require.Equal(t, key.GetAlgo(), mnemonic.GetAlgo()) + require.Equal(t, key.GetType(), mnemonic.GetType()) +} diff --git a/crypto/keyring/base_keybase.go b/crypto/keyring/base_keybase.go index abec03359a86..be63deda817f 100644 --- a/crypto/keyring/base_keybase.go +++ b/crypto/keyring/base_keybase.go @@ -72,7 +72,6 @@ func SecpPrivKeyGen(bz []byte) tmcrypto.PrivKey { func (kb baseKeybase) CreateAccount( keyWriter keyWriter, name, mnemonic, bip39Passphrase, encryptPasswd, hdPath string, algo SigningAlgo, ) (Info, error) { - // create master key and derive first key for keyring derivedPriv, err := kb.options.deriveFunc(mnemonic, bip39Passphrase, hdPath, algo) if err != nil { diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index d1ebfa0b6f3d..d9aca87d4b47 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -15,7 +15,6 @@ import ( "github.com/pkg/errors" "github.com/tendermint/crypto/bcrypt" - "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/crypto" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" @@ -42,69 +41,6 @@ const ( var _ Keybase = keyringKeybase{} -// Keyring exposes operations on a generic keystore -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 types.Address) (Info, error) - - //// Delete and DeleteByAddress remove keys. - Delete(uid string) error - DeleteByAddress(address types.Address) error - - // NewMnemonic generates a new mnemonic, derives a hierarchical deterministic - // key from that, and persists it to 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, algo SigningAlgo) (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 SigningAlgo) (Info, error) - - // SaveLedgerKey retrieves a public key reference from a Ledger device and persists it. - SaveLedgerKey(uid string, algo SigningAlgo, hrp string, account, index uint32) (Info, error) - - // SavePubKey stores a public key and returns the persisted Info structure. - SavePubKey(uid string, pubkey tmcrypto.PubKey, algo SigningAlgo) (Info, error) - - // SaveMultisig stores, stores, and returns a new multsig (offline) key reference - SaveMultisig(uid string, pubkey tmcrypto.PubKey) (Info, error) - - Signer - - Importer - Exporter -} - -// 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) - - // SignByAddress sign byte messages with a user key providing the address. - SignByAddress(address types.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(uid, armor, passphrase string) error - 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 types.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 types.Address, encryptPassphrase string) (armor string, err error) -} - // keyringKeybase implements the Keybase interface by using the Keyring library // for account key persistence. type keyringKeybase struct { @@ -650,6 +586,10 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) { } } -func addrHexKey(address types.AccAddress) []byte { - return []byte(fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix)) +func addrHexKey(address types.Address) []byte { + return []byte(addrHexKeyAsString(address)) +} + +func addrHexKeyAsString(address types.Address) string { + return fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix) }