crypto/keyring: deprecate old keybase (#5889)
Remove the Update method from the Keybase interface. Remove redundant lazy keybase implementation altogether. Created LegacyKeybase interface to restrict capabilities to only those required by keys commands that deal with legacy keybase such as update and migrate. Rename keyring.New() -> keyring.NewLegacy(). Rename client/keys.NewKeyBaseFromDir -> NewLegacyKeyBaseFromDir. crypto/keyiring.NewInMemory() now returns a in-memory keyring. BackendMemory is added yet not exposed via command line --keyring-backend flag. keys add uses it when --dry-run flag is on.
This commit is contained in:
parent
db76afe840
commit
2a7a408d35
@ -51,6 +51,7 @@ that parse log messages.
|
||||
* (client) [\#5799](https://github.com/cosmos/cosmos-sdk/pull/5799) The `tx encode/decode` commands, due to change on encoding break compatibility with
|
||||
older clients.
|
||||
* (x/auth) [\#5844](https://github.com/cosmos/cosmos-sdk/pull/5844) `tx sign` command now returns an error when signing is attempted with offline/multisig keys.
|
||||
* (client/keys) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Remove `keys update` command.
|
||||
|
||||
### API Breaking Changes
|
||||
|
||||
@ -77,6 +78,11 @@ to now accept a `codec.JSONMarshaler` for modular serialization of genesis state
|
||||
* (crypto/keyring) [\#5866](https://github.com/cosmos/cosmos-sdk/pull/5866) Move `Keyring` and `Keybase` implementations and their associated types from `crypto/keys/` to `crypto/keyring/`.
|
||||
* (crypto) [\#5880](https://github.com/cosmos/cosmos-sdk/pull/5880) Merge `crypto/keys/mintkey` into `crypto`.
|
||||
* (crypto/keyring) [\#5858](https://github.com/cosmos/cosmos-sdk/pull/5858) Make Keyring store keys by name and address's hexbytes representation.
|
||||
* (crypto/keyring) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Deprecate old keybase implementation:
|
||||
- Remove `Update` from the `Keybase` interface.
|
||||
- `NewKeyring()` now accepts a new backend: `MemoryBackend`.
|
||||
- `New()` has been renamed to`NewLegacy()`, which now returns a `LegacyKeybase` type that only allows migration of keys from the legacy keybase to a new keyring.
|
||||
* (client/keys) [\#5889](https://github.com/cosmos/cosmos-sdk/pull/5889) Rename `NewKeyBaseFromDir()` -> `NewLegacyKeyBaseFromDir()`.
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
@ -27,7 +27,6 @@ const (
|
||||
flagInteractive = "interactive"
|
||||
flagRecover = "recover"
|
||||
flagNoBackup = "no-backup"
|
||||
flagDryRun = "dry-run"
|
||||
flagAccount = "account"
|
||||
flagIndex = "index"
|
||||
flagMultisig = "multisig"
|
||||
@ -72,7 +71,7 @@ the flag --nosort is set.
|
||||
cmd.Flags().Bool(flags.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
|
||||
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
|
||||
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||
cmd.Flags().Bool(flags.FlagDryRun, false, "Perform action, but don't add key to local keystore")
|
||||
cmd.Flags().String(flagHDPath, "", "Manual HD Path derivation (overrides BIP44 config)")
|
||||
cmd.Flags().Uint32(flagAccount, 0, "Account number for HD derivation")
|
||||
cmd.Flags().Uint32(flagIndex, 0, "Address index number for HD derivation")
|
||||
@ -83,7 +82,7 @@ the flag --nosort is set.
|
||||
|
||||
func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) {
|
||||
if transient {
|
||||
return keyring.NewInMemory(), nil
|
||||
return keyring.NewKeyring(sdk.KeyringServiceName(), keyring.BackendMemory, viper.GetString(flags.FlagHome), buf)
|
||||
}
|
||||
|
||||
return keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), buf)
|
||||
@ -91,7 +90,7 @@ func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) {
|
||||
|
||||
func runAddCmd(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
kb, err := getKeybase(viper.GetBool(flagDryRun), inBuf)
|
||||
kb, err := getKeybase(viper.GetBool(flags.FlagDryRun), inBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -124,7 +123,7 @@ func RunAddCmd(cmd *cobra.Command, args []string, kb keyring.Keybase, inBuf *buf
|
||||
return keyring.ErrUnsupportedSigningAlgo
|
||||
}
|
||||
|
||||
if !viper.GetBool(flagDryRun) {
|
||||
if !viper.GetBool(flags.FlagDryRun) {
|
||||
_, err = kb.Get(name)
|
||||
if err == nil {
|
||||
// account exists, ask for user confirmation
|
||||
|
||||
@ -17,7 +17,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
config := sdk.GetConfig()
|
||||
|
||||
bech32PrefixAccAddr := "terra"
|
||||
@ -58,9 +57,6 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
|
||||
kb.Delete("keyname1", "", false)
|
||||
})
|
||||
mockIn.Reset("test1234\n")
|
||||
if runningUnattended {
|
||||
mockIn.Reset("test1234\ntest1234\n")
|
||||
}
|
||||
key1, err := kb.Get("keyname1")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key1)
|
||||
@ -79,7 +75,6 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_runAddCmdLedger(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
cmd := AddKeyCommand()
|
||||
require.NotNil(t, cmd)
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmd)
|
||||
@ -105,9 +100,6 @@ func Test_runAddCmdLedger(t *testing.T) {
|
||||
kb.Delete("keyname1", "", false)
|
||||
})
|
||||
mockIn.Reset("test1234\n")
|
||||
if runningUnattended {
|
||||
mockIn.Reset("test1234\ntest1234\n")
|
||||
}
|
||||
key1, err := kb.Get("keyname1")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key1)
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runAddCmdBasic(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
cmd := AddKeyCommand()
|
||||
assert.NotNil(t, cmd)
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmd)
|
||||
@ -27,31 +26,27 @@ func Test_runAddCmdBasic(t *testing.T) {
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
viper.Set(cli.OutputFlag, OutputFormatText)
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
} else {
|
||||
mockIn.Reset("y\n")
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
kb.Delete("keyname2", "", false) // nolint:errcheck
|
||||
})
|
||||
}
|
||||
mockIn.Reset("y\n")
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
kb.Delete("keyname2", "", false) // nolint:errcheck
|
||||
})
|
||||
assert.NoError(t, runAddCmd(cmd, []string{"keyname1"}))
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\nN\n")
|
||||
} else {
|
||||
mockIn.Reset("N\n")
|
||||
}
|
||||
mockIn.Reset("N\n")
|
||||
assert.Error(t, runAddCmd(cmd, []string{"keyname1"}))
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\nN\n")
|
||||
} else {
|
||||
mockIn.Reset("y\n")
|
||||
}
|
||||
err := runAddCmd(cmd, []string{"keyname2"})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
|
||||
assert.Error(t, runAddCmd(cmd, []string{"keyname2"}))
|
||||
mockIn.Reset("y\n")
|
||||
assert.NoError(t, runAddCmd(cmd, []string{"keyname2"}))
|
||||
|
||||
// test --dry-run
|
||||
assert.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
|
||||
assert.Error(t, runAddCmd(cmd, []string{"keyname4"}))
|
||||
|
||||
viper.Set(flags.FlagDryRun, true)
|
||||
assert.NoError(t, runAddCmd(cmd, []string{"keyname4"}))
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runDeleteCmd(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
deleteKeyCommand := DeleteKeyCommand()
|
||||
mockIn, _, _ := tests.ApplyMockIO(deleteKeyCommand)
|
||||
|
||||
@ -26,38 +25,27 @@ func Test_runDeleteCmd(t *testing.T) {
|
||||
|
||||
fakeKeyName1 := "runDeleteCmd_Key1"
|
||||
fakeKeyName2 := "runDeleteCmd_Key2"
|
||||
if !runningUnattended {
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("runDeleteCmd_Key1", "", false) // nolint:errcheck
|
||||
kb.Delete("runDeleteCmd_Key2", "", false) // nolint:errcheck
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("runDeleteCmd_Key1", "", false) // nolint:errcheck
|
||||
kb.Delete("runDeleteCmd_Key2", "", false) // nolint:errcheck
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
// Now add a temporary keybase
|
||||
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanUp)
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
|
||||
// Now
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
|
||||
kb, err = keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn)
|
||||
require.NoError(t, err)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
err = runDeleteCmd(deleteKeyCommand, []string{"blah"})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "The specified item could not be found in the keyring", err.Error())
|
||||
@ -65,24 +53,14 @@ func Test_runDeleteCmd(t *testing.T) {
|
||||
// User confirmation missing
|
||||
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1})
|
||||
require.Error(t, err)
|
||||
if runningUnattended {
|
||||
require.Equal(t, "aborted", err.Error())
|
||||
} else {
|
||||
require.Equal(t, "EOF", err.Error())
|
||||
}
|
||||
require.Equal(t, "EOF", err.Error())
|
||||
|
||||
{
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\n")
|
||||
}
|
||||
_, err = kb.Get(fakeKeyName1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now there is a confirmation
|
||||
viper.Set(flagYes, true)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
require.NoError(t, runDeleteCmd(deleteKeyCommand, []string{fakeKeyName1}))
|
||||
|
||||
_, err = kb.Get(fakeKeyName1)
|
||||
@ -90,14 +68,8 @@ func Test_runDeleteCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
viper.Set(flagYes, true)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\n")
|
||||
}
|
||||
_, err = kb.Get(fakeKeyName2)
|
||||
require.NoError(t, err)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ny\ntestpass1\n")
|
||||
}
|
||||
err = runDeleteCmd(deleteKeyCommand, []string{fakeKeyName2})
|
||||
require.NoError(t, err)
|
||||
_, err = kb.Get(fakeKeyName2)
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runExportCmd(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
exportKeyCommand := ExportKeyCommand()
|
||||
mockIn, _, _ := tests.ApplyMockIO(exportKeyCommand)
|
||||
|
||||
@ -25,23 +24,14 @@ func Test_runExportCmd(t *testing.T) {
|
||||
// create a key
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
if !runningUnattended {
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
})
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
})
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
_, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", "", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now enter password
|
||||
if runningUnattended {
|
||||
mockIn.Reset("123456789\n123456789\ntestpass1\n")
|
||||
} else {
|
||||
mockIn.Reset("123456789\n123456789\n")
|
||||
}
|
||||
mockIn.Reset("123456789\n123456789\n")
|
||||
require.NoError(t, runExportCmd(exportKeyCommand, []string{"keyname1"}))
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runImportCmd(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
importKeyCommand := ImportKeyCommand()
|
||||
mockIn, _, _ := tests.ApplyMockIO(importKeyCommand)
|
||||
|
||||
@ -24,13 +23,11 @@ func Test_runImportCmd(t *testing.T) {
|
||||
t.Cleanup(cleanUp)
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
|
||||
if !runningUnattended {
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
})
|
||||
}
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
kb.Delete("keyname1", "", false) // nolint:errcheck
|
||||
})
|
||||
|
||||
keyfile := filepath.Join(kbHome, "key.asc")
|
||||
armoredKey := `-----BEGIN TENDERMINT PRIVATE KEY-----
|
||||
@ -45,10 +42,6 @@ HbP+c6JmeJy9JXe2rbbF1QtCX1gLqGcDQPBXiCtFvP7/8wTZtVOPj8vREzhZ9ElO
|
||||
require.NoError(t, ioutil.WriteFile(keyfile, []byte(armoredKey), 0644))
|
||||
|
||||
// Now enter password
|
||||
if runningUnattended {
|
||||
mockIn.Reset("123456789\n12345678\n12345678\n")
|
||||
} else {
|
||||
mockIn.Reset("123456789\n")
|
||||
}
|
||||
mockIn.Reset("123456789\n")
|
||||
require.NoError(t, runImportCmd(importKeyCommand, []string{"keyname1", keyfile}))
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_runListCmd(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
type args struct {
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
@ -34,9 +33,6 @@ func Test_runListCmd(t *testing.T) {
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmdBasic)
|
||||
kb, err := keyring.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn)
|
||||
require.NoError(t, err)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
|
||||
_, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", "", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
@ -57,18 +53,12 @@ func Test_runListCmd(t *testing.T) {
|
||||
for _, tt := range testData {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
viper.Set(flagListNames, false)
|
||||
viper.Set(flags.FlagHome, tt.kbDir)
|
||||
if err := runListCmd(tt.args.cmd, tt.args.args); (err != nil) != tt.wantErr {
|
||||
t.Errorf("runListCmd() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
viper.Set(flagListNames, true)
|
||||
if err := runListCmd(tt.args.cmd, tt.args.args); (err != nil) != tt.wantErr {
|
||||
t.Errorf("runListCmd() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
@ -43,10 +43,12 @@ It is recommended to run in 'dry-run' mode first to verify all key migration mat
|
||||
func runMigrateCmd(cmd *cobra.Command, args []string) error {
|
||||
// instantiate legacy keybase
|
||||
rootDir := viper.GetString(flags.FlagHome)
|
||||
legacyKb, err := NewKeyBaseFromDir(rootDir)
|
||||
var legacyKb keyring.LegacyKeybase
|
||||
legacyKb, err := NewLegacyKeyBaseFromDir(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer legacyKb.Close()
|
||||
|
||||
// fetch list of keys from legacy keybase
|
||||
oldKeys, err := legacyKb.List()
|
||||
|
||||
@ -45,7 +45,6 @@ The pass backend requires GnuPG: https://gnupg.org/
|
||||
ShowKeysCmd(),
|
||||
flags.LineBreak,
|
||||
DeleteKeyCommand(),
|
||||
UpdateKeyCommand(),
|
||||
ParseKeyStringCommand(),
|
||||
MigrateCommand(),
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ func TestCommands(t *testing.T) {
|
||||
assert.NotNil(t, rootCommands)
|
||||
|
||||
// Commands are registered
|
||||
assert.Equal(t, 11, len(rootCommands.Commands()))
|
||||
assert.Equal(t, 10, len(rootCommands.Commands()))
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@ -35,7 +35,6 @@ func Test_showKeysCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_runShowCmd(t *testing.T) {
|
||||
runningUnattended := isRunningUnattended()
|
||||
cmd := ShowKeysCmd()
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmd)
|
||||
require.EqualError(t, runShowCmd(cmd, []string{"invalid"}), "invalid is not a valid name or address: decoding bech32 failed: invalid bech32 string length 7")
|
||||
@ -55,29 +54,17 @@ func Test_runShowCmd(t *testing.T) {
|
||||
kb.Delete("runShowCmd_Key1", "", false)
|
||||
kb.Delete("runShowCmd_Key2", "", false)
|
||||
})
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\n")
|
||||
}
|
||||
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now try single key
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\n")
|
||||
}
|
||||
require.EqualError(t, runShowCmd(cmd, []string{fakeKeyName1}), "invalid Bech32 prefix encoding provided: ")
|
||||
|
||||
// Now try single key - set bech to acc
|
||||
viper.Set(FlagBechPrefix, sdk.PrefixAccount)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\n")
|
||||
}
|
||||
|
||||
// try fetch by name
|
||||
require.NoError(t, runShowCmd(cmd, []string{fakeKeyName1}))
|
||||
@ -88,17 +75,11 @@ func Test_runShowCmd(t *testing.T) {
|
||||
|
||||
// Now try multisig key - set bech to acc
|
||||
viper.Set(FlagBechPrefix, sdk.PrefixAccount)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
require.EqualError(t, runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2}), "threshold must be a positive integer")
|
||||
|
||||
// Now try multisig key - set bech to acc + threshold=2
|
||||
viper.Set(FlagBechPrefix, sdk.PrefixAccount)
|
||||
viper.Set(flagMultiSigThreshold, 2)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -106,23 +87,14 @@ func Test_runShowCmd(t *testing.T) {
|
||||
viper.Set(FlagBechPrefix, "acc")
|
||||
viper.Set(FlagDevice, true)
|
||||
viper.Set(flagMultiSigThreshold, 2)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||
require.EqualError(t, err, "the device flag (-d) can only be used for accounts stored in devices")
|
||||
|
||||
viper.Set(FlagBechPrefix, "val")
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||
require.EqualError(t, err, "the device flag (-d) can only be used for accounts")
|
||||
|
||||
viper.Set(FlagPublicKey, true)
|
||||
if runningUnattended {
|
||||
mockIn.Reset("testpass1\ntestpass1\n")
|
||||
}
|
||||
err = runShowCmd(cmd, []string{fakeKeyName1, fakeKeyName2})
|
||||
require.EqualError(t, err, "the device flag (-d) can only be used for addresses not pubkeys")
|
||||
|
||||
|
||||
BIN
client/keys/testdata/keys/keys.db/000002.ldb
vendored
Normal file
BIN
client/keys/testdata/keys/keys.db/000002.ldb
vendored
Normal file
Binary file not shown.
1
client/keys/testdata/keys/keys.db/CURRENT
vendored
Normal file
1
client/keys/testdata/keys/keys.db/CURRENT
vendored
Normal file
@ -0,0 +1 @@
|
||||
MANIFEST-000004
|
||||
1
client/keys/testdata/keys/keys.db/CURRENT.bak
vendored
Normal file
1
client/keys/testdata/keys/keys.db/CURRENT.bak
vendored
Normal file
@ -0,0 +1 @@
|
||||
MANIFEST-000000
|
||||
0
client/keys/testdata/keys/keys.db/LOCK
vendored
Normal file
0
client/keys/testdata/keys/keys.db/LOCK
vendored
Normal file
18
client/keys/testdata/keys/keys.db/LOG
vendored
Normal file
18
client/keys/testdata/keys/keys.db/LOG
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
=============== Mar 30, 2020 (CEST) ===============
|
||||
02:07:34.137606 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
|
||||
02:07:34.144547 db@open opening
|
||||
02:07:34.144770 version@stat F·[] S·0B[] Sc·[]
|
||||
02:07:34.145843 db@janitor F·2 G·0
|
||||
02:07:34.145875 db@open done T·1.315251ms
|
||||
02:07:34.335635 db@close closing
|
||||
02:07:34.335736 db@close done T·98.95µs
|
||||
=============== Mar 30, 2020 (CEST) ===============
|
||||
02:08:33.239115 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
|
||||
02:08:33.239264 version@stat F·[] S·0B[] Sc·[]
|
||||
02:08:33.239281 db@open opening
|
||||
02:08:33.239310 journal@recovery F·1
|
||||
02:08:33.239398 journal@recovery recovering @1
|
||||
02:08:33.322008 memdb@flush created L0@2 N·4 S·391B "cos..ess,v4":"run..nfo,v3"
|
||||
02:08:33.323091 version@stat F·[1] S·391B[391B] Sc·[0.25]
|
||||
02:08:33.421979 db@janitor F·3 G·0
|
||||
02:08:33.422153 db@open done T·182.707962ms
|
||||
BIN
client/keys/testdata/keys/keys.db/MANIFEST-000004
vendored
Normal file
BIN
client/keys/testdata/keys/keys.db/MANIFEST-000004
vendored
Normal file
Binary file not shown.
@ -1,55 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/input"
|
||||
)
|
||||
|
||||
// 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",
|
||||
Deprecated: `it takes no effect with the new keyring
|
||||
based backend and is provided only for backward compatibility with the
|
||||
legacy LevelDB based backend.
|
||||
Refer to your operating system's manual to learn how to change your
|
||||
keyring's password.
|
||||
`,
|
||||
RunE: runUpdateCmd,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpdateCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
buf := bufio.NewReader(cmd.InOrStdin())
|
||||
kb, err := NewKeyBaseFromDir(viper.GetString(flags.FlagHome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldpass, err := input.GetPassword("Enter the current passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getNewpass := func() (string, error) {
|
||||
return input.GetCheckPassword(
|
||||
"Enter the new passphrase:",
|
||||
"Repeat the new passphrase:", buf)
|
||||
}
|
||||
if err := kb.Update(name, oldpass, getNewpass); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PrintErrln("Password successfully updated!")
|
||||
return nil
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
func Test_updateKeyCommand(t *testing.T) {
|
||||
require.NotNil(t, UpdateKeyCommand())
|
||||
// No flags or defaults to validate
|
||||
}
|
||||
|
||||
func Test_runUpdateCmd(t *testing.T) {
|
||||
fakeKeyName1 := "runUpdateCmd_Key1"
|
||||
fakeKeyName2 := "runUpdateCmd_Key2"
|
||||
|
||||
cmd := UpdateKeyCommand()
|
||||
|
||||
// fails because it requests a password
|
||||
err := runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||
|
||||
require.EqualError(t, err, "EOF")
|
||||
|
||||
// try again
|
||||
mockIn, _, _ := tests.ApplyMockIO(cmd)
|
||||
mockIn.Reset("pass1234\n")
|
||||
err = runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||
require.True(t, errors.Is(err, sdkerrors.ErrKeyNotFound))
|
||||
|
||||
// Prepare a key base
|
||||
// Now add a temporary keybase
|
||||
kbHome, cleanUp1 := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanUp1)
|
||||
viper.Set(flags.FlagHome, kbHome)
|
||||
|
||||
kb, err := NewKeyBaseFromDir(viper.GetString(flags.FlagHome))
|
||||
require.NoError(t, err)
|
||||
_, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", "0", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
_, err = kb.CreateAccount(fakeKeyName2, tests.TestMnemonic, "", "", "1", keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try again now that we have keys
|
||||
// Incorrect key type
|
||||
mockIn.Reset("pass1234\nNew1234\nNew1234")
|
||||
err = runUpdateCmd(cmd, []string{fakeKeyName1})
|
||||
require.EqualError(t, err, "locally stored key required. Received: keyring.offlineInfo")
|
||||
|
||||
// TODO: Check for other type types?
|
||||
}
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -25,17 +24,14 @@ const (
|
||||
|
||||
type bechKeyOutFn func(keyInfo cryptokeyring.Info) (cryptokeyring.KeyOutput, error)
|
||||
|
||||
// NewKeyBaseFromDir initializes a keybase at the rootDir directory. Keybase
|
||||
// NewLegacyKeyBaseFromDir initializes a legacy keybase at the rootDir directory. Keybase
|
||||
// options can be applied when generating this new Keybase.
|
||||
func NewKeyBaseFromDir(rootDir string, opts ...cryptokeyring.KeybaseOption) (cryptokeyring.Keybase, error) {
|
||||
return getLazyKeyBaseFromDir(rootDir, opts...)
|
||||
func NewLegacyKeyBaseFromDir(rootDir string, opts ...cryptokeyring.KeybaseOption) (cryptokeyring.LegacyKeybase, error) {
|
||||
return getLegacyKeyBaseFromDir(rootDir, opts...)
|
||||
}
|
||||
|
||||
// NewInMemoryKeyBase returns a storage-less keybase.
|
||||
func NewInMemoryKeyBase() cryptokeyring.Keybase { return cryptokeyring.NewInMemory() }
|
||||
|
||||
func getLazyKeyBaseFromDir(rootDir string, opts ...cryptokeyring.KeybaseOption) (cryptokeyring.Keybase, error) {
|
||||
return cryptokeyring.New(defaultKeyDBName, filepath.Join(rootDir, "keys"), opts...), nil
|
||||
func getLegacyKeyBaseFromDir(rootDir string, opts ...cryptokeyring.KeybaseOption) (cryptokeyring.LegacyKeybase, error) {
|
||||
return cryptokeyring.NewLegacy(defaultKeyDBName, filepath.Join(rootDir, "keys"), opts...)
|
||||
}
|
||||
|
||||
func printKeyInfo(w io.Writer, keyInfo cryptokeyring.Info, bechKeyOut bechKeyOutFn) {
|
||||
@ -116,8 +112,3 @@ func printPubKey(w io.Writer, info cryptokeyring.Info, bechKeyOut bechKeyOutFn)
|
||||
|
||||
fmt.Fprintln(w, ko.PubKey)
|
||||
}
|
||||
|
||||
func isRunningUnattended() bool {
|
||||
backends := keyring.AvailableBackends()
|
||||
return len(backends) == 2 && backends[1] == keyring.BackendType("file")
|
||||
}
|
||||
|
||||
@ -33,6 +33,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var fundraiserPath = types.GetConfig().GetFullFundraiserPath()
|
||||
|
||||
// newBaseKeybase generates the base keybase with defaulting to tendermint SECP256K1 key type
|
||||
func newBaseKeybase(optionsFns ...KeybaseOption) baseKeybase {
|
||||
// Default options for keybase
|
||||
@ -139,7 +141,7 @@ func (kb baseKeybase) CreateMnemonic(
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
info, err = kb.CreateAccount(keyWriter, name, mnemonic, DefaultBIP39Passphrase, passwd, types.GetConfig().GetFullFundraiserPath(), algo)
|
||||
info, err = kb.CreateAccount(keyWriter, name, mnemonic, DefaultBIP39Passphrase, passwd, fundraiserPath, algo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@ -1,432 +0,0 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
var _ Keybase = dbKeybase{}
|
||||
|
||||
// dbKeybase combines encryption and storage implementation to provide a
|
||||
// full-featured key manager.
|
||||
//
|
||||
// NOTE: dbKeybase will be deprecated in favor of keyringKeybase.
|
||||
type dbKeybase struct {
|
||||
base baseKeybase
|
||||
db dbm.DB
|
||||
}
|
||||
|
||||
// newDBKeybase creates a new dbKeybase instance using the provided DB for
|
||||
// reading and writing keys.
|
||||
func newDBKeybase(db dbm.DB, opts ...KeybaseOption) Keybase {
|
||||
return dbKeybase{
|
||||
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.
|
||||
// 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.
|
||||
// It returns an error if it fails to generate a key for the given key algorithm
|
||||
// type, or if another key is already stored under the same name.
|
||||
func (kb dbKeybase) CreateMnemonic(
|
||||
name string, language Language, passwd string, algo SigningAlgo,
|
||||
) (info Info, mnemonic string, err error) {
|
||||
|
||||
return kb.base.CreateMnemonic(kb, name, language, passwd, algo)
|
||||
}
|
||||
|
||||
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
|
||||
// with the given password.
|
||||
func (kb dbKeybase) CreateAccount(
|
||||
name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo,
|
||||
) (Info, error) {
|
||||
|
||||
return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
|
||||
}
|
||||
|
||||
// CreateLedger creates a new locally-stored reference to a Ledger keypair.
|
||||
// It returns the created key info and an error if the Ledger could not be queried.
|
||||
func (kb dbKeybase) CreateLedger(
|
||||
name string, algo SigningAlgo, hrp string, account, index uint32,
|
||||
) (Info, error) {
|
||||
|
||||
return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
|
||||
}
|
||||
|
||||
// CreateOffline creates a new reference to an offline keypair. It returns the
|
||||
// created key info.
|
||||
func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) {
|
||||
return kb.base.writeOfflineKey(kb, name, pub, algo), nil
|
||||
}
|
||||
|
||||
// CreateMulti creates a new reference to a multisig (offline) keypair. It
|
||||
// returns the created key info.
|
||||
func (kb dbKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) {
|
||||
return kb.base.writeMultisigKey(kb, name, pub), nil
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
func (kb dbKeybase) List() ([]Info, error) {
|
||||
var res []Info
|
||||
|
||||
iter, err := kb.db.Iterator(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer iter.Close()
|
||||
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
key := string(iter.Key())
|
||||
|
||||
// need to include only keys in storage that have an info suffix
|
||||
if strings.HasSuffix(key, infoSuffix) {
|
||||
info, err := unmarshalInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, info)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get returns the public information about one key.
|
||||
func (kb dbKeybase) Get(name string) (Info, error) {
|
||||
bs, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(bs) == 0 {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, name)
|
||||
}
|
||||
|
||||
return unmarshalInfo(bs)
|
||||
}
|
||||
|
||||
// GetByAddress returns Info based on a provided AccAddress. An error is returned
|
||||
// if the address does not exist.
|
||||
func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) {
|
||||
ik, err := kb.db.Get(addrStringKey(address))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ik) == 0 {
|
||||
return nil, fmt.Errorf("key with address %s not found", address)
|
||||
}
|
||||
|
||||
bs, err := kb.db.Get(ik)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalInfo(bs)
|
||||
}
|
||||
|
||||
// Sign signs the msg with the named key. It returns an error if the key doesn't
|
||||
// exist or the decryption fails.
|
||||
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var priv tmcrypto.PrivKey
|
||||
|
||||
switch i := info.(type) {
|
||||
case localInfo:
|
||||
if i.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
|
||||
priv, _, err = crypto.UnarmorDecryptPrivKey(i.PrivKeyArmor, passphrase)
|
||||
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
|
||||
}
|
||||
|
||||
// ExportPrivateKeyObject returns a PrivKey object given the key name and
|
||||
// passphrase. An error is returned if the key does not exist or if the Info for
|
||||
// the key is invalid.
|
||||
func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var priv tmcrypto.PrivKey
|
||||
|
||||
switch i := info.(type) {
|
||||
case localInfo:
|
||||
linfo := i
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priv, _, err = crypto.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case ledgerInfo, offlineInfo, multiInfo:
|
||||
return nil, errors.New("only works on local private keys")
|
||||
}
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
|
||||
return crypto.ArmorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format. It retrieves a Info
|
||||
// object by its name and return the public key in a portable format.
|
||||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
|
||||
info, err := unmarshalInfo(bz)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), string(info.GetAlgo())), nil
|
||||
}
|
||||
|
||||
// ExportPrivKey returns a private key in ASCII armored format.
|
||||
// It returns an error if the key does not exist or a wrong encryption passphrase
|
||||
// is supplied.
|
||||
func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||
encryptPassphrase string) (armor string, err error) {
|
||||
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
|
||||
}
|
||||
|
||||
// ImportPrivKey imports a private key in ASCII armor format. It returns an
|
||||
// error if a key with the same name exists or a wrong encryption passphrase is
|
||||
// supplied.
|
||||
func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||
if _, err := kb.Get(name); err == nil {
|
||||
return errors.New("Cannot overwrite key " + name)
|
||||
}
|
||||
|
||||
privKey, algo, err := crypto.UnarmorDecryptPrivKey(armor, passphrase)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "couldn't import private key")
|
||||
}
|
||||
|
||||
kb.writeLocalKey(name, privKey, passphrase, SigningAlgo(algo))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bz) > 0 {
|
||||
return errors.New("cannot overwrite data for name " + name)
|
||||
}
|
||||
|
||||
infoBytes, err := crypto.UnarmorInfoBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return kb.db.Set(infoKey(name), infoBytes)
|
||||
}
|
||||
|
||||
// ImportPubKey imports ASCII-armored public keys. Store a new Info object holding
|
||||
// a public key only, i.e. it will not be possible to sign with it as it lacks the
|
||||
// secret key.
|
||||
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bz) > 0 {
|
||||
return errors.New("cannot overwrite data for name " + name)
|
||||
}
|
||||
|
||||
pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key forever, but we must present the proper passphrase before
|
||||
// deleting it (for security). It returns an error if the key doesn't exist or
|
||||
// passphrases don't match. Passphrase is ignored when deleting references to
|
||||
// offline and Ledger / HW wallet keys.
|
||||
func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error {
|
||||
// verify we have the proper password before deleting
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if linfo, ok := info.(localInfo); ok && !skipPass {
|
||||
if _, _, err = crypto.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
batch := kb.db.NewBatch()
|
||||
defer batch.Close()
|
||||
|
||||
batch.Delete(addrStringKey(info.GetAddress()))
|
||||
batch.Delete(infoKey(name))
|
||||
|
||||
return batch.WriteSync()
|
||||
}
|
||||
|
||||
// Update changes the passphrase with which an already stored key is
|
||||
// encrypted.
|
||||
//
|
||||
// oldpass must be the current passphrase used for encryption,
|
||||
// getNewpass is a function to get the passphrase to permanently replace
|
||||
// the current passphrase
|
||||
func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch i := info.(type) {
|
||||
case localInfo:
|
||||
linfo := i
|
||||
|
||||
key, _, err := crypto.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newpass, err := getNewpass()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kb.writeLocalKey(name, key, newpass, i.GetAlgo())
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String())
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedAlgos returns a list of supported signing algorithms.
|
||||
func (kb dbKeybase) SupportedAlgos() []SigningAlgo {
|
||||
return kb.base.SupportedAlgos()
|
||||
}
|
||||
|
||||
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
|
||||
func (kb dbKeybase) SupportedAlgosLedger() []SigningAlgo {
|
||||
return kb.base.SupportedAlgosLedger()
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string, algo SigningAlgo) Info {
|
||||
// encrypt private key using passphrase
|
||||
privArmor := crypto.EncryptArmorPrivKey(priv, passphrase, string(algo))
|
||||
|
||||
// make Info
|
||||
pub := priv.PubKey()
|
||||
info := newLocalInfo(name, pub, privArmor, algo)
|
||||
|
||||
kb.writeInfo(name, info)
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeInfo(name string, info Info) {
|
||||
// write the info by key
|
||||
key := infoKey(name)
|
||||
serializedInfo := marshalInfo(info)
|
||||
|
||||
kb.db.SetSync(key, serializedInfo)
|
||||
|
||||
// store a pointer to the infokey by address for fast lookup
|
||||
kb.db.SetSync(addrStringKey(info.GetAddress()), key)
|
||||
}
|
||||
|
||||
// this is to be removed together with dbKeybase and the old Keybase interface
|
||||
func addrStringKey(address types.AccAddress) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix))
|
||||
}
|
||||
|
||||
func addrHexKey(address types.AccAddress) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", hex.EncodeToString(address.Bytes()), addressSuffix))
|
||||
}
|
||||
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", name, infoSuffix))
|
||||
}
|
||||
@ -6,12 +6,11 @@
|
||||
// The Keybase interface defines the methods that a type needs to implement to be used
|
||||
// as key storage backend. This package provides few implementations out-of-the-box.
|
||||
//
|
||||
// New
|
||||
// NewLegacy
|
||||
//
|
||||
// The New constructor returns an on-disk implementation backed by LevelDB storage that has been
|
||||
// The NewLegacy constructor returns an on-disk implementation backed by LevelDB storage that has been
|
||||
// the default implementation used by the SDK until v0.38.0. Due to security concerns, it is
|
||||
// recommended to drop it in favor of the NewKeyring or NewKeyringFile constructors as it will be
|
||||
// removed in future releases.
|
||||
// recommended to drop it in favor of the NewKeyring constructor as it will be removed in future releases.
|
||||
//
|
||||
// NewInMemory
|
||||
//
|
||||
@ -42,4 +41,6 @@
|
||||
// https://www.passwordstore.org/
|
||||
// test This backend stores keys insecurely to disk. It does not prompt for a password to
|
||||
// be unlocked and it should be use only for testing purposes.
|
||||
// memory Same instance as returned by NewInMemory. This backend uses a transient storage. Keys
|
||||
// are discarded when the process terminates or the type instance is garbage collected.
|
||||
package keyring
|
||||
|
||||
@ -40,9 +40,6 @@ type Keybase interface {
|
||||
// CreateMulti creates, stores, and returns a new multsig (offline) key reference
|
||||
CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error)
|
||||
|
||||
// The following operations will *only* work on locally-stored keys
|
||||
Update(name, oldpass string, getNewpass func() (string, error)) error
|
||||
|
||||
// Import imports ASCII armored Info objects.
|
||||
Import(name string, armor string) (err error)
|
||||
|
||||
|
||||
@ -1,495 +0,0 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.BcryptSecurityParameter = 1
|
||||
}
|
||||
|
||||
const (
|
||||
nums = "1234"
|
||||
foobar = "foobar"
|
||||
)
|
||||
|
||||
func TestLanguage(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
_, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "unsupported language: only english is supported", err.Error())
|
||||
}
|
||||
|
||||
func TestCreateAccountInvalidMnemonic(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
_, err := kb.CreateAccount(
|
||||
"some_account",
|
||||
"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
|
||||
"", "", CreateHDPath(0, 0).String(), Secp256k1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "Invalid mnemonic", err.Error())
|
||||
}
|
||||
|
||||
func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
|
||||
supportedLedgerAlgos := kb.SupportedAlgosLedger()
|
||||
for _, supportedAlgo := range supportedLedgerAlgos {
|
||||
if Ed25519 == supportedAlgo {
|
||||
require.FailNow(t, "Was not an unsupported algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
_, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "unsupported signing algo", err.Error())
|
||||
}
|
||||
|
||||
func TestCreateLedger(t *testing.T) {
|
||||
kb := NewInMemory(WithSupportedAlgosLedger([]SigningAlgo{Secp256k1, Ed25519}))
|
||||
|
||||
// test_cover and test_unit will result in different answers
|
||||
// test_cover does not compile some dependencies so ledger is disabled
|
||||
// test_unit may add a ledger mock
|
||||
// both cases are acceptable
|
||||
supportedLedgerAlgos := kb.SupportedAlgosLedger()
|
||||
secpSupported := false
|
||||
edSupported := false
|
||||
for _, supportedAlgo := range supportedLedgerAlgos {
|
||||
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
|
||||
edSupported = edSupported || (supportedAlgo == Ed25519)
|
||||
}
|
||||
require.True(t, secpSupported)
|
||||
require.True(t, edSupported)
|
||||
|
||||
ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1)
|
||||
|
||||
if err != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
|
||||
require.Nil(t, ledger)
|
||||
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
|
||||
return
|
||||
}
|
||||
|
||||
// The mock is available, check that the address is correct
|
||||
pubKey := ledger.GetPubKey()
|
||||
pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||
|
||||
// Check that restoring the key gets the same results
|
||||
restoredKey, err := kb.Get("some_account")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, restoredKey)
|
||||
require.Equal(t, "some_account", restoredKey.GetName())
|
||||
require.Equal(t, TypeLedger, restoredKey.GetType())
|
||||
pubKey = restoredKey.GetPubKey()
|
||||
pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||
|
||||
path, err := restoredKey.GetPath()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "44'/118'/3'/0/1", path.String())
|
||||
}
|
||||
|
||||
// TestKeyManagement makes sure we can manipulate these keys well
|
||||
func TestKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory(WithSupportedAlgos([]SigningAlgo{Secp256k1, Sr25519}))
|
||||
|
||||
// Test modified supported algos
|
||||
supportedAlgos := cstore.SupportedAlgos()
|
||||
secpSupported := false
|
||||
edSupported := false
|
||||
srSupported := false
|
||||
for _, supportedAlgo := range supportedAlgos {
|
||||
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
|
||||
edSupported = edSupported || (supportedAlgo == Ed25519)
|
||||
srSupported = srSupported || (supportedAlgo == Sr25519)
|
||||
}
|
||||
require.True(t, secpSupported)
|
||||
require.False(t, edSupported)
|
||||
require.True(t, srSupported)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2, n3 := "personal", "business", "other"
|
||||
p1, p2 := nums, "really-secure!@#$"
|
||||
|
||||
// Check empty state
|
||||
l, err := cstore.List()
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, l)
|
||||
|
||||
_, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519)
|
||||
require.Error(t, err, "ed25519 keys are currently not supported by keybase")
|
||||
|
||||
// create some keys
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
i, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n1, i.GetName())
|
||||
_, _, err = cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := cstore.Get(n2)
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.GetByAddress(accAddr(i2))
|
||||
require.NoError(t, err)
|
||||
addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.GetByAddress(addr)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
// note these are in alphabetical order
|
||||
require.Equal(t, n2, keyS[0].GetName())
|
||||
require.Equal(t, n1, keyS[1].GetName())
|
||||
require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
|
||||
|
||||
// deleting a key removes it
|
||||
err = cstore.Delete("bad name", "foo", false)
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(n1, p1, false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
|
||||
// create an offline key
|
||||
o1 := "offline"
|
||||
priv1 := ed25519.GenPrivKey()
|
||||
pub1 := priv1.PubKey()
|
||||
i, err = cstore.CreateOffline(o1, pub1, algo)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pub1, i.GetPubKey())
|
||||
require.Equal(t, o1, i.GetName())
|
||||
iOffline := i.(*offlineInfo)
|
||||
require.Equal(t, algo, iOffline.GetAlgo())
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
|
||||
// delete the offline key
|
||||
err = cstore.Delete(o1, "", false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
|
||||
// addr cache gets nuked - and test skip flag
|
||||
err = cstore.Delete(n2, "", true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestSignVerify does some detailed checks on how we sign and validate
|
||||
// signatures
|
||||
func TestSignVerify(t *testing.T) {
|
||||
cstore := NewInMemory()
|
||||
algo := Secp256k1
|
||||
|
||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||
p1, p2, p3 := nums, foobar, foobar
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := cstore.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
err = cstore.ImportPubKey(n3, armor)
|
||||
require.NoError(t, err)
|
||||
i3, err := cstore.Get(n3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, i3.GetName(), n3)
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
d3 := []byte("feels like I forgot something...")
|
||||
|
||||
// try signing both data with both ..
|
||||
s11, pub1, err := cstore.Sign(n1, p1, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s12, pub1, err := cstore.Sign(n1, p1, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s21, pub2, err := cstore.Sign(n2, p2, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
s22, pub2, err := cstore.Sign(n2, p2, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
key tmcrypto.PubKey
|
||||
data []byte
|
||||
sig []byte
|
||||
valid bool
|
||||
}{
|
||||
// proper matches
|
||||
{i1.GetPubKey(), d1, s11, true},
|
||||
// change data, pubkey, or signature leads to fail
|
||||
{i1.GetPubKey(), d2, s11, false},
|
||||
{i2.GetPubKey(), d1, s11, false},
|
||||
{i1.GetPubKey(), d1, s21, false},
|
||||
// make sure other successes
|
||||
{i1.GetPubKey(), d2, s12, true},
|
||||
{i2.GetPubKey(), d1, s21, true},
|
||||
{i2.GetPubKey(), d2, s22, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
require.Equal(t, tc.valid, valid, "%d", i)
|
||||
}
|
||||
|
||||
// Now try to sign data with a secret-less key
|
||||
_, _, err = cstore.Sign(n3, p3, d3)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "cannot sign with offline keys", err.Error())
|
||||
}
|
||||
|
||||
func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) {
|
||||
getNewpass := func() (string, error) { return pass, nil }
|
||||
err := cstore.Update(name, badpass, getNewpass)
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Update(name, pass, getNewpass)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
// TestExportImport tests exporting and importing
|
||||
func TestExportImport(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
johnAddr := info.GetPubKey().Address()
|
||||
|
||||
armor, err := cstore.Export("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cstore.Import("john2", armor)
|
||||
require.NoError(t, err)
|
||||
|
||||
john2, err := cstore.Get("john2")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, john.GetPubKey().Address(), johnAddr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
//
|
||||
func TestExportImportPubKey(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
// CreateMnemonic a private-public key pair and ensure consistency
|
||||
notPasswd := "n9y25ah7"
|
||||
info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, info, "")
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := cstore.ExportPubKey("john")
|
||||
require.NoError(t, err)
|
||||
// Import it under a different name
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NoError(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := cstore.Get("john-pubkey-only")
|
||||
require.NoError(t, err)
|
||||
// Compare the public keys
|
||||
require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestAdvancedKeyManagement verifies update, import, export functionality
|
||||
func TestAdvancedKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "old-name", "new name"
|
||||
p1, p2 := nums, foobar
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assertPassword(t, cstore, n1, p1, p2)
|
||||
|
||||
// update password requires the existing password
|
||||
getNewpass := func() (string, error) { return p2, nil }
|
||||
err = cstore.Update(n1, "jkkgkg", getNewpass)
|
||||
require.NotNil(t, err)
|
||||
assertPassword(t, cstore, n1, p1, p2)
|
||||
|
||||
// then it changes the password when correct
|
||||
err = cstore.Update(n1, p1, getNewpass)
|
||||
require.NoError(t, err)
|
||||
// p2 is now the proper one!
|
||||
assertPassword(t, cstore, n1, p2, p1)
|
||||
|
||||
// exporting requires the proper name and passphrase
|
||||
_, err = cstore.Export(n1 + ".notreal")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(" " + n1)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(n1 + " ")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export("")
|
||||
require.NotNil(t, err)
|
||||
exported, err := cstore.Export(n1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// import succeeds
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NoError(t, err)
|
||||
|
||||
// second import fails
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestSeedPhrase verifies restoring from a seed phrase
|
||||
func TestSeedPhrase(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "lost-key", "found-again"
|
||||
p1, p2 := nums, foobar
|
||||
|
||||
// make sure key works with initial password
|
||||
info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Equal(t, n1, info.GetName())
|
||||
require.NotEmpty(t, mnemonic)
|
||||
|
||||
// now, let us delete this key
|
||||
err = cstore.Delete(n1, p1, false)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
_, err = cstore.Get(n1)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// let us re-create it from the mnemonic-phrase
|
||||
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
|
||||
hdPath := params.String()
|
||||
newInfo, err := cstore.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n2, newInfo.GetName())
|
||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
|
||||
}
|
||||
|
||||
func ExampleNew() {
|
||||
// Select the encryption and storage for your cryptostore
|
||||
customKeyGenFunc := func(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) {
|
||||
var bzArr [32]byte
|
||||
copy(bzArr[:], bz)
|
||||
return secp256k1.PrivKeySecp256k1(bzArr), nil
|
||||
}
|
||||
cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc))
|
||||
|
||||
sec := Secp256k1
|
||||
|
||||
// Add keys and see they return in alphabetical order
|
||||
bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
// return info here just like in List
|
||||
fmt.Println(bob.GetName())
|
||||
}
|
||||
_, _, _ = cstore.CreateMnemonic("Alice", English, "secret", sec)
|
||||
_, _, _ = cstore.CreateMnemonic("Carl", English, "mitm", sec)
|
||||
info, _ := cstore.List()
|
||||
for _, i := range info {
|
||||
fmt.Println(i.GetName())
|
||||
}
|
||||
|
||||
// We need to use passphrase to generate a signature
|
||||
tx := []byte("deadbeef")
|
||||
sig, pub, err := cstore.Sign("Bob", "friend", tx)
|
||||
if err != nil {
|
||||
fmt.Println("don't accept real passphrase")
|
||||
}
|
||||
|
||||
// and we can validate the signature with publicly available info
|
||||
binfo, _ := cstore.Get("Bob")
|
||||
if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
|
||||
fmt.Println("Get and Create return different keys")
|
||||
}
|
||||
|
||||
if pub.Equals(binfo.GetPubKey()) {
|
||||
fmt.Println("signed by Bob")
|
||||
}
|
||||
if !pub.VerifyBytes(tx, sig) {
|
||||
fmt.Println("invalid signature")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Bob
|
||||
// Alice
|
||||
// Bob
|
||||
// Carl
|
||||
// signed by Bob
|
||||
}
|
||||
|
||||
func accAddr(info Info) sdk.AccAddress {
|
||||
return (sdk.AccAddress)(info.GetPubKey().Address())
|
||||
}
|
||||
@ -2,6 +2,7 @@ package keyring
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -29,6 +30,7 @@ const (
|
||||
BackendKWallet = "kwallet"
|
||||
BackendPass = "pass"
|
||||
BackendTest = "test"
|
||||
BackendMemory = "memory"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,6 +68,8 @@ func NewKeyring(
|
||||
var err error
|
||||
|
||||
switch backend {
|
||||
case BackendMemory:
|
||||
return NewInMemory(opts...), nil
|
||||
case BackendTest:
|
||||
db, err = keyring.Open(lkbToKeyringConfig(appName, rootDir, nil, true))
|
||||
case BackendFile:
|
||||
@ -86,6 +90,13 @@ func NewKeyring(
|
||||
return newKeyringKeybase(db, opts...), nil
|
||||
}
|
||||
|
||||
// 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 ...KeybaseOption) Keybase {
|
||||
return newKeyringKeybase(keyring.NewArrayKeyring(nil), 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.
|
||||
// An error is returned if it fails to generate a key for the given algo type,
|
||||
@ -414,13 +425,6 @@ func (kb keyringKeybase) Delete(name, _ string, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update changes the passphrase with which an already stored key is encrypted.
|
||||
// The oldpass must be the current passphrase used for encryption, getNewpass is
|
||||
// a function to get the passphrase to permanently replace the current passphrase.
|
||||
func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||
return errors.New("unsupported operation")
|
||||
}
|
||||
|
||||
// SupportedAlgos returns a list of supported signing algorithms.
|
||||
func (kb keyringKeybase) SupportedAlgos() []SigningAlgo {
|
||||
return kb.base.SupportedAlgos()
|
||||
@ -581,3 +585,7 @@ 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))
|
||||
}
|
||||
|
||||
@ -2,19 +2,49 @@ package keyring
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/go-amino"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
tmamino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestLazyKeyManagementKeyRing(t *testing.T) {
|
||||
func init() {
|
||||
crypto.BcryptSecurityParameter = 1
|
||||
}
|
||||
|
||||
const (
|
||||
nums = "1234"
|
||||
foobar = "foobar"
|
||||
)
|
||||
|
||||
func TestNewKeyring(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
mockIn := strings.NewReader("")
|
||||
t.Cleanup(cleanup)
|
||||
kr, err := NewKeyring("cosmos", BackendFile, dir, mockIn)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockIn.Reset("password\npassword\n")
|
||||
info, _, err := kr.CreateMnemonic("foo", English, "password", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foo", info.GetName())
|
||||
}
|
||||
|
||||
func TestKeyManagementKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -99,7 +129,7 @@ func TestLazyKeyManagementKeyRing(t *testing.T) {
|
||||
|
||||
// TestSignVerify does some detailed checks on how we sign and validate
|
||||
// signatures
|
||||
func TestLazySignVerifyKeyRingWithLedger(t *testing.T) {
|
||||
func TestSignVerifyKeyRingWithLedger(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -134,7 +164,7 @@ func TestLazySignVerifyKeyRingWithLedger(t *testing.T) {
|
||||
require.Equal(t, "not a ledger object", err.Error())
|
||||
}
|
||||
|
||||
func TestLazySignVerifyKeyRing(t *testing.T) {
|
||||
func TestSignVerifyKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -184,7 +214,7 @@ func TestLazySignVerifyKeyRing(t *testing.T) {
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
key crypto.PubKey
|
||||
key tmcrypto.PubKey
|
||||
data []byte
|
||||
sig []byte
|
||||
valid bool
|
||||
@ -212,7 +242,7 @@ func TestLazySignVerifyKeyRing(t *testing.T) {
|
||||
require.Equal(t, "cannot sign with offline keys", err.Error())
|
||||
}
|
||||
|
||||
func TestLazyExportImportKeyRing(t *testing.T) {
|
||||
func TestExportImportKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -241,7 +271,7 @@ func TestLazyExportImportKeyRing(t *testing.T) {
|
||||
require.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
|
||||
func TestExportImportPubKeyKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -282,7 +312,7 @@ func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) {
|
||||
func TestExportPrivateKeyObjectKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -298,7 +328,7 @@ func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) {
|
||||
require.True(t, exported.PubKey().Equals(info.GetPubKey()))
|
||||
}
|
||||
|
||||
func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) {
|
||||
func TestAdvancedKeyManagementKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -332,7 +362,7 @@ func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) {
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLazySeedPhraseKeyRing(t *testing.T) {
|
||||
func TestSeedPhraseKeyRing(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
@ -393,15 +423,6 @@ func TestKeyringKeybaseExportImportPrivKey(t *testing.T) {
|
||||
require.Equal(t, "The specified item could not be found in the keyring", err.Error())
|
||||
}
|
||||
|
||||
func TestKeyringKeybaseUpdate(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := NewKeyring("keybasename", "test", dir, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "unsupported operation", kb.Update("john", "oldpassword",
|
||||
func() (string, error) { return "", nil }).Error())
|
||||
}
|
||||
|
||||
func TestSupportedAlgos(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
@ -410,3 +431,575 @@ func TestSupportedAlgos(t *testing.T) {
|
||||
require.Equal(t, []SigningAlgo{"secp256k1"}, kb.SupportedAlgos())
|
||||
require.Equal(t, []SigningAlgo{"secp256k1"}, kb.SupportedAlgosLedger())
|
||||
}
|
||||
|
||||
func TestInMemoryLanguage(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
_, _, err := kb.CreateMnemonic("something", Japanese, "no_pass", Secp256k1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "unsupported language: only english is supported", err.Error())
|
||||
}
|
||||
|
||||
func TestInMemoryCreateMultisig(t *testing.T) {
|
||||
kb, err := NewKeyring("keybasename", "memory", "", nil)
|
||||
require.NoError(t, err)
|
||||
multi := multisig.PubKeyMultisigThreshold{
|
||||
K: 1,
|
||||
PubKeys: []tmcrypto.PubKey{secp256k1.GenPrivKey().PubKey()},
|
||||
}
|
||||
_, err = kb.CreateMulti("multi", multi)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInMemoryCreateAccountInvalidMnemonic(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
_, err := kb.CreateAccount(
|
||||
"some_account",
|
||||
"malarkey pair crucial catch public canyon evil outer stage ten gym tornado",
|
||||
"", "", CreateHDPath(0, 0).String(), Secp256k1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "Invalid mnemonic", err.Error())
|
||||
}
|
||||
|
||||
func TestInMemoryCreateLedgerUnsupportedAlgo(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
|
||||
supportedLedgerAlgos := kb.SupportedAlgosLedger()
|
||||
for _, supportedAlgo := range supportedLedgerAlgos {
|
||||
if Ed25519 == supportedAlgo {
|
||||
require.FailNow(t, "Was not an unsupported algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
_, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "unsupported signing algo", err.Error())
|
||||
}
|
||||
|
||||
func TestInMemoryCreateLedger(t *testing.T) {
|
||||
kb := NewInMemory(WithSupportedAlgosLedger([]SigningAlgo{Secp256k1, Ed25519}))
|
||||
|
||||
// test_cover and test_unit will result in different answers
|
||||
// test_cover does not compile some dependencies so ledger is disabled
|
||||
// test_unit may add a ledger mock
|
||||
// both cases are acceptable
|
||||
supportedLedgerAlgos := kb.SupportedAlgosLedger()
|
||||
secpSupported := false
|
||||
edSupported := false
|
||||
for _, supportedAlgo := range supportedLedgerAlgos {
|
||||
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
|
||||
edSupported = edSupported || (supportedAlgo == Ed25519)
|
||||
}
|
||||
require.True(t, secpSupported)
|
||||
require.True(t, edSupported)
|
||||
|
||||
ledger, err := kb.CreateLedger("some_account", Secp256k1, "cosmos", 3, 1)
|
||||
|
||||
if err != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error())
|
||||
require.Nil(t, ledger)
|
||||
t.Skip("ledger nano S: support for ledger devices is not available in this executable")
|
||||
return
|
||||
}
|
||||
|
||||
// The mock is available, check that the address is correct
|
||||
pubKey := ledger.GetPubKey()
|
||||
pk, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||
|
||||
// Check that restoring the key gets the same results
|
||||
restoredKey, err := kb.Get("some_account")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, restoredKey)
|
||||
require.Equal(t, "some_account", restoredKey.GetName())
|
||||
require.Equal(t, TypeLedger, restoredKey.GetType())
|
||||
pubKey = restoredKey.GetPubKey()
|
||||
pk, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "cosmospub1addwnpepqdszcr95mrqqs8lw099aa9h8h906zmet22pmwe9vquzcgvnm93eqygufdlv", pk)
|
||||
|
||||
path, err := restoredKey.GetPath()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "44'/118'/3'/0/1", path.String())
|
||||
}
|
||||
|
||||
// TestInMemoryKeyManagement makes sure we can manipulate these keys well
|
||||
func TestInMemoryKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory(WithSupportedAlgos([]SigningAlgo{Secp256k1, Sr25519}))
|
||||
|
||||
// Test modified supported algos
|
||||
supportedAlgos := cstore.SupportedAlgos()
|
||||
secpSupported := false
|
||||
edSupported := false
|
||||
srSupported := false
|
||||
for _, supportedAlgo := range supportedAlgos {
|
||||
secpSupported = secpSupported || (supportedAlgo == Secp256k1)
|
||||
edSupported = edSupported || (supportedAlgo == Ed25519)
|
||||
srSupported = srSupported || (supportedAlgo == Sr25519)
|
||||
}
|
||||
require.True(t, secpSupported)
|
||||
require.False(t, edSupported)
|
||||
require.True(t, srSupported)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2, n3 := "personal", "business", "other"
|
||||
p1, p2 := nums, "really-secure!@#$"
|
||||
|
||||
// Check empty state
|
||||
l, err := cstore.List()
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, l)
|
||||
|
||||
_, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519)
|
||||
require.Error(t, err, "ed25519 keys are currently not supported by keybase")
|
||||
|
||||
// create some keys
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
i, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n1, i.GetName())
|
||||
_, _, err = cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := cstore.Get(n2)
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.Get(n3)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.GetByAddress(accAddr(i2))
|
||||
require.NoError(t, err)
|
||||
addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
|
||||
require.NoError(t, err)
|
||||
_, err = cstore.GetByAddress(addr)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
// note these are in alphabetical order
|
||||
require.Equal(t, n2, keyS[0].GetName())
|
||||
require.Equal(t, n1, keyS[1].GetName())
|
||||
require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
|
||||
|
||||
// deleting a key removes it
|
||||
err = cstore.Delete("bad name", "foo", false)
|
||||
require.NotNil(t, err)
|
||||
err = cstore.Delete(n1, p1, false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
_, err = cstore.Get(n1)
|
||||
require.Error(t, err)
|
||||
|
||||
// create an offline key
|
||||
o1 := "offline"
|
||||
priv1 := ed25519.GenPrivKey()
|
||||
pub1 := priv1.PubKey()
|
||||
i, err = cstore.CreateOffline(o1, pub1, algo)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pub1, i.GetPubKey())
|
||||
require.Equal(t, o1, i.GetName())
|
||||
iOffline := i.(*offlineInfo)
|
||||
require.Equal(t, algo, iOffline.GetAlgo())
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
|
||||
// delete the offline key
|
||||
err = cstore.Delete(o1, "", false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = cstore.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
|
||||
// addr cache gets nuked - and test skip flag
|
||||
err = cstore.Delete(n2, "", true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestInMemorySignVerify does some detailed checks on how we sign and validate
|
||||
// signatures
|
||||
func TestInMemorySignVerify(t *testing.T) {
|
||||
cstore := NewInMemory()
|
||||
algo := Secp256k1
|
||||
|
||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||
p1, p2, p3 := nums, foobar, foobar
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := cstore.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
err = cstore.ImportPubKey(n3, armor)
|
||||
require.NoError(t, err)
|
||||
i3, err := cstore.Get(n3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, i3.GetName(), n3)
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
d3 := []byte("feels like I forgot something...")
|
||||
|
||||
// try signing both data with both ..
|
||||
s11, pub1, err := cstore.Sign(n1, p1, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s12, pub1, err := cstore.Sign(n1, p1, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s21, pub2, err := cstore.Sign(n2, p2, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
s22, pub2, err := cstore.Sign(n2, p2, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
key tmcrypto.PubKey
|
||||
data []byte
|
||||
sig []byte
|
||||
valid bool
|
||||
}{
|
||||
// proper matches
|
||||
{i1.GetPubKey(), d1, s11, true},
|
||||
// change data, pubkey, or signature leads to fail
|
||||
{i1.GetPubKey(), d2, s11, false},
|
||||
{i2.GetPubKey(), d1, s11, false},
|
||||
{i1.GetPubKey(), d1, s21, false},
|
||||
// make sure other successes
|
||||
{i1.GetPubKey(), d2, s12, true},
|
||||
{i2.GetPubKey(), d1, s21, true},
|
||||
{i2.GetPubKey(), d2, s22, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
require.Equal(t, tc.valid, valid, "%d", i)
|
||||
}
|
||||
|
||||
// Now try to sign data with a secret-less key
|
||||
_, _, err = cstore.Sign(n3, p3, d3)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "cannot sign with offline keys", err.Error())
|
||||
}
|
||||
|
||||
// TestInMemoryExportImport tests exporting and importing
|
||||
func TestInMemoryExportImport(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
johnAddr := info.GetPubKey().Address()
|
||||
|
||||
armor, err := cstore.Export("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cstore.Import("john2", armor)
|
||||
require.NoError(t, err)
|
||||
|
||||
john2, err := cstore.Get("john2")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, john.GetPubKey().Address(), johnAddr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
func TestInMemoryExportImportPrivKey(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
|
||||
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
priv1, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
// decrypt local private key, and produce encrypted ASCII armored output
|
||||
armored, err := kb.ExportPrivKey("john", "secretcpw", "new_secretcpw")
|
||||
require.NoError(t, err)
|
||||
|
||||
// delete exported key
|
||||
require.NoError(t, kb.Delete("john", "", true))
|
||||
_, err = kb.Get("john")
|
||||
require.Error(t, err)
|
||||
|
||||
// import armored key
|
||||
require.NoError(t, kb.ImportPrivKey("john", armored, "new_secretcpw"))
|
||||
|
||||
// ensure old and new keys match
|
||||
priv2, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv1.GetPubKey().Equals(priv2.GetPubKey()))
|
||||
}
|
||||
|
||||
func TestInMemoryExportImportPubKey(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
// CreateMnemonic a private-public key pair and ensure consistency
|
||||
notPasswd := "n9y25ah7"
|
||||
info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, info, "")
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
john, err := cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := cstore.ExportPubKey("john")
|
||||
require.NoError(t, err)
|
||||
// Import it under a different name
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NoError(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := cstore.Get("john-pubkey-only")
|
||||
require.NoError(t, err)
|
||||
// Compare the public keys
|
||||
require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = cstore.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestInMemoryExportPrivateKeyObject(t *testing.T) {
|
||||
kb := NewInMemory()
|
||||
|
||||
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
// export private key object
|
||||
_, err = kb.ExportPrivateKeyObject("john", "invalid")
|
||||
require.NoError(t, err, "%+v", err)
|
||||
exported, err := kb.ExportPrivateKeyObject("john", "secretcpw")
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.True(t, exported.PubKey().Equals(info.GetPubKey()))
|
||||
}
|
||||
|
||||
// TestInMemoryAdvancedKeyManagement verifies update, import, export functionality
|
||||
func TestInMemoryAdvancedKeyManagement(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "old-name", "new name"
|
||||
p1 := nums
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// exporting requires the proper name and passphrase
|
||||
_, err = cstore.Export(n1 + ".notreal")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(" " + n1)
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export(n1 + " ")
|
||||
require.NotNil(t, err)
|
||||
_, err = cstore.Export("")
|
||||
require.NotNil(t, err)
|
||||
exported, err := cstore.Export(n1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// import succeeds
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NoError(t, err)
|
||||
|
||||
// second import fails
|
||||
err = cstore.Import(n2, exported)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestInMemorySeedPhrase verifies restoring from a seed phrase
|
||||
func TestInMemorySeedPhrase(t *testing.T) {
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := NewInMemory()
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "lost-key", "found-again"
|
||||
p1, p2 := nums, foobar
|
||||
|
||||
// make sure key works with initial password
|
||||
info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Equal(t, n1, info.GetName())
|
||||
require.NotEmpty(t, mnemonic)
|
||||
|
||||
// now, let us delete this key
|
||||
err = cstore.Delete(n1, p1, false)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
_, err = cstore.Get(n1)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// let us re-create it from the mnemonic-phrase
|
||||
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
|
||||
hdPath := params.String()
|
||||
newInfo, err := cstore.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n2, newInfo.GetName())
|
||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
|
||||
}
|
||||
|
||||
func ExampleNew() {
|
||||
// Select the encryption and storage for your cryptostore
|
||||
customKeyGenFunc := func(bz []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) {
|
||||
var bzArr [32]byte
|
||||
copy(bzArr[:], bz)
|
||||
return secp256k1.PrivKeySecp256k1(bzArr), nil
|
||||
}
|
||||
cstore := NewInMemory(WithKeygenFunc(customKeyGenFunc))
|
||||
|
||||
sec := Secp256k1
|
||||
|
||||
// Add keys and see they return in alphabetical order
|
||||
bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
// return info here just like in List
|
||||
fmt.Println(bob.GetName())
|
||||
}
|
||||
_, _, _ = cstore.CreateMnemonic("Alice", English, "secret", sec)
|
||||
_, _, _ = cstore.CreateMnemonic("Carl", English, "mitm", sec)
|
||||
info, _ := cstore.List()
|
||||
for _, i := range info {
|
||||
fmt.Println(i.GetName())
|
||||
}
|
||||
|
||||
// We need to use passphrase to generate a signature
|
||||
tx := []byte("deadbeef")
|
||||
sig, pub, err := cstore.Sign("Bob", "friend", tx)
|
||||
if err != nil {
|
||||
fmt.Println("don't accept real passphrase")
|
||||
}
|
||||
|
||||
// and we can validate the signature with publicly available info
|
||||
binfo, _ := cstore.Get("Bob")
|
||||
if !binfo.GetPubKey().Equals(bob.GetPubKey()) {
|
||||
fmt.Println("Get and Create return different keys")
|
||||
}
|
||||
|
||||
if pub.Equals(binfo.GetPubKey()) {
|
||||
fmt.Println("signed by Bob")
|
||||
}
|
||||
if !pub.VerifyBytes(tx, sig) {
|
||||
fmt.Println("invalid signature")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Bob
|
||||
// Alice
|
||||
// Bob
|
||||
// Carl
|
||||
// signed by Bob
|
||||
}
|
||||
|
||||
func accAddr(info Info) sdk.AccAddress {
|
||||
return (sdk.AccAddress)(info.GetPubKey().Address())
|
||||
}
|
||||
|
||||
var _ tmcrypto.PrivKey = testPriv{}
|
||||
var _ tmcrypto.PubKey = testPub{}
|
||||
var testCdc *amino.Codec
|
||||
|
||||
type testPriv []byte
|
||||
|
||||
func (privkey testPriv) PubKey() tmcrypto.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 tmcrypto.PrivKey) bool { return true }
|
||||
|
||||
type testPub []byte
|
||||
|
||||
func (key testPub) Address() tmcrypto.Address { return tmcrypto.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 tmcrypto.PubKey) bool { return true }
|
||||
|
||||
func TestInMemoryKeygenOverride(t *testing.T) {
|
||||
// Save existing codec and reset after test
|
||||
cryptoCdc := CryptoCdc
|
||||
t.Cleanup(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 []byte, algo SigningAlgo) (tmcrypto.PrivKey, error) {
|
||||
overrideCalled = true
|
||||
return testPriv(bz), nil
|
||||
}
|
||||
|
||||
kb := NewInMemory(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()))
|
||||
}
|
||||
|
||||
@ -1,221 +0,0 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var _ Keybase = lazyKeybase{}
|
||||
|
||||
// NOTE: lazyKeybase will be deprecated in favor of lazyKeybaseKeyring.
|
||||
type lazyKeybase struct {
|
||||
name string
|
||||
dir string
|
||||
options []KeybaseOption
|
||||
}
|
||||
|
||||
// New creates a new instance of a lazy keybase.
|
||||
func New(name, dir string, opts ...KeybaseOption) Keybase {
|
||||
if err := tmos.EnsureDir(dir, 0700); err != nil {
|
||||
panic(fmt.Sprintf("failed to create Keybase directory: %s", err))
|
||||
}
|
||||
|
||||
return lazyKeybase{name: name, dir: dir, options: opts}
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) List() ([]Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).List()
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Get(name string) (Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).Get(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).GetByAddress(address)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).Delete(name, passphrase, skipPass)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
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) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).CreateMnemonic(name, language, passwd, algo)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo) (Info, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db,
|
||||
lkb.options...).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).CreateLedger(name, algo, hrp, account, index)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey, algo SigningAlgo) (info Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).CreateOffline(name, pubkey, algo)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).CreateMulti(name, pubkey)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).Update(name, oldpass, getNewpass)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Import(name string, armor string) (err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).Import(name, armor)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).ImportPrivKey(name, armor, passphrase)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).ImportPubKey(name, armor)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) Export(name string) (armor string, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).Export(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).ExportPubKey(name)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).ExportPrivateKeyObject(name, passphrase)
|
||||
}
|
||||
|
||||
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||
encryptPassphrase string) (armor string, err error) {
|
||||
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return newDBKeybase(db, lkb.options...).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
|
||||
}
|
||||
|
||||
// SupportedAlgos returns a list of supported signing algorithms.
|
||||
func (lkb lazyKeybase) SupportedAlgos() []SigningAlgo {
|
||||
return newBaseKeybase(lkb.options...).SupportedAlgos()
|
||||
}
|
||||
|
||||
// SupportedAlgosLedger returns a list of supported ledger signing algorithms.
|
||||
func (lkb lazyKeybase) SupportedAlgosLedger() []SigningAlgo {
|
||||
return newBaseKeybase(lkb.options...).SupportedAlgosLedger()
|
||||
}
|
||||
@ -1,453 +0,0 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
lazykb, ok := kb.(lazyKeybase)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, lazykb.name, "keybasename")
|
||||
require.Equal(t, lazykb.dir, dir)
|
||||
}
|
||||
|
||||
func TestLazyKeyManagement(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2, n3 := "personal", "business", "other"
|
||||
p1, p2 := nums, "really-secure!@#$"
|
||||
|
||||
// Check empty state
|
||||
l, err := kb.List()
|
||||
require.Nil(t, err)
|
||||
assert.Empty(t, l)
|
||||
|
||||
_, _, err = kb.CreateMnemonic(n1, English, p1, Ed25519)
|
||||
require.Error(t, err, "ed25519 keys are currently not supported by keybase")
|
||||
|
||||
// create some keys
|
||||
_, err = kb.Get(n1)
|
||||
require.Error(t, err)
|
||||
i, _, err := kb.CreateMnemonic(n1, English, p1, algo)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n1, i.GetName())
|
||||
_, _, err = kb.CreateMnemonic(n2, English, p2, algo)
|
||||
require.NoError(t, err)
|
||||
|
||||
// we can get these keys
|
||||
i2, err := kb.Get(n2)
|
||||
require.NoError(t, err)
|
||||
_, err = kb.Get(n3)
|
||||
require.NotNil(t, err)
|
||||
_, err = kb.GetByAddress(accAddr(i2))
|
||||
require.NoError(t, err)
|
||||
addr, err := sdk.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t")
|
||||
require.NoError(t, err)
|
||||
_, err = kb.GetByAddress(addr)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// list shows them in order
|
||||
keyS, err := kb.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
// note these are in alphabetical order
|
||||
require.Equal(t, n2, keyS[0].GetName())
|
||||
require.Equal(t, n1, keyS[1].GetName())
|
||||
require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey())
|
||||
|
||||
// deleting a key removes it
|
||||
err = kb.Delete("bad name", "foo", false)
|
||||
require.NotNil(t, err)
|
||||
err = kb.Delete(n1, p1, false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = kb.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
_, err = kb.Get(n1)
|
||||
require.Error(t, err)
|
||||
|
||||
// create an offline key
|
||||
o1 := "offline"
|
||||
priv1 := ed25519.GenPrivKey()
|
||||
pub1 := priv1.PubKey()
|
||||
i, err = kb.CreateOffline(o1, pub1, algo)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pub1, i.GetPubKey())
|
||||
require.Equal(t, o1, i.GetName())
|
||||
keyS, err = kb.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keyS))
|
||||
|
||||
// delete the offline key
|
||||
err = kb.Delete(o1, "", false)
|
||||
require.NoError(t, err)
|
||||
keyS, err = kb.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keyS))
|
||||
|
||||
// addr cache gets nuked - and test skip flag
|
||||
err = kb.Delete(n2, "", true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLazySignVerify(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
algo := Secp256k1
|
||||
|
||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||
p1, p2, p3 := nums, foobar, foobar
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := kb.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
i2, _, err := kb.CreateMnemonic(n2, English, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := kb.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
err = kb.ImportPubKey(n3, armor)
|
||||
require.NoError(t, err)
|
||||
i3, err := kb.Get(n3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, i3.GetName(), n3)
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
d3 := []byte("feels like I forgot something...")
|
||||
|
||||
// try signing both data with both ..
|
||||
s11, pub1, err := kb.Sign(n1, p1, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s12, pub1, err := kb.Sign(n1, p1, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i1.GetPubKey(), pub1)
|
||||
|
||||
s21, pub2, err := kb.Sign(n2, p2, d1)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
s22, pub2, err := kb.Sign(n2, p2, d2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i2.GetPubKey(), pub2)
|
||||
|
||||
// let's try to validate and make sure it only works when everything is proper
|
||||
cases := []struct {
|
||||
key crypto.PubKey
|
||||
data []byte
|
||||
sig []byte
|
||||
valid bool
|
||||
}{
|
||||
// proper matches
|
||||
{i1.GetPubKey(), d1, s11, true},
|
||||
// change data, pubkey, or signature leads to fail
|
||||
{i1.GetPubKey(), d2, s11, false},
|
||||
{i2.GetPubKey(), d1, s11, false},
|
||||
{i1.GetPubKey(), d1, s21, false},
|
||||
// make sure other successes
|
||||
{i1.GetPubKey(), d2, s12, true},
|
||||
{i2.GetPubKey(), d1, s21, true},
|
||||
{i2.GetPubKey(), d2, s22, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
require.Equal(t, tc.valid, valid, "%d", i)
|
||||
}
|
||||
|
||||
// Now try to sign data with a secret-less key
|
||||
_, _, err = kb.Sign(n3, p3, d3)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLazyExportImport(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
john, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
johnAddr := info.GetPubKey().Address()
|
||||
|
||||
armor, err := kb.Export("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = kb.Import("john2", armor)
|
||||
require.NoError(t, err)
|
||||
|
||||
john2, err := kb.Get("john2")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, john.GetPubKey().Address(), johnAddr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
func TestLazyExportImportPrivKey(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
priv1, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
|
||||
// decrypt local private key, and produce encrypted ASCII armored output
|
||||
armored, err := kb.ExportPrivKey("john", "secretcpw", "new_secretcpw")
|
||||
require.NoError(t, err)
|
||||
|
||||
// delete exported key
|
||||
require.NoError(t, kb.Delete("john", "", true))
|
||||
_, err = kb.Get("john")
|
||||
require.Error(t, err)
|
||||
|
||||
// import armored key
|
||||
require.NoError(t, kb.ImportPrivKey("john", armored, "new_secretcpw"))
|
||||
|
||||
// ensure old and new keys match
|
||||
priv2, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.True(t, priv1.GetPubKey().Equals(priv2.GetPubKey()))
|
||||
}
|
||||
|
||||
func TestLazyExportImportPubKey(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
algo := Secp256k1
|
||||
|
||||
// CreateMnemonic a private-public key pair and ensure consistency
|
||||
notPasswd := "n9y25ah7"
|
||||
info, _, err := kb.CreateMnemonic("john", English, notPasswd, algo)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, info, "")
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
addr := info.GetPubKey().Address()
|
||||
john, err := kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := kb.ExportPubKey("john")
|
||||
require.NoError(t, err)
|
||||
// Import it under a different name
|
||||
err = kb.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NoError(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := kb.Get("john-pubkey-only")
|
||||
require.NoError(t, err)
|
||||
// Compare the public keys
|
||||
require.True(t, john.GetPubKey().Equals(john2.GetPubKey()))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = kb.Get("john")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, john.GetPubKey().Address(), addr)
|
||||
require.Equal(t, john.GetName(), "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = kb.ImportPubKey("john-pubkey-only", armor)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLazyExportPrivateKeyObject(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, info.GetName(), "john")
|
||||
|
||||
// export private key object
|
||||
_, err = kb.ExportPrivateKeyObject("john", "invalid")
|
||||
require.NotNil(t, err, "%+v", err)
|
||||
exported, err := kb.ExportPrivateKeyObject("john", "secretcpw")
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.True(t, exported.PubKey().Equals(info.GetPubKey()))
|
||||
}
|
||||
|
||||
func TestLazyAdvancedKeyManagement(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "old-name", "new name"
|
||||
p1, p2 := nums, foobar
|
||||
|
||||
// make sure key works with initial password
|
||||
_, _, err := kb.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
assertPassword(t, kb, n1, p1, p2)
|
||||
|
||||
// update password requires the existing password
|
||||
getNewpass := func() (string, error) { return p2, nil }
|
||||
err = kb.Update(n1, "jkkgkg", getNewpass)
|
||||
require.NotNil(t, err)
|
||||
assertPassword(t, kb, n1, p1, p2)
|
||||
|
||||
// then it changes the password when correct
|
||||
err = kb.Update(n1, p1, getNewpass)
|
||||
require.NoError(t, err)
|
||||
// p2 is now the proper one!
|
||||
assertPassword(t, kb, n1, p2, p1)
|
||||
|
||||
// exporting requires the proper name and passphrase
|
||||
_, err = kb.Export(n1 + ".notreal")
|
||||
require.NotNil(t, err)
|
||||
_, err = kb.Export(" " + n1)
|
||||
require.NotNil(t, err)
|
||||
_, err = kb.Export(n1 + " ")
|
||||
require.NotNil(t, err)
|
||||
_, err = kb.Export("")
|
||||
require.NotNil(t, err)
|
||||
exported, err := kb.Export(n1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
|
||||
// import succeeds
|
||||
err = kb.Import(n2, exported)
|
||||
require.NoError(t, err)
|
||||
|
||||
// second import fails
|
||||
err = kb.Import(n2, exported)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestSeedPhrase verifies restoring from a seed phrase
|
||||
func TestLazySeedPhrase(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb := New("keybasename", dir)
|
||||
|
||||
algo := Secp256k1
|
||||
n1, n2 := "lost-key", "found-again"
|
||||
p1, p2 := nums, foobar
|
||||
|
||||
// make sure key works with initial password
|
||||
info, mnemonic, err := kb.CreateMnemonic(n1, English, p1, algo)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Equal(t, n1, info.GetName())
|
||||
assert.NotEmpty(t, mnemonic)
|
||||
|
||||
// now, let us delete this key
|
||||
err = kb.Delete(n1, p1, false)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
_, err = kb.Get(n1)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// let us re-create it from the mnemonic-phrase
|
||||
params := *hd.NewFundraiserParams(0, sdk.CoinType, 0)
|
||||
hdPath := params.String()
|
||||
newInfo, err := kb.CreateAccount(n2, mnemonic, DefaultBIP39Passphrase, p2, hdPath, algo)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n2, newInfo.GetName())
|
||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
require.Equal(t, info.GetPubKey(), newInfo.GetPubKey())
|
||||
}
|
||||
|
||||
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)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
// Save existing codec and reset after test
|
||||
cryptoCdc := CryptoCdc
|
||||
t.Cleanup(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 []byte, algo SigningAlgo) (crypto.PrivKey, error) {
|
||||
overrideCalled = true
|
||||
return testPriv(bz), nil
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
188
crypto/keyring/legacy.go
Normal file
188
crypto/keyring/legacy.go
Normal file
@ -0,0 +1,188 @@
|
||||
package keyring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// LegacyKeybase is implemented by the legacy keybase implementation.
|
||||
type LegacyKeybase interface {
|
||||
List() ([]Info, error)
|
||||
Export(name string) (armor string, err error)
|
||||
ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error)
|
||||
ExportPubKey(name string) (armor string, err error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NewLegacy creates a new instance of a legacy keybase.
|
||||
func NewLegacy(name, dir string, opts ...KeybaseOption) (LegacyKeybase, error) {
|
||||
if err := tmos.EnsureDir(dir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create Keybase directory: %s", err)
|
||||
}
|
||||
|
||||
db, err := sdk.NewLevelDB(name, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newDBKeybase(db, opts...), nil
|
||||
}
|
||||
|
||||
var _ LegacyKeybase = dbKeybase{}
|
||||
|
||||
// dbKeybase combines encryption and storage implementation to provide a
|
||||
// full-featured key manager.
|
||||
//
|
||||
// NOTE: dbKeybase will be deprecated in favor of keyringKeybase.
|
||||
type dbKeybase struct {
|
||||
db dbm.DB
|
||||
}
|
||||
|
||||
// newDBKeybase creates a new dbKeybase instance using the provided DB for
|
||||
// reading and writing keys.
|
||||
func newDBKeybase(db dbm.DB, opts ...KeybaseOption) dbKeybase {
|
||||
return dbKeybase{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// List returns the keys from storage in alphabetical order.
|
||||
func (kb dbKeybase) List() ([]Info, error) {
|
||||
var res []Info
|
||||
|
||||
iter, err := kb.db.Iterator(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer iter.Close()
|
||||
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
key := string(iter.Key())
|
||||
|
||||
// need to include only keys in storage that have an info suffix
|
||||
if strings.HasSuffix(key, infoSuffix) {
|
||||
info, err := unmarshalInfo(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, info)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get returns the public information about one key.
|
||||
func (kb dbKeybase) Get(name string) (Info, error) {
|
||||
bs, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(bs) == 0 {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, name)
|
||||
}
|
||||
|
||||
return unmarshalInfo(bs)
|
||||
}
|
||||
|
||||
// ExportPrivateKeyObject returns a PrivKey object given the key name and
|
||||
// passphrase. An error is returned if the key does not exist or if the Info for
|
||||
// the key is invalid.
|
||||
func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var priv tmcrypto.PrivKey
|
||||
|
||||
switch i := info.(type) {
|
||||
case localInfo:
|
||||
linfo := i
|
||||
if linfo.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priv, _, err = crypto.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case ledgerInfo, offlineInfo, multiInfo:
|
||||
return nil, errors.New("only works on local private keys")
|
||||
}
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Export(name string) (armor string, err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
|
||||
return crypto.ArmorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format. It retrieves a Info
|
||||
// object by its name and return the public key in a portable format.
|
||||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
bz, err := kb.db.Get(infoKey(name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
|
||||
info, err := unmarshalInfo(bz)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return crypto.ArmorPubKeyBytes(info.GetPubKey().Bytes(), string(info.GetAlgo())), nil
|
||||
}
|
||||
|
||||
// ExportPrivKey returns a private key in ASCII armored format.
|
||||
// It returns an error if the key does not exist or a wrong encryption passphrase
|
||||
// is supplied.
|
||||
func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||
encryptPassphrase string) (armor string, err error) {
|
||||
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
info, err := kb.Get(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return crypto.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
|
||||
}
|
||||
|
||||
// Close the underlying storage.
|
||||
func (kb dbKeybase) Close() error {
|
||||
return kb.db.Close()
|
||||
}
|
||||
|
||||
func infoKey(name string) []byte {
|
||||
return []byte(fmt.Sprintf("%s.%s", name, infoSuffix))
|
||||
}
|
||||
44
crypto/keyring/legacy_test.go
Normal file
44
crypto/keyring/legacy_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package keyring_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
)
|
||||
|
||||
func TestNewLegacyKeyBase(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
kb, err := keyring.NewLegacy("keybasename", dir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, kb.Close())
|
||||
}
|
||||
|
||||
func TestLegacyKeybase(t *testing.T) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
// Backup testdata
|
||||
require.NoError(t, copy.Copy("testdata", dir))
|
||||
|
||||
kb, err := keyring.NewLegacy("keys", filepath.Join(dir, "keys"))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { kb.Close() })
|
||||
|
||||
keys, err := kb.List()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(keys))
|
||||
|
||||
armor, err := kb.ExportPubKey(keys[0].GetName())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, armor)
|
||||
|
||||
armoredInfo, err := kb.Export(keys[0].GetName())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, armoredInfo)
|
||||
}
|
||||
BIN
crypto/keyring/testdata/keys/keys.db/000002.ldb
vendored
Normal file
BIN
crypto/keyring/testdata/keys/keys.db/000002.ldb
vendored
Normal file
Binary file not shown.
1
crypto/keyring/testdata/keys/keys.db/CURRENT
vendored
Normal file
1
crypto/keyring/testdata/keys/keys.db/CURRENT
vendored
Normal file
@ -0,0 +1 @@
|
||||
MANIFEST-000004
|
||||
1
crypto/keyring/testdata/keys/keys.db/CURRENT.bak
vendored
Normal file
1
crypto/keyring/testdata/keys/keys.db/CURRENT.bak
vendored
Normal file
@ -0,0 +1 @@
|
||||
MANIFEST-000000
|
||||
0
crypto/keyring/testdata/keys/keys.db/LOCK
vendored
Normal file
0
crypto/keyring/testdata/keys/keys.db/LOCK
vendored
Normal file
18
crypto/keyring/testdata/keys/keys.db/LOG
vendored
Normal file
18
crypto/keyring/testdata/keys/keys.db/LOG
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
=============== Mar 30, 2020 (CEST) ===============
|
||||
02:07:34.137606 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
|
||||
02:07:34.144547 db@open opening
|
||||
02:07:34.144770 version@stat F·[] S·0B[] Sc·[]
|
||||
02:07:34.145843 db@janitor F·2 G·0
|
||||
02:07:34.145875 db@open done T·1.315251ms
|
||||
02:07:34.335635 db@close closing
|
||||
02:07:34.335736 db@close done T·98.95µs
|
||||
=============== Mar 30, 2020 (CEST) ===============
|
||||
02:08:33.239115 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
|
||||
02:08:33.239264 version@stat F·[] S·0B[] Sc·[]
|
||||
02:08:33.239281 db@open opening
|
||||
02:08:33.239310 journal@recovery F·1
|
||||
02:08:33.239398 journal@recovery recovering @1
|
||||
02:08:33.322008 memdb@flush created L0@2 N·4 S·391B "cos..ess,v4":"run..nfo,v3"
|
||||
02:08:33.323091 version@stat F·[1] S·391B[391B] Sc·[0.25]
|
||||
02:08:33.421979 db@janitor F·3 G·0
|
||||
02:08:33.422153 db@open done T·182.707962ms
|
||||
BIN
crypto/keyring/testdata/keys/keys.db/MANIFEST-000004
vendored
Normal file
BIN
crypto/keyring/testdata/keys/keys.db/MANIFEST-000004
vendored
Normal file
Binary file not shown.
1
go.mod
1
go.mod
@ -14,6 +14,7 @@ require (
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/otiai10/copy v1.1.1
|
||||
github.com/pelletier/go-toml v1.6.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rakyll/statik v0.1.7
|
||||
|
||||
8
go.sum
8
go.sum
@ -297,6 +297,14 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo=
|
||||
github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
|
||||
clkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
@ -14,7 +13,7 @@ import (
|
||||
func GenerateCoinKey() (sdk.AccAddress, string, error) {
|
||||
|
||||
// generate a private key, with recovery phrase
|
||||
info, secret, err := clkeys.NewInMemoryKeyBase().CreateMnemonic(
|
||||
info, secret, err := keyring.NewInMemory().CreateMnemonic(
|
||||
"name", keyring.English, "pass", keyring.Secp256k1)
|
||||
if err != nil {
|
||||
return sdk.AccAddress([]byte{}), "", err
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
@ -17,7 +16,7 @@ func TestGenerateCoinKey(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test creation
|
||||
info, err := keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
|
||||
info, err := keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addr, info.GetAddress())
|
||||
}
|
||||
@ -39,7 +38,7 @@ func TestGenerateSaveCoinKey(t *testing.T) {
|
||||
require.Equal(t, addr, info.GetAddress())
|
||||
|
||||
// Test in-memory recovery
|
||||
info, err = keys.NewInMemoryKeyBase().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
|
||||
info, err = keyring.NewInMemory().CreateAccount("xxx", mnemonic, "", "012345678", keyring.CreateHDPath(0, 0).String(), keyring.Secp256k1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addr, info.GetAddress())
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user