github.com/amazechain/amc@v0.1.3/cmd/amc/accountcmd.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain 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 AmazeChain 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 AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "fmt" 21 "github.com/amazechain/amc/log" 22 "github.com/urfave/cli/v2" 23 "os" 24 25 "github.com/amazechain/amc/cmd/utils" 26 27 "github.com/amazechain/amc/accounts" 28 "github.com/amazechain/amc/accounts/keystore" 29 "github.com/amazechain/amc/common/crypto" 30 "github.com/amazechain/amc/conf" 31 "github.com/amazechain/amc/internal/node" 32 ) 33 34 var ( 35 walletCommand = &cli.Command{ 36 Name: "wallet", 37 Usage: "Manage AmazeChain presale wallets", 38 ArgsUsage: "", 39 Description: ` 40 AmazeChain 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: importWallet, 52 Flags: []cli.Flag{ 53 DataDirFlag, 54 KeyStoreDirFlag, 55 PasswordFileFlag, 56 LightKDFFlag, 57 }, 58 Description: ` 59 AmazeChain 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 Description: ` 72 73 Manage accounts, list all existing accounts, import a private key into a new 74 account, create a new account or update an existing account. 75 76 It supports interactive mode, when you are prompted for password as well as 77 non-interactive mode where passwords are supplied via a given password file. 78 Non-interactive mode is only meant for scripted use on test networks or known 79 safe environments. 80 81 Make sure you remember the password you gave when creating a new account (with 82 either new or import). Without it you are not able to unlock your account. 83 84 Note that exporting your key in unencrypted format is NOT supported. 85 86 Keys are stored under <DATADIR>/keystore. 87 It is safe to transfer the entire directory or the individual keys therein 88 between ethereum nodes by simply copying. 89 90 Make sure you backup your keys regularly.`, 91 Subcommands: []*cli.Command{ 92 { 93 Name: "list", 94 Usage: "Print summary of existing accounts", 95 Action: accountList, 96 Flags: []cli.Flag{ 97 DataDirFlag, 98 KeyStoreDirFlag, 99 }, 100 Description: ` 101 Print a short summary of all accounts`, 102 }, 103 { 104 Name: "new", 105 Usage: "Create a new account", 106 Action: accountCreate, 107 Flags: []cli.Flag{ 108 DataDirFlag, 109 KeyStoreDirFlag, 110 PasswordFileFlag, 111 LightKDFFlag, 112 }, 113 Description: ` 114 AmazeChain 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 password. 119 120 You must remember this password to unlock your account in the future. 121 122 For non-interactive use the password 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: accountUpdate, 132 ArgsUsage: "<address>", 133 Flags: []cli.Flag{ 134 DataDirFlag, 135 KeyStoreDirFlag, 136 LightKDFFlag, 137 }, 138 Description: ` 139 AmazeChain account update <address> 140 141 Update an existing account. 142 143 The account is saved in the newest version in encrypted format, you are prompted 144 for a password to unlock the account and another to save the updated file. 145 146 This same command can therefore be used to migrate an account of a deprecated 147 format to the newest format or change the password for an account. 148 149 For non-interactive use the password can be specified with the --password flag: 150 151 AmazeChain account update [options] <address> 152 153 Since only one password can be given, only format update can be performed, 154 changing your password is only possible interactively. 155 `, 156 }, 157 { 158 Name: "import", 159 Usage: "Import a private key into a new account", 160 Action: accountImport, 161 Flags: []cli.Flag{ 162 DataDirFlag, 163 KeyStoreDirFlag, 164 PasswordFileFlag, 165 LightKDFFlag, 166 }, 167 ArgsUsage: "<keyFile>", 168 Description: ` 169 AmazeChain account import <keyfile> 170 171 Imports an unencrypted private key from <keyfile> and creates a new account. 172 Prints the address. 173 174 The keyfile is assumed to contain an unencrypted private key in hexadecimal format. 175 176 The account is saved in encrypted format, you are prompted for a password. 177 178 You must remember this password to unlock your account in the future. 179 180 For non-interactive use the password can be specified with the -password flag: 181 182 AmazeChain account import [options] <keyfile> 183 184 Note: 185 As you can directly copy your encrypted accounts to another ethereum instance, 186 this import mechanism is not needed when you transfer an account between 187 nodes. 188 `, 189 }, 190 }, 191 } 192 ) 193 194 func importWallet(ctx *cli.Context) error { 195 if ctx.Args().Len() != 1 { 196 utils.Fatalf("keyfile must be given as the only argument") 197 } 198 keyfile := ctx.Args().First() 199 keyJSON, err := os.ReadFile(keyfile) 200 if err != nil { 201 utils.Fatalf("Could not read wallet file: %v", err) 202 } 203 204 stack, err := node.NewNode(ctx, &DefaultConfig) 205 if err != nil { 206 return err 207 } 208 209 passphrase := utils.GetPassPhraseWithList("", false, 0, MakePasswordList(ctx)) 210 211 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 212 acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) 213 if err != nil { 214 utils.Fatalf("%v", err) 215 } 216 fmt.Printf("Address: {%x}\n", acct.Address) 217 return nil 218 } 219 220 func accountList(ctx *cli.Context) error { 221 cfg := DefaultConfig 222 // Load config file. 223 if len(cfgFile) > 0 { 224 if err := conf.LoadConfigFromFile(cfgFile, &cfg); err != nil { 225 utils.Fatalf("%v", err) 226 } 227 } 228 229 stack, err := node.NewNode(ctx, &cfg) 230 if err != nil { 231 return err 232 } 233 234 var index int 235 for _, wallet := range stack.AccountManager().Wallets() { 236 for _, account := range wallet.Accounts() { 237 fmt.Printf("Account #%d: {%s} %s\n", index, account.Address, &account.URL) 238 index++ 239 } 240 } 241 return nil 242 } 243 244 // accountCreate creates a new account into the keystore defined by the CLI flags. 245 func accountCreate(ctx *cli.Context) error { 246 cfg := DefaultConfig 247 // Load config file. 248 if len(cfgFile) > 0 { 249 if err := conf.LoadConfigFromFile(cfgFile, &cfg); err != nil { 250 utils.Fatalf("%v", err) 251 } 252 } 253 254 keydir, err := cfg.NodeCfg.KeyDirConfig() 255 if err != nil { 256 utils.Fatalf("Failed to read configuration: %v", err) 257 } 258 scryptN := keystore.StandardScryptN 259 scryptP := keystore.StandardScryptP 260 if cfg.NodeCfg.UseLightweightKDF { 261 scryptN = keystore.LightScryptN 262 scryptP = keystore.LightScryptP 263 } 264 265 password := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx)) 266 267 account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) 268 269 if err != nil { 270 utils.Fatalf("Failed to create account: %v", err) 271 } 272 fmt.Printf("\nYour new key was generated\n\n") 273 fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) 274 fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) 275 fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") 276 fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") 277 fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") 278 fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") 279 return nil 280 } 281 282 // tries unlocking the specified account a few times. 283 func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { 284 account, err := utils.MakeAddress(ks, address) 285 if err != nil { 286 utils.Fatalf("Could not list accounts: %v", err) 287 } 288 for trials := 0; trials < 3; trials++ { 289 prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) 290 password := utils.GetPassPhraseWithList(prompt, false, i, passwords) 291 err = ks.Unlock(account, password) 292 if err == nil { 293 log.Info("Unlocked account", "address", account.Address.Hex()) 294 return account, password 295 } 296 if err, ok := err.(*keystore.AmbiguousAddrError); ok { 297 log.Info("Unlocked account", "address", account.Address.Hex()) 298 return ambiguousAddrRecovery(ks, err, password), password 299 } 300 if err != keystore.ErrDecrypt { 301 // No need to prompt again if the error is not decryption-related. 302 break 303 } 304 } 305 // All trials expended to unlock account, bail out 306 utils.Fatalf("Failed to unlock account %s (%v)", address, err) 307 308 return accounts.Account{}, "" 309 } 310 311 func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { 312 fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) 313 for _, a := range err.Matches { 314 fmt.Println(" ", a.URL) 315 } 316 fmt.Println("Testing your password against all of them...") 317 var match *accounts.Account 318 for i, a := range err.Matches { 319 if e := ks.Unlock(a, auth); e == nil { 320 match = &err.Matches[i] 321 break 322 } 323 } 324 if match == nil { 325 utils.Fatalf("None of the listed files could be unlocked.") 326 return accounts.Account{} 327 } 328 fmt.Printf("Your password unlocked %s\n", match.URL) 329 fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") 330 for _, a := range err.Matches { 331 if a != *match { 332 fmt.Println(" ", a.URL) 333 } 334 } 335 return *match 336 } 337 338 // accountUpdate transitions an account from a previous format to the current 339 // one, also providing the possibility to change the pass-phrase. 340 func accountUpdate(ctx *cli.Context) error { 341 if ctx.Args().Len() == 0 { 342 utils.Fatalf("No accounts specified to update") 343 } 344 345 stack, err := node.NewNode(ctx, &DefaultConfig) 346 if err != nil { 347 return err 348 } 349 350 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 351 352 for _, addr := range ctx.Args().Slice() { 353 account, oldPassword := unlockAccount(ks, addr, 0, nil) 354 newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) 355 if err := ks.Update(account, oldPassword, newPassword); err != nil { 356 utils.Fatalf("Could not update the account: %v", err) 357 } 358 } 359 return nil 360 } 361 362 func accountImport(ctx *cli.Context) error { 363 if ctx.Args().Len() != 1 { 364 utils.Fatalf("keyfile must be given as the only argument") 365 } 366 keyfile := ctx.Args().First() 367 key, err := crypto.LoadECDSA(keyfile) 368 if err != nil { 369 utils.Fatalf("Failed to load the private key: %v", err) 370 } 371 372 stack, err := node.NewNode(ctx, &DefaultConfig) 373 if err != nil { 374 return err 375 } 376 377 passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx)) 378 379 ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 380 acct, err := ks.ImportECDSA(key, passphrase) 381 if err != nil { 382 utils.Fatalf("Could not create the account: %v", err) 383 } 384 fmt.Printf("Address: {%x}\n", acct.Address) 385 return nil 386 }