Skip to content

Commit 0e05785

Browse files
feat: import hex keys (#17424)
1 parent 0e8cef5 commit 0e05785

File tree

7 files changed

+169
-3
lines changed

7 files changed

+169
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4040

4141
### Features
4242

43+
* (keyring) [#17424](https://github.com/cosmos/cosmos-sdk/pull/17424) Allows to import private keys encoded in hex.
4344
* (x/bank) [#16795](https://github.com/cosmos/cosmos-sdk/pull/16852) Add `DenomMetadataByQueryString` query in bank module to support metadata query by query string.
4445
* (baseapp) [#16239](https://github.com/cosmos/cosmos-sdk/pull/16239) Add Gas Limits to allow node operators to resource bound queries.
4546
* (baseapp) [#17393](https://github.com/cosmos/cosmos-sdk/pull/17394) Check BlockID Flag on Votes in `ValidateVoteExtensions`

client/keys/import.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package keys
22

33
import (
44
"bufio"
5+
"fmt"
56
"os"
67

78
"github.com/spf13/cobra"
89

910
"github.com/cosmos/cosmos-sdk/client"
11+
"github.com/cosmos/cosmos-sdk/client/flags"
1012
"github.com/cosmos/cosmos-sdk/client/input"
13+
"github.com/cosmos/cosmos-sdk/crypto/hd"
14+
"github.com/cosmos/cosmos-sdk/version"
1115
)
1216

1317
// ImportKeyCommand imports private keys from a keyfile.
@@ -38,3 +42,22 @@ func ImportKeyCommand() *cobra.Command {
3842
},
3943
}
4044
}
45+
46+
func ImportKeyHexCommand() *cobra.Command {
47+
cmd := &cobra.Command{
48+
Use: "import-hex <name> <hex>",
49+
Short: "Import private keys into the local keybase",
50+
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),
51+
Args: cobra.ExactArgs(2),
52+
RunE: func(cmd *cobra.Command, args []string) error {
53+
clientCtx, err := client.GetClientQueryContext(cmd)
54+
if err != nil {
55+
return err
56+
}
57+
keyType, _ := cmd.Flags().GetString(flags.FlagKeyType)
58+
return clientCtx.Keyring.ImportPrivKeyHex(args[0], args[1], keyType)
59+
},
60+
}
61+
cmd.Flags().String(flags.FlagKeyType, string(hd.Secp256k1Type), "private key signing algorithm kind")
62+
return cmd
63+
}

client/keys/import_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,60 @@ HbP+c6JmeJy9JXe2rbbF1QtCX1gLqGcDQPBXiCtFvP7/8wTZtVOPj8vREzhZ9ElO
115115
})
116116
}
117117
}
118+
119+
func Test_runImportHexCmd(t *testing.T) {
120+
cdc := moduletestutil.MakeTestEncodingConfig().Codec
121+
testCases := []struct {
122+
name string
123+
keyringBackend string
124+
hexKey string
125+
keyType string
126+
expectError bool
127+
}{
128+
{
129+
name: "test backend success",
130+
keyringBackend: keyring.BackendTest,
131+
hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
132+
keyType: "secp256k1",
133+
},
134+
}
135+
136+
for _, tc := range testCases {
137+
t.Run(tc.name, func(t *testing.T) {
138+
cmd := ImportKeyHexCommand()
139+
cmd.Flags().AddFlagSet(Commands().PersistentFlags())
140+
mockIn := testutil.ApplyMockIODiscardOutErr(cmd)
141+
142+
// Now add a temporary keybase
143+
kbHome := t.TempDir()
144+
kb, err := keyring.New(sdk.KeyringServiceName(), tc.keyringBackend, kbHome, nil, cdc)
145+
require.NoError(t, err)
146+
147+
clientCtx := client.Context{}.
148+
WithKeyringDir(kbHome).
149+
WithKeyring(kb).
150+
WithInput(mockIn).
151+
WithCodec(cdc)
152+
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
153+
154+
t.Cleanup(cleanupKeys(t, kb, "keyname1"))
155+
156+
defer func() {
157+
_ = os.RemoveAll(kbHome)
158+
}()
159+
160+
cmd.SetArgs([]string{
161+
"keyname1", tc.hexKey,
162+
fmt.Sprintf("--%s=%s", flags.FlagKeyType, tc.keyType),
163+
fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, tc.keyringBackend),
164+
})
165+
166+
err = cmd.ExecuteContext(ctx)
167+
if tc.expectError {
168+
require.Error(t, err)
169+
} else {
170+
require.NoError(t, err)
171+
}
172+
})
173+
}
174+
}

