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