github.com/klaytn/klaytn@v1.10.2/cmd/utils/nodecmd/accountcmd.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // go-ethereum is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // go-ethereum is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from cmd/geth/accountcmd.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package nodecmd
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"github.com/klaytn/klaytn/accounts"
    27  	"github.com/klaytn/klaytn/accounts/keystore"
    28  	"github.com/klaytn/klaytn/api/debug"
    29  	"github.com/klaytn/klaytn/cmd/utils"
    30  	"github.com/klaytn/klaytn/console"
    31  	"github.com/klaytn/klaytn/crypto"
    32  	"github.com/klaytn/klaytn/log"
    33  	"gopkg.in/urfave/cli.v1"
    34  )
    35  
    36  var AccountCommand = cli.Command{
    37  	Name:     "account",
    38  	Usage:    "Manage accounts",
    39  	Category: "ACCOUNT COMMANDS",
    40  	Description: `
    41  
    42  Manage accounts, list all existing accounts, import a private key into a new
    43  account, create a new account or update an existing account.
    44  
    45  It supports interactive mode, when you are prompted for password as well as
    46  non-interactive mode where passwords are supplied via a given password file.
    47  Non-interactive mode is only meant for scripted use on test networks or known
    48  safe environments.
    49  
    50  Make sure you remember the password you gave when creating a new account (with
    51  either new or import). Without it you are not able to unlock your account.
    52  
    53  Note that exporting your key in unencrypted format is NOT supported.
    54  
    55  Keys are stored under <DATADIR>/keystore.
    56  It is safe to transfer the entire directory or the individual keys therein
    57  between klay nodes by simply copying.
    58  
    59  Make sure you backup your keys regularly.`,
    60  	Subcommands: []cli.Command{
    61  		{
    62  			Name:   "list",
    63  			Usage:  "Print summary of existing accounts",
    64  			Action: utils.MigrateFlags(accountList),
    65  			Flags: []cli.Flag{
    66  				utils.DataDirFlag,
    67  				utils.KeyStoreDirFlag,
    68  			},
    69  			Description: `
    70  Print a short summary of all accounts`,
    71  		},
    72  		{
    73  			Name:   "new",
    74  			Usage:  "Create a new account",
    75  			Action: utils.MigrateFlags(accountCreate),
    76  			Flags: []cli.Flag{
    77  				utils.DataDirFlag,
    78  				utils.KeyStoreDirFlag,
    79  				utils.PasswordFileFlag,
    80  				utils.LightKDFFlag,
    81  			},
    82  			Description: `
    83      klay account new
    84  
    85  Creates a new account and prints the address.
    86  
    87  The account is saved in encrypted format, you are prompted for a passphrase.
    88  
    89  You must remember this passphrase to unlock your account in the future.
    90  
    91  For non-interactive use the passphrase can be specified with the --password flag:
    92  
    93  Note, this is meant to be used for testing only, it is a bad idea to save your
    94  password to file or expose in any other way.
    95  `,
    96  		},
    97  		{
    98  			Name:      "update",
    99  			Usage:     "Update an existing account",
   100  			Action:    utils.MigrateFlags(accountUpdate),
   101  			ArgsUsage: "<address>",
   102  			Flags: []cli.Flag{
   103  				utils.DataDirFlag,
   104  				utils.KeyStoreDirFlag,
   105  				utils.LightKDFFlag,
   106  			},
   107  			Description: `
   108      klay account update <address>
   109  
   110  Update an existing account.
   111  
   112  The account is saved in the newest version in encrypted format, you are prompted
   113  for a passphrase to unlock the account and another to save the updated file.
   114  
   115  This same command can therefore be used to migrate an account of a deprecated
   116  format to the newest format or change the password for an account.
   117  
   118  For non-interactive use the passphrase can be specified with the --password flag:
   119  
   120      klay account update [options] <address>
   121  
   122  Since only one password can be given, only format update can be performed,
   123  changing your password is only possible interactively.
   124  `,
   125  		},
   126  		{
   127  			Name:   "import",
   128  			Usage:  "Import a private key into a new account",
   129  			Action: utils.MigrateFlags(accountImport),
   130  			Flags: []cli.Flag{
   131  				utils.DataDirFlag,
   132  				utils.KeyStoreDirFlag,
   133  				utils.PasswordFileFlag,
   134  				utils.LightKDFFlag,
   135  			},
   136  			ArgsUsage: "<keyFile>",
   137  			Description: `
   138      klay account import <keyfile>
   139  
   140  Imports an unencrypted private key from <keyfile> and creates a new account.
   141  Prints the address.
   142  
   143  The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
   144  
   145  The account is saved in encrypted format, you are prompted for a passphrase.
   146  
   147  You must remember this passphrase to unlock your account in the future.
   148  
   149  For non-interactive use the passphrase can be specified with the -password flag:
   150  
   151      klay account import [options] <keyfile>
   152  
   153  Note:
   154  As you can directly copy your encrypted accounts to another klay instance,
   155  this import mechanism is not needed when you transfer an account between
   156  nodes.
   157  `,
   158  		},
   159  	},
   160  }
   161  
   162  func accountList(ctx *cli.Context) error {
   163  	if glogger, err := debug.GetGlogger(); err == nil {
   164  		log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError))
   165  	}
   166  	stack, _ := utils.MakeConfigNode(ctx)
   167  	var index int
   168  	for _, wallet := range stack.AccountManager().Wallets() {
   169  		for _, account := range wallet.Accounts() {
   170  			fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL)
   171  			index++
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  // tries unlocking the specified account a few times.
   178  func UnlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
   179  	account, err := utils.MakeAddress(ks, address)
   180  	if err != nil {
   181  		log.Fatalf("Could not list accounts: %v", err)
   182  	}
   183  	for trials := 0; trials < 3; trials++ {
   184  		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
   185  		password := getPassPhrase(prompt, false, i, passwords)
   186  		err = ks.Unlock(account, password)
   187  		if err == nil {
   188  			logger.Info("Unlocked account", "address", account.Address.Hex())
   189  			return account, password
   190  		}
   191  		if err, ok := err.(*keystore.AmbiguousAddrError); ok {
   192  			logger.Info("Unlocked account", "address", account.Address.Hex())
   193  			return ambiguousAddrRecovery(ks, err, password), password
   194  		}
   195  		if err != keystore.ErrDecrypt {
   196  			// No need to prompt again if the error is not decryption-related.
   197  			break
   198  		}
   199  	}
   200  	// All trials expended to unlock account, bail out
   201  	log.Fatalf("Failed to unlock account %s (%v)", address, err)
   202  
   203  	return accounts.Account{}, ""
   204  }
   205  
   206  // getPassPhrase retrieves the password associated with an account, either fetched
   207  // from a list of preloaded passphrases, or requested interactively from the user.
   208  func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
   209  	// If a list of passwords was supplied, retrieve from them
   210  	if len(passwords) > 0 {
   211  		if i < len(passwords) {
   212  			return passwords[i]
   213  		}
   214  		return passwords[len(passwords)-1]
   215  	}
   216  	// Otherwise prompt the user for the password
   217  	if prompt != "" {
   218  		fmt.Println(prompt)
   219  	}
   220  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   221  	if err != nil {
   222  		log.Fatalf("Failed to read passphrase: %v", err)
   223  	}
   224  	if confirmation {
   225  		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
   226  		if err != nil {
   227  			log.Fatalf("Failed to read passphrase confirmation: %v", err)
   228  		}
   229  		if password != confirm {
   230  			log.Fatalf("Passphrases do not match")
   231  		}
   232  	}
   233  	return password
   234  }
   235  
   236  func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account {
   237  	fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
   238  	for _, a := range err.Matches {
   239  		fmt.Println("  ", a.URL)
   240  	}
   241  	fmt.Println("Testing your passphrase against all of them...")
   242  	var match *accounts.Account
   243  	for _, a := range err.Matches {
   244  		if err := ks.Unlock(a, auth); err == nil {
   245  			match = &a
   246  			break
   247  		}
   248  	}
   249  	if match == nil {
   250  		log.Fatalf("None of the listed files could be unlocked.")
   251  	}
   252  	fmt.Printf("Your passphrase unlocked %s\n", match.URL)
   253  	fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:")
   254  	for _, a := range err.Matches {
   255  		if a != *match {
   256  			fmt.Println("  ", a.URL)
   257  		}
   258  	}
   259  	return *match
   260  }
   261  
   262  // accountCreate creates a new account into the keystore defined by the CLI flags.
   263  func accountCreate(ctx *cli.Context) error {
   264  	if glogger, err := debug.GetGlogger(); err == nil {
   265  		log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError))
   266  	}
   267  	cfg := utils.KlayConfig{Node: utils.DefaultNodeConfig()}
   268  	// Load config file.
   269  	if file := ctx.GlobalString(utils.ConfigFileFlag.Name); file != "" {
   270  		if err := utils.LoadConfig(file, &cfg); err != nil {
   271  			log.Fatalf("%v", err)
   272  		}
   273  	}
   274  	cfg.SetNodeConfig(ctx)
   275  	scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()
   276  	if err != nil {
   277  		log.Fatalf("Failed to read configuration: %v", err)
   278  	}
   279  
   280  	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
   281  
   282  	address, err := keystore.StoreKey(keydir, password, scryptN, scryptP)
   283  	if err != nil {
   284  		log.Fatalf("Failed to create account: %v", err)
   285  	}
   286  	fmt.Printf("Address: {%x}\n", address)
   287  	return nil
   288  }
   289  
   290  // accountUpdate transitions an account from a previous format to the current
   291  // one, also providing the possibility to change the pass-phrase.
   292  func accountUpdate(ctx *cli.Context) error {
   293  	if glogger, err := debug.GetGlogger(); err == nil {
   294  		log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError))
   295  	}
   296  	if len(ctx.Args()) == 0 {
   297  		log.Fatalf("No accounts specified to update")
   298  	}
   299  	stack, _ := utils.MakeConfigNode(ctx)
   300  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   301  
   302  	for _, addr := range ctx.Args() {
   303  		account, oldPassword := UnlockAccount(ctx, ks, addr, 0, nil)
   304  		newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
   305  		if err := ks.Update(account, oldPassword, newPassword); err != nil {
   306  			log.Fatalf("Could not update the account: %v", err)
   307  		}
   308  	}
   309  	return nil
   310  }
   311  
   312  func accountImport(ctx *cli.Context) error {
   313  	if glogger, err := debug.GetGlogger(); err == nil {
   314  		log.ChangeGlobalLogLevel(glogger, log.Lvl(log.LvlError))
   315  	}
   316  	keyfile := ctx.Args().First()
   317  	if len(keyfile) == 0 {
   318  		log.Fatalf("keyfile must be given as argument")
   319  	}
   320  	key, err := crypto.LoadECDSA(keyfile)
   321  	if err != nil {
   322  		log.Fatalf("Failed to load the private key: %v", err)
   323  	}
   324  	stack, _ := utils.MakeConfigNode(ctx)
   325  	passphrase := 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  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   328  	acct, err := ks.ImportECDSA(key, passphrase)
   329  	if err != nil {
   330  		log.Fatalf("Could not create the account: %v", err)
   331  	}
   332  	fmt.Printf("Address: {%x}\n", acct.Address)
   333  	if _acct, err := ks.Find(acct); err == nil {
   334  		fmt.Println("Your account is imported at", _acct.URL.Path)
   335  	}
   336  	return nil
   337  }