client/keys/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The pass backend requires GnuPG: https://gnupg.org/
4141
AddKeyCommand(),
4242
ExportKeyCommand(),
4343
ImportKeyCommand(),
44+
ImportKeyHexCommand(),
4445
ListKeysCmd(),
4546
ListKeyTypesCmd(),
4647
ShowKeysCmd(),

client/keys/root_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ func TestCommands(t *testing.T) {
1111
assert.Assert(t, rootCommands != nil)
1212

1313
// Commands are registered
14-
assert.Equal(t, 11, len(rootCommands.Commands()))
14+
assert.Equal(t, 12, len(rootCommands.Commands()))
1515
}

crypto/keyring/keyring.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const (
4545

4646
// temporary pass phrase for exporting a key during a key rename
4747
passPhrase = "temp"
48+
// prefix for exported hex private keys
49+
hexPrefix = "0x"
4850
)
4951

5052
var (
@@ -118,7 +120,8 @@ type Signer interface {
118120
type Importer interface {
119121
// ImportPrivKey imports ASCII armored passphrase-encrypted private keys.
120122
ImportPrivKey(uid, armor, passphrase string) error
121-
123+
// ImportPrivKeyHex imports hex encoded keys.
124+
ImportPrivKeyHex(uid, privKey, algoStr string) error
122125
// ImportPubKey imports ASCII armored public keys.
123126
ImportPubKey(uid, armor string) error
124127
}
@@ -338,6 +341,29 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
338341
return nil
339342
}
340343

344+
func (ks keystore) ImportPrivKeyHex(uid, privKey, algoStr string) error {
345+
if _, err := ks.Key(uid); err == nil {
346+
return errorsmod.Wrap(ErrOverwriteKey, uid)
347+
}
348+
if privKey[:2] == hexPrefix {
349+
privKey = privKey[2:]
350+
}
351+
decodedPriv, err := hex.DecodeString(privKey)
352+
if err != nil {
353+
return err
354+
}
355+
algo, err := NewSigningAlgoFromString(algoStr, ks.options.SupportedAlgos)
356+
if err != nil {
357+
return err
358+
}
359+
priv := algo.Generate()(decodedPriv)
360+
_, err = ks.writeLocalKey(uid, priv)
361+
if err != nil {
362+
return err
363+
}
364+
return nil
365+
}
366+
341367
func (ks keystore) ImportPubKey(uid, armor string) error {
342368
if _, err := ks.Key(uid); err == nil {
343369
return errorsmod.Wrap(ErrOverwriteKey, uid)

crypto/keyring/keyring_test.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,65 @@ func TestImportPrivKey(t *testing.T) {
564564
}
565565
}
566566

567-
func TestExportImportPrivKey(t *testing.T) {
567+
func TestImportPrivKeyHex(t *testing.T) {
568+
cdc := getCodec()
569+
tests := []struct {
570+
name string
571+
uid string
572+
backend string
573+
hexKey string
574+
algo string
575+
expectedErr error
576+
}{
577+
{
578+
name: "correct import",
579+
uid: "hexImport",
580+
backend: BackendTest,
581+
hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
582+
algo: "secp256k1",
583+
expectedErr: nil,
584+
},
585+
{
586+
name: "correct import without prefix",
587+
uid: "hexImport",
588+
backend: BackendTest,
589+
hexKey: "a3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
590+
algo: "secp256k1",
591+
expectedErr: nil,
592+
},
593+
{
594+
name: "wrong hex length",
595+
uid: "hexImport",
596+
backend: BackendTest,
597+
hexKey: "0xae57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
598+
algo: "secp256k1",
599+
expectedErr: hex.ErrLength,
600+
},
601+
{
602+
name: "unsupported algo",
603+
uid: "hexImport",
604+
backend: BackendTest,
605+
hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
606+
algo: "notSupportedAlgo",
607+
expectedErr: ErrUnsupportedSigningAlgo,
608+
},
609+
}
610+
for _, tt := range tests {
611+
t.Run(tt.name, func(t *testing.T) {
612+
kb, err := New("TestExport", tt.backend, t.TempDir(), nil, cdc)
613+
require.NoError(t, err)
614+
err = kb.ImportPrivKeyHex(tt.uid, tt.hexKey, tt.algo)
615+
if tt.expectedErr == nil {
616+
require.NoError(t, err)
617+
} else {
618+
require.Error(t, err)
619+
require.True(t, errors.Is(err, tt.expectedErr))
620+
}
621+
})
622+
}
623+
}
624+
625+
func TestExportImportPrivKeyArmor(t *testing.T) {
568626
cdc := getCodec()
569627
tests := []struct {
570628
name string

0 commit comments

Comments
 (0)