Skip to content

Commit e5abcca

Browse files
authoredJul 9, 2024··
move keys command to it's own. add deprecation message to other (#156)
* move keys command to it's own. add deprecation message to other * skip existing tests since they are run in other folder
·
v0.13.2v0.9.0
1 parent 8ab1344 commit e5abcca

File tree

18 files changed

+1289
-1
lines changed

18 files changed

+1289
-1
lines changed
 

‎.github/workflows/integration-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
- name: Import Operator Keys and send funds
4646
run: |
4747
cd eigenlayer-cli
48-
echo "" | ./bin/eigenlayer operator keys import --key-type ecdsa --insecure opr0 ea25637d76e7ddae9dab9bfac7467d76a1e3bf2d67941b267edc60f2b80d9413 | cat
48+
echo "" | ./bin/eigenlayer keys import --key-type ecdsa --insecure opr0 ea25637d76e7ddae9dab9bfac7467d76a1e3bf2d67941b267edc60f2b80d9413 | cat
4949
cast send 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 --value 10ether --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
5050
5151
- name: Register Operator

‎cmd/eigenlayer/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func main() {
3535

3636
app.Commands = append(app.Commands, pkg.OperatorCmd(prompter))
3737
app.Commands = append(app.Commands, pkg.RewardsCmd(prompter))
38+
app.Commands = append(app.Commands, pkg.KeysCmd(prompter))
3839

3940
if err := app.Run(os.Args); err != nil {
4041
_, err := fmt.Fprintln(os.Stderr, err)

‎pkg/keys.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package pkg
2+
3+
import (
4+
"github.com/Layr-Labs/eigenlayer-cli/pkg/keys"
5+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
6+
7+
"github.com/urfave/cli/v2"
8+
)
9+
10+
func KeysCmd(p utils.Prompter) *cli.Command {
11+
var keysCmd = &cli.Command{
12+
Name: "keys",
13+
Usage: "Manage the keys used in EigenLayer ecosystem",
14+
Subcommands: []*cli.Command{
15+
keys.CreateCmd(p),
16+
keys.ListCmd(),
17+
keys.ImportCmd(p),
18+
keys.ExportCmd(p),
19+
},
20+
}
21+
22+
return keysCmd
23+
24+
}

‎pkg/keys/create.go

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
package keys
2+
3+
import (
4+
"crypto/ecdsa"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"regexp"
12+
"strings"
13+
14+
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
15+
16+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
17+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
18+
sdkEcdsa "github.com/Layr-Labs/eigensdk-go/crypto/ecdsa"
19+
"github.com/ethereum/go-ethereum/common/hexutil"
20+
"github.com/ethereum/go-ethereum/crypto"
21+
"github.com/urfave/cli/v2"
22+
passwordvalidator "github.com/wagslane/go-password-validator"
23+
)
24+
25+
const (
26+
OperatorKeystoreSubFolder = ".eigenlayer/operator_keys"
27+
28+
KeyTypeECDSA = "ecdsa"
29+
KeyTypeBLS = "bls"
30+
31+
// MinEntropyBits For password validation
32+
MinEntropyBits = 70
33+
)
34+
35+
func CreateCmd(p utils.Prompter) *cli.Command {
36+
createCmd := &cli.Command{
37+
Name: "create",
38+
Usage: "Used to create encrypted keys in local keystore",
39+
UsageText: "create --key-type <key-type> [flags] <keyname>",
40+
Description: `
41+
Used to create ecdsa and bls key in local keystore
42+
43+
keyname (required) - This will be the name of the created key file. It will be saved as <keyname>.ecdsa.key.json or <keyname>.bls.key.json
44+
45+
use --key-type ecdsa/bls to create ecdsa/bls key.
46+
It will prompt for password to encrypt the key, which is optional but highly recommended.
47+
If you want to create a key with weak/no password, use --insecure flag. Do NOT use those keys in production
48+
49+
This command also support piping the password from stdin.
50+
For example: echo "password" | eigenlayer keys create --key-type ecdsa keyname
51+
52+
This command will create keys in $HOME/.eigenlayer/operator_keys/ location
53+
`,
54+
Flags: []cli.Flag{
55+
&KeyTypeFlag,
56+
&InsecureFlag,
57+
},
58+
After: telemetry.AfterRunAction(),
59+
Action: func(ctx *cli.Context) error {
60+
args := ctx.Args()
61+
if args.Len() != 1 {
62+
return fmt.Errorf("%w: accepts 1 arg, received %d", ErrInvalidNumberOfArgs, args.Len())
63+
}
64+
65+
keyName := args.Get(0)
66+
if err := validateKeyName(keyName); err != nil {
67+
return err
68+
}
69+
70+
// Check if input is available in the pipe and read the password from it
71+
stdInPassword, readFromPipe := utils.GetStdInPassword()
72+
73+
keyType := ctx.String(KeyTypeFlag.Name)
74+
insecure := ctx.Bool(InsecureFlag.Name)
75+
76+
switch keyType {
77+
case KeyTypeECDSA:
78+
privateKey, err := crypto.GenerateKey()
79+
if err != nil {
80+
return err
81+
}
82+
return saveEcdsaKey(keyName, p, privateKey, insecure, stdInPassword, readFromPipe)
83+
case KeyTypeBLS:
84+
blsKeyPair, err := bls.GenRandomBlsKeys()
85+
if err != nil {
86+
return err
87+
}
88+
return saveBlsKey(keyName, p, blsKeyPair, insecure, stdInPassword, readFromPipe)
89+
default:
90+
return ErrInvalidKeyType
91+
}
92+
},
93+
}
94+
return createCmd
95+
}
96+
97+
func validateKeyName(keyName string) error {
98+
if len(keyName) == 0 {
99+
return ErrEmptyKeyName
100+
}
101+
102+
if match, _ := regexp.MatchString("\\s", keyName); match {
103+
return ErrKeyContainsWhitespaces
104+
}
105+
106+
return nil
107+
}
108+
109+
func saveBlsKey(
110+
keyName string,
111+
p utils.Prompter,
112+
keyPair *bls.KeyPair,
113+
insecure bool,
114+
stdInPassword string,
115+
readFromPipe bool,
116+
) error {
117+
homePath, err := os.UserHomeDir()
118+
if err != nil {
119+
return err
120+
}
121+
keyFileName := keyName + ".bls.key.json"
122+
fileLoc := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder, keyFileName))
123+
if checkIfKeyExists(fileLoc) {
124+
return errors.New("key name already exists. Please choose a different name")
125+
}
126+
127+
var password string
128+
if !readFromPipe {
129+
password, err = getPasswordFromPrompt(p, insecure, "Enter password to encrypt the bls private key:")
130+
if err != nil {
131+
return err
132+
}
133+
} else {
134+
password = stdInPassword
135+
if !insecure {
136+
err = validatePassword(password)
137+
if err != nil {
138+
return err
139+
}
140+
}
141+
}
142+
143+
err = keyPair.SaveToFile(fileLoc, password)
144+
if err != nil {
145+
return err
146+
}
147+
148+
privateKeyHex := keyPair.PrivKey.String()
149+
publicKeyHex := keyPair.PubKey.String()
150+
151+
fmt.Printf("\nKey location: %s\nPublic Key: %s\n\n", fileLoc, publicKeyHex)
152+
return displayWithLess(privateKeyHex, KeyTypeBLS)
153+
}
154+
155+
func saveEcdsaKey(
156+
keyName string,
157+
p utils.Prompter,
158+
privateKey *ecdsa.PrivateKey,
159+
insecure bool,
160+
stdInPassword string,
161+
readFromPipe bool,
162+
) error {
163+
homePath, err := os.UserHomeDir()
164+
if err != nil {
165+
return err
166+
}
167+
keyFileName := keyName + ".ecdsa.key.json"
168+
fileLoc := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder, keyFileName))
169+
if checkIfKeyExists(fileLoc) {
170+
return errors.New("key name already exists. Please choose a different name")
171+
}
172+
173+
var password string
174+
if !readFromPipe {
175+
password, err = getPasswordFromPrompt(p, insecure, "Enter password to encrypt the ecdsa private key:")
176+
if err != nil {
177+
return err
178+
}
179+
} else {
180+
password = stdInPassword
181+
if !insecure {
182+
err = validatePassword(password)
183+
if err != nil {
184+
return err
185+
}
186+
}
187+
}
188+
189+
err = sdkEcdsa.WriteKey(fileLoc, privateKey, password)
190+
if err != nil {
191+
return err
192+
}
193+
194+
privateKeyHex := hex.EncodeToString(privateKey.D.Bytes())
195+
196+
publicKey := privateKey.Public()
197+
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
198+
if !ok {
199+
return errors.New("error casting public key to ECDSA public key")
200+
}
201+
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
202+
publicKeyHex := hexutil.Encode(publicKeyBytes)[4:]
203+
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
204+
205+
fmt.Printf("\nKey location: %s\nPublic Key hex: %s\nEthereum Address: %s\n\n", fileLoc, publicKeyHex, address)
206+
return displayWithLess(privateKeyHex, KeyTypeECDSA)
207+
}
208+
209+
func padLeft(str string, length int) string {
210+
for len(str) < length {
211+
str = "0" + str
212+
}
213+
return str
214+
}
215+
216+
func displayWithLess(privateKeyHex string, keyType string) error {
217+
var message, border, keyLine string
218+
tabSpace := " "
219+
220+
// Pad with 0 to match size of 64 bytes
221+
if keyType == KeyTypeECDSA {
222+
privateKeyHex = padLeft(privateKeyHex, 64)
223+
}
224+
keyContent := tabSpace + privateKeyHex + tabSpace
225+
borderLength := len(keyContent) + 4
226+
border = strings.Repeat("/", borderLength)
227+
paddingLine := "//" + strings.Repeat(" ", borderLength-4) + "//"
228+
229+
keyLine = fmt.Sprintf("//%s//", keyContent)
230+
231+
if keyType == KeyTypeECDSA {
232+
message = fmt.Sprintf(`
233+
ECDSA Private Key (Hex):
234+
235+
%s
236+
%s
237+
%s
238+
%s
239+
%s
240+
241+
🔐 Please backup the above private key hex in a safe place 🔒
242+
243+
`, border, paddingLine, keyLine, paddingLine, border)
244+
} else if keyType == KeyTypeBLS {
245+
message = fmt.Sprintf(`
246+
BLS Private Key (Hex):
247+
248+
%s
249+
%s
250+
%s
251+
%s
252+
%s
253+
254+
🔐 Please backup the above private key hex in a safe place 🔒
255+
256+
`, border, paddingLine, keyLine, paddingLine, border)
257+
}
258+
259+
cmd := exec.Command("less", "-R")
260+
cmd.Stdout = os.Stdout
261+
cmd.Stderr = os.Stderr
262+
263+
stdin, err := cmd.StdinPipe()
264+
if err != nil {
265+
return fmt.Errorf("error creating stdin pipe: %w", err)
266+
}
267+
268+
if err := cmd.Start(); err != nil {
269+
return fmt.Errorf("error starting less command: %w", err)
270+
}
271+
272+
if _, err := stdin.Write([]byte(message)); err != nil {
273+
return fmt.Errorf("error writing message to less command: %w", err)
274+
}
275+
276+
if err := stdin.Close(); err != nil {
277+
return fmt.Errorf("error closing stdin pipe: %w", err)
278+
}
279+
280+
if err := cmd.Wait(); err != nil {
281+
return fmt.Errorf("error waiting for less command: %w", err)
282+
}
283+
284+
return nil
285+
}
286+
287+
func getPasswordFromPrompt(p utils.Prompter, insecure bool, prompt string) (string, error) {
288+
password, err := p.InputHiddenString(prompt, "",
289+
func(s string) error {
290+
if insecure {
291+
return nil
292+
}
293+
return validatePassword(s)
294+
},
295+
)
296+
if err != nil {
297+
return "", err
298+
}
299+
_, err = p.InputHiddenString("Please confirm your password:", "",
300+
func(s string) error {
301+
if s != password {
302+
return errors.New("passwords are not matched")
303+
}
304+
return nil
305+
},
306+
)
307+
if err != nil {
308+
return "", err
309+
}
310+
return password, nil
311+
}
312+
313+
func checkIfKeyExists(fileLoc string) bool {
314+
_, err := os.Stat(fileLoc)
315+
return !os.IsNotExist(err)
316+
}
317+
318+
func validatePassword(password string) error {
319+
err := passwordvalidator.Validate(password, MinEntropyBits)
320+
if err != nil {
321+
fmt.Println(
322+
"if you want to create keys for testing with weak/no password, use --insecure flag. Do NOT use those keys in production",
323+
)
324+
}
325+
return err
326+
}

