Skip to content

Commit

Permalink
add export key command (#22)
Browse files Browse the repository at this point in the history
* add export key command

* fix lint

* make it less comlicated

* add test for filename

* fix lint
  • Loading branch information
shrimalmadhur authored Dec 21, 2023
1 parent 9a4201b commit b77e55e
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/operator/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func KeysCmd(p utils.Prompter) *cli.Command {
keys.CreateCmd(p),
keys.ListCmd(),
keys.ImportCmd(p),
keys.ExportCmd(p),
},
}

Expand Down
134 changes: 134 additions & 0 deletions pkg/operator/keys/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package keys

import (
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"

"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
"github.com/Layr-Labs/eigensdk-go/crypto/ecdsa"
"github.com/urfave/cli/v2"
)

func ExportCmd(p utils.Prompter) *cli.Command {
exportCmd := &cli.Command{
Name: "export",
Usage: "Used to export existing keys from local keystore",
UsageText: "export --key-type <key-type> [flags] [keyname]",
Description: `Used to export ecdsa and bls key from local keystore
keyname - This will be the name of the key to be imported. If the path of keys is
different from default path created by "create"/"import" command, then provide the
full path using --key-path flag.
If both keyname is provided and --key-path flag is provided, then keyname will be used.
use --key-type ecdsa/bls to export ecdsa/bls key.
- ecdsa - exported key should be plaintext hex encoded private key
- bls - exported key should be plaintext bls private key
It will prompt for password to encrypt the key.
This command will import keys from $HOME/.eigenlayer/operator_keys/ location
But if you want it to export from a different location, use --key-path flag`,

Flags: []cli.Flag{
&KeyTypeFlag,
&KeyPathFlag,
},
Action: func(c *cli.Context) error {
keyType := c.String(KeyTypeFlag.Name)

keyName := c.Args().Get(0)
if err := validateKeyName(keyName); err != nil {
return err
}

keyPath := c.String(KeyPathFlag.Name)
if len(keyPath) == 0 && len(keyName) == 0 {
return errors.New("one of keyname or --key-path is required")
}

if len(keyPath) > 0 && len(keyName) > 0 {
return errors.New("keyname and --key-path both are provided. Please provide only one")
}

filePath, err := getKeyPath(keyPath, keyName, keyType)
if err != nil {
return err
}

confirm, err := p.Confirm("This will show your private key. Are you sure you want to export?")
if err != nil {
return err
}
if !confirm {
return nil
}

password, err := p.InputHiddenString("Enter password to decrypt the key", "", func(s string) error {
return nil
})
if err != nil {
return err
}
fmt.Println("exporting key from: ", filePath)

privateKey, err := getPrivateKey(keyType, filePath, password)
if err != nil {
return err
}
fmt.Println("Private key: ", privateKey)
return nil
},
}

return exportCmd
}

func getPrivateKey(keyType string, filePath string, password string) (string, error) {
switch keyType {
case KeyTypeECDSA:
key, err := ecdsa.ReadKey(filePath, password)
if err != nil {
return "", err
}
return hex.EncodeToString(key.D.Bytes()), nil
case KeyTypeBLS:
key, err := bls.ReadPrivateKeyFromFile(filePath, password)
if err != nil {
return "", err
}
return key.PrivKey.String(), nil
default:
return "", ErrInvalidKeyType
}
}

func getKeyPath(keyPath string, keyName string, keyType string) (string, error) {
homePath, err := os.UserHomeDir()
if err != nil {
return "", err
}

var filePath string
if len(keyName) > 0 {
switch keyType {
case KeyTypeECDSA:
filePath = filepath.Join(homePath, OperatorKeystoreSubFolder, keyName+".ecdsa.key.json")
case KeyTypeBLS:
filePath = filepath.Join(homePath, OperatorKeystoreSubFolder, keyName+".bls.key.json")
default:
return "", ErrInvalidKeyType
}

} else {
filePath = filepath.Clean(keyPath)
}

return filePath, nil
}
55 changes: 55 additions & 0 deletions pkg/operator/keys/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package keys

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetKeyPath(t *testing.T) {
homePath, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}

tests := []struct {
name string
keyType string
keyPath string
keyName string
err error
expectedPath string
}{
{
name: "correct key path using keyname",
keyType: KeyTypeECDSA,
keyName: "test",
err: nil,
expectedPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "test.ecdsa.key.json"),
},
{
name: "correct key path using keypath",
keyType: KeyTypeECDSA,
keyPath: filepath.Join(homePath, "x.json"),
err: nil,
expectedPath: filepath.Join(homePath, "x.json"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path, err := getKeyPath(tt.keyPath, tt.keyName, tt.keyType)
if err != nil {
t.Fatal(err)
}

if tt.err != nil {
assert.EqualError(t, err, tt.err.Error())
} else {
assert.Equal(t, tt.expectedPath, path)
}
})
}
}
7 changes: 7 additions & 0 deletions pkg/operator/keys/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ var (
Usage: "Use this flag to skip password validation",
EnvVars: []string{"INSECURE"},
}

KeyPathFlag = cli.StringFlag{
Name: "key-path",
Aliases: []string{"p"},
Usage: "Use this flag to specify the path of the key",
EnvVars: []string{"KEY_PATH"},
}
)

0 comments on commit b77e55e

Please sign in to comment.