github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/accountcmd.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package launcher 18 19 import ( 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/unicornultrafoundation/go-u2u/accounts" 28 "github.com/unicornultrafoundation/go-u2u/accounts/keystore" 29 "github.com/unicornultrafoundation/go-u2u/cmd/utils" 30 "github.com/unicornultrafoundation/go-u2u/common" 31 "github.com/unicornultrafoundation/go-u2u/console/prompt" 32 "github.com/unicornultrafoundation/go-u2u/crypto" 33 "github.com/unicornultrafoundation/go-u2u/log" 34 "gopkg.in/urfave/cli.v1" 35 ) 36 37 var ( 38 walletCommand = cli.Command{ 39 Name: "wallet", 40 Usage: "Manage Ethereum presale wallets", 41 ArgsUsage: "", 42 Category: "ACCOUNT COMMANDS", 43 Description: ` 44 u2u wallet import /path/to/my/presale.wallet 45 46 will prompt for your password and imports your ether presale account. 47 It can be used non-interactively with the --password option taking a 48 passwordfile as argument containing the wallet password in plaintext.`, 49 Subcommands: []cli.Command{ 50 { 51 52 Name: "import", 53 Usage: "Import Ethereum presale wallet", 54 ArgsUsage: "<keyFile>", 55 Action: utils.MigrateFlags(importWallet), 56 Category: "ACCOUNT COMMANDS", 57 Flags: []cli.Flag{ 58 DataDirFlag, 59 utils.KeyStoreDirFlag, 60 utils.PasswordFileFlag, 61 utils.LightKDFFlag, 62 }, 63 Description: ` 64 u2u wallet [options] /path/to/my/presale.wallet 65 66 will prompt for your password and imports your ether presale account. 67 It can be used non-interactively with the --password option taking a 68 passwordfile as argument containing the wallet password in plaintext.`, 69 }, 70 }, 71 } 72 73 accountCommand = cli.Command{ 74 Name: "account", 75 Usage: "Manage accounts", 76 Category: "ACCOUNT COMMANDS", 77 Description: ` 78 79 Manage accounts, list all existing accounts, import a private key into a new 80 account, create a new account or update an existing account. 81 82 It supports interactive mode, when you are prompted for password as well as 83 non-interactive mode where passwords are supplied via a given password file. 84 Non-interactive mode is only meant for scripted use on test networks or known 85 safe environments. 86 87 Make sure you remember the password you gave when creating a new account (with 88 either new or import). Without it you are not able to unlock your account. 89 90 Note that exporting your key in unencrypted format is NOT supported. 91 92 Keys are stored under <DATADIR>/keystore. 93 It is safe to transfer the entire directory or the individual keys therein 94 between ethereum nodes by simply copying. 95 96 Make sure you backup your keys regularly.`, 97 Subcommands: []cli.Command{ 98 { 99 Name: "list", 100 Usage: "Print summary of existing accounts", 101 Action: utils.MigrateFlags(accountList), 102 Flags: []cli.Flag{ 103 DataDirFlag, 104 utils.KeyStoreDirFlag, 105 }, 106 Description: ` 107 Print a short summary of all accounts`, 108 }, 109 { 110 Name: "new", 111 Usage: "Create a new account", 112 Action: utils.MigrateFlags(accountCreate), 113 Flags: []cli.Flag{ 114 DataDirFlag, 115 utils.KeyStoreDirFlag, 116 utils.PasswordFileFlag, 117 utils.LightKDFFlag, 118 }, 119 Description: ` 120 u2u account new 121 122 Creates a new account and prints the address. 123 124 The account is saved in encrypted format, you are prompted for a passphrase. 125 126 You must remember this passphrase to unlock your account in the future. 127 128 For non-interactive use the passphrase can be specified with the --password flag: 129 130 Note, this is meant to be used for testing only, it is a bad idea to save your 131 password to file or expose in any other way. 132 `, 133 }, 134 { 135 Name: "update", 136 Usage: "Update an existing account", 137 Action: utils.MigrateFlags(accountUpdate), 138 ArgsUsage: "<address>", 139 Flags: []cli.Flag{ 140 DataDirFlag, 141 utils.KeyStoreDirFlag, 142 utils.LightKDFFlag, 143 }, 144 Description: ` 145 u2u account update <address> 146 147 Update an existing account. 148 149 The account is saved in the newest version in encrypted format, you are prompted 150 for a passphrase to unlock the account and another to save the updated file. 151 152 This same command can therefore be used to migrate an account of a deprecated 153 format to the newest format or change the password for an account. 154 155 For non-interactive use the passphrase can be specified with the --password flag: 156 157 u2u account update [options] <address> 158 159 Since only one password can be given, only format update can be performed, 160 changing your password is only possible interactively. 161 `, 162 }, 163 { 164 Name: "import", 165 Usage: "Import a private key into a new account", 166 Action: utils.MigrateFlags(accountImport), 167 Flags: []cli.Flag{ 168 DataDirFlag, 169 utils.KeyStoreDirFlag, 170 utils.PasswordFileFlag, 171 utils.LightKDFFlag, 172 }, 173 ArgsUsage: "<keyFile>", 174 Description: ` 175 u2u account import <keyfile> 176 177 Imports an unencrypted private key from <keyfile> and creates a new account. 178 Prints the address. 179 180 The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 181 182 The account is saved in encrypted format, you are prompted for a passphrase. 183 184 You must remember this passphrase to unlock your account in the future. 185 186 For non-interactive use the passphrase can be specified with the -password flag: 187 188 u2u account import [options] <keyfile> 189 190 Note: 191 As you can directly copy your encrypted accounts to another ethereum instance, 192 this import mechanism is not needed when you transfer an account between 193 nodes. 194 `, 195 }, 196 }, 197 } 198 ) 199 200 func accountList(ctx *cli.Context) error { 201 cfg := makeAllConfigs(ctx) 202 stack := makeConfigNode(ctx, &cfg.Node) 203 var index int 204 for _, wallet := range stack.AccountManager().Wallets() { 205 for _, account := range wallet.Accounts() { 206 fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 207 index++ 208 } 209 } 210 return nil 211 } 212 213 // tries unlocking the specified account a few times. 214 func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 215 account, err := utils.MakeAddress(ks, address) 216 if err != nil { 217 utils.Fatalf("Could not list accounts: %v", err) 218 } 219 for trials := 0; trials < 3; trials++ { 220 prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 221 password := getPassPhrase(prompt, false, i, passwords) 222 err = ks.Unlock(account, password) 223 if err == nil { 224 log.Info("Unlocked account", "address", account.Address.Hex()) 225 return account, password 226 } 227 if err, ok := err.(*keystore.AmbiguousAddrError); ok { 228 log.Info("Unlocked account", "address", account.Address.Hex()) 229 return ambiguousAddrRecovery(ks, err, password), password 230 } 231 if err != keystore.ErrDecrypt { 232 // No need to prompt again if the error is not decryption-related. 233 break 234 } 235 } 236 // All trials expended to unlock account, bail out 237 utils.Fatalf("Failed to unlock account %s (%v)", address, err) 238 239 return accounts.Account{}, "" 240 } 241 242 // getPassPhrase retrieves the password associated with an account, either fetched 243 // from a list of preloaded passphrases, or requested interactively from the user. 244 func getPassPhrase(msg string, confirmation bool, i int, passwords []string) string { 245 // If a list of passwords was supplied, retrieve from them 246 if len(passwords) > 0 { 247 if i < len(passwords) { 248 return passwords[i] 249 } 250 return passwords[len(passwords)-1] 251 } 252 // Otherwise prompt the user for the password 253 if msg != "" { 254 fmt.Println(msg) 255 } 256 password, err := prompt.Stdin.PromptPassword("Passphrase: ") 257 if err != nil { 258 utils.Fatalf("Failed to read passphrase: %v", err) 259 } 260 if confirmation { 261 confirm, err := prompt.Stdin.PromptPassword("Repeat passphrase: ") 262 if err != nil { 263 utils.Fatalf("Failed to read passphrase confirmation: %v", err) 264 } 265 if password != confirm { 266 utils.Fatalf("Passphrases do not match") 267 } 268 } 269 return password 270 } 271 272 func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 273 fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 274 for _, a := range err.Matches { 275 fmt.Println(" ", a.URL) 276 } 277 fmt.Println("Testing your passphrase against all of them...") 278 var match *accounts.Account 279 for _, a := range err.Matches { 280 if err := ks.Unlock(a, auth); err == nil { 281 match = &a 282 break 283 } 284 } 285 if match == nil { 286 utils.Fatalf("None of the listed files could be unlocked.") 287 } 288 fmt.Printf("Your passphrase unlocked %s\n", match.URL) 289 fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 290 for _, a := range err.Matches { 291 if a != *match { 292 fmt.Println(" ", a.URL) 293 } 294 } 295 return *match 296 } 297 298 // accountCreate creates a new account into the keystore defined by the CLI flags. 299 func accountCreate(ctx *cli.Context) error { 300 cfg := makeAllConfigs(ctx) 301 utils.SetNodeConfig(ctx, &cfg.Node) 302 scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 303 304 if err != nil { 305 utils.Fatalf("Failed to read configuration: %v", err) 306 } 307 308 password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 309 310 account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 311 312 if err != nil { 313 utils.Fatalf("Failed to create account: %v", err) 314 } 315 fmt.Printf("\nYour new key was generated\n\n") 316 fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) 317 fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) 318 fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") 319 fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") 320 fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") 321 fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") 322 return nil 323 } 324 325 // accountUpdate transitions an account from a previous format to the current 326 // one, also providing the possibility to change the pass-phrase. 327 func accountUpdate(ctx *cli.Context) error { 328 if len(ctx.Args()) == 0 { 329 utils.Fatalf("No accounts specified to update") 330 } 331 332 cfg := makeAllConfigs(ctx) 333 stack := makeConfigNode(ctx, &cfg.Node) 334 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 335 336 for _, addr := range ctx.Args() { 337 account, oldPassword := unlockAccount(ks, addr, 0, nil) 338 newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 339 if err := ks.Update(account, oldPassword, newPassword); err != nil { 340 utils.Fatalf("Could not update the account: %v", err) 341 } 342 } 343 return nil 344 } 345 346 func importWallet(ctx *cli.Context) error { 347 keyfile := ctx.Args().First() 348 if len(keyfile) == 0 { 349 utils.Fatalf("keyfile must be given as argument") 350 } 351 keyJSON, err := ioutil.ReadFile(keyfile) 352 if err != nil { 353 utils.Fatalf("Could not read wallet file: %v", err) 354 } 355 356 cfg := makeAllConfigs(ctx) 357 stack := makeConfigNode(ctx, &cfg.Node) 358 passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) 359 360 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 361 acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) 362 if err != nil { 363 utils.Fatalf("%v", err) 364 } 365 fmt.Printf("Address: {%x}\n", acct.Address) 366 return nil 367 } 368 369 func accountImport(ctx *cli.Context) error { 370 keyfile := ctx.Args().First() 371 if len(keyfile) == 0 { 372 utils.Fatalf("keyfile must be given as argument") 373 } 374 key, err := crypto.LoadECDSA(keyfile) 375 if err != nil { 376 utils.Fatalf("Failed to load the private key: %v", err) 377 } 378 379 cfg := makeAllConfigs(ctx) 380 stack := makeConfigNode(ctx, &cfg.Node) 381 passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 382 383 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 384 acct, err := ks.ImportECDSA(key, passphrase) 385 if err != nil { 386 utils.Fatalf("Could not create the account: %v", err) 387 } 388 fmt.Printf("Address: {%x}\n", acct.Address) 389 return nil 390 } 391 392 func FindAccountKeypath(addr common.Address, keydir string) (keypath string, err error) { 393 addrStr := strings.ToLower(addr.String())[2:] 394 // find key path 395 err = filepath.Walk(keydir, func(walk string, info os.FileInfo, err error) error { 396 if err != nil { 397 return err 398 } 399 _, filename := filepath.Split(walk) 400 if strings.Contains(strings.ToLower(filename), addrStr) { 401 keypath = walk 402 return filepath.SkipDir 403 } 404 return nil 405 }) 406 if err != nil { 407 return keypath, err 408 } 409 if len(keypath) == 0 { 410 return keypath, errors.New("account not found") 411 } 412 return keypath, nil 413 }