From 914311044f46e6a742557c3c9d2e83238112df47 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 15 Oct 2024 22:05:58 -0400 Subject: [PATCH 1/3] chore: deprecate operator keys command --- pkg/operator/keys/create.go | 326 ------------------------------- pkg/operator/keys/create_test.go | 132 ------------- pkg/operator/keys/error.go | 14 -- pkg/operator/keys/export.go | 134 ------------- pkg/operator/keys/export_test.go | 56 ------ pkg/operator/keys/flags.go | 27 --- pkg/operator/keys/import.go | 118 ----------- pkg/operator/keys/import_test.go | 217 -------------------- pkg/operator/keys/list.go | 186 ------------------ pkg/operator/keys/list_test.go | 30 --- 10 files changed, 1240 deletions(-) delete mode 100644 pkg/operator/keys/create.go delete mode 100644 pkg/operator/keys/create_test.go delete mode 100644 pkg/operator/keys/error.go delete mode 100644 pkg/operator/keys/export.go delete mode 100644 pkg/operator/keys/export_test.go delete mode 100644 pkg/operator/keys/flags.go delete mode 100644 pkg/operator/keys/import.go delete mode 100644 pkg/operator/keys/import_test.go delete mode 100644 pkg/operator/keys/list.go delete mode 100644 pkg/operator/keys/list_test.go diff --git a/pkg/operator/keys/create.go b/pkg/operator/keys/create.go deleted file mode 100644 index 09d147f7..00000000 --- a/pkg/operator/keys/create.go +++ /dev/null @@ -1,326 +0,0 @@ -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 [flags] ", - 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 .ecdsa.key.json or .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 -} diff --git a/pkg/operator/keys/create_test.go b/pkg/operator/keys/create_test.go deleted file mode 100644 index 03de8c20..00000000 --- a/pkg/operator/keys/create_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package keys - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/urfave/cli/v2" - - prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" -) - -func TestCreateCmd(t *testing.T) { - t.Skip("Skip test") - homePath, err := os.UserHomeDir() - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - args []string - err error - keyPath string - promptMock func(p *prompterMock.MockPrompter) - }{ - { - name: "key-name flag not set", - args: []string{}, - err: errors.New("Required flag \"key-type\" not set"), - }, - { - name: "more than one argument", - args: []string{"--key-type", "ecdsa", "arg1", "arg2"}, - err: fmt.Errorf("%w: accepts 1 arg, received 2", ErrInvalidNumberOfArgs), - }, - { - name: "empty name argument", - args: []string{"--key-type", "ecdsa", ""}, - err: ErrEmptyKeyName, - }, - { - name: "keyname with whitespaces", - args: []string{"--key-type", "ecdsa", "hello world"}, - err: ErrKeyContainsWhitespaces, - }, - { - name: "invalid key type", - args: []string{"--key-type", "invalid", "do_not_use_this_name"}, - err: ErrInvalidKeyType, - }, - { - name: "invalid password based on validation function - ecdsa", - args: []string{"--key-type", "ecdsa", "do_not_use_this_name"}, - 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", "do_not_use_this_name"}, - err: ErrInvalidPassword, - promptMock: func(p *prompterMock.MockPrompter) { - p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", ErrInvalidPassword) - }, - }, - { - name: "valid ecdsa key creation", - args: []string{"--key-type", "ecdsa", "do_not_use_this_name"}, - err: nil, - promptMock: func(p *prompterMock.MockPrompter) { - p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) - p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) - }, - keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/do_not_use_this_name.ecdsa.key.json"), - }, - { - name: "valid bls key creation", - args: []string{"--key-type", "bls", "do_not_use_this_name"}, - err: nil, - promptMock: func(p *prompterMock.MockPrompter) { - p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) - p.EXPECT().InputHiddenString(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) - }, - keyPath: filepath.Join(homePath, OperatorKeystoreSubFolder, "/do_not_use_this_name.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) - } - - createCmd := CreateCmd(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 := createCmd.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) - } - } else { - assert.EqualError(t, err, tt.err.Error()) - } - }) - } -} diff --git a/pkg/operator/keys/error.go b/pkg/operator/keys/error.go deleted file mode 100644 index f4daf412..00000000 --- a/pkg/operator/keys/error.go +++ /dev/null @@ -1,14 +0,0 @@ -package keys - -import "errors" - -var ( - ErrInvalidNumberOfArgs = errors.New("invalid number of arguments") - ErrEmptyKeyName = errors.New("key name cannot be empty") - ErrEmptyPrivateKey = errors.New("private key cannot be empty") - ErrKeyContainsWhitespaces = errors.New("key name cannot contain spaces") - ErrPrivateKeyContainsWhitespaces = errors.New("private key cannot contain spaces") - ErrInvalidKeyType = errors.New("invalid key type. key type must be either 'ecdsa' or 'bls'") - ErrInvalidPassword = errors.New("invalid password") - ErrInvalidHexPrivateKey = errors.New("invalid hex private key") -) diff --git a/pkg/operator/keys/export.go b/pkg/operator/keys/export.go deleted file mode 100644 index e5675f91..00000000 --- a/pkg/operator/keys/export.go +++ /dev/null @@ -1,134 +0,0 @@ -package keys - -import ( - "encoding/hex" - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" - - "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 [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, - }, - After: telemetry.AfterRunAction(), - Action: func(c *cli.Context) error { - keyType := c.String(KeyTypeFlag.Name) - - keyName := c.Args().Get(0) - - 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 -} diff --git a/pkg/operator/keys/export_test.go b/pkg/operator/keys/export_test.go deleted file mode 100644 index f6a1d3cf..00000000 --- a/pkg/operator/keys/export_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package keys - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetKeyPath(t *testing.T) { - t.Skip("Skip test") - 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) - } - }) - } -} diff --git a/pkg/operator/keys/flags.go b/pkg/operator/keys/flags.go deleted file mode 100644 index 523b889b..00000000 --- a/pkg/operator/keys/flags.go +++ /dev/null @@ -1,27 +0,0 @@ -package keys - -import "github.com/urfave/cli/v2" - -var ( - KeyTypeFlag = cli.StringFlag{ - Name: "key-type", - Aliases: []string{"k"}, - Required: true, - Usage: "Type of key you want to create. Currently supports 'ecdsa' and 'bls'", - EnvVars: []string{"KEY_TYPE"}, - } - - InsecureFlag = cli.BoolFlag{ - Name: "insecure", - Aliases: []string{"i"}, - 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"}, - } -) diff --git a/pkg/operator/keys/import.go b/pkg/operator/keys/import.go deleted file mode 100644 index d3b749e3..00000000 --- a/pkg/operator/keys/import.go +++ /dev/null @@ -1,118 +0,0 @@ -package keys - -import ( - "fmt" - "math/big" - "regexp" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" - "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" - "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 [flags] ", - 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 .ecdsa.key.json or .bls.key.json - -use --key-type ecdsa/bls to import ecdsa/bls key. -- ecdsa - should be plaintext hex encoded private key -- bls - 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 also support piping the password from stdin. -For example: echo "password" | eigenlayer keys import --key-type ecdsa keyname privateKey - -This command will import 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() != 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 - } - - // 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 = common.Trim0x(privateKey) - privateKeyPair, err := crypto.HexToECDSA(privateKey) - if err != nil { - return err - } - return saveEcdsaKey(keyName, p, privateKeyPair, insecure, stdInPassword, readFromPipe) - case KeyTypeBLS: - privateKeyBigInt := new(big.Int) - _, ok := privateKeyBigInt.SetString(privateKey, 10) - var blsKeyPair *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 = common.Trim0x(privateKey) - _, 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, stdInPassword, readFromPipe) - 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 -} diff --git a/pkg/operator/keys/import_test.go b/pkg/operator/keys/import_test.go deleted file mode 100644 index bcd0eb1f..00000000 --- a/pkg/operator/keys/import_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package keys - -import ( - "context" - "encoding/hex" - "errors" - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/Layr-Labs/eigensdk-go/crypto/bls" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/internal/common" - prompterMock "github.com/Layr-Labs/eigenlayer-cli/pkg/utils/mocks" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" - "go.uber.org/mock/gomock" -) - -func TestImportCmd(t *testing.T) { - t.Skip("Skip test") - 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) - 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) - 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) - 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) - 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, common.Trim0x(tt.args[3]), 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()) - } - }) - } -} diff --git a/pkg/operator/keys/list.go b/pkg/operator/keys/list.go deleted file mode 100644 index c5729804..00000000 --- a/pkg/operator/keys/list.go +++ /dev/null @@ -1,186 +0,0 @@ -package keys - -import ( - "crypto/ecdsa" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/consensys/gnark-crypto/ecc/bn254" - "github.com/consensys/gnark-crypto/ecc/bn254/fp" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/telemetry" - "github.com/Layr-Labs/eigensdk-go/crypto/bls" - "github.com/Layr-Labs/eigensdk-go/types" - - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/urfave/cli/v2" -) - -func ListCmd() *cli.Command { - listCmd := &cli.Command{ - Name: "list", - Usage: "List all the keys created by this create command", - UsageText: "list", - Description: ` -This command will list both ecdsa and bls key created using create command - -It will only list keys created in the default folder (./operator_keys/) - `, - After: telemetry.AfterRunAction(), - Action: func(context *cli.Context) error { - homePath, err := os.UserHomeDir() - if err != nil { - return err - } - keyStorePath := filepath.Clean(filepath.Join(homePath, OperatorKeystoreSubFolder)) - files, err := os.ReadDir(keyStorePath) - if err != nil { - return err - } - - for _, file := range files { - keySplits := strings.Split(file.Name(), ".") - fileName := keySplits[0] - keyType := keySplits[1] - fmt.Println("Key Name: " + fileName) - switch keyType { - case KeyTypeECDSA: - fmt.Println("Key Type: ECDSA") - keyFilePath := filepath.Join(keyStorePath, file.Name()) - address, err := GetAddress(filepath.Clean(keyFilePath)) - if err != nil { - return err - } - fmt.Println("Address: 0x" + address) - fmt.Println("Key location: " + keyFilePath) - fmt.Println("====================================================================================") - fmt.Println() - case KeyTypeBLS: - fmt.Println("Key Type: BLS") - keyFilePath := filepath.Join(keyStorePath, file.Name()) - pubKey, err := GetPubKey(filepath.Clean(keyFilePath)) - if err != nil { - return err - } - fmt.Println("Public Key: " + pubKey) - operatorIdStr, err := GetOperatorIdFromBLSPubKey(pubKey) - if err != nil { - return err - } - fmt.Println("Operator Id: 0x" + operatorIdStr) - fmt.Println("Key location: " + keyFilePath) - fmt.Println("====================================================================================") - fmt.Println() - } - - } - return nil - }, - } - return listCmd -} - -func GetPubKey(keyStoreFile string) (string, error) { - keyJson, err := os.ReadFile(keyStoreFile) - if err != nil { - return "", err - } - - m := make(map[string]interface{}) - if err := json.Unmarshal(keyJson, &m); err != nil { - return "", err - } - - if pubKey, ok := m["pubKey"].(string); !ok { - return "", fmt.Errorf("pubKey not found in key file") - } else { - return pubKey, nil - } -} - -func GetOperatorIdFromBLSPubKey(pubKey string) (string, error) { - // The pubkey 's string is generated from this code: - // ```go - // func (p *G1Affine) String() string { - // if p.IsInfinity() { - // return "O" - // } - // return "E([" + p.X.String() + "," + p.Y.String() + "])" - // } - // ``` - // - // This code just parser this string: - // E([498211989701534593628498974128726712526336918939770789545660245177948853517,19434346619705907282579203143605058653932187676054178921788041096426532277474]) - - if pubKey == "O" { - return "", fmt.Errorf("pubKey is Infinity") - } - - if pubKey[:3] != "E([" && pubKey[len(pubKey)-2:] != "])" { - return "", fmt.Errorf("pubKey format failed by not E([x,y])") - } - - pubKeyStr := pubKey[3 : len(pubKey)-2] - strs := strings.Split(pubKeyStr, ",") - if len(strs) != 2 { - return "", fmt.Errorf("pubkey format failed by not x,y") - } - - xe, err := new(fp.Element).SetString(strs[0]) - if err != nil { - return "", err - } - - ye, err := new(fp.Element).SetString(strs[1]) - if err != nil { - return "", err - } - - point := &bls.G1Point{ - G1Affine: &bn254.G1Affine{ - X: *xe, - Y: *ye, - }, - } - - operatorId := types.OperatorIdFromG1Pubkey(point) - - return operatorId.LogValue().String(), nil -} - -func GetAddress(keyStoreFile string) (string, error) { - keyJson, err := os.ReadFile(keyStoreFile) - if err != nil { - return "", err - } - - m := make(map[string]interface{}) - if err := json.Unmarshal(keyJson, &m); err != nil { - return "", err - } - - if address, ok := m["address"].(string); !ok { - return "", fmt.Errorf("address not found in key file") - } else { - return address, nil - } -} - -// GetECDSAPrivateKey - Keeping it right now as we might need this function to export -// the keys -func GetECDSAPrivateKey(keyStoreFile string, password string) (*ecdsa.PrivateKey, error) { - keyStoreContents, err := os.ReadFile(keyStoreFile) - if err != nil { - return nil, err - } - - sk, err := keystore.DecryptKey(keyStoreContents, password) - if err != nil { - return nil, err - } - - return sk.PrivateKey, nil -} diff --git a/pkg/operator/keys/list_test.go b/pkg/operator/keys/list_test.go deleted file mode 100644 index 9139db5f..00000000 --- a/pkg/operator/keys/list_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package keys - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetOperatorIdFromPubKey(t *testing.T) { - t.Skip("Skip test") - keys := []string{ - "E([498211989701534593628498974128726712526336918939770789545660245177948853517,19434346619705907282579203143605058653932187676054178921788041096426532277474])", - "E([12892371960839255271113718323208293712766673033393673346290824524331477194807,6396504257730939433250301841781896216352727642879531794135340241924955921098])", - "E([8853142719404209253990126746578519340276504871809603927346768258823924530979,4464334502955619293730816404270634846469810746510824584853386555393710785481])", - "E([13817619904229904480359100813294586448353387608055146955542339872965842194541,13023353346332823262268273898508207656373251044010677832259145526114592960745])", - } - - operatorIds := []string{ - "5a76fe9014f9cd296a69ac589c2bbd2c6a354c5e4c0c79ee35c5b8202b8523a2", - "5a4c1940fd4437cea8649845b353ac5e0502a8bd15df09da6f4d73111517fdcf", - "c1d5a4eb52316da9a5cbd876d17125380f2a9e4184d779993c7cc75cd843c7bb", - "c89a00fede0eac2fcd4d4a13faa07ff806b9537990652934f79f3883789e48a8", - } - - for i, key := range keys { - id, err := GetOperatorIdFromBLSPubKey(key) - assert.NoError(t, err, "get operator id from pubkey for %s", key) - assert.Equal(t, operatorIds[i], id, "operator id from pubkey for %s should eq", key) - } -} From 14718afe7a55c911691d986102935e6b1203afea Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 15 Oct 2024 22:13:05 -0400 Subject: [PATCH 2/3] chore: deprecate operator keys command --- pkg/operator/keys.go | 46 -------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 pkg/operator/keys.go diff --git a/pkg/operator/keys.go b/pkg/operator/keys.go deleted file mode 100644 index 0980eec1..00000000 --- a/pkg/operator/keys.go +++ /dev/null @@ -1,46 +0,0 @@ -package operator - -import ( - "fmt" - "strings" - - "github.com/Layr-Labs/eigenlayer-cli/pkg/operator/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 operator's keys", - Before: func(context *cli.Context) error { - deprecationMessage() - return nil - }, - After: func(context *cli.Context) error { - deprecationMessage() - return nil - }, - Subcommands: []*cli.Command{ - keys.CreateCmd(p), - keys.ListCmd(), - keys.ImportCmd(p), - keys.ExportCmd(p), - }, - } - - return keysCmd -} - -func deprecationMessage() { - line1 := "# The keys commands have been moved to the 'keys' subcommand. #" - line2 := "# Please see 'eigenlayer keys --help' for more information. #" - line3 := "# This command will be deprecated in the future. #" - fmt.Println(strings.Repeat("#", len(line1))) - fmt.Println(line1) - fmt.Println(line2) - fmt.Println(line3) - fmt.Println(strings.Repeat("#", len(line1))) - fmt.Println() -} From 256aa5309d1846a110866d1869d7848bc7154f4b Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Tue, 15 Oct 2024 22:13:34 -0400 Subject: [PATCH 3/3] chore: deprecate operator keys command --- pkg/operator.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/operator.go b/pkg/operator.go index 4fafe4f4..9d72a560 100644 --- a/pkg/operator.go +++ b/pkg/operator.go @@ -11,7 +11,6 @@ func OperatorCmd(p utils.Prompter) *cli.Command { Name: "operator", Usage: "Execute onchain operations for the operator", Subcommands: []*cli.Command{ - operator.KeysCmd(p), operator.ConfigCmd(p), operator.RegisterCmd(p), operator.StatusCmd(p),