‎pkg/keys/create_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package keys
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/urfave/cli/v2"
12+
13+
prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks"
14+
"github.com/stretchr/testify/assert"
15+
"go.uber.org/mock/gomock"
16+
)
17+
18+
func TestCreateCmd(t *testing.T) {
19+
homePath, err := os.UserHomeDir()
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
tests := []struct {
25+
name string
26+
args []string
27+
err error
28+
keyPath string
29+
promptMock func(p *prompterMock.MockPrompter)
30+
}{
31+
{
32+
name: "key-name flag not set",
33+
args: []string{},
34+
err: errors.New("Required flag \"key-type\" not set"),
35+
},
36+
{
37+
name: "more than one argument",
38+
args: []string{"--key-type", "ecdsa", "arg1", "arg2"},
39+
err: fmt.Errorf("%w: accepts 1 arg, received 2", ErrInvalidNumberOfArgs),
40+
},
41+
{
42+
name: "empty name argument",
43+
args: []string{"--key-type", "ecdsa", ""},
44+
err: ErrEmptyKeyName,
45+
},
46+
{
47+
name: "keyname with whitespaces",
48+
args: []string{"--key-type", "ecdsa", "hello world"},
49+
err: ErrKeyContainsWhitespaces,
50+
},
51+
{
52+
name: "invalid key type",
53+
args: []string{"--key-type", "invalid", "do_not_use_this_name"},
54+
err: ErrInvalidKeyType,
55+
},
56+
{
57+
name: "invalid password based on validation function - ecdsa",
58+
args: []string{"--key-type", "ecdsa", "do_not_use_this_name"},
59+
err: ErrInvalidPassword,
60+
promptMock: func(p *prompterMock.MockPrompter) {
61+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword)
62+
},
63+
},
64+
{
65+
name: "invalid password based on validation function - bls",
66+
args: []string{"--key-type", "bls", "do_not_use_this_name"},
67+
err: ErrInvalidPassword,
68+
promptMock: func(p *prompterMock.MockPrompter) {
69+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword)
70+
},
71+
},
72+
{
73+
name: "valid ecdsa key creation",
74+
args: []string{"--key-type", "ecdsa", "do_not_use_this_name"},
75+
err: nil,
76+
promptMock: func(p *prompterMock.MockPrompter) {
77+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
78+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
79+
},
80+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/do_not_use_this_name.ecdsa.key.json"),
81+
},
82+
{
83+
name: "valid bls key creation",
84+
args: []string{"--key-type", "bls", "do_not_use_this_name"},
85+
err: nil,
86+
promptMock: func(p *prompterMock.MockPrompter) {
87+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
88+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
89+
},
90+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/do_not_use_this_name.bls.key.json"),
91+
},
92+
}
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
t.Cleanup(func() {
96+
_ = os.Remove(tt.keyPath)
97+
})
98+
controller := gomock.NewController(t)
99+
p := prompterMock.NewMockPrompter(controller)
100+
if tt.promptMock != nil {
101+
tt.promptMock(p)
102+
}
103+
104+
createCmd := CreateCmd(p)
105+
app := cli.NewApp()
106+
107+
// We do this because the in the parsing of arguments it ignores the first argument
108+
// for commands, so we add a blank string as the first argument
109+
// I suspect it does this because it is expecting the first argument to be the name of the command
110+
// But when we are testing the command, we don't want to have to specify the name of the command
111+
// since we are creating the command ourselves
112+
// https://github.com/urfave/cli/blob/c023d9bc5a3122830c9355a0a8c17137e0c8556f/command.go#L323
113+
args := append([]string{""}, tt.args...)
114+
115+
cCtx := cli.NewContext(app, nil, &cli.Context{Context: context.Background()})
116+
err := createCmd.Run(cCtx, args...)
117+
118+
if tt.err == nil {
119+
assert.NoError(t, err)
120+
_, err := os.Stat(tt.keyPath)
121+
122+
// Check if the error indicates that the file does not exist
123+
if os.IsNotExist(err) {
124+
assert.Failf(t, "file does not exist", "file %s does not exist", tt.keyPath)
125+
}
126+
} else {
127+
assert.EqualError(t, err, tt.err.Error())
128+
}
129+
})
130+
}
131+
}

