-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add batch key utility * fmt * fix lint * add util to read the generated folder * fix golint error * addressed comments * add command descriptions * add go install * add better descriptions * update readme * update readme * moved the readutil to crypto util * move types too * lint * password without symbols for testing * keep 3 test files
- Loading branch information
1 parent
8a60549
commit 9a026d0
Showing
18 changed files
with
447 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
## egnkey | ||
This tool is used to manage keys for AVS development purpose | ||
|
||
Features: | ||
- [Generate ecdsa or bls key in batches](#generate-ecdsa-or-bls-key-in-batches) | ||
|
||
### How to install | ||
#### Install from source | ||
```bash | ||
go install github.com/Layr-Labs/eigensdk-go/cmd/egnkey@latest | ||
``` | ||
|
||
#### Build from source | ||
Navigate to [egnkey](../egnkey/) directory and run | ||
```bash | ||
go install | ||
``` | ||
|
||
### Generate ecdsa or bls key in batches | ||
|
||
To create in a random folder | ||
```bash | ||
egnkey generate --key-type ecdsa --num-keys <num_key> | ||
``` | ||
|
||
To create in specific folder | ||
```bash | ||
egnkey generate --key-type ecdsa --num-keys <num_key> --output-dir <path_to_folder> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"math/rand" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/crypto/bls" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/crypto/ecdsa" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/google/uuid" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
const ( | ||
DefaultKeyFolder = "keys" | ||
|
||
PasswordFile = "password.txt" | ||
PrivateKeyHexFile = "private_key_hex.txt" | ||
) | ||
|
||
var ( | ||
KeyTypeFlag = &cli.StringFlag{ | ||
Name: "key-type", | ||
Usage: "key type to create (ecdsa or bls)", | ||
Required: true, | ||
} | ||
NumKeysFlag = &cli.IntFlag{ | ||
Name: "num-keys", | ||
Usage: "number of keys to create", | ||
Required: true, | ||
DefaultText: "1", | ||
} | ||
OutputDirFlag = &cli.StringFlag{ | ||
Name: "output-dir", | ||
Usage: "folder to store keys", | ||
Required: false, | ||
} | ||
) | ||
|
||
var commandGenerate = &cli.Command{ | ||
Name: "generate", | ||
Aliases: []string{"g"}, | ||
Description: `Generate keys for testing purpose. | ||
This command creates ecdsa or bls key pair for testing purposes. It generates | ||
all the relevant files for reading and keys and decrypted it and also gets | ||
you the private keys in plaintext. | ||
It creates the following artifacts based on arguments | ||
- passwords.txt - contains all passwords to decrypt keys | ||
- private_key_hex.txt - will create plaintext private keys | ||
- keys/* - create all the encrypted json files in this folder | ||
`, | ||
Action: generate, | ||
Flags: []cli.Flag{ | ||
KeyTypeFlag, | ||
NumKeysFlag, | ||
OutputDirFlag, | ||
}, | ||
} | ||
|
||
func generate(c *cli.Context) error { | ||
keyType := c.String(KeyTypeFlag.Name) | ||
if keyType != "ecdsa" && keyType != "bls" { | ||
return cli.Exit("Invalid key type", 1) | ||
} | ||
numKeys := c.Int(NumKeysFlag.Name) | ||
if numKeys < 1 { | ||
return cli.Exit("Invalid number of keys", 1) | ||
} | ||
|
||
folder := c.String(OutputDirFlag.Name) | ||
if folder == "" { | ||
id, err := uuid.NewUUID() | ||
if err != nil { | ||
return cli.Exit("Failed to generate UUID", 1) | ||
} | ||
folder = id.String() | ||
} | ||
|
||
_, err := os.Stat(folder) | ||
if !os.IsNotExist(err) { | ||
return cli.Exit("Folder already exists, choose a different folder or delete the existing folder", 1) | ||
} | ||
|
||
err = os.MkdirAll(folder+"/"+DefaultKeyFolder, 0755) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
passwordFile, err := os.Create(filepath.Clean(folder + "/" + PasswordFile)) | ||
if err != nil { | ||
return err | ||
} | ||
privateKeyFile, err := os.Create(filepath.Clean(folder + "/" + PrivateKeyHexFile)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if keyType == "ecdsa" { | ||
err := generateECDSAKeys(numKeys, folder, passwordFile, privateKeyFile) | ||
if err != nil { | ||
return err | ||
} | ||
} else if keyType == "bls" { | ||
err := generateBlsKeys(numKeys, folder, passwordFile, privateKeyFile) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
return cli.Exit("Invalid key type", 1) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func generateBlsKeys(numKeys int, path string, passwordFile, privateKeyFile *os.File) error { | ||
for i := 0; i < numKeys; i++ { | ||
key, err := bls.GenRandomBlsKeys() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
password := generateRandomPassword() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
privateKeyHex := key.PrivKey.String() | ||
fileName := fmt.Sprintf("%d.bls.key.json", i+1) | ||
err = key.SaveToFile(filepath.Clean(path+"/"+DefaultKeyFolder+"/"+fileName), password) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = passwordFile.WriteString(password + "\n") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = privateKeyFile.WriteString(privateKeyHex + "\n") | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func generateECDSAKeys(numKeys int, path string, passwordFile, privateKeyFile *os.File) error { | ||
for i := 0; i < numKeys; i++ { | ||
key, err := crypto.GenerateKey() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
password := generateRandomPassword() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
privateKeyHex := hex.EncodeToString(key.D.Bytes()) | ||
fileName := fmt.Sprintf("%d.ecdsa.key.json", i+1) | ||
err = ecdsa.WriteKey(filepath.Clean(path+"/"+DefaultKeyFolder+"/"+fileName), key, password) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = passwordFile.WriteString(password + "\n") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = privateKeyFile.WriteString("0x" + privateKeyHex + "\n") | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func generateRandomPassword() string { | ||
// Seed the random number generator | ||
random := rand.New(rand.NewSource(time.Now().UnixNano())) | ||
|
||
// Define character sets for the password | ||
uppercaseLetters := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
lowercaseLetters := "abcdefghijklmnopqrstuvwxyz" | ||
digits := "0123456789" | ||
//specialSymbols := "!@#$%^&*()-_+=[]{}|;:,.<>?/\\" | ||
|
||
// Combine character sets into one | ||
//allCharacters := uppercaseLetters + lowercaseLetters + digits + specialSymbols | ||
allCharacters := uppercaseLetters + lowercaseLetters + digits | ||
|
||
// Length of the password you want | ||
passwordLength := 20 | ||
|
||
// Generate the password | ||
password := make([]byte, passwordLength) | ||
for i := range password { | ||
password[i] = allCharacters[random.Intn(len(allCharacters))] | ||
} | ||
return string(password) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func main() { | ||
app := cli.NewApp() | ||
app.Name = "egnkey" | ||
app.Description = "Eigenlayer batch keys manager" | ||
app.Commands = []*cli.Command{ | ||
commandGenerate, | ||
} | ||
|
||
app.Usage = "Used to manage batch keys for testing" | ||
|
||
if err := app.Run(os.Args); err != nil { | ||
fmt.Println("Error: ", err) | ||
os.Exit(1) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package utils | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
const ( | ||
// TODO: move to a common constants file which | ||
// can be used by utils and cmd | ||
DefaultKeyFolder = "keys" | ||
PasswordFile = "password.txt" | ||
PrivateKeyHexFile = "private_key_hex.txt" | ||
) | ||
|
||
// ReadBatchKeys reads the batch keys from the given folder | ||
// and returns the list of BatchKey | ||
// folder: folder where the keys are stored, relative to the current directory | ||
func ReadBatchKeys(folder string, isECDSA bool) ([]BatchKey, error) { | ||
var batchKey []BatchKey | ||
absFolder, err := filepath.Abs(folder) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// read the private key file | ||
privateKeyFile, err := os.Open(filepath.Clean(absFolder + "/" + PrivateKeyHexFile)) | ||
if err != nil { | ||
fmt.Println("Error opening the file:", err) | ||
return nil, err | ||
} | ||
defer func(privateKeyFile *os.File) { | ||
err := privateKeyFile.Close() | ||
if err != nil { | ||
_ = fmt.Errorf("error closing the file: %s", err) | ||
return | ||
} | ||
}(privateKeyFile) | ||
|
||
// read the password file | ||
passwordFile, err := os.Open(filepath.Clean(absFolder + "/" + PasswordFile)) | ||
if err != nil { | ||
fmt.Println("Error opening the file:", err) | ||
return nil, err | ||
} | ||
defer func(passwordFile *os.File) { | ||
err := passwordFile.Close() | ||
if err != nil { | ||
_ = fmt.Errorf("error closing the file: %s", err) | ||
return | ||
} | ||
}(passwordFile) | ||
|
||
privateKeyScanner := bufio.NewScanner(privateKeyFile) | ||
passwordScanner := bufio.NewScanner(passwordFile) | ||
|
||
// To create file names | ||
fileCtr := 1 | ||
keyType := "ecdsa" | ||
if !isECDSA { | ||
keyType = "bls" | ||
} | ||
for privateKeyScanner.Scan() && passwordScanner.Scan() { | ||
privateKey := privateKeyScanner.Text() | ||
password := passwordScanner.Text() | ||
fileName := fmt.Sprintf("%d.%s.key.json", fileCtr, keyType) | ||
filePath := fmt.Sprintf("%s/%s/%s", absFolder, DefaultKeyFolder, fileName) | ||
// Since a last line with empty string is also read | ||
// I need to break here | ||
// TODO(madhur): remove empty string when it gets created | ||
if privateKey == "" { | ||
break | ||
} | ||
|
||
bK := BatchKey{ | ||
FilePath: filePath, | ||
Password: password, | ||
PrivateKey: privateKey, | ||
} | ||
batchKey = append(batchKey, bK) | ||
fileCtr++ | ||
} | ||
|
||
if err := privateKeyScanner.Err(); err != nil { | ||
fmt.Println("Error reading privateKey file: ", err) | ||
return nil, err | ||
} | ||
|
||
if err := passwordScanner.Err(); err != nil { | ||
fmt.Println("Error reading password file: ", err) | ||
return nil, err | ||
} | ||
|
||
return batchKey, nil | ||
} |
Oops, something went wrong.