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