github.com/klaytn/klaytn@v1.10.2/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 "fmt" 25 26 "github.com/klaytn/klaytn/accounts" 27 "github.com/klaytn/klaytn/accounts/keystore" 28 "github.com/klaytn/klaytn/api/debug" 29 "github.com/klaytn/klaytn/cmd/utils" 30 "github.com/klaytn/klaytn/console" 31 "github.com/klaytn/klaytn/crypto" 32 "github.com/klaytn/klaytn/log" 33 "gopkg.in/urfave/cli.v1" 34 ) 35 36 var AccountCommand = cli.Command{ 37 Name: "account", 38 Usage: "Manage accounts", 39 Category: "ACCOUNT COMMANDS", 40 Description: ` 41 42 Manage accounts, list all existing accounts, import a private key into a new 43 account, create a new account or update an existing account. 44 45 It supports interactive mode, when you are prompted for password as well as 46 non-interactive mode where passwords are supplied via a given password file. 47 Non-interactive mode is only meant for scripted use on test networks or known 48 safe environments. 49 50 Make sure you remember the password you gave when creating a new account (with 51 either new or import). Without it you are not able to unlock your account. 52 53 Note that exporting your key in unencrypted format is NOT supported. 54 55 Keys are stored under <DATADIR>/keystore. 56 It is safe to transfer the entire directory or the individual keys therein 57 between klay nodes by simply copying. 58 59 Make sure you backup your keys regularly.`, 60 Subcommands: []cli.Command{ 61 { 62 Name: "list", 63 Usage: "Print summary of existing accounts", 64 Action: utils.MigrateFlags(accountList), 65 Flags: []cli.Flag{ 66 utils.DataDirFlag, 67 utils.KeyStoreDirFlag, 68 }, 69 Description: ` 70 Print a short summary of all accounts`, 71 }, 72 { 73 Name: "new", 74 Usage: "Create a new account", 75 Action: utils.MigrateFlags(accountCreate), 76 Flags: []cli.Flag{ 77 utils.DataDirFlag, 78 utils.KeyStoreDirFlag, 79 utils.PasswordFileFlag, 80 utils.LightKDFFlag, 81 }, 82 Description: ` 83 klay account new 84 85 Creates a new account and prints the address. 86 87 The account is saved in encrypted format, you are prompted for a passphrase. 88 89 You must remember this passphrase to unlock your account in the future. 90 91 For non-interactive use the passphrase can be specified with the --password flag: 92 93 Note, this is meant to be used for testing only, it is a bad idea to save your 94 password to file or expose in any other way. 95 `, 96 }, 97 { 98 Name: "update", 99 Usage: "Update an existing account", 100 Action: utils.MigrateFlags(accountUpdate), 101 ArgsUsage: "<address>", 102 Flags: []cli.Flag{ 103 utils.DataDirFlag, 104 utils.KeyStoreDirFlag, 105 utils.LightKDFFlag, 106 }, 107 Description: ` 108 klay account update <address> 109 110 Update an existing account. 111 112 The account is saved in the newest version in encrypted format, you are prompted 113 for a passphrase to unlock the account and another to save the updated file. 114 115 This same command can therefore be used to migrate an account of a deprecated 116 format to the newest format or change the password for an account. 117 118 For non-interactive use the passphrase can be specified with the --password flag: 119 120 klay account update [options] <address> 121 122 Since only one password can be given, only format update can be performed, 123 changing your password is only possible interactively. 124 `, 125 }, 126 { 127 Name: "import", 128 Usage: "Import a private key into a new account", 129 Action: utils.MigrateFlags(accountImport), 130 Flags: []cli.Flag{ 131 utils.DataDirFlag, 132 utils.KeyStoreDirFlag, 133 utils.PasswordFileFlag, 134 utils.LightKDFFlag, 135 }, 136 ArgsUsage: "<keyFile>", 137 Description: ` 138 klay account import <keyfile> 139 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 klay account import [options] <keyfile> 152 153 Note: 154 As you can directly copy your encrypted accounts to another klay instance, 155 this import mechanism is not needed when you transfer an account between 156 nodes. 157 `, 158 }, 159 }, 160 } 161 162 func accountList(ctx *cli.Context) error { 163 if glogger, err := debug.GetGlogger(); err == nil { 164 log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError)) 165 } 166 stack, _ := utils.MakeConfigNode(ctx) 167 var index int 168 for _, wallet := range stack.AccountManager().Wallets() { 169 for _, account := range wallet.Accounts() { 170 fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 171 index++ 172 } 173 } 174 return nil 175 } 176 177 // tries unlocking the specified account a few times. 178 func UnlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 179 account, err := utils.MakeAddress(ks, address) 180 if err != nil { 181 log.Fatalf("Could not list accounts: %v", err) 182 } 183 for trials := 0; trials < 3; trials++ { 184 prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 185 password := getPassPhrase(prompt, false, i, passwords) 186 err = ks.Unlock(account, password) 187 if err == nil { 188 logger.Info("Unlocked account", "address", account.Address.Hex()) 189 return account, password 190 } 191 if err, ok := err.(*keystore.AmbiguousAddrError); ok { 192 logger.Info("Unlocked account", "address", account.Address.Hex()) 193 return ambiguousAddrRecovery(ks, err, password), password 194 } 195 if err != keystore.ErrDecrypt { 196 // No need to prompt again if the error is not decryption-related. 197 break 198 } 199 } 200 // All trials expended to unlock account, bail out 201 log.Fatalf("Failed to unlock account %s (%v)", address, err) 202 203 return accounts.Account{}, "" 204 } 205 206 // getPassPhrase retrieves the password associated with an account, either fetched 207 // from a list of preloaded passphrases, or requested interactively from the user. 208 func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { 209 // If a list of passwords was supplied, retrieve from them 210 if len(passwords) > 0 { 211 if i < len(passwords) { 212 return passwords[i] 213 } 214 return passwords[len(passwords)-1] 215 } 216 // Otherwise prompt the user for the password 217 if prompt != "" { 218 fmt.Println(prompt) 219 } 220 password, err := console.Stdin.PromptPassword("Passphrase: ") 221 if err != nil { 222 log.Fatalf("Failed to read passphrase: %v", err) 223 } 224 if confirmation { 225 confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 226 if err != nil { 227 log.Fatalf("Failed to read passphrase confirmation: %v", err) 228 } 229 if password != confirm { 230 log.Fatalf("Passphrases do not match") 231 } 232 } 233 return password 234 } 235 236 func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 237 fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 238 for _, a := range err.Matches { 239 fmt.Println(" ", a.URL) 240 } 241 fmt.Println("Testing your passphrase against all of them...") 242 var match *accounts.Account 243 for _, a := range err.Matches { 244 if err := ks.Unlock(a, auth); err == nil { 245 match = &a 246 break 247 } 248 } 249 if match == nil { 250 log.Fatalf("None of the listed files could be unlocked.") 251 } 252 fmt.Printf("Your passphrase unlocked %s\n", match.URL) 253 fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 254 for _, a := range err.Matches { 255 if a != *match { 256 fmt.Println(" ", a.URL) 257 } 258 } 259 return *match 260 } 261 262 // accountCreate creates a new account into the keystore defined by the CLI flags. 263 func accountCreate(ctx *cli.Context) error { 264 if glogger, err := debug.GetGlogger(); err == nil { 265 log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError)) 266 } 267 cfg := utils.KlayConfig{Node: utils.DefaultNodeConfig()} 268 // Load config file. 269 if file := ctx.GlobalString(utils.ConfigFileFlag.Name); file != "" { 270 if err := utils.LoadConfig(file, &cfg); err != nil { 271 log.Fatalf("%v", err) 272 } 273 } 274 cfg.SetNodeConfig(ctx) 275 scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 276 if err != nil { 277 log.Fatalf("Failed to read configuration: %v", err) 278 } 279 280 password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 281 282 address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 283 if err != nil { 284 log.Fatalf("Failed to create account: %v", err) 285 } 286 fmt.Printf("Address: {%x}\n", address) 287 return nil 288 } 289 290 // accountUpdate transitions an account from a previous format to the current 291 // one, also providing the possibility to change the pass-phrase. 292 func accountUpdate(ctx *cli.Context) error { 293 if glogger, err := debug.GetGlogger(); err == nil { 294 log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError)) 295 } 296 if len(ctx.Args()) == 0 { 297 log.Fatalf("No accounts specified to update") 298 } 299 stack, _ := utils.MakeConfigNode(ctx) 300 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 301 302 for _, addr := range ctx.Args() { 303 account, oldPassword := UnlockAccount(ctx, ks, addr, 0, nil) 304 newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 305 if err := ks.Update(account, oldPassword, newPassword); err != nil { 306 log.Fatalf("Could not update the account: %v", err) 307 } 308 } 309 return nil 310 } 311 312 func accountImport(ctx *cli.Context) error { 313 if glogger, err := debug.GetGlogger(); err == nil { 314 log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError)) 315 } 316 keyfile := ctx.Args().First() 317 if len(keyfile) == 0 { 318 log.Fatalf("keyfile must be given as argument") 319 } 320 key, err := crypto.LoadECDSA(keyfile) 321 if err != nil { 322 log.Fatalf("Failed to load the private key: %v", err) 323 } 324 stack, _ := utils.MakeConfigNode(ctx) 325 passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 326 327 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 328 acct, err := ks.ImportECDSA(key, passphrase) 329 if err != nil { 330 log.Fatalf("Could not create the account: %v", err) 331 } 332 fmt.Printf("Address: {%x}\n", acct.Address) 333 if _acct, err := ks.Find(acct); err == nil { 334 fmt.Println("Your account is imported at", _acct.URL.Path) 335 } 336 return nil 337 }