-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add keys import * add keys import * add to keys
- Loading branch information
1 parent
4e75fed
commit 0425d5c
Showing
5 changed files
with
299 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package keys | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils" | ||
"github.com/Layr-Labs/eigensdk-go/crypto/bls" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func ImportCmd(p utils.Prompter) *cli.Command { | ||
importCmd := &cli.Command{ | ||
Name: "import", | ||
Usage: "Used to import existing keys in local keystore", | ||
UsageText: "import --key-type <key-type> [flags] <keyname> <private-key>", | ||
Description: ` | ||
Used to import ecdsa and bls key in local keystore | ||
keyname (required) - This will be the name of the imported key file. It will be saved as <keyname>.ecdsa.key.json or <keyname>.bls.key.json | ||
use --key-type ecdsa/bls to import ecdsa/bls key. | ||
- ecdsa - <private-key> should be plaintext hex encoded private key | ||
- bls - <private-key> should be plaintext bls private key | ||
It will prompt for password to encrypt the key, which is optional but highly recommended. | ||
If you want to import a key with weak/no password, use --insecure flag. Do NOT use those keys in production | ||
This command will import keys in $HOME/.eigenlayer/operator_keys/ location | ||
`, | ||
Flags: []cli.Flag{ | ||
&KeyTypeFlag, | ||
&InsecureFlag, | ||
}, | ||
|
||
Action: func(ctx *cli.Context) error { | ||
args := ctx.Args() | ||
if args.Len() != 2 { | ||
return fmt.Errorf("%w: accepts 2 arg, received %d", ErrInvalidNumberOfArgs, args.Len()) | ||
} | ||
|
||
keyName := args.Get(0) | ||
if err := validateKeyName(keyName); err != nil { | ||
return err | ||
} | ||
|
||
privateKey := args.Get(1) | ||
if err := validatePrivateKey(privateKey); err != nil { | ||
return err | ||
} | ||
|
||
keyType := ctx.String(KeyTypeFlag.Name) | ||
insecure := ctx.Bool(InsecureFlag.Name) | ||
|
||
switch keyType { | ||
case KeyTypeECDSA: | ||
privateKey = strings.TrimPrefix(privateKey, "0x") | ||
privateKeyPair, err := crypto.HexToECDSA(privateKey) | ||
if err != nil { | ||
return err | ||
} | ||
return saveEcdsaKey(keyName, p, privateKeyPair, insecure) | ||
case KeyTypeBLS: | ||
privateKeyBigInt := new(big.Int) | ||
_, ok := privateKeyBigInt.SetString(privateKey, 10) | ||
blsKeyPair := new(bls.KeyPair) | ||
var err error | ||
if ok { | ||
fmt.Println("Importing from large integer") | ||
blsKeyPair, err = bls.NewKeyPairFromString(privateKey) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
// Try to parse as hex | ||
fmt.Println("Importing from hex") | ||
z := new(big.Int) | ||
privateKey = strings.TrimPrefix(privateKey, "0x") | ||
_, ok := z.SetString(privateKey, 16) | ||
if !ok { | ||
return ErrInvalidHexPrivateKey | ||
} | ||
blsKeyPair, err = bls.NewKeyPairFromString(z.String()) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return saveBlsKey(keyName, p, blsKeyPair, insecure) | ||
default: | ||
return ErrInvalidKeyType | ||
} | ||
}, | ||
} | ||
return importCmd | ||
} | ||
|
||
func validatePrivateKey(pk string) error { | ||
if len(pk) == 0 { | ||
return ErrEmptyPrivateKey | ||
} | ||
|
||
if match, _ := regexp.MatchString("\\s", pk); match { | ||
return ErrPrivateKeyContainsWhitespaces | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package keys | ||
|
||
import ( | ||
"context" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/urfave/cli/v2" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/crypto/bls" | ||
|
||
prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks" | ||
"github.com/stretchr/testify/assert" | ||
"go.uber.org/mock/gomock" | ||
) | ||
|
||
func TestImportCmd(t *testing.T) { | ||
homePath, err := os.UserHomeDir() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
args []string | ||
err error | ||
keyPath string | ||
expectedPrivKey string | ||
promptMock func(p *prompterMock.MockPrompter) | ||
}{ | ||
{ | ||
name: "key-name flag not set", | ||
args: []string{}, | ||
err: errors.New("Required flag \"key-type\" not set"), | ||
}, | ||
{ | ||
name: "one argument", | ||
args: []string{"--key-type", "ecdsa", "arg1"}, | ||
err: fmt.Errorf("%w: accepts 2 arg, received 1", ErrInvalidNumberOfArgs), | ||
}, | ||
|
||
{ | ||
name: "more than two argument", | ||
args: []string{"--key-type", "ecdsa", "arg1", "arg2", "arg3"}, | ||
err: fmt.Errorf("%w: accepts 2 arg, received 3", ErrInvalidNumberOfArgs), | ||
}, | ||
{ | ||
name: "empty key name argument", | ||
args: []string{"--key-type", "ecdsa", "", ""}, | ||
err: ErrEmptyKeyName, | ||
}, | ||
{ | ||
name: "keyname with whitespaces", | ||
args: []string{"--key-type", "ecdsa", "hello world", ""}, | ||
err: ErrKeyContainsWhitespaces, | ||
}, | ||
{ | ||
name: "empty private key argument", | ||
args: []string{"--key-type", "ecdsa", "hello", ""}, | ||
err: ErrEmptyPrivateKey, | ||
}, | ||
{ | ||
name: "keyname with whitespaces", | ||
args: []string{"--key-type", "ecdsa", "hello", "hello world"}, | ||
err: ErrPrivateKeyContainsWhitespaces, | ||
}, | ||
{ | ||
name: "invalid key type", | ||
args: []string{"--key-type", "invalid", "hello", "privkey"}, | ||
err: ErrInvalidKeyType, | ||
}, | ||
{ | ||
name: "invalid password based on validation function - ecdsa", | ||
args: []string{"--key-type", "ecdsa", "test", "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6"}, | ||
err: ErrInvalidPassword, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword) | ||
}, | ||
}, | ||
{ | ||
name: "invalid password based on validation function - bls", | ||
args: []string{"--key-type", "bls", "test", "123"}, | ||
err: ErrInvalidPassword, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword) | ||
}, | ||
}, | ||
{ | ||
name: "valid ecdsa key import", | ||
args: []string{"--key-type", "ecdsa", "test", "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6"}, | ||
err: nil, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) | ||
}, | ||
expectedPrivKey: "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6", | ||
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"), | ||
}, | ||
{ | ||
name: "valid ecdsa key import with 0x prefix", | ||
args: []string{"--key-type", "ecdsa", "test", "0x6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6"}, | ||
err: nil, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) | ||
}, | ||
expectedPrivKey: "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6", | ||
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"), | ||
}, | ||
{ | ||
name: "valid bls key import", | ||
args: []string{"--key-type", "bls", "test", "20030410000080487431431153104351076122223465926814327806350179952713280726583"}, | ||
err: nil, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) | ||
}, | ||
expectedPrivKey: "20030410000080487431431153104351076122223465926814327806350179952713280726583", | ||
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"), | ||
}, | ||
{ | ||
name: "valid bls key import for hex key", | ||
args: []string{"--key-type", "bls", "test", "0xfe198b992d97545b3b0174f026f781039f167c13f6d0ce9f511d0d2e973b7f02"}, | ||
err: nil, | ||
promptMock: func(p *prompterMock.MockPrompter) { | ||
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) | ||
}, | ||
expectedPrivKey: "5491383829988096583828972342810831790467090979842721151380259607665538989821", | ||
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"), | ||
}, | ||
{ | ||
name: "invalid bls key import for hex key", | ||
args: []string{"--key-type", "bls", "test", "0xfes"}, | ||
err: ErrInvalidHexPrivateKey, | ||
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"), | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Cleanup(func() { | ||
_ = os.Remove(tt.keyPath) | ||
}) | ||
controller := gomock.NewController(t) | ||
p := prompterMock.NewMockPrompter(controller) | ||
if tt.promptMock != nil { | ||
tt.promptMock(p) | ||
} | ||
|
||
importCmd := ImportCmd(p) | ||
app := cli.NewApp() | ||
|
||
// We do this because the in the parsing of arguments it ignores the first argument | ||
// for commands, so we add a blank string as the first argument | ||
// I suspect it does this because it is expecting the first argument to be the name of the command | ||
// But when we are testing the command, we don't want to have to specify the name of the command | ||
// since we are creating the command ourselves | ||
// https://github.com/urfave/cli/blob/c023d9bc5a3122830c9355a0a8c17137e0c8556f/command.go#L323 | ||
args := append([]string{""}, tt.args...) | ||
cCtx := cli.NewContext(app, nil, &cli.Context{Context: context.Background()}) | ||
err := importCmd.Run(cCtx, args...) | ||
|
||
if tt.err == nil { | ||
assert.NoError(t, err) | ||
_, err := os.Stat(tt.keyPath) | ||
|
||
// Check if the error indicates that the file does not exist | ||
if os.IsNotExist(err) { | ||
assert.Failf(t, "file does not exist", "file %s does not exist", tt.keyPath) | ||
} | ||
|
||
if tt.args[1] == KeyTypeECDSA { | ||
key, err := GetECDSAPrivateKey(tt.keyPath, "") | ||
assert.NoError(t, err) | ||
assert.Equal(t, strings.Trim(tt.args[3], "0x"), hex.EncodeToString(key.D.Bytes())) | ||
} else if tt.args[1] == KeyTypeBLS { | ||
key, err := bls.ReadPrivateKeyFromFile(tt.keyPath, "") | ||
assert.NoError(t, err) | ||
assert.Equal(t, tt.expectedPrivKey, key.PrivKey.String()) | ||
} | ||
} else { | ||
assert.EqualError(t, err, tt.err.Error()) | ||
} | ||
}) | ||
} | ||
} |