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: support bip39 mnemonic for importing keys #212

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions pkg/keys/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ This command will create keys in $HOME/.eigenlayer/operator_keys/ location

switch keyType {
case KeyTypeECDSA:
privateKey, mnemonic, err := generateEcdsaKeyWithMnemonic()
// Passing empty string to generate a new mnemonic
privateKey, mnemonic, err := generateEcdsaKeyWithMnemonic("")
if err != nil {
return err
}
Expand All @@ -98,17 +99,18 @@ This command will create keys in $HOME/.eigenlayer/operator_keys/ location
return createCmd
}

func generateEcdsaKeyWithMnemonic() (*ecdsa.PrivateKey, string, error) {
// Generate entropy for mnemonic
entropy, err := bip39.NewEntropy(128)
if err != nil {
return nil, "", fmt.Errorf("failed to generate entropy: %v", err)
}

// Generate mnemonic
mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", fmt.Errorf("failed to generate mnemonic: %v", err)
func generateEcdsaKeyWithMnemonic(mnemonic string) (*ecdsa.PrivateKey, string, error) {
if mnemonic == "" {
// Generate entropy for mnemonic
entropy, err := bip39.NewEntropy(128)
if err != nil {
return nil, "", fmt.Errorf("failed to generate entropy: %v", err)
}
// Generate mnemonic
mnemonic, err = bip39.NewMnemonic(entropy)
if err != nil {
return nil, "", fmt.Errorf("failed to generate mnemonic: %v", err)
}
}

// Create HD wallet
Expand Down
3 changes: 3 additions & 0 deletions pkg/keys/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ var (
ErrInvalidKeyType = errors.New("invalid key type. key type must be either 'ecdsa' or 'bls'")
ErrInvalidPassword = errors.New("invalid password")
ErrInvalidHexPrivateKey = errors.New("invalid hex private key")
ErrInvalidKeyFormat = errors.New(
"invalid key format. Please provide a single hex encoded private key or a 12-word mnemonic",
)
)
42 changes: 22 additions & 20 deletions pkg/keys/import.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package keys

import (
"crypto/ecdsa"
"fmt"
"math/big"
"regexp"
"strings"

"github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common"
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
Expand Down Expand Up @@ -53,8 +54,13 @@ This command will import keys in $HOME/.eigenlayer/operator_keys/ location
}

privateKey := args.Get(1)
if err := validatePrivateKey(privateKey); err != nil {
return err
if privateKey == "" {
return ErrEmptyPrivateKey
}

pkSlice := strings.Split(privateKey, " ")
if len(pkSlice) != 1 && len(pkSlice) != 12 {
return ErrInvalidKeyFormat
}

// Check if input is available in the pipe and read the password from it
Expand All @@ -65,12 +71,20 @@ This command will import keys in $HOME/.eigenlayer/operator_keys/ location

switch keyType {
case KeyTypeECDSA:
privateKey = common.Trim0x(privateKey)
privateKeyPair, err := crypto.HexToECDSA(privateKey)
if err != nil {
return err
var privateKeyPair *ecdsa.PrivateKey
var err error
if len(pkSlice) == 1 {
privateKey = common.Trim0x(privateKey)
privateKeyPair, err = crypto.HexToECDSA(privateKey)
if err != nil {
return err
}
} else {
privateKeyPair, _, err = generateEcdsaKeyWithMnemonic(privateKey)
if err != nil {
return err
}
}
// TODO: Add support for mnemonic imports
return saveEcdsaKey(keyName, p, privateKeyPair, insecure, stdInPassword, readFromPipe, "")
case KeyTypeBLS:
privateKeyBigInt := new(big.Int)
Expand Down Expand Up @@ -105,15 +119,3 @@ This command will import keys in $HOME/.eigenlayer/operator_keys/ location
}
return importCmd
}

func validatePrivateKey(pk string) error {
if len(pk) == 0 {
return ErrEmptyPrivateKey
}

if match, _ := regexp.MatchString("\\s", pk); match {
return ErrPrivateKeyContainsWhitespaces
}

return nil
}
21 changes: 18 additions & 3 deletions pkg/keys/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/Layr-Labs/eigensdk-go/crypto/bls"

"github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common"
prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -67,7 +66,7 @@ func TestImportCmd(t *testing.T) {
{
name: "keyname with whitespaces",
args: []string{"--key-type", "ecdsa", "hello", "hello world"},
err: ErrPrivateKeyContainsWhitespaces,
err: ErrInvalidKeyFormat,
},
{
name: "invalid key type",
Expand Down Expand Up @@ -127,6 +126,22 @@ func TestImportCmd(t *testing.T) {
expectedPrivKey: "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"),
},
{
name: "valid ecdsa key import with mnemonic",
args: []string{
"--key-type",
"ecdsa",
"test",
"kidney various problem toe ready mass exhibit volume shuffle must glue sketch",
},
err: nil,
promptMock: func(p *prompterMock.MockPrompter) {
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
},
expectedPrivKey: "aee7f88721a86c9e269f50ba9a8675609ee8eef54947827fcdce818d8aafd3b1",
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"),
},
{
name: "valid bls key import",
args: []string{
Expand Down Expand Up @@ -202,7 +217,7 @@ func TestImportCmd(t *testing.T) {
if tt.args[1] == KeyTypeECDSA {
key, err := GetECDSAPrivateKey(tt.keyPath, "")
assert.NoError(t, err)
assert.Equal(t, common.Trim0x(tt.args[3]), hex.EncodeToString(key.D.Bytes()))
assert.Equal(t, tt.expectedPrivKey, hex.EncodeToString(key.D.Bytes()))
} else if tt.args[1] == KeyTypeBLS {
key, err := bls.ReadPrivateKeyFromFile(tt.keyPath, "")
assert.NoError(t, err)
Expand Down
Loading