‎pkg/keys/error.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package keys
2+
3+
import "errors"
4+
5+
var (
6+
ErrInvalidNumberOfArgs = errors.New("invalid number of arguments")
7+
ErrEmptyKeyName = errors.New("key name cannot be empty")
8+
ErrEmptyPrivateKey = errors.New("private key cannot be empty")
9+
ErrKeyContainsWhitespaces = errors.New("key name cannot contain spaces")
10+
ErrPrivateKeyContainsWhitespaces = errors.New("private key cannot contain spaces")
11+
ErrInvalidKeyType = errors.New("invalid key type. key type must be either 'ecdsa' or 'bls'")
12+
ErrInvalidPassword = errors.New("invalid password")
13+
ErrInvalidHexPrivateKey = errors.New("invalid hex private key")
14+
)

‎pkg/keys/export.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package keys
2+
3+
import (
4+
"encoding/hex"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
11+
12+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
13+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
14+
"github.com/Layr-Labs/eigensdk-go/crypto/ecdsa"
15+
"github.com/urfave/cli/v2"
16+
)
17+
18+
func ExportCmd(p utils.Prompter) *cli.Command {
19+
exportCmd := &cli.Command{
20+
Name: "export",
21+
Usage: "Used to export existing keys from local keystore",
22+
UsageText: "export --key-type <key-type> [flags] [keyname]",
23+
Description: `Used to export ecdsa and bls key from local keystore
24+
25+
keyname - This will be the name of the key to be imported. If the path of keys is
26+
different from default path created by "create"/"import" command, then provide the
27+
full path using --key-path flag.
28+
29+
If both keyname is provided and --key-path flag is provided, then keyname will be used.
30+
31+
use --key-type ecdsa/bls to export ecdsa/bls key.
32+
- ecdsa - exported key should be plaintext hex encoded private key
33+
- bls - exported key should be plaintext bls private key
34+
35+
It will prompt for password to encrypt the key.
36+
37+
This command will import keys from $HOME/.eigenlayer/operator_keys/ location
38+
39+
But if you want it to export from a different location, use --key-path flag`,
40+
41+
Flags: []cli.Flag{
42+
&KeyTypeFlag,
43+
&KeyPathFlag,
44+
},
45+
After: telemetry.AfterRunAction(),
46+
Action: func(c *cli.Context) error {
47+
keyType := c.String(KeyTypeFlag.Name)
48+
49+
keyName := c.Args().Get(0)
50+
51+
keyPath := c.String(KeyPathFlag.Name)
52+
if len(keyPath) == 0 && len(keyName) == 0 {
53+
return errors.New("one of keyname or --key-path is required")
54+
}
55+
56+
if len(keyPath) > 0 && len(keyName) > 0 {
57+
return errors.New("keyname and --key-path both are provided. Please provide only one")
58+
}
59+
60+
filePath, err := getKeyPath(keyPath, keyName, keyType)
61+
if err != nil {
62+
return err
63+
}
64+
65+
confirm, err := p.Confirm("This will show your private key. Are you sure you want to export?")
66+
if err != nil {
67+
return err
68+
}
69+
if !confirm {
70+
return nil
71+
}
72+
73+
password, err := p.InputHiddenString("Enter password to decrypt the key", "", func(s string) error {
74+
return nil
75+
})
76+
if err != nil {
77+
return err
78+
}
79+
fmt.Println("exporting key from: ", filePath)
80+
81+
privateKey, err := getPrivateKey(keyType, filePath, password)
82+
if err != nil {
83+
return err
84+
}
85+
fmt.Println("Private key: ", privateKey)
86+
return nil
87+
},
88+
}
89+
90+
return exportCmd
91+
}
92+
93+
func getPrivateKey(keyType string, filePath string, password string) (string, error) {
94+
switch keyType {
95+
case KeyTypeECDSA:
96+
key, err := ecdsa.ReadKey(filePath, password)
97+
if err != nil {
98+
return "", err
99+
}
100+
return hex.EncodeToString(key.D.Bytes()), nil
101+
case KeyTypeBLS:
102+
key, err := bls.ReadPrivateKeyFromFile(filePath, password)
103+
if err != nil {
104+
return "", err
105+
}
106+
return key.PrivKey.String(), nil
107+
default:
108+
return "", ErrInvalidKeyType
109+
}
110+
}
111+
112+
func getKeyPath(keyPath string, keyName string, keyType string) (string, error) {
113+
homePath, err := os.UserHomeDir()
114+
if err != nil {
115+
return "", err
116+
}
117+
118+
var filePath string
119+
if len(keyName) > 0 {
120+
switch keyType {
121+
case KeyTypeECDSA:
122+
filePath = filepath.Join(homePath, OperatorKeystoreSubFolder, keyName+".ecdsa.key.json")
123+
case KeyTypeBLS:
124+
filePath = filepath.Join(homePath, OperatorKeystoreSubFolder, keyName+".bls.key.json")
125+
default:
126+
return "", ErrInvalidKeyType
127+
}
128+
129+
} else {
130+
filePath = filepath.Clean(keyPath)
131+
}
132+
133+
return filePath, nil
134+
}

