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