Interchangable PrivKey implementations in keybase (#5278)

Allow for the keybase to be configured to override the implementation
of the key that is saved to the keybase.

Closes: #4941
This commit is contained in:
Austin Abell 2019-12-12 16:52:24 -05:00 committed by Alessio Treglia
parent efcd5fd315
commit 0e28da23e7
32 changed files with 273 additions and 118 deletions

View File

@ -76,6 +76,8 @@ if the provided arguments are invalid.
`StdTx.Signatures` to get back the array of StdSignatures `[]StdSignature`.
* (modules) [\#5299](https://github.com/cosmos/cosmos-sdk/pull/5299) `HandleDoubleSign` along with params `MaxEvidenceAge`
and `DoubleSignJailEndTime` have moved from the `x/slashing` module to the `x/evidence` module.
* (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Initializing a new keybase through `NewKeyringFromHomeFlag`, `NewKeyringFromDir`, `NewKeyBaseFromHomeFlag`, `NewKeyBaseFromDir`, or `NewInMemory` functions now accept optional parameters of type `KeybaseOption`. These optional parameters are also added on the keys subcommands functions, which are now public, and allows these options to be set on the commands or ignored to default to previous behavior.
* The option introduced in this PR is `WithKeygenFunc` which allows a custom bytes to key implementation to be defined when keys are created.
### Client Breaking Changes
@ -146,6 +148,7 @@ that allows for arbitrary vesting periods.
* `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks.
* (cli) [\#5223](https://github.com/cosmos/cosmos-sdk/issues/5223) Cosmos Ledger App v2.0.0 is now supported. The changes are backwards compatible and App v1.5.x is still supported.
* (modules) [\#5249](https://github.com/cosmos/cosmos-sdk/pull/5249) Funds are now allowed to be directly sent to the community pool (via the distribution module account).
* (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Introduce keybase option to allow overriding the default private key implementation of a key generated through the `keys add` cli command.
### Improvements

View File

@ -5,6 +5,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"sort"
bip39 "github.com/bartekn/go-bip39"
@ -36,7 +37,8 @@ const (
DefaultKeyPass = "12345678"
)
func addKeyCommand() *cobra.Command {
// AddKeyCommand defines a keys command to add a generated or recovered private key to keybase.
func AddKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Add an encrypted private key (either newly generated or recovered), encrypt it, and save to disk",
@ -75,6 +77,24 @@ the flag --nosort is set.
return cmd
}
func getKeybase(transient bool, buf io.Reader) (keys.Keybase, error) {
if transient {
return keys.NewInMemory(), nil
}
return NewKeyringFromHomeFlag(buf)
}
func runAddCmd(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
kb, err := getKeybase(viper.GetBool(flagDryRun), inBuf)
if err != nil {
return err
}
return RunAddCmd(cmd, args, kb, inBuf)
}
/*
input
- bip39 mnemonic
@ -84,26 +104,15 @@ input
output
- armor encrypted private key (saved to file)
*/
func runAddCmd(cmd *cobra.Command, args []string) error {
var kb keys.Keybase
func RunAddCmd(cmd *cobra.Command, args []string, kb keys.Keybase, inBuf *bufio.Reader) error {
var err error
inBuf := bufio.NewReader(cmd.InOrStdin())
name := args[0]
interactive := viper.GetBool(flagInteractive)
showMnemonic := !viper.GetBool(flagNoBackup)
if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly
kb = keys.NewInMemory()
} else {
kb, err = NewKeyringFromHomeFlag(cmd.InOrStdin())
if err != nil {
return err
}
if !viper.GetBool(flagDryRun) {
_, err = kb.Get(name)
if err == nil {
// account exists, ask for user confirmation
@ -273,9 +282,9 @@ func printCreate(cmd *cobra.Command, info keys.Info, showMnemonic bool, mnemonic
var jsonString []byte
if viper.GetBool(flags.FlagIndentResponse) {
jsonString, err = cdc.MarshalJSONIndent(out, "", " ")
jsonString, err = KeysCdc.MarshalJSONIndent(out, "", " ")
} else {
jsonString, err = cdc.MarshalJSON(out)
jsonString, err = KeysCdc.MarshalJSON(out)
}
if err != nil {

View File

@ -33,7 +33,7 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
config.SetBech32PrefixForValidator(bech32PrefixValAddr, bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(bech32PrefixConsAddr, bech32PrefixConsPub)
cmd := addKeyCommand()
cmd := AddKeyCommand()
require.NotNil(t, cmd)
// Prepare a keybase
@ -80,7 +80,7 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
func Test_runAddCmdLedger(t *testing.T) {
runningUnattended := isRunningUnattended()
cmd := addKeyCommand()
cmd := AddKeyCommand()
require.NotNil(t, cmd)
mockIn, _, _ := tests.ApplyMockIO(cmd)

View File

@ -15,7 +15,7 @@ import (
func Test_runAddCmdBasic(t *testing.T) {
runningUnattended := isRunningUnattended()
cmd := addKeyCommand()
cmd := AddKeyCommand()
assert.NotNil(t, cmd)
mockIn, _, _ := tests.ApplyMockIO(cmd)

View File

@ -4,20 +4,21 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
)
var cdc *codec.Codec
// KeysCdc defines codec to be used with key operations
var KeysCdc *codec.Codec
func init() {
cdc = codec.New()
codec.RegisterCrypto(cdc)
cdc.Seal()
KeysCdc = codec.New()
codec.RegisterCrypto(KeysCdc)
KeysCdc.Seal()
}
// marshal keys
func MarshalJSON(o interface{}) ([]byte, error) {
return cdc.MarshalJSON(o)
return KeysCdc.MarshalJSON(o)
}
// unmarshal json
func UnmarshalJSON(bz []byte, ptr interface{}) error {
return cdc.UnmarshalJSON(bz, ptr)
return KeysCdc.UnmarshalJSON(bz, ptr)
}

View File

@ -16,7 +16,8 @@ const (
flagForce = "force"
)
func deleteKeyCommand() *cobra.Command {
// DeleteKeyCommand deletes a key from the key store.
func DeleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>...",
Short: "Delete the given keys",

View File

@ -14,7 +14,7 @@ import (
func Test_runDeleteCmd(t *testing.T) {
runningUnattended := isRunningUnattended()
deleteKeyCommand := deleteKeyCommand()
deleteKeyCommand := DeleteKeyCommand()
mockIn, _, _ := tests.ApplyMockIO(deleteKeyCommand)
yesF, _ := deleteKeyCommand.Flags().GetBool(flagYes)

View File

@ -8,7 +8,8 @@ import (
"github.com/cosmos/cosmos-sdk/client/input"
)
func exportKeyCommand() *cobra.Command {
// ExportKeyCommand exports private keys from the key store.
func ExportKeyCommand() *cobra.Command {
return &cobra.Command{
Use: "export <name>",
Short: "Export private keys",

View File

@ -12,7 +12,7 @@ import (
func Test_runExportCmd(t *testing.T) {
runningUnattended := isRunningUnattended()
exportKeyCommand := exportKeyCommand()
exportKeyCommand := ExportKeyCommand()
mockIn, _, _ := tests.ApplyMockIO(exportKeyCommand)
// Now add a temporary keybase

View File

@ -9,7 +9,8 @@ import (
"github.com/cosmos/cosmos-sdk/client/input"
)
func importKeyCommand() *cobra.Command {
// ImportKeyCommand imports private keys from a keyfile.
func ImportKeyCommand() *cobra.Command {
return &cobra.Command{
Use: "import <name> <keyfile>",
Short: "Import private keys into the local keybase",

View File

@ -14,7 +14,7 @@ import (
func Test_runImportCmd(t *testing.T) {
runningUnattended := isRunningUnattended()
importKeyCommand := importKeyCommand()
importKeyCommand := ImportKeyCommand()
mockIn, _, _ := tests.ApplyMockIO(importKeyCommand)
// Now add a temporary keybase

View File

@ -9,7 +9,8 @@ import (
const flagListNames = "list-names"
func listKeysCmd() *cobra.Command {
// ListKeysCmd lists all keys in the key store.
func ListKeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List all keys",

View File

@ -18,7 +18,7 @@ func Test_runListCmd(t *testing.T) {
args []string
}
cmdBasic := listKeysCmd()
cmdBasic := ListKeysCmd()
// Prepare some keybases
kbHome1, cleanUp1 := tests.NewTestCaseDir(t)

View File

@ -18,7 +18,8 @@ import (
// is not needed for importing into the Keyring keystore.
const migratePassphrase = "NOOP_PASSPHRASE"
func migrateCommand() *cobra.Command {
// MigrateCommand migrates key information from legacy keybase to OS secret store.
func MigrateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "migrate",
Short: "Migrate key information from the lagacy key database to the OS secret store, or encrypted file store as a fall-back and save it",

View File

@ -13,7 +13,7 @@ import (
)
func Test_runMigrateCmd(t *testing.T) {
cmd := addKeyCommand()
cmd := AddKeyCommand()
assert.NotNil(t, cmd)
mockIn, _, _ := tests.ApplyMockIO(cmd)
@ -29,7 +29,7 @@ func Test_runMigrateCmd(t *testing.T) {
assert.NoError(t, err)
viper.Set(flags.FlagDryRun, true)
cmd = migrateCommand()
cmd = MigrateCommand()
mockIn, _, _ = tests.ApplyMockIO(cmd)
mockIn.Reset("test1234\n")
assert.NoError(t, runMigrateCmd(cmd, []string{}))

View File

@ -17,7 +17,8 @@ const (
mnemonicEntropySize = 256
)
func mnemonicKeyCommand() *cobra.Command {
// MnemonicKeyCommand computes the bip39 memonic for input entropy.
func MnemonicKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "mnemonic",
Short: "Compute the bip39 mnemonic for some input entropy",

View File

@ -11,13 +11,13 @@ import (
)
func Test_RunMnemonicCmdNormal(t *testing.T) {
cmdBasic := mnemonicKeyCommand()
cmdBasic := MnemonicKeyCommand()
err := runMnemonicCmd(cmdBasic, []string{})
require.NoError(t, err)
}
func Test_RunMnemonicCmdUser(t *testing.T) {
cmdUser := mnemonicKeyCommand()
cmdUser := MnemonicKeyCommand()
err := cmdUser.Flags().Set(flagUserEntropy, "1")
assert.NoError(t, err)

View File

@ -67,7 +67,8 @@ func (bo bech32Output) String() string {
return fmt.Sprintf("Bech32 Formats:\n%s", strings.Join(out, "\n"))
}
func parseKeyStringCommand() *cobra.Command {
// ParseKeyStringCommand parses an address from hex to bech32 and vice versa.
func ParseKeyStringCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "parse <hex-or-bech32-address>",
Short: "Parse address from hex to bech32 and vice versa",
@ -124,9 +125,9 @@ func displayParseKeyInfo(stringer fmt.Stringer) {
case OutputFormatJSON:
if viper.GetBool(flags.FlagIndentResponse) {
out, err = cdc.MarshalJSONIndent(stringer, "", " ")
out, err = KeysCdc.MarshalJSONIndent(stringer, "", " ")
} else {
out = cdc.MustMarshalJSON(stringer)
out = KeysCdc.MustMarshalJSON(stringer)
}
}

View File

@ -20,17 +20,17 @@ func Commands() *cobra.Command {
needs to sign with a private key.`,
}
cmd.AddCommand(
mnemonicKeyCommand(),
addKeyCommand(),
exportKeyCommand(),
importKeyCommand(),
listKeysCmd(),
showKeysCmd(),
MnemonicKeyCommand(),
AddKeyCommand(),
ExportKeyCommand(),
ImportKeyCommand(),
ListKeysCmd(),
ShowKeysCmd(),
flags.LineBreak,
deleteKeyCommand(),
updateKeyCommand(),
parseKeyStringCommand(),
migrateCommand(),
DeleteKeyCommand(),
UpdateKeyCommand(),
ParseKeyStringCommand(),
MigrateCommand(),
)
cmd.PersistentFlags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
viper.BindPFlag(flags.FlagKeyringBackend, cmd.Flags().Lookup(flags.FlagKeyringBackend))

View File

@ -32,7 +32,8 @@ const (
defaultMultiSigKeyName = "multi"
)
func showKeysCmd() *cobra.Command {
// ShowKeysCmd shows key information for a given key name.
func ShowKeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "show [name [name...]]",
Short: "Show key info for the given name",

View File

@ -28,7 +28,7 @@ func Test_multiSigKey_Properties(t *testing.T) {
}
func Test_showKeysCmd(t *testing.T) {
cmd := showKeysCmd()
cmd := ShowKeysCmd()
require.NotNil(t, cmd)
require.Equal(t, "false", cmd.Flag(FlagAddress).DefValue)
require.Equal(t, "false", cmd.Flag(FlagPublicKey).DefValue)
@ -36,7 +36,7 @@ func Test_showKeysCmd(t *testing.T) {
func Test_runShowCmd(t *testing.T) {
runningUnattended := isRunningUnattended()
cmd := showKeysCmd()
cmd := ShowKeysCmd()
mockIn, _, _ := tests.ApplyMockIO(cmd)
require.EqualError(t, runShowCmd(cmd, []string{"invalid"}), "The specified item could not be found in the keyring")
require.EqualError(t, runShowCmd(cmd, []string{"invalid1", "invalid2"}), "The specified item could not be found in the keyring")

View File

@ -8,7 +8,9 @@ import (
"github.com/cosmos/cosmos-sdk/client/input"
)
func updateKeyCommand() *cobra.Command {
// UpdateKeyCommand changes the password of a key in the keybase.
// It takes no effect on keys managed by new the keyring-based keybase implementation.
func UpdateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",

View File

@ -11,7 +11,7 @@ import (
)
func Test_updateKeyCommand(t *testing.T) {
cmd := updateKeyCommand()
cmd := UpdateKeyCommand()
assert.NotNil(t, cmd)
// No flags or defaults to validate
}
@ -20,7 +20,7 @@ func Test_runUpdateCmd(t *testing.T) {
fakeKeyName1 := "runUpdateCmd_Key1"
fakeKeyName2 := "runUpdateCmd_Key2"
cmd := updateKeyCommand()
cmd := UpdateKeyCommand()
// fails because it requests a password
assert.EqualError(t, runUpdateCmd(cmd, []string{fakeKeyName1}), "EOF")

View File

@ -26,30 +26,33 @@ const (
type bechKeyOutFn func(keyInfo keys.Info) (keys.KeyOutput, error)
// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration.
func NewKeyBaseFromHomeFlag() (keys.Keybase, error) {
// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. Keybase
// options can be applied when generating this new Keybase.
func NewKeyBaseFromHomeFlag(opts ...keys.KeybaseOption) (keys.Keybase, error) {
rootDir := viper.GetString(flags.FlagHome)
return NewKeyBaseFromDir(rootDir)
return NewKeyBaseFromDir(rootDir, opts...)
}
// NewKeyBaseFromDir initializes a keybase at a particular dir.
func NewKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
return getLazyKeyBaseFromDir(rootDir)
// NewKeyBaseFromDir initializes a keybase at the rootDir directory. Keybase
// options can be applied when generating this new Keybase.
func NewKeyBaseFromDir(rootDir string, opts ...keys.KeybaseOption) (keys.Keybase, error) {
return getLazyKeyBaseFromDir(rootDir, opts...)
}
// NewInMemoryKeyBase returns a storage-less keybase.
func NewInMemoryKeyBase() keys.Keybase { return keys.NewInMemory() }
// NewKeyBaseFromHomeFlag initializes a keyring based on configuration.
func NewKeyringFromHomeFlag(input io.Reader) (keys.Keybase, error) {
return NewKeyringFromDir(viper.GetString(flags.FlagHome), input)
// NewKeyBaseFromHomeFlag initializes a keyring based on configuration. Keybase
// options can be applied when generating this new Keybase.
func NewKeyringFromHomeFlag(input io.Reader, opts ...keys.KeybaseOption) (keys.Keybase, error) {
return NewKeyringFromDir(viper.GetString(flags.FlagHome), input, opts...)
}
// NewKeyBaseFromDir initializes a keyring at the given directory.
// If the viper flag flags.FlagKeyringBackend is set to file, it returns an on-disk keyring with
// CLI prompt support only. If flags.FlagKeyringBackend is set to test it will return an on-disk,
// password-less keyring that could be used for testing purposes.
func NewKeyringFromDir(rootDir string, input io.Reader) (keys.Keybase, error) {
func NewKeyringFromDir(rootDir string, input io.Reader, opts ...keys.KeybaseOption) (keys.Keybase, error) {
keyringBackend := viper.GetString(flags.FlagKeyringBackend)
switch keyringBackend {
case flags.KeyringBackendTest:
@ -62,8 +65,8 @@ func NewKeyringFromDir(rootDir string, input io.Reader) (keys.Keybase, error) {
return nil, fmt.Errorf("unknown keyring backend %q", keyringBackend)
}
func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil
func getLazyKeyBaseFromDir(rootDir string, opts ...keys.KeybaseOption) (keys.Keybase, error) {
return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys"), opts...), nil
}
func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
@ -80,9 +83,9 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
var out []byte
var err error
if viper.GetBool(flags.FlagIndentResponse) {
out, err = cdc.MarshalJSONIndent(ko, "", " ")
out, err = KeysCdc.MarshalJSONIndent(ko, "", " ")
} else {
out, err = cdc.MarshalJSON(ko)
out, err = KeysCdc.MarshalJSON(ko)
}
if err != nil {
panic(err)
@ -107,9 +110,9 @@ func printInfos(infos []keys.Info) {
var err error
if viper.GetBool(flags.FlagIndentResponse) {
out, err = cdc.MarshalJSONIndent(kos, "", " ")
out, err = KeysCdc.MarshalJSONIndent(kos, "", " ")
} else {
out, err = cdc.MarshalJSON(kos)
out, err = KeysCdc.MarshalJSON(kos)
}
if err != nil {

View File

@ -7,16 +7,22 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
)
var cdc *codec.Codec
// CryptoCdc defines the codec required for keys and info
var CryptoCdc *codec.Codec
func init() {
cdc = codec.New()
cryptoAmino.RegisterAmino(cdc)
CryptoCdc = codec.New()
cryptoAmino.RegisterAmino(CryptoCdc)
RegisterCodec(CryptoCdc)
CryptoCdc.Seal()
}
// RegisterCodec registers concrete types and interfaces on the given codec.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*Info)(nil), nil)
cdc.RegisterConcrete(hd.BIP44Params{}, "crypto/keys/hd/BIP44Params", nil)
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil)
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil)
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil)
cdc.RegisterConcrete(multiInfo{}, "crypto/keys/multiInfo", nil)
cdc.Seal()
}

View File

@ -75,16 +75,17 @@ type dbKeybase struct {
// newDBKeybase creates a new dbKeybase instance using the provided DB for
// reading and writing keys.
func newDBKeybase(db dbm.DB) Keybase {
func newDBKeybase(db dbm.DB, opts ...KeybaseOption) Keybase {
return dbKeybase{
base: baseKeybase{},
base: newBaseKeybase(opts...),
db: db,
}
}
// 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 newDBKeybase(dbm.NewMemDB()) }
// Keybase options can be applied when generating this new Keybase.
func NewInMemory(opts ...KeybaseOption) Keybase { return newDBKeybase(dbm.NewMemDB(), opts...) }
// CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. It returns the generated mnemonic and the key Info.

View File

@ -16,9 +16,15 @@ import (
)
type (
kbOptions struct {
keygenFunc PrivKeyGenFunc
}
// baseKeybase is an auxiliary type that groups Keybase storage agnostic features
// together.
baseKeybase struct{}
baseKeybase struct {
options kbOptions
}
keyWriter interface {
writeLocalKeyer
@ -34,6 +40,30 @@ type (
}
)
// WithKeygenFunc applies an overridden key generation function to generate the private key.
func WithKeygenFunc(f PrivKeyGenFunc) KeybaseOption {
return func(o *kbOptions) {
o.keygenFunc = f
}
}
// newBaseKeybase generates the base keybase with defaulting to tendermint SECP256K1 key type
func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase {
// Default options for keybase
options := kbOptions{keygenFunc: baseSecpPrivKeyGen}
for _, optionFn := range optionsFns {
optionFn(&options)
}
return baseKeybase{options: options}
}
// baseSecpPrivKeyGen generates a secp256k1 private key from the given bytes
func baseSecpPrivKeyGen(bz [32]byte) tmcrypto.PrivKey {
return secp256k1.PrivKeySecp256k1(bz)
}
// 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.
@ -72,7 +102,7 @@ func (kb baseKeybase) DecodeSignature(info Info, msg []byte) (sig []byte, pub tm
return nil, nil, err
}
if err := cdc.UnmarshalBinaryLengthPrefixed([]byte(signed), sig); err != nil {
if err := CryptoCdc.UnmarshalBinaryLengthPrefixed([]byte(signed), sig); err != nil {
return nil, nil, errors.Wrap(err, "failed to decode signature")
}
@ -101,9 +131,9 @@ func (kb baseKeybase) persistDerivedKey(
var info Info
if passwd != "" {
info = keyWriter.writeLocalKey(name, secp256k1.PrivKeySecp256k1(derivedPriv), passwd)
info = keyWriter.writeLocalKey(name, kb.options.keygenFunc(derivedPriv), passwd)
} else {
info = kb.writeOfflineKey(keyWriter, name, secp256k1.PrivKeySecp256k1(derivedPriv).PubKey())
info = kb.writeOfflineKey(keyWriter, name, kb.options.keygenFunc(derivedPriv).PubKey())
}
return info, nil

View File

@ -10,6 +10,7 @@ import (
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
@ -401,7 +402,8 @@ func TestSeedPhrase(t *testing.T) {
func ExampleNew() {
// Select the encryption and storage for your cryptostore
cstore := NewInMemory()
customKeyGenFunc := func(bz [32]byte) crypto.PrivKey { return secp256k1.PrivKeySecp256k1(bz) }
cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc))
sec := Secp256k1

View File

@ -42,14 +42,17 @@ type keyringKeybase struct {
var maxPassphraseEntryAttempts = 3
// NewKeyring creates a new instance of a keyring.
func NewKeyring(name string, dir string, userInput io.Reader) (Keybase, error) {
// NewKeyring creates a new instance of a keyring. Keybase
// options can be applied when generating this new Keybase.
func NewKeyring(
name string, dir string, userInput io.Reader, opts ...KeybaseOption,
) (Keybase, error) {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, userInput, false))
if err != nil {
return nil, err
}
return newKeyringKeybase(db), nil
return newKeyringKeybase(db, opts...), nil
}
// NewKeyringFile creates a new instance of an encrypted file-backed keyring.
@ -64,13 +67,13 @@ func NewKeyringFile(name string, dir string, userInput io.Reader) (Keybase, erro
// NewTestKeyring creates a new instance of an on-disk keyring for
// testing purposes that does not prompt users for password.
func NewTestKeyring(name string, dir string) (Keybase, error) {
func NewTestKeyring(name string, dir string, opts ...KeybaseOption) (Keybase, error) {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, nil, true))
if err != nil {
return nil, err
}
return newKeyringKeybase(db), nil
return newKeyringKeybase(db, opts...), nil
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
@ -572,9 +575,9 @@ func fakePrompt(prompt string) (string, error) {
return "test", nil
}
func newKeyringKeybase(db keyring.Keyring) Keybase {
func newKeyringKeybase(db keyring.Keyring, opts ...KeybaseOption) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
base: newBaseKeybase(opts...),
}
}

View File

@ -14,17 +14,18 @@ var _ Keybase = lazyKeybase{}
// NOTE: lazyKeybase will be deprecated in favor of lazyKeybaseKeyring.
type lazyKeybase struct {
name string
dir string
name string
dir string
options []KeybaseOption
}
// New creates a new instance of a lazy keybase.
func New(name, dir string) Keybase {
func New(name, dir string, opts ...KeybaseOption) Keybase {
if err := cmn.EnsureDir(dir, 0700); err != nil {
panic(fmt.Sprintf("failed to create Keybase directory: %s", err))
}
return lazyKeybase{name: name, dir: dir}
return lazyKeybase{name: name, dir: dir, options: opts}
}
func (lkb lazyKeybase) List() ([]Info, error) {
@ -34,7 +35,7 @@ func (lkb lazyKeybase) List() ([]Info, error) {
}
defer db.Close()
return newDBKeybase(db).List()
return newDBKeybase(db, lkb.options...).List()
}
func (lkb lazyKeybase) Get(name string) (Info, error) {
@ -44,7 +45,7 @@ func (lkb lazyKeybase) Get(name string) (Info, error) {
}
defer db.Close()
return newDBKeybase(db).Get(name)
return newDBKeybase(db, lkb.options...).Get(name)
}
func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) {
@ -54,7 +55,7 @@ func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) {
}
defer db.Close()
return newDBKeybase(db).GetByAddress(address)
return newDBKeybase(db, lkb.options...).GetByAddress(address)
}
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
@ -64,7 +65,7 @@ func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
}
defer db.Close()
return newDBKeybase(db).Delete(name, passphrase, skipPass)
return newDBKeybase(db, lkb.options...).Delete(name, passphrase, skipPass)
}
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
@ -74,7 +75,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, lkb.options...).Sign(name, passphrase, msg)
}
func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) {
@ -84,7 +85,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, lkb.options...).CreateMnemonic(name, language, passwd, algo)
}
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) {
@ -94,7 +95,8 @@ func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd
}
defer db.Close()
return newDBKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
return newDBKeybase(db,
lkb.options...).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) {
@ -104,7 +106,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, lkb.options...).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
}
func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) {
@ -114,7 +116,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, lkb.options...).CreateLedger(name, algo, hrp, account, index)
}
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) {
@ -124,7 +126,7 @@ func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info In
}
defer db.Close()
return newDBKeybase(db).CreateOffline(name, pubkey)
return newDBKeybase(db, lkb.options...).CreateOffline(name, pubkey)
}
func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) {
@ -134,7 +136,7 @@ func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info
}
defer db.Close()
return newDBKeybase(db).CreateMulti(name, pubkey)
return newDBKeybase(db, lkb.options...).CreateMulti(name, pubkey)
}
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
@ -144,7 +146,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, lkb.options...).Update(name, oldpass, getNewpass)
}
func (lkb lazyKeybase) Import(name string, armor string) (err error) {
@ -154,7 +156,7 @@ func (lkb lazyKeybase) Import(name string, armor string) (err error) {
}
defer db.Close()
return newDBKeybase(db).Import(name, armor)
return newDBKeybase(db, lkb.options...).Import(name, armor)
}
func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
@ -164,7 +166,7 @@ func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase strin
}
defer db.Close()
return newDBKeybase(db).ImportPrivKey(name, armor, passphrase)
return newDBKeybase(db, lkb.options...).ImportPrivKey(name, armor, passphrase)
}
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
@ -174,7 +176,7 @@ func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
}
defer db.Close()
return newDBKeybase(db).ImportPubKey(name, armor)
return newDBKeybase(db, lkb.options...).ImportPubKey(name, armor)
}
func (lkb lazyKeybase) Export(name string) (armor string, err error) {
@ -184,7 +186,7 @@ func (lkb lazyKeybase) Export(name string) (armor string, err error) {
}
defer db.Close()
return newDBKeybase(db).Export(name)
return newDBKeybase(db, lkb.options...).Export(name)
}
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
@ -194,7 +196,7 @@ func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
}
defer db.Close()
return newDBKeybase(db).ExportPubKey(name)
return newDBKeybase(db, lkb.options...).ExportPubKey(name)
}
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
@ -204,7 +206,7 @@ func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (c
}
defer db.Close()
return newDBKeybase(db).ExportPrivateKeyObject(name, passphrase)
return newDBKeybase(db, lkb.options...).ExportPrivateKeyObject(name, passphrase)
}
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
@ -216,7 +218,7 @@ func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
}
defer db.Close()
return newDBKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
return newDBKeybase(db, lkb.options...).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
}
func (lkb lazyKeybase) CloseDB() {}

