github.com/prysmaticlabs/prysm@v1.4.4/tools/keystores/main.go (about) 1 // This tool allows for simple encrypting and decrypting of EIP-2335 compliant, BLS12-381 2 // keystore.json files which as password protected. This is helpful in development to inspect 3 // the contents of keystores created by Ethereum validator wallets or to easily produce keystores from a 4 // specified secret to move them around in a standard format between Ethereum consensus clients. 5 package main 6 7 import ( 8 "encoding/hex" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "log" 13 "os" 14 "path/filepath" 15 "strings" 16 17 "github.com/google/uuid" 18 "github.com/logrusorgru/aurora" 19 "github.com/pkg/errors" 20 "github.com/prysmaticlabs/prysm/shared/bls" 21 "github.com/prysmaticlabs/prysm/shared/fileutil" 22 "github.com/prysmaticlabs/prysm/shared/promptutil" 23 "github.com/prysmaticlabs/prysm/validator/keymanager" 24 "github.com/urfave/cli/v2" 25 keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" 26 ) 27 28 var ( 29 keystoresFlag = &cli.StringFlag{ 30 Name: "keystores", 31 Value: "", 32 Usage: "Path to a file or directory containing keystore files", 33 Required: true, 34 } 35 passwordFlag = &cli.StringFlag{ 36 Name: "password", 37 Value: "", 38 Usage: "Password for the keystore(s)", 39 } 40 privateKeyFlag = &cli.StringFlag{ 41 Name: "private-key", 42 Value: "", 43 Usage: "Hex string for the BLS12-381 private key you wish encrypt into a keystore file", 44 Required: true, 45 } 46 outputPathFlag = &cli.StringFlag{ 47 Name: "output-path", 48 Value: "", 49 Usage: "Output path to write the newly encrypted keystore file", 50 Required: true, 51 } 52 au = aurora.NewAurora(true /* enable colors */) 53 ) 54 55 func main() { 56 app := &cli.App{ 57 Name: "Keystore utility", 58 Description: "Utility to encrypt and decrypt EIP-2335 compliant keystore.json files for BLS12-381 private keys", 59 Usage: "", 60 Commands: []*cli.Command{ 61 { 62 Name: "decrypt", 63 Usage: "decrypt a specified keystore file or directory containing keystore files", 64 Flags: []cli.Flag{ 65 keystoresFlag, 66 passwordFlag, 67 }, 68 Action: decrypt, 69 }, 70 { 71 Name: "encrypt", 72 Usage: "encrypt a specified hex value of a BLS12-381 private key into a keystore file", 73 Flags: []cli.Flag{ 74 passwordFlag, 75 privateKeyFlag, 76 outputPathFlag, 77 }, 78 Action: encrypt, 79 }, 80 }, 81 } 82 err := app.Run(os.Args) 83 if err != nil { 84 log.Fatal(err) 85 } 86 } 87 88 func decrypt(cliCtx *cli.Context) error { 89 keystorePath := cliCtx.String(keystoresFlag.Name) 90 if keystorePath == "" { 91 return errors.New("--keystore must be set") 92 } 93 fullPath, err := fileutil.ExpandPath(keystorePath) 94 if err != nil { 95 return errors.Wrapf(err, "could not expand path: %s", keystorePath) 96 } 97 password := cliCtx.String(passwordFlag.Name) 98 isPasswordSet := cliCtx.IsSet(passwordFlag.Name) 99 if !isPasswordSet { 100 password, err = promptutil.PasswordPrompt("Input the keystore(s) password", func(s string) error { 101 // Any password is valid. 102 return nil 103 }) 104 if err != nil { 105 return err 106 } 107 } 108 isDir, err := fileutil.HasDir(fullPath) 109 if err != nil { 110 return errors.Wrapf(err, "could not check if path exists: %s", fullPath) 111 } 112 if isDir { 113 files, err := ioutil.ReadDir(fullPath) 114 if err != nil { 115 return errors.Wrapf(err, "could not read directory: %s", fullPath) 116 } 117 for _, f := range files { 118 if f.IsDir() { 119 continue 120 } 121 keystorePath := filepath.Join(fullPath, f.Name()) 122 if err := readAndDecryptKeystore(keystorePath, password); err != nil { 123 fmt.Printf("could not read nor decrypt keystore at path %s: %v\n", keystorePath, err) 124 } 125 } 126 return nil 127 } 128 return readAndDecryptKeystore(fullPath, password) 129 } 130 131 // Attempts to encrypt a passed-in BLS12-3381 private key into the EIP-2335 132 // keystore.json format. If a file at the specified output path exists, asks the user 133 // to confirm overwriting its contents. If the value passed in is not a valid BLS12-381 134 // private key, the function will fail. 135 func encrypt(cliCtx *cli.Context) error { 136 var err error 137 password := cliCtx.String(passwordFlag.Name) 138 isPasswordSet := cliCtx.IsSet(passwordFlag.Name) 139 if !isPasswordSet { 140 password, err = promptutil.PasswordPrompt("Input the keystore(s) password", func(s string) error { 141 // Any password is valid. 142 return nil 143 }) 144 if err != nil { 145 return err 146 } 147 } 148 privateKeyString := cliCtx.String(privateKeyFlag.Name) 149 if privateKeyString == "" { 150 return errors.New("--private-key must not be empty") 151 } 152 outputPath := cliCtx.String(outputPathFlag.Name) 153 if outputPath == "" { 154 return errors.New("--output-path must be set") 155 } 156 fullPath, err := fileutil.ExpandPath(outputPath) 157 if err != nil { 158 return errors.Wrapf(err, "could not expand path: %s", outputPath) 159 } 160 if fileutil.FileExists(fullPath) { 161 response, err := promptutil.ValidatePrompt( 162 os.Stdin, 163 fmt.Sprintf("file at path %s already exists, are you sure you want to overwrite it? [y/n]", fullPath), 164 func(s string) error { 165 input := strings.ToLower(s) 166 if input != "y" && input != "n" { 167 return errors.New("please confirm the above text") 168 } 169 return nil 170 }, 171 ) 172 if err != nil { 173 return errors.Wrap(err, "could not validate prompt confirmation") 174 } 175 if response == "n" { 176 return nil 177 } 178 } 179 if len(privateKeyString) > 2 && strings.Contains(privateKeyString, "0x") { 180 privateKeyString = privateKeyString[2:] // Strip the 0x prefix, if any. 181 } 182 bytesValue, err := hex.DecodeString(privateKeyString) 183 if err != nil { 184 return errors.Wrapf(err, "could not decode as hex string: %s", privateKeyString) 185 } 186 privKey, err := bls.SecretKeyFromBytes(bytesValue) 187 if err != nil { 188 return errors.Wrap(err, "not a valid BLS12-381 private key") 189 } 190 pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal()) 191 encryptor := keystorev4.New() 192 id, err := uuid.NewRandom() 193 if err != nil { 194 return errors.Wrap(err, "could not generate new random uuid") 195 } 196 cryptoFields, err := encryptor.Encrypt(bytesValue, password) 197 if err != nil { 198 return errors.Wrap(err, "could not encrypt into new keystore") 199 } 200 item := &keymanager.Keystore{ 201 Crypto: cryptoFields, 202 ID: id.String(), 203 Version: encryptor.Version(), 204 Pubkey: pubKey, 205 Name: encryptor.Name(), 206 } 207 encodedFile, err := json.MarshalIndent(item, "", "\t") 208 if err != nil { 209 return errors.Wrap(err, "could not json marshal keystore") 210 } 211 if err := fileutil.WriteFile(fullPath, encodedFile); err != nil { 212 return errors.Wrapf(err, "could not write file at path: %s", fullPath) 213 } 214 fmt.Printf( 215 "\nWrote encrypted keystore file at path %s\n", 216 au.BrightMagenta(fullPath), 217 ) 218 fmt.Printf("Pubkey: %s\n", au.BrightGreen( 219 fmt.Sprintf("%#x", privKey.PublicKey().Marshal()), 220 )) 221 return nil 222 } 223 224 // Reads the keystore file at the provided path and attempts 225 // to decrypt it with the specified passwords. 226 func readAndDecryptKeystore(fullPath, password string) error { 227 file, err := ioutil.ReadFile(fullPath) 228 if err != nil { 229 return errors.Wrapf(err, "could not read file at path: %s", fullPath) 230 } 231 decryptor := keystorev4.New() 232 keystoreFile := &keymanager.Keystore{} 233 234 if err := json.Unmarshal(file, keystoreFile); err != nil { 235 return errors.Wrap(err, "could not JSON unmarshal keystore file") 236 } 237 // We extract the validator signing private key from the keystore 238 // by utilizing the password. 239 privKeyBytes, err := decryptor.Decrypt(keystoreFile.Crypto, password) 240 if err != nil { 241 if strings.Contains(err.Error(), "invalid checksum") { 242 return fmt.Errorf("incorrect password for keystore at path: %s", fullPath) 243 } 244 return err 245 } 246 247 var pubKeyBytes []byte 248 // Attempt to use the pubkey present in the keystore itself as a field. If unavailable, 249 // then utilize the public key directly from the private key. 250 if keystoreFile.Pubkey != "" { 251 pubKeyBytes, err = hex.DecodeString(keystoreFile.Pubkey) 252 if err != nil { 253 return errors.Wrap(err, "could not decode pubkey from keystore") 254 } 255 } else { 256 privKey, err := bls.SecretKeyFromBytes(privKeyBytes) 257 if err != nil { 258 return errors.Wrap(err, "could not initialize private key from bytes") 259 } 260 pubKeyBytes = privKey.PublicKey().Marshal() 261 } 262 fmt.Printf("\nDecrypted keystore %s\n", au.BrightMagenta(fullPath)) 263 fmt.Printf("Privkey: %#x\n", au.BrightGreen(privKeyBytes)) 264 fmt.Printf("Pubkey: %#x\n", au.BrightGreen(pubKeyBytes)) 265 return nil 266 }