Skip to content

Commit

Permalink
move keys command to it's own. add deprecation message to other (#156)
Browse files Browse the repository at this point in the history
* move keys command to it's own. add deprecation message to other

* skip existing tests since they are run in other folder
  • Loading branch information
shrimalmadhur authored Jul 9, 2024
1 parent 8ab1344 commit e5abcca
Show file tree
Hide file tree
Showing 18 changed files with 1,289 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- name: Import Operator Keys and send funds
run: |
cd eigenlayer-cli
echo "" | ./bin/eigenlayer operator keys import --key-type ecdsa --insecure opr0 ea25637d76e7ddae9dab9bfac7467d76a1e3bf2d67941b267edc60f2b80d9413 | cat
echo "" | ./bin/eigenlayer keys import --key-type ecdsa --insecure opr0 ea25637d76e7ddae9dab9bfac7467d76a1e3bf2d67941b267edc60f2b80d9413 | cat
cast send 0xcaB1b44dd1f1C265405878Ac1179cd94D0dBA634 --value 10ether --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
- name: Register Operator
Expand Down
1 change: 1 addition & 0 deletions cmd/eigenlayer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func main() {

app.Commands = append(app.Commands, pkg.OperatorCmd(prompter))
app.Commands = append(app.Commands, pkg.RewardsCmd(prompter))
app.Commands = append(app.Commands, pkg.KeysCmd(prompter))

if err := app.Run(os.Args); err != nil {
_, err := fmt.Fprintln(os.Stderr, err)
Expand Down
24 changes: 24 additions & 0 deletions pkg/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package pkg

import (
"github.com/Layr-Labs/eigenlayer-cli/pkg/keys"
"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"

"github.com/urfave/cli/v2"
)

func KeysCmd(p utils.Prompter) *cli.Command {
var keysCmd = &cli.Command{
Name: "keys",
Usage: "Manage the keys used in EigenLayer ecosystem",
Subcommands: []*cli.Command{
keys.CreateCmd(p),
keys.ListCmd(),
keys.ImportCmd(p),
keys.ExportCmd(p),
},
}

return keysCmd

}
326 changes: 326 additions & 0 deletions pkg/keys/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
package keys

import (
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry"

"github.com/Layr-Labs/eigenlayer-cli/pkg/utils"
"github.com/Layr-Labs/eigensdk-go/crypto/bls"
sdkEcdsa "github.com/Layr-Labs/eigensdk-go/crypto/ecdsa"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli/v2"
passwordvalidator "github.com/wagslane/go-password-validator"
)

const (
OperatorKeystoreSubFolder = ".eigenlayer/operator_keys"

KeyTypeECDSA = "ecdsa"
KeyTypeBLS = "bls"

// MinEntropyBits For password validation
MinEntropyBits = 70
)

func CreateCmd(p utils.Prompter) *cli.Command {
createCmd := &cli.Command{
Name: "create",
Usage: "Used to create encrypted keys in local keystore",
UsageText: "create --key-type <key-type> [flags] <keyname>",
Description: `
Used to create ecdsa and bls key in local keystore
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
use --key-type ecdsa/bls to create ecdsa/bls key.
It will prompt for password to encrypt the key, which is optional but highly recommended.
If you want to create a key with weak/no password, use --insecure flag. Do NOT use those keys in production
This command also support piping the password from stdin.
For example: echo "password" | eigenlayer keys create --key-type ecdsa keyname
This command will create keys in $HOME/.eigenlayer/operator_keys/ location
`,
Flags: []cli.Flag{
&KeyTypeFlag,
&InsecureFlag,
},
After: telemetry.AfterRunAction(),
Action: func(ctx *cli.Context) error {
args := ctx.Args()
if args.Len() != 1 {
return fmt.Errorf("%w: accepts 1 arg, received %d", ErrInvalidNumberOfArgs, args.Len())
}

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

// Check if input is available in the pipe and read the password from it
stdInPassword, readFromPipe := utils.GetStdInPassword()

keyType := ctx.String(KeyTypeFlag.Name)
insecure := ctx.Bool(InsecureFlag.Name)

switch keyType {
case KeyTypeECDSA:
privateKey, err := crypto.GenerateKey()
if err != nil {
return err
}
return saveEcdsaKey(keyName, p, privateKey, insecure, stdInPassword, readFromPipe)
case KeyTypeBLS:
blsKeyPair, err := bls.GenRandomBlsKeys()
if err != nil {
return err
}
return saveBlsKey(keyName, p, blsKeyPair, insecure, stdInPassword, readFromPipe)
default:
return ErrInvalidKeyType
}
},
}
return createCmd
}

func validateKeyName(keyName string) error {
if len(keyName) == 0 {
return ErrEmptyKeyName
}

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

return nil
}

func saveBlsKey(
keyName string,
p utils.Prompter,
keyPair *bls.KeyPair,
insecure bool,
stdInPassword string,
readFromPipe bool,
) error {
homePath, err := os.UserHomeDir()
if err != nil {
return err
}
keyFileName := keyName + ".bls.key.json"
fileLoc := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder, keyFileName))
if checkIfKeyExists(fileLoc) {
return errors.New("key name already exists. Please choose a different name")
}

var password string
if !readFromPipe {
password, err = getPasswordFromPrompt(p, insecure, "Enter password to encrypt the bls private key:")
if err != nil {
return err
}
} else {
password = stdInPassword
if !insecure {
err = validatePassword(password)
if err != nil {
return err
}
}
}

err = keyPair.SaveToFile(fileLoc, password)
if err != nil {
return err
}

privateKeyHex := keyPair.PrivKey.String()
publicKeyHex := keyPair.PubKey.String()

fmt.Printf("\nKey location: %s\nPublic Key: %s\n\n", fileLoc, publicKeyHex)
return displayWithLess(privateKeyHex, KeyTypeBLS)
}

