From 2a7a408d358e1343994e2b016a126b229957ffb4 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Mon, 30 Mar 2020 20:30:50 +0200 Subject: [PATCH] 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. --- CHANGELOG.md | 6 + client/keys/add.go | 9 +- client/keys/add_ledger_test.go | 8 - client/keys/add_test.go | 43 +- client/keys/delete_test.go | 44 +- client/keys/export_test.go | 20 +- client/keys/import_test.go | 19 +- client/keys/list_test.go | 10 - client/keys/migrate.go | 4 +- client/keys/root.go | 1 - client/keys/root_test.go | 2 +- client/keys/show_test.go | 28 - client/keys/testdata/keys/keys.db/000002.ldb | Bin 0 -> 391 bytes client/keys/testdata/keys/keys.db/CURRENT | 1 + client/keys/testdata/keys/keys.db/CURRENT.bak | 1 + client/keys/testdata/keys/keys.db/LOCK | 0 client/keys/testdata/keys/keys.db/LOG | 18 + .../testdata/keys/keys.db/MANIFEST-000004 | Bin 0 -> 237 bytes client/keys/update.go | 55 -- client/keys/update_test.go | 58 -- client/keys/utils.go | 19 +- crypto/keyring/base_keybase.go | 4 +- crypto/keyring/db_keybase.go | 432 ------------ crypto/keyring/doc.go | 9 +- crypto/keyring/keybase.go | 3 - crypto/keyring/keybase_test.go | 495 -------------- crypto/keyring/keyring.go | 22 +- crypto/keyring/keyring_test.go | 631 +++++++++++++++++- crypto/keyring/lazy_keybase.go | 221 ------ crypto/keyring/lazy_keybase_test.go | 453 ------------- crypto/keyring/legacy.go | 188 ++++++ crypto/keyring/legacy_test.go | 44 ++ .../keyring/testdata/keys/keys.db/000002.ldb | Bin 0 -> 391 bytes crypto/keyring/testdata/keys/keys.db/CURRENT | 1 + .../keyring/testdata/keys/keys.db/CURRENT.bak | 1 + crypto/keyring/testdata/keys/keys.db/LOCK | 0 crypto/keyring/testdata/keys/keys.db/LOG | 18 + .../testdata/keys/keys.db/MANIFEST-000004 | Bin 0 -> 237 bytes go.mod | 1 + go.sum | 8 + server/init.go | 3 +- server/init_test.go | 5 +- 42 files changed, 976 insertions(+), 1909 deletions(-) create mode 100644 client/keys/testdata/keys/keys.db/000002.ldb create mode 100644 client/keys/testdata/keys/keys.db/CURRENT create mode 100644 client/keys/testdata/keys/keys.db/CURRENT.bak create mode 100644 client/keys/testdata/keys/keys.db/LOCK create mode 100644 client/keys/testdata/keys/keys.db/LOG create mode 100644 client/keys/testdata/keys/keys.db/MANIFEST-000004 delete mode 100644 client/keys/update.go delete mode 100644 client/keys/update_test.go delete mode 100644 crypto/keyring/db_keybase.go delete mode 100644 crypto/keyring/keybase_test.go delete mode 100644 crypto/keyring/lazy_keybase.go delete mode 100644 crypto/keyring/lazy_keybase_test.go create mode 100644 crypto/keyring/legacy.go create mode 100644 crypto/keyring/legacy_test.go create mode 100644 crypto/keyring/testdata/keys/keys.db/000002.ldb create mode 100644 crypto/keyring/testdata/keys/keys.db/CURRENT create mode 100644 crypto/keyring/testdata/keys/keys.db/CURRENT.bak create mode 100644 crypto/keyring/testdata/keys/keys.db/LOCK create mode 100644 crypto/keyring/testdata/keys/keys.db/LOG create mode 100644 crypto/keyring/testdata/keys/keys.db/MANIFEST-000004 diff --git a/CHANGELOG.md b/CHANGELOG.md index a0356406f9b6..33da257d94ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/client/keys/add.go b/client/keys/add.go index 530431eb808d..1170c714ee3f 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -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 diff --git a/client/keys/add_ledger_test.go b/client/keys/add_ledger_test.go index dd53d1b361ce..b21a0b1850d6 100644 --- a/client/keys/add_ledger_test.go +++ b/client/keys/add_ledger_test.go @@ -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) diff --git a/client/keys/add_test.go b/client/keys/add_test.go index 5276e942d0ce..172d7d9e1ce6 100644 --- a/client/keys/add_test.go +++ b/client/keys/add_test.go @@ -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"})) } diff --git a/client/keys/delete_test.go b/client/keys/delete_test.go index 80da06ad8d95..73db7faecb6a 100644 --- a/client/keys/delete_test.go +++ b/client/keys/delete_test.go @@ -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) diff --git a/client/keys/export_test.go b/client/keys/export_test.go index e7e6fb4915b0..175acdfe92fc 100644 --- a/client/keys/export_test.go +++ b/client/keys/export_test.go @@ -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 - }) - } - - if runningUnattended { - mockIn.Reset("testpass1\ntestpass1\n") - } + t.Cleanup(func() { + kb.Delete("keyname1", "", false) // nolint:errcheck + }) + _, 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"})) } diff --git a/client/keys/import_test.go b/client/keys/import_test.go index 1bcae042cc4d..cad3d6a44d3f 100644 --- a/client/keys/import_test.go +++ b/client/keys/import_test.go @@ -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})) } diff --git a/client/keys/list_test.go b/client/keys/list_test.go index c259793e9b80..d5d4eebcf706 100644 --- a/client/keys/list_test.go +++ b/client/keys/list_test.go @@ -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) diff --git a/client/keys/migrate.go b/client/keys/migrate.go index 2f7f3c95df5b..840db70f10e1 100644 --- a/client/keys/migrate.go +++ b/client/keys/migrate.go @@ -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() diff --git a/client/keys/root.go b/client/keys/root.go index d3df8e046868..801cc065aedd 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -45,7 +45,6 @@ The pass backend requires GnuPG: https://gnupg.org/ ShowKeysCmd(), flags.LineBreak, DeleteKeyCommand(), - UpdateKeyCommand(), ParseKeyStringCommand(), MigrateCommand(), ) diff --git a/client/keys/root_test.go b/client/keys/root_test.go index daafecf7b9fa..29fbe145504c 100644 --- a/client/keys/root_test.go +++ b/client/keys/root_test.go @@ -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) { diff --git a/client/keys/show_test.go b/client/keys/show_test.go index fe1070e1ffb7..cf157c4c588c 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -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") diff --git a/client/keys/testdata/keys/keys.db/000002.ldb b/client/keys/testdata/keys/keys.db/000002.ldb new file mode 100644 index 0000000000000000000000000000000000000000..b36586df3626e4a01631c987005dd5b167ff1da2 GIT binary patch literal 391 zcmey$^r4Z#RxCNcI5)r8Fr~Dl+#<2KsJx`KtSZ&C+#n^bq%tF`u)-ugH7~cY)V$I% zr?5&dF(suawYZp(g#iqTO7lVsQW8s2opV#-y;CcV^fL3(^4ZPAathMRi}EtE%8be? z%1q6&N-NC^O3Kqxat-nfiYqI#QcBBG%aVb{ar!ebvHCgrGpHDX^)kqL7g=R6a7wZ; zGIE+rc**mTQf0Uyr1m=MWxFC%+6+;l*L&_w+s))UMK~hzht#w>p1XUJc$A%Z zPwESPNR;9%PE9T_GBwLK6yWnVVl8E1WG;$fU@+p1Im5hYq0HxtQ@kC!+?#3MRPJ a`@iD$YnizAFo}VL@PqK*4c#iG?zaJJVsO>~ literal 0 HcmV?d00001 diff --git a/client/keys/testdata/keys/keys.db/CURRENT b/client/keys/testdata/keys/keys.db/CURRENT new file mode 100644 index 000000000000..cacca7574c03 --- /dev/null +++ b/client/keys/testdata/keys/keys.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000004 diff --git a/client/keys/testdata/keys/keys.db/CURRENT.bak b/client/keys/testdata/keys/keys.db/CURRENT.bak new file mode 100644 index 000000000000..feda7d6b2481 --- /dev/null +++ b/client/keys/testdata/keys/keys.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000000 diff --git a/client/keys/testdata/keys/keys.db/LOCK b/client/keys/testdata/keys/keys.db/LOCK new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/client/keys/testdata/keys/keys.db/LOG b/client/keys/testdata/keys/keys.db/LOG new file mode 100644 index 000000000000..386101e4fb91 --- /dev/null +++ b/client/keys/testdata/keys/keys.db/LOG @@ -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 diff --git a/client/keys/testdata/keys/keys.db/MANIFEST-000004 b/client/keys/testdata/keys/keys.db/MANIFEST-000004 new file mode 100644 index 0000000000000000000000000000000000000000..557b4bdbbc93fd41441cfe30d1ca16f08f47f344 GIT binary patch literal 237 zcmeykNagJ_21Z7yoYb<^oRlOzr^=Gl^338?=ltA)#G=HK{30f1W>ywfb_S+)X4~Za z;@tdV!<5pJa*M>`qVkf`vZ_?ma)Xq#lFE#%!U~i0)V$onQu9j7oWd%-#FUhx)Z$`B l76veoD=N(kEl5c$Np;RmiT6&eG}6n=OUq|uhDg)i1^~DrOBnzF literal 0 HcmV?d00001 diff --git a/client/keys/update.go b/client/keys/update.go deleted file mode 100644 index 2ca46d43289b..000000000000 --- a/client/keys/update.go +++ /dev/null @@ -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 ", - 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 -} diff --git a/client/keys/update_test.go b/client/keys/update_test.go deleted file mode 100644 index 2c951b7c31f1..000000000000 --- a/client/keys/update_test.go +++ /dev/null @@ -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? -} diff --git a/client/keys/utils.go b/client/keys/utils.go index 4581d6861603..1a6f1d019207 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -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") -} diff --git a/crypto/keyring/base_keybase.go b/crypto/keyring/base_keybase.go index c2f7952b820a..abec03359a86 100644 --- a/crypto/keyring/base_keybase.go +++ b/crypto/keyring/base_keybase.go @@ -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 } diff --git a/crypto/keyring/db_keybase.go b/crypto/keyring/db_keybase.go deleted file mode 100644 index 0ed7cbfd927a..000000000000 --- a/crypto/keyring/db_keybase.go +++ /dev/null @@ -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)) -} diff --git a/crypto/keyring/doc.go b/crypto/keyring/doc.go index 7b09f25ac5e8..369f053b76a3 100644 --- a/crypto/keyring/doc.go +++ b/crypto/keyring/doc.go @@ -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 diff --git a/crypto/keyring/keybase.go b/crypto/keyring/keybase.go index b2f619aff3e6..149069ff50be 100644 --- a/crypto/keyring/keybase.go +++ b/crypto/keyring/keybase.go @@ -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) diff --git a/crypto/keyring/keybase_test.go b/crypto/keyring/keybase_test.go deleted file mode 100644 index e55995db9d31..000000000000 --- a/crypto/keyring/keybase_test.go +++ /dev/null @@ -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()) -} diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index c48e0ba4a9c7..a5b62ba3eef2 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -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)) +} diff --git a/crypto/keyring/keyring_test.go b/crypto/keyring/keyring_test.go index c4c3ee301423..e994f91db0b3 100644 --- a/crypto/keyring/keyring_test.go +++ b/crypto/keyring/keyring_test.go @@ -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())) +} diff --git a/crypto/keyring/lazy_keybase.go b/crypto/keyring/lazy_keybase.go deleted file mode 100644 index c2b5aa972cf3..000000000000 --- a/crypto/keyring/lazy_keybase.go +++ /dev/null @@ -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() -} diff --git a/crypto/keyring/lazy_keybase_test.go b/crypto/keyring/lazy_keybase_test.go deleted file mode 100644 index d639f500c327..000000000000 --- a/crypto/keyring/lazy_keybase_test.go +++ /dev/null @@ -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())) -} diff --git a/crypto/keyring/legacy.go b/crypto/keyring/legacy.go new file mode 100644 index 000000000000..2d61f3155418 --- /dev/null +++ b/crypto/keyring/legacy.go @@ -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)) +} diff --git a/crypto/keyring/legacy_test.go b/crypto/keyring/legacy_test.go new file mode 100644 index 000000000000..1e2b475660b6 --- /dev/null +++ b/crypto/keyring/legacy_test.go @@ -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) +} diff --git a/crypto/keyring/testdata/keys/keys.db/000002.ldb b/crypto/keyring/testdata/keys/keys.db/000002.ldb new file mode 100644 index 0000000000000000000000000000000000000000..b36586df3626e4a01631c987005dd5b167ff1da2 GIT binary patch literal 391 zcmey$^r4Z#RxCNcI5)r8Fr~Dl+#<2KsJx`KtSZ&C+#n^bq%tF`u)-ugH7~cY)V$I% zr?5&dF(suawYZp(g#iqTO7lVsQW8s2opV#-y;CcV^fL3(^4ZPAathMRi}EtE%8be? z%1q6&N-NC^O3Kqxat-nfiYqI#QcBBG%aVb{ar!ebvHCgrGpHDX^)kqL7g=R6a7wZ; zGIE+rc**mTQf0Uyr1m=MWxFC%+6+;l*L&_w+s))UMK~hzht#w>p1XUJc$A%Z zPwESPNR;9%PE9T_GBwLK6yWnVVl8E1WG;$fU@+p1Im5hYq0HxtQ@kC!+?#3MRPJ a`@iD$YnizAFo}VL@PqK*4c#iG?zaJJVsO>~ literal 0 HcmV?d00001 diff --git a/crypto/keyring/testdata/keys/keys.db/CURRENT b/crypto/keyring/testdata/keys/keys.db/CURRENT new file mode 100644 index 000000000000..cacca7574c03 --- /dev/null +++ b/crypto/keyring/testdata/keys/keys.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000004 diff --git a/crypto/keyring/testdata/keys/keys.db/CURRENT.bak b/crypto/keyring/testdata/keys/keys.db/CURRENT.bak new file mode 100644 index 000000000000..feda7d6b2481 --- /dev/null +++ b/crypto/keyring/testdata/keys/keys.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000000 diff --git a/crypto/keyring/testdata/keys/keys.db/LOCK b/crypto/keyring/testdata/keys/keys.db/LOCK new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crypto/keyring/testdata/keys/keys.db/LOG b/crypto/keyring/testdata/keys/keys.db/LOG new file mode 100644 index 000000000000..386101e4fb91 --- /dev/null +++ b/crypto/keyring/testdata/keys/keys.db/LOG @@ -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 diff --git a/crypto/keyring/testdata/keys/keys.db/MANIFEST-000004 b/crypto/keyring/testdata/keys/keys.db/MANIFEST-000004 new file mode 100644 index 0000000000000000000000000000000000000000..557b4bdbbc93fd41441cfe30d1ca16f08f47f344 GIT binary patch literal 237 zcmeykNagJ_21Z7yoYb<^oRlOzr^=Gl^338?=ltA)#G=HK{30f1W>ywfb_S+)X4~Za z;@tdV!<5pJa*M>`qVkf`vZ_?ma)Xq#lFE#%!U~i0)V$onQu9j7oWd%-#FUhx)Z$`B l76veoD=N(kEl5c$Np;RmiT6&eG}6n=OUq|uhDg)i1^~DrOBnzF literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 79db3d450f75..9e6361c8504f 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 5a22393f253f..c3ce5db04216 100644 --- a/go.sum +++ b/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= diff --git a/server/init.go b/server/init.go index 3a410ffed766..054cdb460863 100644 --- a/server/init.go +++ b/server/init.go @@ -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 diff --git a/server/init_test.go b/server/init_test.go index e1c56a26ee49..e51b88effdeb 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -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()) }