Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: import hex keys #17424

Merged
merged 11 commits into from
Aug 17, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### 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`
Expand Down
23 changes: 23 additions & 0 deletions client/keys/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -38,3 +42,22 @@ func ImportKeyCommand() *cobra.Command {
},
}
}

func ImportKeyHexCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "import-hex <name> <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
}
57 changes: 57 additions & 0 deletions client/keys/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
1 change: 1 addition & 0 deletions client/keys/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The pass backend requires GnuPG: https://gnupg.org/
AddKeyCommand(),
ExportKeyCommand(),
ImportKeyCommand(),
ImportKeyHexCommand(),
ListKeysCmd(),
ListKeyTypesCmd(),
ShowKeysCmd(),
Expand Down
2 changes: 1 addition & 1 deletion client/keys/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}
28 changes: 27 additions & 1 deletion crypto/keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const (

// temporary pass phrase for exporting a key during a key rename
passPhrase = "temp"
// prefix for exported hex private keys
hexPrefix = "0x"
)

var (
Expand Down Expand Up @@ -118,7 +120,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, armor string) error
}
Expand Down Expand Up @@ -338,6 +341,29 @@ func (ks keystore) ImportPrivKey(uid, armor, passphrase string) error {
return nil
}

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 {
if _, err := ks.Key(uid); err == nil {
return errorsmod.Wrap(ErrOverwriteKey, uid)
Expand Down
60 changes: 59 additions & 1 deletion crypto/keyring/keyring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,65 @@ func TestImportPrivKey(t *testing.T) {
}
}

func TestExportImportPrivKey(t *testing.T) {
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
Expand Down