github.com/ethereum/go-ethereum@v1.16.1/cmd/geth/accountcmd.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "strings" 24 25 "github.com/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/accounts/keystore" 27 "github.com/ethereum/go-ethereum/cmd/utils" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/urfave/cli/v2" 31 ) 32 33 var ( 34 walletCommand = &cli.Command{ 35 Name: "wallet", 36 Usage: "Manage Ethereum presale wallets", 37 ArgsUsage: "", 38 Description: ` 39 geth wallet import /path/to/my/presale.wallet 40 41 will prompt for your password and imports your ether presale account. 42 It can be used non-interactively with the --password option taking a 43 passwordfile as argument containing the wallet password in plaintext.`, 44 Subcommands: []*cli.Command{ 45 { 46 47 Name: "import", 48 Usage: "Import Ethereum presale wallet", 49 ArgsUsage: "<keyFile>", 50 Action: importWallet, 51 Flags: []cli.Flag{ 52 utils.DataDirFlag, 53 utils.KeyStoreDirFlag, 54 utils.PasswordFileFlag, 55 utils.LightKDFFlag, 56 }, 57 Description: ` 58 geth wallet [options] /path/to/my/presale.wallet 59 60 will prompt for your password and imports your ether presale account. 61 It can be used non-interactively with the --password option taking a 62 passwordfile as argument containing the wallet password in plaintext.`, 63 }, 64 }, 65 } 66 67 accountCommand = &cli.Command{ 68 Name: "account", 69 Usage: "Manage accounts", 70 Description: ` 71 72 Manage accounts, list all existing accounts, import a private key into a new 73 account, create a new account or update an existing account. 74 75 It supports interactive mode, when you are prompted for password as well as 76 non-interactive mode where passwords are supplied via a given password file. 77 Non-interactive mode is only meant for scripted use on test networks or known 78 safe environments. 79 80 Make sure you remember the password you gave when creating a new account (with 81 either new or import). Without it you are not able to unlock your account. 82 83 Note that exporting your key in unencrypted format is NOT supported. 84 85 Keys are stored under <DATADIR>/keystore. 86 It is safe to transfer the entire directory or the individual keys therein 87 between ethereum nodes by simply copying. 88 89 Make sure you backup your keys regularly.`, 90 Subcommands: []*cli.Command{ 91 { 92 Name: "list", 93 Usage: "Print summary of existing accounts", 94 Action: accountList, 95 Flags: []cli.Flag{ 96 utils.DataDirFlag, 97 utils.KeyStoreDirFlag, 98 }, 99 Description: ` 100 Print a short summary of all accounts`, 101 }, 102 { 103 Name: "new", 104 Usage: "Create a new account", 105 Action: accountCreate, 106 Flags: []cli.Flag{ 107 utils.DataDirFlag, 108 utils.KeyStoreDirFlag, 109 utils.PasswordFileFlag, 110 utils.LightKDFFlag, 111 }, 112 Description: ` 113 geth account new 114 115 Creates a new account and prints the address. 116 117 The account is saved in encrypted format, you are prompted for a password. 118 119 You must remember this password to unlock your account in the future. 120 121 For non-interactive use the password can be specified with the --password flag: 122 123 Note, this is meant to be used for testing only, it is a bad idea to save your 124 password to file or expose in any other way. 125 `, 126 }, 127 { 128 Name: "update", 129 Usage: "Update an existing account", 130 Action: accountUpdate, 131 ArgsUsage: "<address>", 132 Flags: []cli.Flag{ 133 utils.DataDirFlag, 134 utils.KeyStoreDirFlag, 135 utils.LightKDFFlag, 136 }, 137 Description: ` 138 geth account update <address> 139 140 Update an existing account. 141 142 The account is saved in the newest version in encrypted format, you are prompted 143 for a password to unlock the account and another to save the updated file. 144 145 This same command can therefore be used to migrate an account of a deprecated 146 format to the newest format or change the password for an account. 147 148 For non-interactive use the password can be specified with the --password flag: 149 150 geth account update [options] <address> 151 152 Since only one password can be given, only format update can be performed, 153 changing your password is only possible interactively. 154 `, 155 }, 156 { 157 Name: "import", 158 Usage: "Import a private key into a new account", 159 Action: accountImport, 160 Flags: []cli.Flag{ 161 utils.DataDirFlag, 162 utils.KeyStoreDirFlag, 163 utils.PasswordFileFlag, 164 utils.LightKDFFlag, 165 }, 166 ArgsUsage: "<keyFile>", 167 Description: ` 168 geth account import <keyfile> 169 170 Imports an unencrypted private key from <keyfile> and creates a new account. 171 Prints the address. 172 173 The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 174 175 The account is saved in encrypted format, you are prompted for a password. 176 177 You must remember this password to unlock your account in the future. 178 179 For non-interactive use the password can be specified with the -password flag: 180 181 geth account import [options] <keyfile> 182 183 Note: 184 As you can directly copy your encrypted accounts to another ethereum instance, 185 this import mechanism is not needed when you transfer an account between 186 nodes. 187 `, 188 }, 189 }, 190 } 191 ) 192 193 // makeAccountManager creates an account manager with backends 194 func makeAccountManager(ctx *cli.Context) *accounts.Manager { 195 cfg := loadBaseConfig(ctx) 196 am := accounts.NewManager(nil) 197 keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir() 198 if err != nil { 199 utils.Fatalf("Failed to get the keystore directory: %v", err) 200 } 201 if isEphemeral { 202 utils.Fatalf("Can't use ephemeral directory as keystore path") 203 } 204 205 if err := setAccountManagerBackends(&cfg.Node, am, keydir); err != nil { 206 utils.Fatalf("Failed to set account manager backends: %v", err) 207 } 208 return am 209 } 210 211 func accountList(ctx *cli.Context) error { 212 am := makeAccountManager(ctx) 213 var index int 214 for _, wallet := range am.Wallets() { 215 for _, account := range wallet.Accounts() { 216 fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) 217 index++ 218 } 219 } 220 221 return nil 222 } 223 224 // readPasswordFromFile reads the first line of the given file, trims line endings, 225 // and returns the password and whether the reading was successful. 226 func readPasswordFromFile(path string) (string, bool) { 227 if path == "" { 228 return "", false 229 } 230 text, err := os.ReadFile(path) 231 if err != nil { 232 utils.Fatalf("Failed to read password file: %v", err) 233 } 234 lines := strings.Split(string(text), "\n") 235 if len(lines) == 0 { 236 return "", false 237 } 238 // Sanitise DOS line endings. 239 return strings.TrimRight(lines[0], "\r"), true 240 } 241 242 // accountCreate creates a new account into the keystore defined by the CLI flags. 243 func accountCreate(ctx *cli.Context) error { 244 cfg := loadBaseConfig(ctx) 245 keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir() 246 if err != nil { 247 utils.Fatalf("Failed to get the keystore directory: %v", err) 248 } 249 if isEphemeral { 250 utils.Fatalf("Can't use ephemeral directory as keystore path") 251 } 252 scryptN := keystore.StandardScryptN 253 scryptP := keystore.StandardScryptP 254 if cfg.Node.UseLightweightKDF { 255 scryptN = keystore.LightScryptN 256 scryptP = keystore.LightScryptP 257 } 258 259 password, ok := readPasswordFromFile(ctx.Path(utils.PasswordFileFlag.Name)) 260 if !ok { 261 password = utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true) 262 } 263 account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 264 265 if err != nil { 266 utils.Fatalf("Failed to create account: %v", err) 267 } 268 fmt.Printf("\nYour new key was generated\n\n") 269 fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) 270 fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) 271 fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") 272 fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") 273 fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") 274 fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") 275 return nil 276 } 277 278 // accountUpdate transitions an account from a previous format to the current 279 // one, also providing the possibility to change the pass-phrase. 280 func accountUpdate(ctx *cli.Context) error { 281 if ctx.Args().Len() == 0 { 282 utils.Fatalf("No accounts specified to update") 283 } 284 am := makeAccountManager(ctx) 285 backends := am.Backends(keystore.KeyStoreType) 286 if len(backends) == 0 { 287 utils.Fatalf("Keystore is not available") 288 } 289 ks := backends[0].(*keystore.KeyStore) 290 291 for _, addr := range ctx.Args().Slice() { 292 if !common.IsHexAddress(addr) { 293 return errors.New("address must be specified in hexadecimal form") 294 } 295 account := accounts.Account{Address: common.HexToAddress(addr)} 296 newPassword := utils.GetPassPhrase("Please give a NEW password. Do not forget this password.", true) 297 updateFn := func(attempt int) error { 298 prompt := fmt.Sprintf("Please provide the OLD password for account %s | Attempt %d/%d", addr, attempt+1, 3) 299 password := utils.GetPassPhrase(prompt, false) 300 return ks.Update(account, password, newPassword) 301 } 302 // let user attempt unlock thrice. 303 err := updateFn(0) 304 for attempts := 1; attempts < 3 && errors.Is(err, keystore.ErrDecrypt); attempts++ { 305 err = updateFn(attempts) 306 } 307 if err != nil { 308 return fmt.Errorf("could not update account: %w", err) 309 } 310 } 311 return nil 312 } 313 314 func importWallet(ctx *cli.Context) error { 315 if ctx.Args().Len() != 1 { 316 utils.Fatalf("keyfile must be given as the only argument") 317 } 318 keyfile := ctx.Args().First() 319 keyJSON, err := os.ReadFile(keyfile) 320 if err != nil { 321 utils.Fatalf("Could not read wallet file: %v", err) 322 } 323 324 am := makeAccountManager(ctx) 325 backends := am.Backends(keystore.KeyStoreType) 326 if len(backends) == 0 { 327 utils.Fatalf("Keystore is not available") 328 } 329 password, ok := readPasswordFromFile(ctx.Path(utils.PasswordFileFlag.Name)) 330 if !ok { 331 password = utils.GetPassPhrase("", false) 332 } 333 ks := backends[0].(*keystore.KeyStore) 334 acct, err := ks.ImportPreSaleKey(keyJSON, password) 335 if err != nil { 336 utils.Fatalf("%v", err) 337 } 338 fmt.Printf("Address: {%x}\n", acct.Address) 339 return nil 340 } 341 342 func accountImport(ctx *cli.Context) error { 343 if ctx.Args().Len() != 1 { 344 utils.Fatalf("keyfile must be given as the only argument") 345 } 346 keyfile := ctx.Args().First() 347 key, err := crypto.LoadECDSA(keyfile) 348 if err != nil { 349 utils.Fatalf("Failed to load the private key: %v", err) 350 } 351 am := makeAccountManager(ctx) 352 backends := am.Backends(keystore.KeyStoreType) 353 if len(backends) == 0 { 354 utils.Fatalf("Keystore is not available") 355 } 356 ks := backends[0].(*keystore.KeyStore) 357 password, ok := readPasswordFromFile(ctx.Path(utils.PasswordFileFlag.Name)) 358 if !ok { 359 password = utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true) 360 } 361 acct, err := ks.ImportECDSA(key, password) 362 if err != nil { 363 utils.Fatalf("Could not create the account: %v", err) 364 } 365 fmt.Printf("Address: {%x}\n", acct.Address) 366 return nil 367 }