refactor: keyring errors (#14974)

This commit is contained in:
Julián Toledano 2023-02-09 19:31:54 +01:00 committed by GitHub
parent dc20731bdd
commit a0aef94030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 87 additions and 59 deletions

View File

@ -1,13 +1,38 @@
package keyring
import "github.com/pkg/errors"
import "github.com/cockroachdb/errors"
var (
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
// different signing scheme than secp256k1.
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo")
// ErrUnsupportedLanguage is raised when the caller tries to use a
// different language than english for creating a mnemonic sentence.
ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported")
// ErrUnknownBacked is raised when the keyring backend is unknown
ErrUnknownBacked = errors.New("unknown keyring backend")
// ErrOverwriteKey is raised when a key cannot be overwritten
ErrOverwriteKey = errors.New("cannot overwrite key")
// ErrKeyAlreadyExists is raised when creating a key that already exists
ErrKeyAlreadyExists = errors.Newf("key already exists")
// ErrInvalidSignMode is raised when trying to sign with an invaled method
ErrInvalidSignMode = errors.New("invalid sign mode, expected LEGACY_AMINO_JSON or TEXTUAL")
// ErrMaxPassPhraseAttempts is raised when the maxPassphraseEntryAttempts is reached
ErrMaxPassPhraseAttempts = errors.New("too many failed passphrase attempts")
// ErrUnableToSerialize is raised when codec fails to serialize
ErrUnableToSerialize = errors.New("unable to serialize record")
// ErrOfflineSign is raised when trying to sign offline record.
ErrOfflineSign = errors.New("cannot sign with offline keys")
// ErrDuplicatedAddress is raised when creating a key with the same address as a key that already exists.
ErrDuplicatedAddress = errors.New("duplicated address created")
// ErrLedgerGenerateKey is raised when a ledger can't generate a key
ErrLedgerGenerateKey = errors.New("failed to generate ledger key")
// ErrNotLedgerObj is raised when record.GetLedger() returns nil.
ErrNotLedgerObj = errors.New("not a ledger object")
// ErrLedgerInvalidSignature is raised when ledger generates an invalid signature.
ErrLedgerInvalidSignature = errors.New("Ledger generated an invalid signature. Perhaps you have multiple ledgers and need to try another one")
// ErrLegacyToRecord is raised when cannot be converted to a Record
ErrLegacyToRecord = errors.New("unable to convert LegacyInfo to Record")
// ErrUnknownLegacyType is raised when a LegacyInfo type is unknown.
ErrUnknownLegacyType = errors.New("unknown LegacyInfo type")
)

View File

@ -11,8 +11,8 @@ import (
"strings"
"github.com/99designs/keyring"
"github.com/cockroachdb/errors"
cmtcrypto "github.com/cometbft/cometbft/crypto"
"github.com/pkg/errors"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/codec"
@ -70,7 +70,7 @@ type Keyring interface {
DeleteByAddress(address sdk.Address) error
// Rename an existing key from the Keyring
Rename(from string, to string) error
Rename(from, to string) error
// NewMnemonic generates a new mnemonic, derives a hierarchical deterministic key from it, and
// persists the key to storage. Returns the generated mnemonic and the key Info.
@ -116,7 +116,7 @@ type Importer interface {
ImportPrivKey(uid, armor, passphrase string) error
// ImportPubKey imports ASCII armored public keys.
ImportPubKey(uid string, armor string) error
ImportPubKey(uid, armor string) error
}
// Migrator is implemented by key stores and enables migration of keys from amino to proto
@ -194,7 +194,7 @@ func New(
case BackendPass:
db, err = keyring.Open(newPassBackendKeyringConfig(appName, rootDir, userInput))
default:
return nil, fmt.Errorf("unknown keyring backend %v", backend)
return nil, errors.Wrap(ErrUnknownBacked, backend)
}
if err != nil {
@ -317,7 +317,7 @@ func (ks keystore) ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassp
func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
if k, err := ks.Key(uid); err == nil {
if uid == k.Name {
return fmt.Errorf("cannot overwrite key: %s", uid)
return errors.Wrap(ErrOverwriteKey, uid)
}
}
@ -334,9 +334,9 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
return nil
}
func (ks keystore) ImportPubKey(uid string, armor string) error {
func (ks keystore) ImportPubKey(uid, armor string) error {
if _, err := ks.Key(uid); err == nil {
return fmt.Errorf("cannot overwrite key: %s", uid)
return errors.Wrap(ErrOverwriteKey, uid)
}
pubBytes, _, err := crypto.UnarmorPubKeyBytes(armor)
@ -386,8 +386,7 @@ func (ks keystore) Sign(uid string, msg []byte, signMode signing.SignMode) ([]by
if err != nil {
return nil, nil, err
}
return nil, pub, errors.New("cannot sign with offline keys")
return nil, pub, ErrOfflineSign
}
}
@ -402,17 +401,14 @@ func (ks keystore) SignByAddress(address sdk.Address, msg []byte, signMode signi
func (ks keystore) SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (*Record, error) {
if !ks.options.SupportedAlgosLedger.Contains(algo) {
return nil, fmt.Errorf(
"%w: signature algo %s is not defined in the keyring options",
ErrUnsupportedSigningAlgo, algo.Name(),
)
return nil, errors.Wrap(ErrUnsupportedSigningAlgo, fmt.Sprintf("signature algo %s is not defined in the keyring options", algo.Name()))
}
hdPath := hd.NewFundraiserParams(account, coinType, index)
priv, _, err := ledger.NewPrivKeySecp256k1(*hdPath, hrp)
if err != nil {
return nil, fmt.Errorf("failed to generate ledger key: %w", err)
return nil, errors.CombineErrors(ErrLedgerGenerateKey, err)
}
return ks.writeLedgerKey(uid, priv.PubKey(), hdPath)
@ -452,7 +448,7 @@ func (ks keystore) DeleteByAddress(address sdk.Address) error {
func (ks keystore) Rename(oldName, newName string) error {
_, err := ks.Key(newName)
if err == nil {
return fmt.Errorf("rename failed: %s already exists in the keyring", newName)
return errors.Wrap(ErrKeyAlreadyExists, fmt.Sprintf("rename failed, %s", newName))
}
armor, err := ks.ExportPrivKeyArmor(oldName, passPhrase)
@ -512,7 +508,7 @@ func (ks keystore) KeyByAddress(address sdk.Address) (*Record, error) {
func wrapKeyNotFound(err error, msg string) error {
if err == keyring.ErrKeyNotFound {
return sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, msg)
return errors.Wrap(sdkerrors.ErrKeyNotFound, msg)
}
return err
}
@ -554,7 +550,7 @@ func (ks keystore) NewMnemonic(uid string, language Language, hdPath, bip39Passp
return k, mnemonic, nil
}
func (ks keystore) NewAccount(name string, mnemonic string, bip39Passphrase string, hdPath string, algo SignatureAlgo) (*Record, error) {
func (ks keystore) NewAccount(name, mnemonic, bip39Passphrase, hdPath string, algo SignatureAlgo) (*Record, error) {
if !ks.isSupportedSigningAlgo(algo) {
return nil, ErrUnsupportedSigningAlgo
}
@ -567,11 +563,11 @@ func (ks keystore) NewAccount(name string, mnemonic string, bip39Passphrase stri
privKey := algo.Generate()(derivedPriv)
// check if the a key already exists with the same address and return an error
// check if the key already exists with the same address and return an error
// if found
address := sdk.AccAddress(privKey.PubKey().Address())
if _, err := ks.KeyByAddress(address); err == nil {
return nil, errors.New("duplicated address created")
return nil, ErrDuplicatedAddress
}
return ks.writeLocalKey(name, privKey)
@ -602,7 +598,7 @@ func (ks keystore) SupportedAlgorithms() (SigningAlgoList, SigningAlgoList) {
func SignWithLedger(k *Record, msg []byte, signMode signing.SignMode) (sig []byte, pub types.PubKey, err error) {
ledgerInfo := k.GetLedger()
if ledgerInfo == nil {
return nil, nil, errors.New("not a ledger object")
return nil, nil, ErrNotLedgerObj
}
path := ledgerInfo.GetPath()
@ -624,11 +620,11 @@ func SignWithLedger(k *Record, msg []byte, signMode signing.SignMode) (sig []byt
return nil, nil, err
}
default:
return nil, nil, fmt.Errorf("got invalid sign mode %d, expected LEGACY_AMINO_JSON or TEXTUAL", signMode)
return nil, nil, errors.Wrap(ErrInvalidSignMode, fmt.Sprintf("%v", signMode))
}
if !priv.PubKey().VerifySignature(msg, sig) {
return nil, nil, errors.New("Ledger generated an invalid signature. Perhaps you have multiple ledgers and need to try another one")
return nil, nil, ErrLedgerInvalidSignature
}
return sig, priv.PubKey(), nil
@ -697,7 +693,7 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
case err == nil:
keyhash, err = os.ReadFile(keyhashFilePath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
return "", errors.Wrap(err, fmt.Sprintf("failed to read %s", keyhashFilePath))
}
keyhashStored = true
@ -706,7 +702,7 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
keyhashStored = false
default:
return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
return "", errors.Wrap(err, fmt.Sprintf("failed to open %s", keyhashFilePath))
}
failureCounter := 0
@ -714,7 +710,7 @@ func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
for {
failureCounter++
if failureCounter > maxPassphraseEntryAttempts {
return "", fmt.Errorf("too many failed passphrase attempts")
return "", ErrMaxPassPhraseAttempts
}
buf := bufio.NewReader(buf)
@ -795,12 +791,12 @@ func (ks keystore) writeRecord(k *Record) error {
return err
}
if exists {
return fmt.Errorf("public key %s already exists in keybase", key)
return errors.Wrap(ErrKeyAlreadyExists, key)
}
serializedRecord, err := ks.cdc.Marshal(k)
if err != nil {
return fmt.Errorf("unable to serialize record; %+w", err)
return errors.CombineErrors(ErrUnableToSerialize, err)
}
item := keyring.Item{
@ -927,7 +923,7 @@ func (ks keystore) migrate(key string) (*Record, error) {
}
if len(item.Data) == 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, key)
return nil, errors.Wrap(sdkerrors.ErrKeyNotFound, key)
}
// 2. Try to deserialize using proto
@ -940,18 +936,18 @@ func (ks keystore) migrate(key string) (*Record, error) {
// 4. Try to decode with amino
legacyInfo, err := unMarshalLegacyInfo(item.Data)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal item.Data, err: %w", err)
return nil, errors.Wrap(err, "unable to unmarshal item.Data")
}
// 5. Convert and serialize info using proto
k, err = ks.convertFromLegacyInfo(legacyInfo)
if err != nil {
return nil, fmt.Errorf("convertFromLegacyInfo, err: %w", err)
return nil, errors.Wrap(err, "convertFromLegacyInfo")
}
serializedRecord, err := ks.cdc.Marshal(k)
if err != nil {
return nil, fmt.Errorf("unable to serialize record, err: %w", err)
return nil, errors.CombineErrors(ErrUnableToSerialize, err)
}
item = keyring.Item{
@ -961,7 +957,7 @@ func (ks keystore) migrate(key string) (*Record, error) {
// 6. Overwrite the keyring entry with the new proto-encoded key.
if err := ks.SetItem(item); err != nil {
return nil, fmt.Errorf("unable to set keyring.Item, err: %w", err)
return nil, errors.Wrap(err, "unable to set keyring.Item")
}
fmt.Printf("Successfully migrated key %s.\n", key)
@ -984,7 +980,7 @@ func (ks keystore) SetItem(item keyring.Item) error {
func (ks keystore) convertFromLegacyInfo(info LegacyInfo) (*Record, error) {
if info == nil {
return nil, errors.New("unable to convert LegacyInfo to Record cause info is nil")
return nil, errors.Wrap(ErrLegacyToRecord, "info is nil")
}
name := info.GetName()
@ -1010,7 +1006,7 @@ func (ks keystore) convertFromLegacyInfo(info LegacyInfo) (*Record, error) {
return NewLedgerRecord(name, pk, path)
default:
return nil, errors.New("unknown LegacyInfo type")
return nil, ErrUnknownLegacyType
}
}

View File

@ -5,9 +5,9 @@ package keyring
import (
"bytes"
"strings"
"testing"
"github.com/cockroachdb/errors"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/hd"
@ -101,8 +101,7 @@ func TestAltKeyring_SaveLedgerKey(t *testing.T) {
// Test unsupported Algo
_, err = kr.SaveLedgerKey("key", notSupportedAlgo{}, "cosmos", 118, 0, 0)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), ErrUnsupportedSigningAlgo.Error()))
require.True(t, errors.Is(err, ErrUnsupportedSigningAlgo))
k, err := kr.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1)
if err != nil {

View File

@ -2,6 +2,7 @@ package keyring
import (
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"
@ -52,7 +53,7 @@ func TestNewKeyring(t *testing.T) {
nilKr, err := New("cosmos", "fuzzy", dir, mockIn, cdc)
require.Error(t, err)
require.Nil(t, nilKr)
require.Equal(t, "unknown keyring backend fuzzy", err.Error())
require.True(t, errors.Is(err, ErrUnknownBacked))
mockIn.Reset("password\npassword\n")
k, _, err := kr.NewMnemonic("foo", English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1)
@ -442,7 +443,7 @@ func TestKeyringKeybaseExportImportPrivKey(t *testing.T) {
// overwrite is not allowed
err = kb.ImportPrivKey("john2", keystr, "password")
require.Equal(t, "cannot overwrite key: john2", err.Error())
require.True(t, errors.Is(err, ErrOverwriteKey))
// try export non existing key
_, err = kb.ExportPrivKeyArmor("john3", "wrongpassword")
@ -1254,7 +1255,7 @@ func TestAltKeyring_ImportExportPrivKey(t *testing.T) {
// Should fail importing private key on existing key.
err = kr.ImportPrivKey(newUID, armor, passphrase)
require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID))
require.True(t, errors.Is(err, ErrOverwriteKey))
}
func TestAltKeyring_ImportExportPrivKey_ByAddress(t *testing.T) {
@ -1284,7 +1285,7 @@ func TestAltKeyring_ImportExportPrivKey_ByAddress(t *testing.T) {
// Should fail importing private key on existing key.
err = kr.ImportPrivKey(newUID, armor, passphrase)
require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID))
require.True(t, errors.Is(err, ErrOverwriteKey))
}
func TestAltKeyring_ImportExportPubKey(t *testing.T) {
@ -1307,7 +1308,7 @@ func TestAltKeyring_ImportExportPubKey(t *testing.T) {
// Should fail importing private key on existing key.
err = kr.ImportPubKey(newUID, armor)
require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID))
require.True(t, errors.Is(err, ErrOverwriteKey))
}
func TestAltKeyring_ImportExportPubKey_ByAddress(t *testing.T) {
@ -1332,7 +1333,7 @@ func TestAltKeyring_ImportExportPubKey_ByAddress(t *testing.T) {
// Should fail importing private key on existing key.
err = kr.ImportPubKey(newUID, armor)
require.EqualError(t, err, fmt.Sprintf("cannot overwrite key: %s", newUID))
require.True(t, errors.Is(err, ErrOverwriteKey))
}
func TestAltKeyring_UnsafeExportPrivKeyHex(t *testing.T) {
@ -1426,7 +1427,7 @@ func TestRenameKey(t *testing.T) {
newKeyRecord(t, kr, key1)
newKeyRecord(t, kr, key2)
err := kr.Rename(key2, key1)
require.Equal(t, fmt.Errorf("rename failed: %s already exists in the keyring", key1), err)
require.True(t, errors.Is(err, ErrKeyAlreadyExists))
assertKeysExist(t, kr, key1, key2) // keys should still exist after failed rename
},
},
@ -1436,7 +1437,7 @@ func TestRenameKey(t *testing.T) {
keyName := "keyName"
newKeyRecord(t, kr, keyName)
err := kr.Rename(keyName, keyName)
require.Equal(t, fmt.Errorf("rename failed: %s already exists in the keyring", keyName), err)
require.True(t, errors.Is(err, ErrKeyAlreadyExists))
assertKeysExist(t, kr, keyName)
},
},

View File

@ -1,7 +1,7 @@
package keyring
import (
"errors"
"github.com/cockroachdb/errors"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/hd"
@ -9,8 +9,14 @@ import (
"github.com/cosmos/cosmos-sdk/types"
)
// ErrPrivKeyExtr is used to output an error if extraction of a private key from Local item fails
var ErrPrivKeyExtr = errors.New("private key extraction works only for Local")
var (
// ErrPrivKeyExtr is used to output an error if extraction of a private key from Local item fails.
ErrPrivKeyExtr = errors.New("private key extraction works only for Local")
// ErrPrivKeyNotAvailable is used when a Record_Local.PrivKey is nil.
ErrPrivKeyNotAvailable = errors.New("private key is not available")
// ErrCastAny is used to output an error if cast from types.Any fails.
ErrCastAny = errors.New("unable to cast to cryptotypes")
)
func newRecord(name string, pk cryptotypes.PubKey, item isRecord_Item) (*Record, error) {
any, err := codectypes.NewAnyWithValue(pk)
@ -63,7 +69,7 @@ func NewMultiRecord(name string, pk cryptotypes.PubKey) (*Record, error) {
func (k *Record) GetPubKey() (cryptotypes.PubKey, error) {
pk, ok := k.PubKey.GetCachedValue().(cryptotypes.PubKey)
if !ok {
return nil, errors.New("unable to cast any to cryptotypes.PubKey")
return nil, errors.Wrap(ErrCastAny, "PubKey")
}
return pk, nil
@ -120,12 +126,12 @@ func extractPrivKeyFromRecord(k *Record) (cryptotypes.PrivKey, error) {
func extractPrivKeyFromLocal(rl *Record_Local) (cryptotypes.PrivKey, error) {
if rl.PrivKey == nil {
return nil, errors.New("private key is not available")
return nil, ErrPrivKeyNotAvailable
}
priv, ok := rl.PrivKey.GetCachedValue().(cryptotypes.PrivKey)
if !ok {
return nil, errors.New("unable to cast any to cryptotypes.PrivKey")
return nil, errors.Wrap(ErrCastAny, "PrivKey")
}
return priv, nil

View File

@ -1,9 +1,10 @@
package keyring
import (
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/cosmos/cosmos-sdk/crypto/hd"
)
@ -21,7 +22,7 @@ func NewSigningAlgoFromString(str string, algoList SigningAlgoList) (SignatureAl
return algo, nil
}
}
return nil, fmt.Errorf("provided algorithm %q is not supported", str)
return nil, errors.Wrap(ErrUnsupportedSigningAlgo, str)
}
// SigningAlgoList is a slice of signature algorithms

View File

@ -29,7 +29,7 @@ func TestNewSigningAlgoByString(t *testing.T) {
"notsupportedalgo",
false,
nil,
fmt.Errorf("provided algorithm \"notsupportedalgo\" is not supported"),
ErrUnsupportedSigningAlgo,
},
}
@ -41,7 +41,7 @@ func TestNewSigningAlgoByString(t *testing.T) {
if tt.isSupported {
require.Equal(t, hd.Secp256k1, algorithm)
} else {
require.EqualError(t, err, tt.expectedErr.Error())
require.ErrorIs(t, err, tt.expectedErr)
}
})
}

View File

@ -65,7 +65,7 @@ func (kt KeyType) String() string {
type (
// DeriveKeyFunc defines the function to derive a new key from a seed and hd path
DeriveKeyFunc func(mnemonic string, bip39Passphrase, hdPath string, algo hd.PubKeyType) ([]byte, error)
DeriveKeyFunc func(mnemonic, bip39Passphrase, hdPath string, algo hd.PubKeyType) ([]byte, error)
// PrivKeyGenFunc defines the function to convert derived key bytes to a tendermint private key
PrivKeyGenFunc func(bz []byte, algo hd.PubKeyType) (cryptotypes.PrivKey, error)
)

2
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/cockroachdb/apd/v2 v2.0.2
github.com/cockroachdb/errors v1.9.1
github.com/cometbft/cometbft v0.0.0-20230203130311-387422ac220d
github.com/cosmos/btcutil v1.0.5
github.com/cosmos/cosmos-db v1.0.0-rc.1
@ -71,7 +72,6 @@ require (
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20230203182935-f2e58dc4a0e1 // indirect
github.com/cockroachdb/redact v1.1.3 // indirect