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