Skip to content

Commit b77e55e

Browse files
add export key command (Layr-Labs#22)
* add export key command * fix lint * make it less comlicated * add test for filename * fix lint
1 parent 9a4201b commit b77e55e

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

pkg/operator/keys.go

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func KeysCmd(p utils.Prompter) *cli.Command {
1414
keys.CreateCmd(p),
1515
keys.ListCmd(),
1616
keys.ImportCmd(p),
17+
keys.ExportCmd(p),
1718
},
1819
}
1920

pkg/operator/keys/export.go

+134
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/utils"
11+
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
12+
"github.com/Layr-Labs/eigensdk-go/crypto/ecdsa"
13+
"github.com/urfave/cli/v2"
14+
)
15+
16+
func ExportCmd(p utils.Prompter) *cli.Command {
17+
exportCmd := &cli.Command{
18+
Name: "export",
19+
Usage: "Used to export existing keys from local keystore",
20+
UsageText: "export --key-type <key-type> [flags] [keyname]",
21+
Description: `Used to export ecdsa and bls key from local keystore
22+
23+
keyname - This will be the name of the key to be imported. If the path of keys is
24+
different from default path created by "create"/"import" command, then provide the
25+
full path using --key-path flag.
26+
27+
If both keyname is provided and --key-path flag is provided, then keyname will be used.
28+
29+
use --key-type ecdsa/bls to export ecdsa/bls key.
30+
- ecdsa - exported key should be plaintext hex encoded private key
31+
- bls - exported key should be plaintext bls private key
32+
33+
It will prompt for password to encrypt the key.
34+
35+
This command will import keys from $HOME/.eigenlayer/operator_keys/ location
36+
37+
But if you want it to export from a different location, use --key-path flag`,
38+
39+
Flags: []cli.Flag{
40+
&KeyTypeFlag,
41+
&KeyPathFlag,
42+
},
43+
Action: func(c *cli.Context) error {
44+
keyType := c.String(KeyTypeFlag.Name)
45+
46+
keyName := c.Args().Get(0)
47+
if err := validateKeyName(keyName); err != nil {
48+
return err
49+
}
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/operator/keys/export_test.go

+55
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/operator/keys/flags.go

+7
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,11 @@ var (
1717
Usage: "Use this flag to skip password validation",
1818
EnvVars: []string{"INSECURE"},
1919
}
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+
}
2027
)

0 commit comments

Comments
 (0)