‎pkg/keys/export_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package keys
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestGetKeyPath(t *testing.T) {
12+
homePath, err := os.UserHomeDir()
13+
if err != nil {
14+
t.Fatal(err)
15+
}
16+
17+
tests := []struct {
18+
name string
19+
keyType string
20+
keyPath string
21+
keyName string
22+
err error
23+
expectedPath string
24+
}{
25+
{
26+
name: "correct key path using keyname",
27+
keyType: KeyTypeECDSA,
28+
keyName: "test",
29+
err: nil,
30+
expectedPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "test.ecdsa.key.json"),
31+
},
32+
{
33+
name: "correct key path using keypath",
34+
keyType: KeyTypeECDSA,
35+
keyPath: filepath.Join(homePath, "x.json"),
36+
err: nil,
37+
expectedPath: filepath.Join(homePath, "x.json"),
38+
},
39+
}
40+
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
path, err := getKeyPath(tt.keyPath, tt.keyName, tt.keyType)
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
if tt.err != nil {
49+
assert.EqualError(t, err, tt.err.Error())
50+
} else {
51+
assert.Equal(t, tt.expectedPath, path)
52+
}
53+
})
54+
}
55+
}

‎pkg/keys/flags.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package keys
2+
3+
import "github.com/urfave/cli/v2"
4+
5+
var (
6+
KeyTypeFlag = cli.StringFlag{
7+
Name: "key-type",
8+
Aliases: []string{"k"},
9+
Required: true,
10+
Usage: "Type of key you want to create. Currently supports 'ecdsa' and 'bls'",
11+
EnvVars: []string{"KEY_TYPE"},
12+
}
13+
14+
InsecureFlag = cli.BoolFlag{
15+
Name: "insecure",
16+
Aliases: []string{"i"},
17+
Usage: "Use this flag to skip password validation",
18+
EnvVars: []string{"INSECURE"},
19+
}
20+
21+
KeyPathFlag = cli.StringFlag{
22+
Name: "key-path",
23+
Aliases: []string{"p"},
24+
Usage: "Use this flag to specify the path of the key",
25+
EnvVars: []string{"KEY_PATH"},
26+
}
27+
)

