github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/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  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  
    25  	"github.com/ethereumproject/go-ethereum/accounts"
    26  	"github.com/ethereumproject/go-ethereum/console"
    27  	"github.com/ethereumproject/go-ethereum/crypto"
    28  	"github.com/ethereumproject/go-ethereum/logger"
    29  	"github.com/ethereumproject/go-ethereum/logger/glog"
    30  	"gopkg.in/urfave/cli.v1"
    31  )
    32  
    33  var (
    34  	AccountsIndexFlag = cli.BoolFlag{
    35  		Name:  "index-accounts,indexaccounts",
    36  		Usage: "Enable key-value db store for indexing large amounts of key files",
    37  	}
    38  	walletCommand = cli.Command{
    39  		Name:  "wallet",
    40  		Usage: "Ethereum presale wallet",
    41  		Subcommands: []cli.Command{
    42  			{
    43  				Action: importWallet,
    44  				Name:   "import",
    45  				Usage:  "import ethereum presale wallet",
    46  			},
    47  		},
    48  		Description: `
    49  
    50  geth wallet import /path/to/my/presale.wallet
    51  
    52  	Will prompt for your password and imports your ether presale account.
    53  	It can be used non-interactively with the --password option taking a
    54  	passwordfile as argument containing the wallet password in plaintext.
    55  
    56  	`}
    57  	accountCommand = cli.Command{
    58  		Action: accountMan,
    59  		Name:   "account",
    60  		Usage:  "Manage accounts",
    61  		Description: `
    62  
    63  	Manage accounts lets you create new accounts, list all existing accounts,
    64  	import a private key into a new account.
    65  
    66  	'$ geth account <command> --help' shows help for any subcommand.
    67  
    68  	It supports interactive mode, when you are prompted for password as well as
    69  	non-interactive mode where passwords are supplied via a given password file.
    70  	Non-interactive mode is only meant for scripted use on test networks or known
    71  	safe environments.
    72  
    73  	Make sure you remember the password you gave when creating a new account (with
    74  	either new or import). Without it you are not able to unlock your account.
    75  
    76  	Note that exporting your key in unencrypted format is NOT supported.
    77  
    78  	Keys are stored under <DATADIR>/<CHAINDIR>/keystore.
    79  	It is safe to transfer the entire directory or the individual keys therein
    80  	between ethereum nodes by simply copying.
    81  	Make sure you backup your keys regularly.
    82  
    83  	In order to use your account to send transactions, you need to unlock them using
    84  	the '--unlock' option. The argument is a space separated list of addresses or
    85  	indexes. If used non-interactively with a passwordfile, the file should contain
    86  	the respective passwords one per line. If you unlock n accounts and the password
    87  	file contains less than n entries, then the last password is meant to apply to
    88  	all remaining accounts.
    89  
    90  	And finally. DO NOT FORGET YOUR PASSWORD.
    91  		`,
    92  		Subcommands: []cli.Command{
    93  			{
    94  				Action: accountList,
    95  				Name:   "list",
    96  				Usage:  "Print account addresses",
    97  				Description: `
    98  geth account list
    99  
   100  	Lists all accounts found in geth's keystore.
   101  	Accounts are listed as below:
   102  
   103  		Account #0: {7fa65f0395f5ee0bcbae969d711823ab4353beae} /Users/ia/Library/EthereumClassic/mainnet/keystore/UTC--2017-10-31T13-53-59.993482857Z--7fa65f0395f5ee0bcbae969d711823ab4353beae
   104  		Account #1: {b5c694a4cdbc1820ba4ee8fd6f5ab71a25782534} /Users/ia/Library/EthereumClassic/mainnet/keystore/my_other_key
   105  				`,
   106  			},
   107  			{
   108  				Action: accountCreate,
   109  				Name:   "new",
   110  				Usage:  "Create a new account",
   111  				Description: `
   112  
   113  geth account new
   114  
   115  	Creates a new account. Prints the address.
   116  	The account is saved in encrypted format, you are prompted for a passphrase.
   117  
   118  	You must remember this passphrase to unlock your account in the future.
   119  
   120  	For non-interactive use the passphrase can be specified with the --password flag:
   121  
   122  		geth --password <passwordfile> account new
   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  				Action: accountUpdate,
   130  				Name:   "update",
   131  				Usage:  "Update an existing account",
   132  				Description: `
   133  
   134  geth account update <address>
   135  
   136  	Update an existing account.
   137  	The account is saved in the newest version in encrypted format, you are prompted
   138  	for a passphrase to unlock the account and another to save the updated file.
   139  	This same command can therefore be used to migrate an account of a deprecated
   140  	format to the newest format or change the password for an account.
   141  
   142  	For non-interactive use the passphrase can be specified with the --password flag:
   143  
   144  		geth --password <passwordfile> account update <address>
   145  
   146  	Since only one password can be given, only format update can be performed,
   147  	changing your password is only possible interactively.
   148  				`,
   149  			},
   150  			{
   151  				Action: accountImport,
   152  				Name:   "import",
   153  				Usage:  "Import a private key into a new account",
   154  				Description: `
   155  
   156  geth account import <keyfile>
   157  
   158  	Imports an unencrypted private key from <keyfile> and creates a new account.
   159  	Prints the address.
   160  
   161  	The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
   162  	The account is saved in encrypted format, you are prompted for a passphrase.
   163  
   164  	You must remember this passphrase to unlock your account in the future.
   165  
   166  	For non-interactive use the passphrase can be specified with the -password flag:
   167  
   168  		geth --password <passwordfile> account import <keyfile>
   169  
   170  	Note:
   171  	As you can directly copy your encrypted accounts to another ethereum instance,
   172  	this import mechanism is not needed when you transfer an account between
   173  	nodes.
   174  				`,
   175  			},
   176  			{
   177  				Action: accountIndex,
   178  				Name:   "index",
   179  				Usage:  "Build persistent account index",
   180  				Description: `
   181  
   182  geth account index
   183  
   184  	Create keystore directory index cache database (keystore/accounts.db),
   185  	which is relevant for use with large amounts of key files (>10,000).
   186  
   187  	While idempotent, this command is only intended to handle the work of initial index creation.
   188  	Therefore, it is only useful to run once, when it's your first time using '--index-accounts' flag option,
   189  	and MUST be run in conjunction with that flag.
   190  
   191  	It non-recursively indexes all valid key files from keystore/*
   192  		`,
   193  			},
   194  		},
   195  	}
   196  )
   197  
   198  func accountIndex(ctx *cli.Context) error {
   199  	n := aliasableName(AccountsIndexFlag.Name, ctx)
   200  	if !ctx.GlobalBool(n) {
   201  		if e := ctx.GlobalSet(n, "true"); e != nil {
   202  			log.Fatal("err set flag", e)
   203  		}
   204  	}
   205  	am := MakeAccountManager(ctx)
   206  	errs := am.BuildIndexDB()
   207  	if len(errs) > 0 {
   208  		for _, e := range errs {
   209  			if e != nil {
   210  				glog.V(logger.Error).Errorf("init cache db err: %v", e)
   211  			}
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  func accountMan(ctx *cli.Context) error {
   218  	return cli.ShowSubcommandHelp(ctx)
   219  }
   220  
   221  func accountList(ctx *cli.Context) error {
   222  	accman := MakeAccountManager(ctx)
   223  	for i, acct := range accman.Accounts() {
   224  
   225  		fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
   226  	}
   227  	return nil
   228  }
   229  
   230  // tries unlocking the specified account a few times.
   231  func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) {
   232  	account, err := MakeAddress(accman, address)
   233  	if err != nil {
   234  		log.Fatal("Could not list accounts: ", err)
   235  	}
   236  	for trials := 0; trials < 3; trials++ {
   237  		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
   238  		password := getPassPhrase(prompt, false, i, passwords)
   239  		err = accman.Unlock(account, password)
   240  		if err == nil {
   241  			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
   242  			glog.D(logger.Error).Infof("Unlocked account %x", account.Address)
   243  			return account, password
   244  		}
   245  		if err, ok := err.(*accounts.AmbiguousAddrError); ok {
   246  			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
   247  			glog.D(logger.Error).Infof("Unlocked account %x", account.Address)
   248  			return ambiguousAddrRecovery(accman, err, password), password
   249  		}
   250  		if err != accounts.ErrDecrypt {
   251  			// No need to prompt again if the error is not decryption-related.
   252  			break
   253  		}
   254  	}
   255  	// All trials expended to unlock account, bail out
   256  	log.Fatalf("Failed to unlock account %s (%v)", address, err)
   257  	return accounts.Account{}, ""
   258  }
   259  
   260  // getPassPhrase retrieves the passwor associated with an account, either fetched
   261  // from a list of preloaded passphrases, or requested interactively from the user.
   262  func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
   263  	// If a list of passwords was supplied, retrieve from them
   264  	if len(passwords) > 0 {
   265  		if i < len(passwords) {
   266  			return passwords[i]
   267  		}
   268  		return passwords[len(passwords)-1]
   269  	}
   270  	// Otherwise prompt the user for the password
   271  	if prompt != "" {
   272  		fmt.Println(prompt)
   273  	}
   274  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   275  	if err != nil {
   276  		log.Fatal("Failed to read passphrase: ", err)
   277  	}
   278  	if confirmation {
   279  		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
   280  		if err != nil {
   281  			log.Fatal("Failed to read passphrase confirmation: ", err)
   282  		}
   283  		if password != confirm {
   284  			log.Fatal("Passphrases do not match")
   285  		}
   286  	}
   287  	return password
   288  }
   289  
   290  func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account {
   291  	fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
   292  	for _, a := range err.Matches {
   293  		fmt.Println("  ", a.File)
   294  	}
   295  	fmt.Println("Testing your passphrase against all of them...")
   296  	var match *accounts.Account
   297  	for _, a := range err.Matches {
   298  		if err := am.Unlock(a, auth); err == nil {
   299  			match = &a
   300  			break
   301  		}
   302  	}
   303  	if match == nil {
   304  		log.Fatal("None of the listed files could be unlocked.")
   305  	}
   306  	fmt.Printf("Your passphrase unlocked %s\n", match.File)
   307  	fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:")
   308  	for _, a := range err.Matches {
   309  		if a != *match {
   310  			fmt.Println("  ", a.File)
   311  		}
   312  	}
   313  	return *match
   314  }
   315  
   316  // accountCreate creates a new account into the keystore defined by the CLI flags.
   317  func accountCreate(ctx *cli.Context) error {
   318  	accman := MakeAccountManager(ctx)
   319  	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx))
   320  
   321  	account, err := accman.NewAccount(password)
   322  	if err != nil {
   323  		log.Fatal("Failed to create account: ", err)
   324  	}
   325  	fmt.Printf("Address: {%x}\n", account.Address)
   326  	return nil
   327  }
   328  
   329  // accountUpdate transitions an account from a previous format to the current
   330  // one, also providing the possibility to change the pass-phrase.
   331  func accountUpdate(ctx *cli.Context) error {
   332  	if len(ctx.Args()) == 0 {
   333  		log.Fatal("No accounts specified to update")
   334  	}
   335  	accman := MakeAccountManager(ctx)
   336  
   337  	account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
   338  	newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
   339  	if err := accman.Update(account, oldPassword, newPassword); err != nil {
   340  		log.Fatal("Could not update the account: ", err)
   341  	}
   342  	return nil
   343  }
   344  
   345  func importWallet(ctx *cli.Context) error {
   346  	keyfile := ctx.Args().First()
   347  	if len(keyfile) == 0 {
   348  		log.Fatal("keyfile must be given as argument")
   349  	}
   350  	keyJson, err := ioutil.ReadFile(keyfile)
   351  	if err != nil {
   352  		log.Fatal("Could not read wallet file: ", err)
   353  	}
   354  	accman := MakeAccountManager(ctx)
   355  	passphrase := getPassPhrase("", false, 0, MakePasswordList(ctx))
   356  
   357  	acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
   358  	if err != nil {
   359  		log.Fatal(err)
   360  	}
   361  	fmt.Printf("Address: {%x}\n", acct.Address)
   362  	return nil
   363  }
   364  
   365  func accountImport(ctx *cli.Context) error {
   366  	keyfile := ctx.Args().First()
   367  	if len(keyfile) == 0 {
   368  		log.Fatal("keyfile must be given as argument")
   369  	}
   370  	f, err := os.Open(keyfile)
   371  	if err != nil {
   372  		log.Fatalf("could not open key file: %v", err)
   373  	}
   374  	key, err := crypto.LoadECDSA(f)
   375  	if err != nil {
   376  		log.Fatalf("unable to decode keyfile '%s': %v", keyfile, err)
   377  	}
   378  	err = f.Close()
   379  	if err != nil {
   380  		log.Fatalf("could not close key file: %v", err)
   381  	}
   382  	accman := MakeAccountManager(ctx)
   383  	passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, MakePasswordList(ctx))
   384  	acct, err := accman.ImportECDSA(key, passphrase)
   385  	if err != nil {
   386  		log.Fatal("Could not create the account: ", err)
   387  	}
   388  	fmt.Printf("Address: {%x}\n", acct.Address)
   389  	return nil
   390  }