github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/cmd/intchain/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 "fmt" 21 "github.com/intfoundation/intchain/params" 22 "io/ioutil" 23 24 "github.com/intfoundation/intchain/accounts" 25 "github.com/intfoundation/intchain/accounts/keystore" 26 "github.com/intfoundation/intchain/cmd/utils" 27 "github.com/intfoundation/intchain/console" 28 "github.com/intfoundation/intchain/crypto" 29 "github.com/intfoundation/intchain/log" 30 "gopkg.in/urfave/cli.v1" 31 ) 32 33 var ( 34 walletCommand = cli.Command{ 35 Name: "wallet", 36 Usage: "Manage Ethereum presale wallets", 37 ArgsUsage: "", 38 Category: "ACCOUNT COMMANDS", 39 Description: ` 40 intchain wallet import /path/to/my/presale.wallet 41 42 will prompt for your password and imports your ether presale account. 43 It can be used non-interactively with the --password option taking a 44 passwordfile as argument containing the wallet password in plaintext.`, 45 Subcommands: []cli.Command{ 46 { 47 48 Name: "import", 49 Usage: "Import Ethereum presale wallet", 50 ArgsUsage: "<keyFile>", 51 Action: utils.MigrateFlags(importWallet), 52 Category: "ACCOUNT COMMANDS", 53 Flags: []cli.Flag{ 54 utils.DataDirFlag, 55 utils.KeyStoreDirFlag, 56 utils.PasswordFileFlag, 57 }, 58 Description: ` 59 intchain wallet [options] /path/to/my/presale.wallet 60 61 will prompt for your password and imports your ether presale account. 62 It can be used non-interactively with the --password option taking a 63 passwordfile as argument containing the wallet password in plaintext.`, 64 }, 65 }, 66 } 67 68 accountCommand = cli.Command{ 69 Name: "account", 70 Usage: "Manage accounts", 71 Category: "ACCOUNT COMMANDS", 72 Description: ` 73 74 Manage accounts, list all existing accounts, import a private key into a new 75 account, create a new account or update an existing account. 76 77 It supports interactive mode, when you are prompted for password as well as 78 non-interactive mode where passwords are supplied via a given password file. 79 Non-interactive mode is only meant for scripted use on test networks or known 80 safe environments. 81 82 Make sure you remember the password you gave when creating a new account (with 83 either new or import). Without it you are not able to unlock your account. 84 85 Note that exporting your key in unencrypted format is NOT supported. 86 87 Keys are stored under <DATADIR>/keystore. 88 It is safe to transfer the entire directory or the individual keys therein 89 between ethereum nodes by simply copying. 90 91 Make sure you backup your keys regularly.`, 92 Subcommands: []cli.Command{ 93 { 94 Name: "list", 95 Usage: "Print summary of existing accounts", 96 Action: utils.MigrateFlags(accountList), 97 Flags: []cli.Flag{ 98 utils.DataDirFlag, 99 utils.KeyStoreDirFlag, 100 }, 101 Description: ` 102 Print a short summary of all accounts`, 103 }, 104 { 105 Name: "new", 106 Usage: "Create a new account", 107 Action: utils.MigrateFlags(accountCreate), 108 Flags: []cli.Flag{ 109 utils.DataDirFlag, 110 utils.KeyStoreDirFlag, 111 utils.PasswordFileFlag, 112 }, 113 Description: ` 114 intchain account new 115 116 Creates a new account and prints the address. 117 118 The account is saved in encrypted format, you are prompted for a passphrase. 119 120 You must remember this passphrase to unlock your account in the future. 121 122 For non-interactive use the passphrase can be specified with the --password flag: 123 124 Note, this is meant to be used for testing only, it is a bad idea to save your 125 password to file or expose in any other way. 126 `, 127 }, 128 { 129 Name: "update", 130 Usage: "Update an existing account", 131 Action: utils.MigrateFlags(accountUpdate), 132 ArgsUsage: "<address>", 133 Flags: []cli.Flag{ 134 utils.DataDirFlag, 135 utils.KeyStoreDirFlag, 136 }, 137 Description: ` 138 intchain 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 passphrase 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 passphrase can be specified with the --password flag: 149 150 intchain 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: utils.MigrateFlags(accountImport), 160 Flags: []cli.Flag{ 161 utils.DataDirFlag, 162 utils.KeyStoreDirFlag, 163 utils.PasswordFileFlag, 164 }, 165 ArgsUsage: "<keyFile>", 166 Description: ` 167 intchain account import <keyfile> 168 169 Imports an unencrypted private key from <keyfile> and creates a new account. 170 Prints the address. 171 172 The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 173 174 The account is saved in encrypted format, you are prompted for a passphrase. 175 176 You must remember this passphrase to unlock your account in the future. 177 178 For non-interactive use the passphrase can be specified with the -password flag: 179 180 intchain account import [options] <keyfile> 181 182 Note: 183 As you can directly copy your encrypted accounts to another ethereum instance, 184 this import mechanism is not needed when you transfer an account between 185 nodes. 186 `, 187 }, 188 }, 189 } 190 ) 191 192 func accountList(ctx *cli.Context) error { 193 ChainId := params.MainnetChainConfig.IntChainId 194 195 if ctx.GlobalIsSet(utils.TestnetFlag.Name) { 196 fmt.Printf("testnet: %v\n", params.TestnetChainConfig.IntChainId) 197 ChainId = params.TestnetChainConfig.IntChainId 198 } 199 200 stack, _ := makeConfigNode(ctx, ChainId) 201 var index int 202 for _, wallet := range stack.AccountManager().Wallets() { 203 for _, account := range wallet.Accounts() { 204 fmt.Printf("Account #%d: {%v} %s\n", index, account.Address.String(), &account.URL) 205 index++ 206 } 207 } 208 return nil 209 } 210 211 // tries unlocking the specified account a few times. 212 func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 213 account, err := utils.MakeAddress(ks, address) 214 if err != nil { 215 utils.Fatalf("Could not list accounts: %v", err) 216 } 217 for trials := 0; trials < 3; trials++ { 218 prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 219 password := getPassPhrase(prompt, false, i, passwords) 220 err = ks.Unlock(account, password) 221 if err == nil { 222 //log.Info("Unlocked account", "address", account.Address.Hex()) 223 log.Info("Unlocked account", "address", account.Address.String()) 224 return account, password 225 } 226 if err, ok := err.(*keystore.AmbiguousAddrError); ok { 227 //log.Info("Unlocked account", "address", account.Address.Hex()) 228 log.Info("Unlocked account", "address", account.Address.String()) 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(prompt 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 prompt != "" { 254 fmt.Println(prompt) 255 } 256 password, err := console.Stdin.PromptPassword("Passphrase: ") 257 if err != nil { 258 utils.Fatalf("Failed to read passphrase: %v", err) 259 } 260 if confirmation { 261 confirm, err := console.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 := gethConfig{Node: defaultNodeConfig()} 301 // Load config file. 302 if file := ctx.GlobalString(configFileFlag.Name); file != "" { 303 if err := loadConfig(file, &cfg); err != nil { 304 utils.Fatalf("%v", err) 305 } 306 } 307 308 cfg.Node.ChainId = params.MainnetChainConfig.IntChainId 309 310 if ctx.GlobalIsSet(utils.TestnetFlag.Name) { 311 fmt.Printf("testnet: %v\n", params.TestnetChainConfig.IntChainId) 312 cfg.Node.ChainId = params.TestnetChainConfig.IntChainId 313 } 314 315 utils.SetNodeConfig(ctx, &cfg.Node) 316 scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() 317 318 if err != nil { 319 utils.Fatalf("Failed to read configuration: %v", err) 320 } 321 322 password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 323 324 address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 325 326 if err != nil { 327 utils.Fatalf("Failed to create account: %v", err) 328 } 329 fmt.Printf("Address: %v\n", address.String()) 330 return nil 331 } 332 333 // accountUpdate transitions an account from a previous format to the current 334 // one, also providing the possibility to change the pass-phrase. 335 func accountUpdate(ctx *cli.Context) error { 336 if len(ctx.Args()) == 0 { 337 utils.Fatalf("No accounts specified to update") 338 } 339 stack, _ := makeConfigNode(ctx, clientIdentifier) 340 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 341 342 for _, addr := range ctx.Args() { 343 account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) 344 newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) 345 if err := ks.Update(account, oldPassword, newPassword); err != nil { 346 utils.Fatalf("Could not update the account: %v", err) 347 } 348 } 349 return nil 350 } 351 352 func importWallet(ctx *cli.Context) error { 353 keyfile := ctx.Args().First() 354 if len(keyfile) == 0 { 355 utils.Fatalf("keyfile must be given as argument") 356 } 357 keyJson, err := ioutil.ReadFile(keyfile) 358 if err != nil { 359 utils.Fatalf("Could not read wallet file: %v", err) 360 } 361 362 stack, _ := makeConfigNode(ctx, clientIdentifier) 363 passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) 364 365 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 366 acct, err := ks.ImportPreSaleKey(keyJson, passphrase) 367 if err != nil { 368 utils.Fatalf("%v", err) 369 } 370 fmt.Printf("Address: {%x}\n", acct.Address) 371 return nil 372 } 373 374 func accountImport(ctx *cli.Context) error { 375 keyfile := ctx.Args().First() 376 if len(keyfile) == 0 { 377 utils.Fatalf("keyfile must be given as argument") 378 } 379 key, err := crypto.LoadECDSA(keyfile) 380 if err != nil { 381 utils.Fatalf("Failed to load the private key: %v", err) 382 } 383 stack, _ := makeConfigNode(ctx, clientIdentifier) 384 passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) 385 386 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 387 acct, err := ks.ImportECDSA(key, passphrase) 388 if err != nil { 389 utils.Fatalf("Could not create the account: %v", err) 390 } 391 fmt.Printf("Address: {%x}\n", acct.Address) 392 return nil 393 }