‎pkg/keys/import.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package keys
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
10+
11+
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
12+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
13+
"github.com/ethereum/go-ethereum/crypto"
14+
"github.com/urfave/cli/v2"
15+
)
16+
17+
func ImportCmd(p utils.Prompter) *cli.Command {
18+
importCmd := &cli.Command{
19+
Name: "import",
20+
Usage: "Used to import existing keys in local keystore",
21+
UsageText: "import --key-type <key-type> [flags] <keyname> <private-key>",
22+
Description: `
23+
Used to import ecdsa and bls key in local keystore
24+
25+
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
26+
27+
use --key-type ecdsa/bls to import ecdsa/bls key.
28+
- ecdsa - <private-key> should be plaintext hex encoded private key
29+
- bls - <private-key> should be plaintext bls private key
30+
31+
It will prompt for password to encrypt the key, which is optional but highly recommended.
32+
If you want to import a key with weak/no password, use --insecure flag. Do NOT use those keys in production
33+
34+
This command also support piping the password from stdin.
35+
For example: echo "password" | eigenlayer keys import --key-type ecdsa keyname privateKey
36+
37+
This command will import keys in $HOME/.eigenlayer/operator_keys/ location
38+
`,
39+
Flags: []cli.Flag{
40+
&KeyTypeFlag,
41+
&InsecureFlag,
42+
},
43+
After: telemetry.AfterRunAction(),
44+
Action: func(ctx *cli.Context) error {
45+
args := ctx.Args()
46+
if args.Len() != 2 {
47+
return fmt.Errorf("%w: accepts 2 arg, received %d", ErrInvalidNumberOfArgs, args.Len())
48+
}
49+
50+
keyName := args.Get(0)
51+
if err := validateKeyName(keyName); err != nil {
52+
return err
53+
}
54+
55+
privateKey := args.Get(1)
56+
if err := validatePrivateKey(privateKey); err != nil {
57+
return err
58+
}
59+
60+
// Check if input is available in the pipe and read the password from it
61+
stdInPassword, readFromPipe := utils.GetStdInPassword()
62+
63+
keyType := ctx.String(KeyTypeFlag.Name)
64+
insecure := ctx.Bool(InsecureFlag.Name)
65+
66+
switch keyType {
67+
case KeyTypeECDSA:
68+
privateKey = strings.TrimPrefix(privateKey, "0x")
69+
privateKeyPair, err := crypto.HexToECDSA(privateKey)
70+
if err != nil {
71+
return err
72+
}
73+
return saveEcdsaKey(keyName, p, privateKeyPair, insecure, stdInPassword, readFromPipe)
74+
case KeyTypeBLS:
75+
privateKeyBigInt := new(big.Int)
76+
_, ok := privateKeyBigInt.SetString(privateKey, 10)
77+
var blsKeyPair *bls.KeyPair
78+
var err error
79+
if ok {
80+
fmt.Println("Importing from large integer")
81+
blsKeyPair, err = bls.NewKeyPairFromString(privateKey)
82+
if err != nil {
83+
return err
84+
}
85+
} else {
86+
// Try to parse as hex
87+
fmt.Println("Importing from hex")
88+
z := new(big.Int)
89+
privateKey = strings.TrimPrefix(privateKey, "0x")
90+
_, ok := z.SetString(privateKey, 16)
91+
if !ok {
92+
return ErrInvalidHexPrivateKey
93+
}
94+
blsKeyPair, err = bls.NewKeyPairFromString(z.String())
95+
if err != nil {
96+
return err
97+
}
98+
}
99+
return saveBlsKey(keyName, p, blsKeyPair, insecure, stdInPassword, readFromPipe)
100+
default:
101+
return ErrInvalidKeyType
102+
}
103+
},
104+
}
105+
return importCmd
106+
}
107+
108+
func validatePrivateKey(pk string) error {
109+
if len(pk) == 0 {
110+
return ErrEmptyPrivateKey
111+
}
112+
113+
if match, _ := regexp.MatchString("\\s", pk); match {
114+
return ErrPrivateKeyContainsWhitespaces
115+
}
116+
117+
return nil
118+
}

