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