github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/accountcmd.go (about)

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