‎pkg/keys/import_test.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package keys
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
"testing"
12+
13+
"github.com/urfave/cli/v2"
14+
15+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
16+
17+
prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks"
18+
"github.com/stretchr/testify/assert"
19+
"go.uber.org/mock/gomock"
20+
)
21+
22+
func TestImportCmd(t *testing.T) {
23+
homePath, err := os.UserHomeDir()
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
tests := []struct {
29+
name string
30+
args []string
31+
err error
32+
keyPath string
33+
expectedPrivKey string
34+
promptMock func(p *prompterMock.MockPrompter)
35+
}{
36+
{
37+
name: "key-name flag not set",
38+
args: []string{},
39+
err: errors.New("Required flag \"key-type\" not set"),
40+
},
41+
{
42+
name: "one argument",
43+
args: []string{"--key-type", "ecdsa", "arg1"},
44+
err: fmt.Errorf("%w: accepts 2 arg, received 1", ErrInvalidNumberOfArgs),
45+
},
46+
47+
{
48+
name: "more than two argument",
49+
args: []string{"--key-type", "ecdsa", "arg1", "arg2", "arg3"},
50+
err: fmt.Errorf("%w: accepts 2 arg, received 3", ErrInvalidNumberOfArgs),
51+
},
52+
{
53+
name: "empty key name argument",
54+
args: []string{"--key-type", "ecdsa", "", ""},
55+
err: ErrEmptyKeyName,
56+
},
57+
{
58+
name: "keyname with whitespaces",
59+
args: []string{"--key-type", "ecdsa", "hello world", ""},
60+
err: ErrKeyContainsWhitespaces,
61+
},
62+
{
63+
name: "empty private key argument",
64+
args: []string{"--key-type", "ecdsa", "hello", ""},
65+
err: ErrEmptyPrivateKey,
66+
},
67+
{
68+
name: "keyname with whitespaces",
69+
args: []string{"--key-type", "ecdsa", "hello", "hello world"},
70+
err: ErrPrivateKeyContainsWhitespaces,
71+
},
72+
{
73+
name: "invalid key type",
74+
args: []string{"--key-type", "invalid", "hello", "privkey"},
75+
err: ErrInvalidKeyType,
76+
},
77+
{
78+
name: "invalid password based on validation function - ecdsa",
79+
args: []string{
80+
"--key-type",
81+
"ecdsa",
82+
"test",
83+
"6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
84+
},
85+
err: ErrInvalidPassword,
86+
promptMock: func(p *prompterMock.MockPrompter) {
87+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword)
88+
},
89+
},
90+
{
91+
name: "invalid password based on validation function - bls",
92+
args: []string{"--key-type", "bls", "test", "123"},
93+
err: ErrInvalidPassword,
94+
promptMock: func(p *prompterMock.MockPrompter) {
95+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword)
96+
},
97+
},
98+
{
99+
name: "valid ecdsa key import",
100+
args: []string{
101+
"--key-type",
102+
"ecdsa",
103+
"test",
104+
"6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
105+
},
106+
err: nil,
107+
promptMock: func(p *prompterMock.MockPrompter) {
108+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
109+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
110+
},
111+
expectedPrivKey: "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
112+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"),
113+
},
114+
{
115+
name: "valid ecdsa key import with 0x prefix",
116+
args: []string{
117+
"--key-type",
118+
"ecdsa",
119+
"test",
120+
"0x6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
121+
},
122+
err: nil,
123+
promptMock: func(p *prompterMock.MockPrompter) {
124+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
125+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
126+
},
127+
expectedPrivKey: "6842fb8f5fa574d0482818b8a825a15c4d68f542693197f2c2497e3562f335f6",
128+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.ecdsa.key.json"),
129+
},
130+
{
131+
name: "valid bls key import",
132+
args: []string{
133+
"--key-type",
134+
"bls",
135+
"test",
136+
"20030410000080487431431153104351076122223465926814327806350179952713280726583",
137+
},
138+
err: nil,
139+
promptMock: func(p *prompterMock.MockPrompter) {
140+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
141+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
142+
},
143+
expectedPrivKey: "20030410000080487431431153104351076122223465926814327806350179952713280726583",
144+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"),
145+
},
146+
{
147+
name: "valid bls key import for hex key",
148+
args: []string{
149+
"--key-type",
150+
"bls",
151+
"test",
152+
"0xfe198b992d97545b3b0174f026f781039f167c13f6d0ce9f511d0d2e973b7f02",
153+
},
154+
err: nil,
155+
promptMock: func(p *prompterMock.MockPrompter) {
156+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
157+
p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil)
158+
},
159+
expectedPrivKey: "5491383829988096583828972342810831790467090979842721151380259607665538989821",
160+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"),
161+
},
162+
{
163+
name: "invalid bls key import for hex key",
164+
args: []string{"--key-type", "bls", "test", "0xfes"},
165+
err: ErrInvalidHexPrivateKey,
166+
keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/test.bls.key.json"),
167+
},
168+
}
169+
for _, tt := range tests {
170+
t.Run(tt.name, func(t *testing.T) {
171+
t.Cleanup(func() {
172+
_ = os.Remove(tt.keyPath)
173+
})
174+
controller := gomock.NewController(t)
175+
p := prompterMock.NewMockPrompter(controller)
176+
if tt.promptMock != nil {
177+
tt.promptMock(p)
178+
}
179+
180+
importCmd := ImportCmd(p)
181+
app := cli.NewApp()
182+
183+
// We do this because the in the parsing of arguments it ignores the first argument
184+
// for commands, so we add a blank string as the first argument
185+
// I suspect it does this because it is expecting the first argument to be the name of the command
186+
// But when we are testing the command, we don't want to have to specify the name of the command
187+
// since we are creating the command ourselves
188+
// https://github.com/urfave/cli/blob/c023d9bc5a3122830c9355a0a8c17137e0c8556f/command.go#L323
189+
args := append([]string{""}, tt.args...)
190+
cCtx := cli.NewContext(app, nil, &cli.Context{Context: context.Background()})
191+
err := importCmd.Run(cCtx, args...)
192+
193+
if tt.err == nil {
194+
assert.NoError(t, err)
195+
_, err := os.Stat(tt.keyPath)
196+
197+
// Check if the error indicates that the file does not exist
198+
if os.IsNotExist(err) {
199+
assert.Failf(t, "file does not exist", "file %s does not exist", tt.keyPath)
200+
}
201+
202+
if tt.args[1] == KeyTypeECDSA {
203+
key, err := GetECDSAPrivateKey(tt.keyPath, "")
204+
assert.NoError(t, err)
205+
assert.Equal(t, strings.Trim(tt.args[3], "0x"), hex.EncodeToString(key.D.Bytes()))
206+
} else if tt.args[1] == KeyTypeBLS {
207+
key, err := bls.ReadPrivateKeyFromFile(tt.keyPath, "")
208+
assert.NoError(t, err)
209+
assert.Equal(t, tt.expectedPrivKey, key.PrivKey.String())
210+
}
211+
} else {
212+
assert.EqualError(t, err, tt.err.Error())
213+
}
214+
})
215+
}
216+
}