func saveEcdsaKey(
keyName string,
p utils.Prompter,
privateKey *ecdsa.PrivateKey,
insecure bool,
stdInPassword string,
readFromPipe bool,
) error {
homePath, err := os.UserHomeDir()
if err != nil {
return err
}
keyFileName := keyName + ".ecdsa.key.json"
fileLoc := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder, keyFileName))
if checkIfKeyExists(fileLoc) {
return errors.New("key name already exists. Please choose a different name")
}

var password string
if !readFromPipe {
password, err = getPasswordFromPrompt(p, insecure, "Enter password to encrypt the ecdsa private key:")
if err != nil {
return err
}
} else {
password = stdInPassword
if !insecure {
err = validatePassword(password)
if err != nil {
return err
}
}
}

err = sdkEcdsa.WriteKey(fileLoc, privateKey, password)
if err != nil {
return err
}

privateKeyHex := hex.EncodeToString(privateKey.D.Bytes())

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("error casting public key to ECDSA public key")
}
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
publicKeyHex := hexutil.Encode(publicKeyBytes)[4:]
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()

fmt.Printf("\nKey location: %s\nPublic Key hex: %s\nEthereum Address: %s\n\n", fileLoc, publicKeyHex, address)
return displayWithLess(privateKeyHex, KeyTypeECDSA)
}

func padLeft(str string, length int) string {
for len(str) < length {
str = "0" + str
}
return str
}

func displayWithLess(privateKeyHex string, keyType string) error {
var message, border, keyLine string
tabSpace := " "

// Pad with 0 to match size of 64 bytes
if keyType == KeyTypeECDSA {
privateKeyHex = padLeft(privateKeyHex, 64)
}
keyContent := tabSpace + privateKeyHex + tabSpace
borderLength := len(keyContent) + 4
border = strings.Repeat("/", borderLength)
paddingLine := "//" + strings.Repeat(" ", borderLength-4) + "//"

keyLine = fmt.Sprintf("//%s//", keyContent)

if keyType == KeyTypeECDSA {
message = fmt.Sprintf(`
ECDSA Private Key (Hex):
%s
%s
%s
%s
%s
🔐 Please backup the above private key hex in a safe place 🔒
`, border, paddingLine, keyLine, paddingLine, border)
} else if keyType == KeyTypeBLS {
message = fmt.Sprintf(`
BLS Private Key (Hex):
%s
%s
%s
%s
%s
🔐 Please backup the above private key hex in a safe place 🔒
`, border, paddingLine, keyLine, paddingLine, border)
}

cmd := exec.Command("less", "-R")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

stdin, err := cmd.StdinPipe()
if err != nil {
return fmt.Errorf("error creating stdin pipe: %w", err)
}

if err := cmd.Start(); err != nil {
return fmt.Errorf("error starting less command: %w", err)
}

if _, err := stdin.Write([]byte(message)); err != nil {
return fmt.Errorf("error writing message to less command: %w", err)
}

if err := stdin.Close(); err != nil {
return fmt.Errorf("error closing stdin pipe: %w", err)
}

if err := cmd.Wait(); err != nil {
return fmt.Errorf("error waiting for less command: %w", err)
}

return nil
}

func getPasswordFromPrompt(p utils.Prompter, insecure bool, prompt string) (string, error) {
password, err := p.InputHiddenString(prompt, "",
func(s string) error {
if insecure {
return nil
}
return validatePassword(s)
},
)
if err != nil {
return "", err
}
_, err = p.InputHiddenString("Please confirm your password:", "",
func(s string) error {
if s != password {
return errors.New("passwords are not matched")
}
return nil
},
)
if err != nil {
return "", err
}
return password, nil
}

func checkIfKeyExists(fileLoc string) bool {
_, err := os.Stat(fileLoc)
return !os.IsNotExist(err)
}

func validatePassword(password string) error {
err := passwordvalidator.Validate(password, MinEntropyBits)
if err != nil {
fmt.Println(
"if you want to create keys for testing with weak/no password, use --insecure flag. Do NOT use those keys in production",
)
}
return err
}
Loading

0 comments on commit e5abcca

Please sign in to comment.