View File

@ -5,9 +5,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
tmamino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -371,3 +374,77 @@ func TestLazySeedPhrase(t *testing.T) {
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
}
var _ crypto.PrivKey = testPriv{}
var _ crypto.PubKey = testPub{}
var testCdc *amino.Codec
type testPriv []byte
func (privkey testPriv) PubKey() crypto.PubKey { return testPub{} }
func (privkey testPriv) Bytes() []byte {
return testCdc.MustMarshalBinaryBare(privkey)
}
func (privkey testPriv) Sign(msg []byte) ([]byte, error) { return []byte{}, nil }
func (privkey testPriv) Equals(other crypto.PrivKey) bool { return true }
type testPub []byte
func (key testPub) Address() crypto.Address { return crypto.Address{} }
func (key testPub) Bytes() []byte {
return testCdc.MustMarshalBinaryBare(key)
}
func (key testPub) VerifyBytes(msg []byte, sig []byte) bool { return true }
func (key testPub) Equals(other crypto.PubKey) bool { return true }
func TestKeygenOverride(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
// Save existing codec and reset after test
cryptoCdc := CryptoCdc
defer func() {
CryptoCdc = cryptoCdc
}()
// Setup testCdc encoding and decoding new key type
testCdc = codec.New()
RegisterCodec(testCdc)
tmamino.RegisterAmino(testCdc)
// Set up codecs for using new key types
privName, pubName := "test/priv_name", "test/pub_name"
tmamino.RegisterKeyType(testPriv{}, privName)
tmamino.RegisterKeyType(testPub{}, pubName)
testCdc.RegisterConcrete(testPriv{}, privName, nil)
testCdc.RegisterConcrete(testPub{}, pubName, nil)
CryptoCdc = testCdc
overrideCalled := false
dummyFunc := func(bz [32]byte) crypto.PrivKey {
overrideCalled = true
return testPriv(bz[:])
}
kb := New("keybasename", dir, WithKeygenFunc(dummyFunc))
testName, pw := "name", "testPassword"
// create new key which will generate with
info, _, err := kb.CreateMnemonic(testName, English, pw, Secp256k1)
require.NoError(t, err)
require.Equal(t, info.GetName(), testName)
// Assert overridden function was called
require.True(t, overrideCalled)
// export private key object
exported, err := kb.ExportPrivateKeyObject(testName, pw)
require.Nil(t, err, "%+v", err)
// require that the key type is the new key
_, ok := exported.(testPriv)
require.True(t, ok)
require.True(t, exported.PubKey().Equals(info.GetPubKey()))
}

View File

@ -306,11 +306,19 @@ func (i multiInfo) GetPath() (*hd.BIP44Params, error) {
// encoding info
func marshalInfo(i Info) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(i)
return CryptoCdc.MustMarshalBinaryLengthPrefixed(i)
}
// decoding info
func unmarshalInfo(bz []byte) (info Info, err error) {
err = cdc.UnmarshalBinaryLengthPrefixed(bz, &info)
err = CryptoCdc.UnmarshalBinaryLengthPrefixed(bz, &info)
return
}
type (
// PrivKeyGenFunc defines the function to convert derived key bytes to a tendermint private key
PrivKeyGenFunc func(bz [32]byte) crypto.PrivKey
// KeybaseOption overrides options for the db
KeybaseOption func(*kbOptions)
)