github.com/klaytn/klaytn@v1.12.1/cmd/utils/nodecmd/accountcmd.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2016 The go-ethereum Authors 3 // This file is part of go-ethereum. 4 // 5 // go-ethereum is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // go-ethereum is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from cmd/geth/accountcmd.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package nodecmd 22 23 import ( 24 "encoding/hex" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "io/fs" 29 "os" 30 "path/filepath" 31 32 "github.com/klaytn/klaytn/accounts" 33 "github.com/klaytn/klaytn/accounts/keystore" 34 "github.com/klaytn/klaytn/api/debug" 35 "github.com/klaytn/klaytn/cmd/utils" 36 "github.com/klaytn/klaytn/console" 37 "github.com/klaytn/klaytn/crypto" 38 "github.com/klaytn/klaytn/crypto/bls" 39 "github.com/klaytn/klaytn/log" 40 "github.com/klaytn/klaytn/node" 41 "github.com/urfave/cli/v2" 42 ) 43 44 var AccountCommand = &cli.Command{ 45 Name: "account", 46 Usage: "Manage accounts", 47 Category: "ACCOUNT COMMANDS", 48 Description: ` 49 Manage accounts, list all existing accounts, import a private key into a new 50 account, create a new account or update an existing account. 51 52 It supports interactive mode, when you are prompted for password as well as 53 non-interactive mode where passwords are supplied via a given password file. 54 Non-interactive mode is only meant for scripted use on test networks or known 55 safe environments. 56 57 Make sure you remember the password you gave when creating a new account (with 58 either new or import). Without it you are not able to unlock your account. 59 60 Note that exporting your key in unencrypted format is NOT supported. 61 62 Keys are stored under <DATADIR>/keystore. 63 It is safe to transfer the entire directory or the individual keys therein 64 between klay nodes by simply copying. 65 66 Make sure you backup your keys regularly.`, 67 Before: beforeAccountCmd, 68 Subcommands: []*cli.Command{ 69 { 70 Name: "list", 71 Usage: "Print summary of existing accounts", 72 Action: accountList, 73 Flags: []cli.Flag{ 74 utils.DataDirFlag, 75 utils.KeyStoreDirFlag, 76 }, 77 Description: ` 78 Print a short summary of all accounts`, 79 }, 80 { 81 Name: "new", 82 Usage: "Create a new account", 83 Action: accountCreate, 84 Flags: []cli.Flag{ 85 utils.DataDirFlag, 86 utils.KeyStoreDirFlag, 87 utils.PasswordFileFlag, 88 utils.LightKDFFlag, 89 }, 90 Description: ` 91 Creates a new account and prints the address. 92 93 The account is saved in encrypted format, you are prompted for a passphrase. 94 95 You must remember this passphrase to unlock your account in the future. 96 97 For non-interactive use the passphrase can be specified with the --password flag: 98 99 Note, this is meant to be used for testing only, it is a bad idea to save your 100 password to file or expose in any other way. 101 `, 102 }, 103 { 104 Name: "update", 105 Usage: "Update an existing account", 106 Action: accountUpdate, 107 ArgsUsage: "<address>", 108 Flags: []cli.Flag{ 109 utils.DataDirFlag, 110 utils.KeyStoreDirFlag, 111 utils.LightKDFFlag, 112 }, 113 Description: ` 114 Update an existing account. 115 116 The account is saved in the newest version in encrypted format, you are prompted 117 for a passphrase to unlock the account and another to save the updated file. 118 119 This same command can therefore be used to migrate an account of a deprecated 120 format to the newest format or change the password for an account. 121 122 For non-interactive use the passphrase can be specified with the --password flag: 123 124 Since only one password can be given, only format update can be performed, 125 changing your password is only possible interactively. 126 `, 127 }, 128 { 129 Name: "import", 130 Usage: "Import a private key into a new account", 131 Action: accountImport, 132 Flags: []cli.Flag{ 133 utils.DataDirFlag, 134 utils.KeyStoreDirFlag, 135 utils.PasswordFileFlag, 136 utils.LightKDFFlag, 137 }, 138 ArgsUsage: "<keyFile>", 139 Description: ` 140 Imports an unencrypted private key from <keyfile> and creates a new account. 141 Prints the address. 142 143 The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 144 145 The account is saved in encrypted format, you are prompted for a passphrase. 146 147 You must remember this passphrase to unlock your account in the future. 148 149 For non-interactive use the passphrase can be specified with the --password flag: 150 151 Note, as you can directly copy your encrypted accounts to another klay instance, 152 this import mechanism is not needed when you transfer an account between 153 nodes. 154 `, 155 }, 156 { 157 Name: "bls-info", 158 Usage: "Fetch BLS public key info of the running node", 159 ArgsUsage: "[endpoint]", 160 Action: accountBlsInfo, 161 Flags: []cli.Flag{ 162 utils.DataDirFlag, 163 }, 164 Description: ` 165 Calculate BLS public key info (the public key and proof-of-possession) 166 then saves to bls-publicinfo-NODEID.json. 167 168 EXAMPLES 169 170 # From the local node, by attaching to the default IPC endpoint DATADIR/klay.ipc. 171 kcn account bls-info 172 `, 173 }, 174 { 175 Name: "bls-import", 176 Usage: "Import a BLS private key from an EIP-2335 keystore JSON", 177 Action: accountBlsImport, 178 Flags: []cli.Flag{ 179 utils.DataDirFlag, 180 utils.BlsNodeKeystoreFileFlag, 181 utils.PasswordFileFlag, 182 }, 183 Description: ` 184 Decrypt an EIP-2335 keystore and save the BLS secret key to default location (DATADIR/bls-nodekey). 185 186 EXAMPLES 187 188 kcn account bls-import --bls-nodekeystore bls-keystore.json 189 `, 190 }, 191 { 192 Name: "bls-export", 193 Usage: "Export a BLS private key to an EIP-2335 keystore JSON", 194 Action: accountBlsExport, 195 Flags: []cli.Flag{ 196 utils.DataDirFlag, 197 utils.PasswordFileFlag, 198 utils.LightKDFFlag, 199 }, 200 Description: ` 201 Export the BLS secret key from the default location (DATADIR/bls-nodekey) 202 to an EIP-2335 keystore bls-keystore-NODEID.json. 203 204 EXAMPLES 205 206 kcn account bls-export 207 `, 208 }, 209 }, 210 } 211 212 func beforeAccountCmd(ctx *cli.Context) error { 213 // Silence INFO logs from MakeConfigNode() or SetNodeConfig() 214 // Account commands are almost independent from the regular node operation, 215 // so INFO logs about networking or chain config are not necessary. 216 if glogger, err := debug.GetGlogger(); err == nil { 217 log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlWarn)) 218 } 219 return nil 220 } 221 222 func accountList(ctx *cli.Context) error { 223 stack, _ := utils.MakeConfigNode(ctx) 224 var index int 225 for _, wallet := range stack.AccountManager().Wallets() { 226 for _, account := range wallet.Accounts() { 227 fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 228 index++ 229 } 230 } 231 return nil 232 } 233 234 // tries unlocking the specified account a few times. 235 func UnlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 236 account, err := utils.MakeAddress(ks, address) 237 if err != nil { 238 log.Fatalf("Could not list accounts: %v", err) 239 } 240 for trials := 0; trials < 3; trials++ { 241 prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 242 password := getPassPhrase(prompt, false, i, passwords) 243 err = ks.Unlock(account, password) 244 if err == nil { 245 logger.Info("Unlocked account", "address", account.Address.Hex()) 246 return account, password 247 } 248 if err, ok := err.(*keystore.AmbiguousAddrError); ok { 249 logger.Info("Unlocked account", "address", account.Address.Hex()) 250 return ambiguousAddrRecovery(ks, err, password), password 251 } 252 if err != keystore.ErrDecrypt { 253 // No need to prompt again if the error is not decryption-related. 254 break 255 } 256 } 257 // All trials expended to unlock account, bail out 258 log.Fatalf("Failed to unlock account %s (%v)", address, err) 259 260 return accounts.Account{}, "" 261 } 262 263 // getPassPhrase retrieves the password associated with an account, either fetched 264 // from a list of preloaded passphrases, or requested interactively from the user. 265 func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { 266 // If a list of passwords was supplied, retrieve from them 267 if len(passwords) > 0 { 268 if i < len(passwords) { 269 return passwords[i] 270 } 271 return passwords[len(passwords)-1] 272 } 273 // Otherwise prompt the user for the password 274 if prompt != "" { 275 fmt.Println(prompt) 276 } 277 password, err := console.Stdin.PromptPassword("Passphrase: ") 278 if err != nil { 279 log.Fatalf("Failed to read passphrase: %v", err) 280 } 281 if confirmation { 282 confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 283 if err != nil { 284 log.Fatalf("Failed to read passphrase confirmation: %v", err) 285 } 286 if password != confirm { 287 log.Fatalf("Passphrases do not match") 288 } 289 } 290 return password 291 } 292 293 func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 294 fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 295 for _, a := range err.Matches { 296 fmt.Println(" ", a.URL) 297 } 298 fmt.Println("Testing your passphrase against all of them...") 299 var match *accounts.Account 300 for _, a := range err.Matches { 301 if err := ks.Unlock(a, auth); err == nil { 302 match = &a 303 break 304 } 305 } 306 if match == nil { 307 log.Fatalf("None of the listed files could be unlocked.") 308 } 309 fmt.Printf("Your passphrase unlocked %s\n", match.URL) 310 fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 311 for _, a := range err.Matches { 312 if a != *match { 313 fmt.Println(" ", a.URL) 314 } 315 } 316 return *match 317 } 318 319 // accountCreate creates a new account into the keystore defined by the CLI flags. 320 func accountCreate(ctx *cli.Context) error { 321 cfg := utils.KlayConfig{Node: utils.DefaultNodeConfig()} 322 // Load config file. 323 if file := ctx.String(utils.ConfigFileFlag.Name); file != "" { 324 if err := utils.LoadConfig(file, &cfg); err != nil { 325 log.Fatalf("%v", err) 326 } 327 } 328 cfg.SetNodeConfig(ctx) 329 scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 330 if err != nil { 331 log.Fatalf("Failed to read configuration: %v", err) 332 } 333 334 password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 335 336 address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 337 if err != nil { 338 log.Fatalf("Failed to create account: %v", err) 339 } 340 fmt.Printf("Address: {%x}\n", address) 341 return nil 342 } 343 344 // accountUpdate transitions an account from a previous format to the current 345 // one, also providing the possibility to change the pass-phrase. 346 func accountUpdate(ctx *cli.Context) error { 347 if ctx.Args().Len() == 0 { 348 log.Fatalf("No accounts specified to update") 349 } 350 stack, _ := utils.MakeConfigNode(ctx) 351 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 352 353 for _, addr := range ctx.Args().Slice() { 354 account, oldPassword := UnlockAccount(ctx, ks, addr, 0, nil) 355 newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 356 if err := ks.Update(account, oldPassword, newPassword); err != nil { 357 log.Fatalf("Could not update the account: %v", err) 358 } 359 } 360 return nil 361 } 362 363 func accountImport(ctx *cli.Context) error { 364 keyfile := ctx.Args().First() 365 if len(keyfile) == 0 { 366 log.Fatalf("keyfile must be given as argument") 367 } 368 key, err := crypto.LoadECDSA(keyfile) 369 if err != nil { 370 log.Fatalf("Failed to load the private key: %v", err) 371 } 372 stack, _ := utils.MakeConfigNode(ctx) 373 passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 374 375 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 376 acct, err := ks.ImportECDSA(key, passphrase) 377 if err != nil { 378 log.Fatalf("Could not create the account: %v", err) 379 } 380 fmt.Printf("Address: {%x}\n", acct.Address) 381 if _acct, err := ks.Find(acct); err == nil { 382 fmt.Println("Your account is imported at", _acct.URL.Path) 383 } 384 return nil 385 } 386 387 func accountBlsInfo(ctx *cli.Context) error { 388 endpoint := rpcEndpoint(ctx) 389 client, err := dialRPC(endpoint) 390 if err != nil { 391 log.Fatalf("Unable to attach to remote node: %v", err) 392 } 393 394 var nodeInfo node.NodeInfoOutput 395 err = client.Call(&nodeInfo, "admin_nodeInfo", nil) 396 if err != nil { 397 log.Fatalf("Unable to fetch node info: %v", err) 398 } 399 400 infoJson, err := json.MarshalIndent(map[string]interface{}{ 401 "address": nodeInfo.NodeAddress, 402 "blsPublicKeyInfo": nodeInfo.BlsPublicKeyInfo, 403 }, "", " ") 404 infoJson = append(infoJson, '\n') 405 if err != nil { 406 return err 407 } 408 409 name := fmt.Sprintf("bls-publicinfo-%s.json", nodeInfo.NodeAddress) 410 writeFile(name, infoJson, 0o644) // Ordinary non-secret text file permission. 411 return nil 412 } 413 414 func accountBlsImport(ctx *cli.Context) error { 415 // Load from keystore.json 416 if !ctx.IsSet(utils.BlsNodeKeystoreFileFlag.Name) { 417 return errors.New("no BLS keystore file specified") 418 } 419 blsPriv, err := loadBlsKeystore(ctx) 420 if err != nil { 421 return err 422 } 423 fmt.Printf("Importing BLS key: pub=%x\n", blsPriv.PublicKey().Marshal()) 424 425 // Parse CLI arguments in the same way as running a node. 426 _, cfg := utils.MakeConfigNode(ctx) 427 path := cfg.Node.ResolvePath(node.DatadirBlsSecretKey) 428 429 // Write DATADIR/bls-nodekey 430 // Not using bls.SaveKey to prevent overwriting the existing file. 431 b := []byte(hex.EncodeToString(blsPriv.Marshal())) 432 writeFile(path, b, 0o600) // Secret file permission. 433 return nil 434 } 435 436 func accountBlsExport(ctx *cli.Context) error { 437 // Parse CLI arguments in the same way as running a node. 438 var ( 439 _, cfg = utils.MakeConfigNode(ctx) 440 nodeKey = cfg.Node.NodeKey() 441 blsPriv = cfg.Node.BlsNodeKey() 442 scryptN, scryptP, _, _ = cfg.Node.AccountConfig() 443 ) 444 fmt.Printf("Exporting BLS key: pub=%x\n", blsPriv.PublicKey().Marshal()) 445 446 // Calculate filename from node address. 447 nodeAddr := crypto.PubkeyToAddress(nodeKey.PublicKey) 448 name := fmt.Sprintf("bls-keystore-%s.json", nodeAddr.Hex()) 449 450 // Write bls-keystore-*.json 451 keystoreJson, err := makeBlsKeystore(ctx, blsPriv, scryptN, scryptP) 452 if err != nil { 453 return err 454 } 455 writeFile(name, keystoreJson, 0o600) // Secret file permission. 456 return nil 457 } 458 459 func loadBlsKeystore(ctx *cli.Context) (bls.SecretKey, error) { 460 file := ctx.String(utils.BlsNodeKeystoreFileFlag.Name) 461 content, err := os.ReadFile(file) 462 if err != nil { 463 return nil, err 464 } 465 466 storedPasswords := utils.MakePasswordList(ctx) 467 password := getPassPhrase("Enter the password", 468 false, 0, storedPasswords) 469 470 plainKeystore, err := keystore.DecryptKeyEIP2335(content, password) 471 if err != nil { 472 return nil, err 473 } 474 return plainKeystore.SecretKey, nil 475 } 476 477 func makeBlsKeystore(ctx *cli.Context, sk bls.SecretKey, scryptN, scryptP int) ([]byte, error) { 478 storedPasswords := utils.MakePasswordList(ctx) 479 password := getPassPhrase("Please give a new password. Do not forget this password.", 480 true, 0, storedPasswords) 481 482 plainKeystore := keystore.NewKeyEIP2335(sk) 483 return keystore.EncryptKeyEIP2335(plainKeystore, password, scryptN, scryptP) 484 } 485 486 func writeFile(path string, content []byte, perm fs.FileMode) { 487 if _, err := os.Stat(path); err == nil { 488 log.Fatalf("file '%s' already exists", path) 489 } 490 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { 491 log.Fatalf("cannot write '%s': %v", path, err) 492 } 493 if err := os.WriteFile(path, content, perm); err != nil { 494 log.Fatalf("cannot write '%s': %v", path, err) 495 } 496 fmt.Printf("Successfully wrote '%s'\n", path) 497 }