‎pkg/keys/list.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package keys
2+
3+
import (
4+
"crypto/ecdsa"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/consensys/gnark-crypto/ecc/bn254"
12+
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
13+
14+
"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"
15+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
16+
"github.com/Layr-Labs/eigensdk-go/types"
17+
18+
"github.com/ethereum/go-ethereum/accounts/keystore"
19+
"github.com/urfave/cli/v2"
20+
)
21+
22+
func ListCmd() *cli.Command {
23+
listCmd := &cli.Command{
24+
Name: "list",
25+
Usage: "List all the keys created by this create command",
26+
UsageText: "list",
27+
Description: `
28+
This command will list both ecdsa and bls key created using create command
29+
30+
It will only list keys created in the default folder (./operator_keys/)
31+
`,
32+
After: telemetry.AfterRunAction(),
33+
Action: func(context *cli.Context) error {
34+
homePath, err := os.UserHomeDir()
35+
if err != nil {
36+
return err
37+
}
38+
keyStorePath := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder))
39+
files, err := os.ReadDir(keyStorePath)
40+
if err != nil {
41+
return err
42+
}
43+
44+
for _, file := range files {
45+
keySplits := strings.Split(file.Name(), ".")
46+
fileName := keySplits[0]
47+
keyType := keySplits[1]
48+
fmt.Println("Key Name: " + fileName)
49+
switch keyType {
50+
case KeyTypeECDSA:
51+
fmt.Println("Key Type: ECDSA")
52+
keyFilePath := filepath.Join(keyStorePath, file.Name())
53+
address, err := GetAddress(filepath.Clean(keyFilePath))
54+
if err != nil {
55+
return err
56+
}
57+
fmt.Println("Address: 0x" + address)
58+
fmt.Println("Key location: " + keyFilePath)
59+
fmt.Println("====================================================================================")
60+
fmt.Println()
61+
case KeyTypeBLS:
62+
fmt.Println("Key Type: BLS")
63+
keyFilePath := filepath.Join(keyStorePath, file.Name())
64+
pubKey, err := GetPubKey(filepath.Clean(keyFilePath))
65+
if err != nil {
66+
return err
67+
}
68+
fmt.Println("Public Key: " + pubKey)
69+
operatorIdStr, err := GetOperatorIdFromBLSPubKey(pubKey)
70+
if err != nil {
71+
return err
72+
}
73+
fmt.Println("Operator Id: 0x" + operatorIdStr)
74+
fmt.Println("Key location: " + keyFilePath)
75+
fmt.Println("====================================================================================")
76+
fmt.Println()
77+
}
78+
79+
}
80+
return nil
81+
},
82+
}
83+
return listCmd
84+
}
85+
86+
func GetPubKey(keyStoreFile string) (string, error) {
87+
keyJson, err := os.ReadFile(keyStoreFile)
88+
if err != nil {
89+
return "", err
90+
}
91+
92+
m := make(map[string]interface{})
93+
if err := json.Unmarshal(keyJson, &m); err != nil {
94+
return "", err
95+
}
96+
97+
if pubKey, ok := m["pubKey"].(string); !ok {
98+
return "", fmt.Errorf("pubKey not found in key file")
99+
} else {
100+
return pubKey, nil
101+
}
102+
}
103+
104+
func GetOperatorIdFromBLSPubKey(pubKey string) (string, error) {
105+
// The pubkey 's string is generated from this code:
106+
// ```go
107+
// func (p *G1Affine) String() string {
108+
// if p.IsInfinity() {
109+
// return "O"
110+
// }
111+
// return "E([" + p.X.String() + "," + p.Y.String() + "])"
112+
// }
113+
// ```
114+
//
115+
// This code just parser this string:
116+
// E([498211989701534593628498974128726712526336918939770789545660245177948853517,19434346619705907282579203143605058653932187676054178921788041096426532277474])
117+
118+
if pubKey == "O" {
119+
return "", fmt.Errorf("pubKey is Infinity")
120+
}
121+
122+
if pubKey[:3] != "E([" && pubKey[len(pubKey)-2:] != "])" {
123+
return "", fmt.Errorf("pubKey format failed by not E([x,y])")
124+
}
125+
126+
pubKeyStr := pubKey[3 : len(pubKey)-2]
127+
strs := strings.Split(pubKeyStr, ",")
128+
if len(strs) != 2 {
129+
return "", fmt.Errorf("pubkey format failed by not x,y")
130+
}
131+
132+
xe, err := new(fp.Element).SetString(strs[0])
133+
if err != nil {
134+
return "", err
135+
}
136+
137+
ye, err := new(fp.Element).SetString(strs[1])
138+
if err != nil {
139+
return "", err
140+
}
141+
142+
point := &bls.G1Point{
143+
G1Affine: &bn254.G1Affine{
144+
X: *xe,
145+
Y: *ye,
146+
},
147+
}
148+
149+
operatorId := types.OperatorIdFromG1Pubkey(point)
150+
151+
return operatorId.LogValue().String(), nil
152+
}
153+
154+
func GetAddress(keyStoreFile string) (string, error) {
155+
keyJson, err := os.ReadFile(keyStoreFile)
156+
if err != nil {
157+
return "", err
158+
}
159+
160+
m := make(map[string]interface{})
161+
if err := json.Unmarshal(keyJson, &m); err != nil {
162+
return "", err
163+
}
164+
165+
if address, ok := m["address"].(string); !ok {
166+
return "", fmt.Errorf("address not found in key file")
167+
} else {
168+
return address, nil
169+
}
170+
}
171+
172+
// GetECDSAPrivateKey - Keeping it right now as we might need this function to export
173+
// the keys
174+
func GetECDSAPrivateKey(keyStoreFile string, password string) (*ecdsa.PrivateKey, error) {
175+
keyStoreContents, err := os.ReadFile(keyStoreFile)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
sk, err := keystore.DecryptKey(keyStoreContents, password)
181+
if err != nil {
182+
return nil, err
183+
}
184+
185+
return sk.PrivateKey, nil
186+
}

