From 305c0ab03d16f03b4d32ba0111ec48b6d990ff6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Toledano?= Date: Thu, 17 Aug 2023 17:53:56 +0200 Subject: [PATCH] feat: import hex keys (#17424) (cherry picked from commit 0e057851cc12b18c5297c05fe009b97d860a6cb0) # Conflicts: # CHANGELOG.md # crypto/keyring/keyring.go # crypto/keyring/keyring_test.go --- CHANGELOG.md | 10 + client/keys/import.go | 23 ++ client/keys/import_test.go | 57 ++++ client/keys/root.go | 1 + client/keys/root_test.go | 2 +- crypto/keyring/keyring.go | 32 +- crypto/keyring/keyring_test.go | 606 +++++++++++++++++++++++++++++++++ 7 files changed, 729 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c2a79626a8..787b8baa616f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,16 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +<<<<<<< HEAD +======= +### Features + +* (keyring) [#17424](https://github.com/cosmos/cosmos-sdk/pull/17424) Allows to import private keys encoded in hex. +* (x/bank) [#16795](https://github.com/cosmos/cosmos-sdk/pull/16852) Add `DenomMetadataByQueryString` query in bank module to support metadata query by query string. +* (baseapp) [#16239](https://github.com/cosmos/cosmos-sdk/pull/16239) Add Gas Limits to allow node operators to resource bound queries. +* (baseapp) [#17393](https://github.com/cosmos/cosmos-sdk/pull/17394) Check BlockID Flag on Votes in `ValidateVoteExtensions` + +>>>>>>> 0e057851c (feat: import hex keys (#17424)) ### Improvements * (x/gov) [#17387](https://github.com/cosmos/cosmos-sdk/pull/17387) Add `MsgSubmitProposal` `SetMsgs` method. diff --git a/client/keys/import.go b/client/keys/import.go index a9d5c185acb7..98ccb6547ff0 100644 --- a/client/keys/import.go +++ b/client/keys/import.go @@ -2,12 +2,16 @@ package keys import ( "bufio" + "fmt" "os" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/version" ) // ImportKeyCommand imports private keys from a keyfile. @@ -38,3 +42,22 @@ func ImportKeyCommand() *cobra.Command { }, } } + +func ImportKeyHexCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "import-hex ", + Short: "Import private keys into the local keybase", + Long: fmt.Sprintf("Import hex encoded private key into the local keybase.\nSupported key-types can be obtained with:\n%s list-key-types", version.AppName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + keyType, _ := cmd.Flags().GetString(flags.FlagKeyType) + return clientCtx.Keyring.ImportPrivKeyHex(args[0], args[1], keyType) + }, + } + cmd.Flags().String(flags.FlagKeyType, string(hd.Secp256k1Type), "private key signing algorithm kind") + return cmd +} diff --git a/client/keys/import_test.go b/client/keys/import_test.go index 5ab732632a51..1ff6adcb489a 100644 --- a/client/keys/import_test.go +++ b/client/keys/import_test.go @@ -115,3 +115,60 @@ HbP+c6JmeJy9JXe2rbbF1QtCX1gLqGcDQPBXiCtFvP7/8wTZtVOPj8vREzhZ9ElO }) } } + +func Test_runImportHexCmd(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig().Codec + testCases := []struct { + name string + keyringBackend string + hexKey string + keyType string + expectError bool + }{ + { + name: "test backend success", + keyringBackend: keyring.BackendTest, + hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf", + keyType: "secp256k1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := ImportKeyHexCommand() + cmd.Flags().AddFlagSet(Commands().PersistentFlags()) + mockIn := testutil.ApplyMockIODiscardOutErr(cmd) + + // Now add a temporary keybase + kbHome := t.TempDir() + kb, err := keyring.New(sdk.KeyringServiceName(), tc.keyringBackend, kbHome, nil, cdc) + require.NoError(t, err) + + clientCtx := client.Context{}. + WithKeyringDir(kbHome). + WithKeyring(kb). + WithInput(mockIn). + WithCodec(cdc) + ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx) + + t.Cleanup(cleanupKeys(t, kb, "keyname1")) + + defer func() { + _ = os.RemoveAll(kbHome) + }() + + cmd.SetArgs([]string{ + "keyname1", tc.hexKey, + fmt.Sprintf("--%s=%s", flags.FlagKeyType, tc.keyType), + fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, tc.keyringBackend), + }) + + err = cmd.ExecuteContext(ctx) + if tc.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/client/keys/root.go b/client/keys/root.go index 6faf2f462ce2..e4c9b5a30b8d 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -42,6 +42,7 @@ The pass backend requires GnuPG: https://gnupg.org/ AddKeyCommand(), ExportKeyCommand(), ImportKeyCommand(), + ImportKeyHexCommand(), ListKeysCmd(), ListKeyTypesCmd(), ShowKeysCmd(), diff --git a/client/keys/root_test.go b/client/keys/root_test.go index 20b3f1a23472..08a2a934ec4e 100644 --- a/client/keys/root_test.go +++ b/client/keys/root_test.go @@ -11,5 +11,5 @@ func TestCommands(t *testing.T) { assert.Assert(t, rootCommands != nil) // Commands are registered - assert.Equal(t, 11, len(rootCommands.Commands())) + assert.Equal(t, 12, len(rootCommands.Commands())) } diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index af0d4f8aae4f..da9d3df6c7f5 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -43,6 +43,8 @@ const ( // temporary pass phrase for exporting a key during a key rename passPhrase = "temp" + // prefix for exported hex private keys + hexPrefix = "0x" ) var ( @@ -113,7 +115,8 @@ type Signer interface { type Importer interface { // ImportPrivKey imports ASCII armored passphrase-encrypted private keys. ImportPrivKey(uid, armor, passphrase string) error - + // ImportPrivKeyHex imports hex encoded keys. + ImportPrivKeyHex(uid, privKey, algoStr string) error // ImportPubKey imports ASCII armored public keys. ImportPubKey(uid string, armor string) error } @@ -333,7 +336,34 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error { return nil } +<<<<<<< HEAD func (ks keystore) ImportPubKey(uid string, armor string) error { +======= +func (ks keystore) ImportPrivKeyHex(uid, privKey, algoStr string) error { + if _, err := ks.Key(uid); err == nil { + return errorsmod.Wrap(ErrOverwriteKey, uid) + } + if privKey[:2] == hexPrefix { + privKey = privKey[2:] + } + decodedPriv, err := hex.DecodeString(privKey) + if err != nil { + return err + } + algo, err := NewSigningAlgoFromString(algoStr, ks.options.SupportedAlgos) + if err != nil { + return err + } + priv := algo.Generate()(decodedPriv) + _, err = ks.writeLocalKey(uid, priv) + if err != nil { + return err + } + return nil +} + +func (ks keystore) ImportPubKey(uid, armor string) error { +>>>>>>> 0e057851c (feat: import hex keys (#17424)) if _, err := ks.Key(uid); err == nil { return fmt.Errorf("cannot overwrite key: %s", uid) } diff --git a/crypto/keyring/keyring_test.go b/crypto/keyring/keyring_test.go index e2d994187752..16a34296eeba 100644 --- a/crypto/keyring/keyring_test.go +++ b/crypto/keyring/keyring_test.go @@ -257,7 +257,613 @@ func TestSignVerifyKeyRing(t *testing.T) { func TestExportImportKeyRing(t *testing.T) { cdc := getCodec() +<<<<<<< HEAD kb, err := New("keybasename", "test", t.TempDir(), nil, cdc) +======= + tests := []struct { + name string + uid string + backend string + encryptPassphrase string + createKey func(keystore2 Keyring) (*Record, string, error) + expectedErr error + }{ + { + name: "correct export", + uid: "correctTest", + backend: BackendTest, + encryptPassphrase: "myPassphrase", + createKey: func(keystore Keyring) (*Record, string, error) { + return keystore.NewMnemonic("correctTest", English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, + hd.Secp256k1) + }, + expectedErr: nil, + }, + { + name: "correct in memory export", + uid: "inMemory", + backend: BackendMemory, + encryptPassphrase: "myPassphrase", + createKey: func(keystore Keyring) (*Record, string, error) { + return keystore.NewMnemonic("inMemory", English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, + hd.Secp256k1) + }, + expectedErr: nil, + }, + { + name: "key is not created", + uid: "noKeyTest", + backend: BackendTest, + encryptPassphrase: "myPassphrase", + createKey: func(keystore Keyring) (*Record, string, error) { + return nil, "", nil + }, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("testExport", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + _, _, err = tt.createKey(kb) + require.NoError(t, err) + _, err = kb.ExportPrivKeyArmor(tt.uid, tt.encryptPassphrase) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} + +func TestImportPrivKey(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + encryptPassphrase string + armor string + expectedErr error + }{ + { + name: "correct import", + uid: "testOne", + backend: BackendTest, + encryptPassphrase: "this passphrase has been used for all test vectors", + armor: "-----BEGIN TENDERMINT PRIVATE KEY-----\nkdf: bcrypt\nsalt: 6BC5D5187F9DF241E1A1243EECFF9C17\ntype: secp256k1\n\nGDPpPfrSVZloiwufbal19fmd75QeiqwToZ949SwmnxxM03qL75xXVf3tTD/BrF4l\nFs14HuhwntDBM2xgZvymTBk2edHlEI20Phv6oC0=\n=/zZh\n-----END TENDERMINT PRIVATE KEY-----", + expectedErr: nil, + }, + { + name: "correct import", + uid: "inMemory", + backend: BackendMemory, + encryptPassphrase: "this passphrase has been used for all test vectors", + armor: "-----BEGIN TENDERMINT PRIVATE KEY-----\nkdf: bcrypt\nsalt: 6BC5D5187F9DF241E1A1243EECFF9C17\ntype: secp256k1\n\nGDPpPfrSVZloiwufbal19fmd75QeiqwToZ949SwmnxxM03qL75xXVf3tTD/BrF4l\nFs14HuhwntDBM2xgZvymTBk2edHlEI20Phv6oC0=\n=/zZh\n-----END TENDERMINT PRIVATE KEY-----", + expectedErr: nil, + }, + { + name: "wrong armor", + uid: "testWrongArmor", + backend: BackendTest, + encryptPassphrase: "this passphrase has been used for all test vectors", + armor: "-----BEGIN TENDERMINT PRIVATE KEY-----\nkdf: bcrypt\nsalt: 7BC5D5187F9DF241E1A1243EECFF9C17\ntype: secp256k1\n\nGDPpPfrSVZloiwufbal19fmd75QeiqwToZ949SwmnxxM03qL75xXVf3tTD/BrF4l\nFs14HuhwntDBM2xgZvymTBk2edHlEI20Phv6oC0=\n=/zZh\n-----END TENDERMINT PRIVATE KEY-----", + expectedErr: sdkerrors.ErrWrongPassword, + }, + { + name: "incorrect passphrase", + uid: "testIncorrectPassphrase", + backend: BackendTest, + encryptPassphrase: "wrong passphrase", + armor: "-----BEGIN TENDERMINT PRIVATE KEY-----\nkdf: bcrypt\nsalt: 6BC5D5187F9DF241E1A1243EECFF9C17\ntype: secp256k1\n\nGDPpPfrSVZloiwufbal19fmd75QeiqwToZ949SwmnxxM03qL75xXVf3tTD/BrF4l\nFs14HuhwntDBM2xgZvymTBk2edHlEI20Phv6oC0=\n=/zZh\n-----END TENDERMINT PRIVATE KEY-----", + expectedErr: sdkerrors.ErrWrongPassword, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("TestExport", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + err = kb.ImportPrivKey(tt.uid, tt.armor, tt.encryptPassphrase) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} + +func TestImportPrivKeyHex(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + hexKey string + algo string + expectedErr error + }{ + { + name: "correct import", + uid: "hexImport", + backend: BackendTest, + hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf", + algo: "secp256k1", + expectedErr: nil, + }, + { + name: "correct import without prefix", + uid: "hexImport", + backend: BackendTest, + hexKey: "a3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf", + algo: "secp256k1", + expectedErr: nil, + }, + { + name: "wrong hex length", + uid: "hexImport", + backend: BackendTest, + hexKey: "0xae57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf", + algo: "secp256k1", + expectedErr: hex.ErrLength, + }, + { + name: "unsupported algo", + uid: "hexImport", + backend: BackendTest, + hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf", + algo: "notSupportedAlgo", + expectedErr: ErrUnsupportedSigningAlgo, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("TestExport", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + err = kb.ImportPrivKeyHex(tt.uid, tt.hexKey, tt.algo) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} + +func TestExportImportPrivKeyArmor(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + userInput io.Reader + encryptPassphrase string + importUID string + importPassphrase string + }{ + { + name: "export import", + uid: "testOne", + backend: BackendTest, + userInput: nil, + encryptPassphrase: "apassphrase", + importUID: "importedKey", + importPassphrase: "apassphrase", + }, + { + name: "memory export import", + uid: "inMemory", + backend: BackendMemory, + userInput: nil, + encryptPassphrase: "apassphrase", + importUID: "importedKey", + importPassphrase: "apassphrase", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("TestExport", tt.backend, t.TempDir(), tt.userInput, cdc) + require.NoError(t, err) + k, _, err := kb.NewMnemonic(tt.uid, English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + require.NotNil(t, k) + require.Equal(t, k.Name, tt.uid) + + record, err := kb.Key(tt.uid) + require.NoError(t, err) + require.Equal(t, record.Name, tt.uid) + key, err := k.GetPubKey() + require.NoError(t, err) + + armor, err := kb.ExportPrivKeyArmor(tt.uid, tt.encryptPassphrase) + require.NoError(t, err) + + // Import while key has not been deleted + err = kb.ImportPrivKey(tt.uid, armor, tt.importPassphrase) + require.Error(t, err) + require.True(t, errors.Is(err, ErrOverwriteKey)) + + err = kb.Delete(tt.uid) + require.NoError(t, err) + + err = kb.ImportPrivKey(tt.importUID, armor, tt.importPassphrase) + require.NoError(t, err) + + importedRecord, err := kb.Key(tt.importUID) + require.NoError(t, err) + require.Equal(t, importedRecord.Name, tt.importUID) + importedKey, err := importedRecord.GetPubKey() + require.NoError(t, err) + + require.Equal(t, key.Address(), importedKey.Address()) + + addr, err := record.GetAddress() + require.NoError(t, err) + importedAddr, err := importedRecord.GetAddress() + require.NoError(t, err) + require.True(t, addr.Equals(importedAddr)) + }) + } +} + +func TestImportExportPrivKeyByAddress(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + passphrase string + importPassphrase string + expectedErr error + }{ + { + name: "correct import export", + uid: "okTest", + backend: BackendTest, + passphrase: "exportKey", + importPassphrase: "exportKey", + expectedErr: nil, + }, + { + name: "correct in memory import export", + uid: "inMemory", + backend: BackendMemory, + passphrase: "exportKey", + importPassphrase: "exportKey", + expectedErr: nil, + }, + { + name: "wrong passphrase import", + uid: "incorrectPass", + backend: BackendTest, + passphrase: "exportKey", + importPassphrase: "incorrectPassphrase", + expectedErr: sdkerrors.ErrWrongPassword, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kr, err := New(t.Name(), tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + + mnemonic, _, err := kr.NewMnemonic(tt.uid, English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + + addr, err := mnemonic.GetAddress() + require.NoError(t, err) + armor, err := kr.ExportPrivKeyArmorByAddress(addr, tt.passphrase) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = kr.ImportPrivKey(tt.uid, armor, tt.passphrase) + require.True(t, errors.Is(err, ErrOverwriteKey)) + + err = kr.Delete(tt.uid) + require.NoError(t, err) + + err = kr.ImportPrivKey(tt.uid, armor, tt.importPassphrase) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} + +func TestExportPubkey(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + exportUID string + getPubkey func(r *Record) (types.PubKey, error) + codec codec.Codec + expectedErr error + }{ + { + name: "correct export", + uid: "correctExport", + backend: BackendTest, + exportUID: "correctExport", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: nil, + }, + { + name: "wrong uid at export", + uid: "wrongUID", + backend: BackendTest, + exportUID: "notAValidUID", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + { + name: "previous space on export uid", + uid: "prefixSpace", + backend: BackendTest, + exportUID: " prefixSpace", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + { + name: "export uid with suffix space", + uid: "suffixSpace", + backend: BackendTest, + exportUID: "suffixSpace ", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + { + name: "correct in memory export", + uid: "inMemory", + backend: BackendMemory, + exportUID: "inMemory", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: nil, + }, + { + name: "in memory wrong uid at export", + uid: "wrongUid", + backend: BackendMemory, + exportUID: "notAValidUid", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + { + name: "in memory previous space on export uid", + uid: "prefixSpace", + backend: BackendMemory, + exportUID: " prefixSpace", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + { + name: "in memory export uid with suffix space", + uid: "suffixSpace", + backend: BackendMemory, + exportUID: "suffixSpace ", + getPubkey: func(r *Record) (types.PubKey, error) { + return r.GetPubKey() + }, + codec: cdc, + expectedErr: sdkerrors.ErrKeyNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("keybase", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + k, _, err := kb.NewMnemonic(tt.uid, English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + require.NotNil(t, k) + _, err = tt.getPubkey(k) + require.NoError(t, err) + _, err = kb.ExportPubKeyArmor(tt.exportUID) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} + +func TestImportPubKey(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + armor string + expectedErr string + }{ + { + name: "correct import", + uid: "correctTest", + backend: BackendTest, + armor: "-----BEGIN TENDERMINT PUBLIC KEY-----\nversion: 0.0.1\ntype: secp256k1\n\nCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOlcgxiZM4cR0LA\nwum483+L6zRnXC6zEKtQ4FEa6z0VrA==\n=CqBG\n-----END TENDERMINT PUBLIC KEY-----", + expectedErr: "", + }, + { + name: "modified armor", + uid: "modified", + backend: BackendTest, + armor: "-----BEGIN TENDERMINT PUBLIC KEY-----\nversion: 0.0.1\ntype: secp256k1\n\nCh8vY29zbW8zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOlcgxiZM4cR0LA\nwum483+L6zRnXC6zEKtQ4FEa6z0VrA==\n=CqBG\n-----END TENDERMINT PUBLIC KEY-----", + expectedErr: "couldn't unarmor bytes: openpgp: invalid data: armor invalid", + }, + { + name: "empty armor", + uid: "empty", + backend: BackendTest, + armor: "", + expectedErr: "couldn't unarmor bytes: EOF", + }, + { + name: "correct in memory import", + uid: "inMemory", + backend: BackendMemory, + armor: "-----BEGIN TENDERMINT PUBLIC KEY-----\nversion: 0.0.1\ntype: secp256k1\n\nCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOlcgxiZM4cR0LA\nwum483+L6zRnXC6zEKtQ4FEa6z0VrA==\n=CqBG\n-----END TENDERMINT PUBLIC KEY-----", + expectedErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("keybasename", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + err = kb.ImportPubKey(tt.uid, tt.armor) + if tt.expectedErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tt.expectedErr) + } + }) + } +} + +func TestExportImportPubKeyKey(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + uid string + backend string + importUID string + }{ + { + name: "complete export import", + uid: "testOne", + backend: BackendTest, + importUID: "importedKey", + }, + { + name: "in memory export import", + uid: "inMemory", + backend: BackendMemory, + importUID: "importedKey", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kb, err := New("keybasename", tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + + k, _, err := kb.NewMnemonic(tt.uid, English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1) + require.Nil(t, err) + require.NotNil(t, k) + require.Equal(t, k.Name, tt.uid) + + key, err := k.GetPubKey() + require.NoError(t, err) + + record, err := kb.Key(tt.uid) + require.NoError(t, err) + require.Equal(t, record.Name, tt.uid) + + pk, err := record.GetPubKey() + require.NoError(t, err) + require.Equal(t, key.Address(), pk.Address()) + + // Export the public key only + armor, err := kb.ExportPubKeyArmor(tt.uid) + require.NoError(t, err) + err = kb.Delete(tt.uid) + require.NoError(t, err) + + // Import it under a different name + err = kb.ImportPubKey(tt.importUID, armor) + require.NoError(t, err) + + // Ensure consistency + record2, err := kb.Key(tt.importUID) + require.NoError(t, err) + key2, err := record2.GetPubKey() + require.NoError(t, err) + + // Compare the public keys + require.True(t, key.Equals(key2)) + + // Ensure keys cannot be overwritten + err = kb.ImportPubKey(tt.importUID, armor) + require.NotNil(t, err) + }) + } +} + +func TestImportExportPubKeyByAddress(t *testing.T) { + cdc := getCodec() + tests := []struct { + name string + backend string + uid string + }{ + { + name: "import export", + backend: BackendTest, + uid: "okTest", + }, + { + name: "in memory import export", + backend: BackendMemory, + uid: "okTest", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kr, err := New(t.Name(), tt.backend, t.TempDir(), nil, cdc) + require.NoError(t, err) + + mnemonic, _, err := kr.NewMnemonic(tt.uid, English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + + addr, err := mnemonic.GetAddress() + require.NoError(t, err) + armor, err := kr.ExportPubKeyArmorByAddress(addr) + require.NoError(t, err) + + // Should fail importing private key on existing key. + err = kr.ImportPubKey(tt.uid, armor) + require.True(t, errors.Is(err, ErrOverwriteKey)) + + err = kr.Delete(tt.uid) + require.NoError(t, err) + + err = kr.ImportPubKey(tt.uid, armor) + require.NoError(t, err) + }) + } +} + +func TestAltKeyring_UnsafeExportPrivKeyHex(t *testing.T) { + cdc := getCodec() + kr, err := New(t.Name(), BackendTest, t.TempDir(), nil, cdc) +>>>>>>> 0e057851c (feat: import hex keys (#17424)) require.NoError(t, err) k, _, err := kb.NewMnemonic("john", English, sdk.FullFundraiserPath, DefaultBIP39Passphrase, hd.Secp256k1)