Skip to content

Commit 612f0fe

Browse files
mergify[bot]JulianToledanojulienrbrt
authored
feat: import hex keys (backport #17424) (#17433)
Co-authored-by: Julián Toledano <JulianToledano@users.noreply.github.com> Co-authored-by: Julien Robert <julien@rbrt.fr>
1 parent 72a6397 commit 612f0fe

File tree

7 files changed

+173
-3
lines changed

7 files changed

+173
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
3737

3838
## [Unreleased]
3939

40+
### Features
41+
42+
* (keyring) [#17424](https://github.com/cosmos/cosmos-sdk/pull/17424) Allows to import private keys encoded in hex.
43+
4044
### Improvements
4145

4246
* (x/gov) [#17387](https://github.com/cosmos/cosmos-sdk/pull/17387) Add `MsgSubmitProposal` `SetMsgs` method.

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 := clienttestutil.MakeTestCodec(t)
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("home").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
@@ -42,6 +42,7 @@ The pass backend requires GnuPG: https://gnupg.org/
4242
AddKeyCommand(),
4343
ExportKeyCommand(),
4444
ImportKeyCommand(),
45+
ImportKeyHexCommand(),
4546
ListKeysCmd(),
4647
ListKeyTypesCmd(),
4748
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: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const (
4343

4444
// temporary pass phrase for exporting a key during a key rename
4545
passPhrase = "temp"
46+
// prefix for exported hex private keys
47+
hexPrefix = "0x"
4648
)
4749

4850
var (
@@ -113,7 +115,8 @@ type Signer interface {
113115
type Importer interface {
114116
// ImportPrivKey imports ASCII armored passphrase-encrypted private keys.
115117
ImportPrivKey(uid, armor, passphrase string) error
116-
118+
// ImportPrivKeyHex imports hex encoded keys.
119+
ImportPrivKeyHex(uid, privKey, algoStr string) error
117120
// ImportPubKey imports ASCII armored public keys.
118121
ImportPubKey(uid string, armor string) error
119122
}
@@ -333,7 +336,30 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
333336
return nil
334337
}
335338

336-
func (ks keystore) ImportPubKey(uid string, armor string) error {
339+
func (ks keystore) ImportPrivKeyHex(uid, privKey, algoStr string) error {
340+
if _, err := ks.Key(uid); err == nil {
341+
return fmt.Errorf("cannot overwrite key: %s", uid)
342+
}
343+
if privKey[:2] == hexPrefix {
344+
privKey = privKey[2:]
345+
}
346+
decodedPriv, err := hex.DecodeString(privKey)
347+
if err != nil {
348+
return err
349+
}
350+
algo, err := NewSigningAlgoFromString(algoStr, ks.options.SupportedAlgos)
351+
if err != nil {
352+
return err
353+
}
354+
priv := algo.Generate()(decodedPriv)
355+
_, err = ks.writeLocalKey(uid, priv)
356+
if err != nil {
357+
return err
358+
}
359+
return nil
360+
}
361+
362+
func (ks keystore) ImportPubKey(uid, armor string) error {
337363
if _, err := ks.Key(uid); err == nil {
338364
return fmt.Errorf("cannot overwrite key: %s", uid)
339365
}

crypto/keyring/keyring_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/99designs/keyring"
1212
"github.com/cosmos/go-bip39"
13+
"github.com/pkg/errors"
1314
"github.com/stretchr/testify/require"
1415

1516
"github.com/cosmos/cosmos-sdk/codec"
@@ -1449,6 +1450,64 @@ func TestRenameKey(t *testing.T) {
14491450
}
14501451
}
14511452

1453+
func TestImportPrivKeyHex(t *testing.T) {
1454+
cdc := getCodec()
1455+
tests := []struct {
1456+
name string
1457+
uid string
1458+
backend string
1459+
hexKey string
1460+
algo string
1461+
expectedErr error
1462+
}{
1463+
{
1464+
name: "correct import",
1465+
uid: "hexImport",
1466+
backend: BackendTest,
1467+
hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
1468+
algo: "secp256k1",
1469+
expectedErr: nil,
1470+
},
1471+
{
1472+
name: "correct import without prefix",
1473+
uid: "hexImport",
1474+
backend: BackendTest,
1475+
hexKey: "a3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
1476+
algo: "secp256k1",
1477+
expectedErr: nil,
1478+
},
1479+
{
1480+
name: "wrong hex length",
1481+
uid: "hexImport",
1482+
backend: BackendTest,
1483+
hexKey: "0xae57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
1484+
algo: "secp256k1",
1485+
expectedErr: hex.ErrLength,
1486+
},
1487+
{
1488+
name: "unsupported algo",
1489+
uid: "hexImport",
1490+
backend: BackendTest,
1491+
hexKey: "0xa3e57952e835ed30eea86a2993ac2a61c03e74f2085b3635bd94aa4d7ae0cfdf",
1492+
algo: "notSupportedAlgo",
1493+
expectedErr: errors.New("provided algorithm \"notSupportedAlgo\" is not supported"),
1494+
},
1495+
}
1496+
for _, tt := range tests {
1497+
t.Run(tt.name, func(t *testing.T) {
1498+
kb, err := New("TestExport", tt.backend, t.TempDir(), nil, cdc)
1499+
require.NoError(t, err)
1500+
err = kb.ImportPrivKeyHex(tt.uid, tt.hexKey, tt.algo)
1501+
if tt.expectedErr == nil {
1502+
require.NoError(t, err)
1503+
} else {
1504+
require.Error(t, err)
1505+
require.ErrorContains(t, err, tt.expectedErr.Error())
1506+
}
1507+
})
1508+
}
1509+
}
1510+
14521511
func requireEqualRenamedKey(t *testing.T, key *Record, mnemonic *Record, nameMatch bool) {
14531512
if nameMatch {
14541513
require.Equal(t, key.Name, mnemonic.Name)

0 commit comments

Comments
 (0)