‎pkg/keys/list_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package keys
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestGetOperatorIdFromPubKey(t *testing.T) {
10+
keys := []string{
11+
"E([498211989701534593628498974128726712526336918939770789545660245177948853517,19434346619705907282579203143605058653932187676054178921788041096426532277474])",
12+
"E([12892371960839255271113718323208293712766673033393673346290824524331477194807,6396504257730939433250301841781896216352727642879531794135340241924955921098])",
13+
"E([8853142719404209253990126746578519340276504871809603927346768258823924530979,4464334502955619293730816404270634846469810746510824584853386555393710785481])",
14+
"E([13817619904229904480359100813294586448353387608055146955542339872965842194541,13023353346332823262268273898508207656373251044010677832259145526114592960745])",
15+
}
16+
17+
operatorIds := []string{
18+
"5a76fe9014f9cd296a69ac589c2bbd2c6a354c5e4c0c79ee35c5b8202b8523a2",
19+
"5a4c1940fd4437cea8649845b353ac5e0502a8bd15df09da6f4d73111517fdcf",
20+
"c1d5a4eb52316da9a5cbd876d17125380f2a9e4184d779993c7cc75cd843c7bb",
21+
"c89a00fede0eac2fcd4d4a13faa07ff806b9537990652934f79f3883789e48a8",
22+
}
23+
24+
for i, key := range keys {
25+
id, err := GetOperatorIdFromBLSPubKey(key)
26+
assert.NoError(t, err, "get operator id from pubkey for %s", key)
27+
assert.Equal(t, operatorIds[i], id, "operator id from pubkey for %s should eq", key)
28+
}
29+
}

‎pkg/operator/keys.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
package operator
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/Layr-Labs/eigenlayer-cli/pkg/operator/keys"
58
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
9+
610
"github.com/urfave/cli/v2"
711
)
812

913
func KeysCmd(p utils.Prompter) *cli.Command {
1014
var keysCmd = &cli.Command{
1115
Name: "keys",
1216
Usage: "Manage the operator's keys",
17+
Before: func(context *cli.Context) error {
18+
deprecationMessage()
19+
return nil
20+
},
21+
After: func(context *cli.Context) error {
22+
deprecationMessage()
23+
return nil
24+
},
1325
Subcommands: []*cli.Command{
1426
keys.CreateCmd(p),
1527
keys.ListCmd(),
@@ -19,5 +31,16 @@ func KeysCmd(p utils.Prompter) *cli.Command {
1931
}
2032

2133
return keysCmd
34+
}
2235

36+
func deprecationMessage() {
37+
line1 := "# The keys commands have been moved to the 'keys' subcommand. #"
38+
line2 := "# Please see 'eigenlayer keys --help' for more information. #"
39+
line3 := "# This command will be deprecated in the future. #"
40+
fmt.Println(strings.Repeat("#", len(line1)))
41+
fmt.Println(line1)
42+
fmt.Println(line2)
43+
fmt.Println(line3)
44+
fmt.Println(strings.Repeat("#", len(line1)))
45+
fmt.Println()
2346
}

‎pkg/operator/keys/create_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
)
1717

1818
func TestCreateCmd(t *testing.T) {
19+
t.Skip("Skip test")
1920
homePath, err := os.UserHomeDir()
2021
if err != nil {
2122
t.Fatal(err)

‎pkg/operator/keys/export_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
func TestGetKeyPath(t *testing.T) {
12+
t.Skip("Skip test")
1213
homePath, err := os.UserHomeDir()
1314
if err != nil {
1415
t.Fatal(err)

‎pkg/operator/keys/import_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
)
2121

2222
func TestImportCmd(t *testing.T) {
23+
t.Skip("Skip test")
2324
homePath, err := os.UserHomeDir()
2425
if err != nil {
2526
t.Fatal(err)

‎pkg/operator/keys/list_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
func TestGetOperatorIdFromPubKey(t *testing.T) {
10+
t.Skip("Skip test")
1011
keys := []string{
1112
"E([498211989701534593628498974128726712526336918939770789545660245177948853517,19434346619705907282579203143605058653932187676054178921788041096426532277474])",
1213
"E([12892371960839255271113718323208293712766673033393673346290824524331477194807,6396504257730939433250301841781896216352727642879531794135340241924955921098])",

0 commit comments

Comments
 (0)
Please sign in to comment.