github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/cmd/intchain/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  	"github.com/intfoundation/intchain/params"
    22  	"io/ioutil"
    23  
    24  	"github.com/intfoundation/intchain/accounts"
    25  	"github.com/intfoundation/intchain/accounts/keystore"
    26  	"github.com/intfoundation/intchain/cmd/utils"
    27  	"github.com/intfoundation/intchain/console"
    28  	"github.com/intfoundation/intchain/crypto"
    29  	"github.com/intfoundation/intchain/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      intchain 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  				},
    58  				Description: `
    59  	intchain wallet [options] /path/to/my/presale.wallet
    60  
    61  will prompt for your password and imports your ether presale account.
    62  It can be used non-interactively with the --password option taking a
    63  passwordfile as argument containing the wallet password in plaintext.`,
    64  			},
    65  		},
    66  	}
    67  
    68  	accountCommand = cli.Command{
    69  		Name:     "account",
    70  		Usage:    "Manage accounts",
    71  		Category: "ACCOUNT COMMANDS",
    72  		Description: `
    73  
    74  Manage accounts, list all existing accounts, import a private key into a new
    75  account, create a new account or update an existing account.
    76  
    77  It supports interactive mode, when you are prompted for password as well as
    78  non-interactive mode where passwords are supplied via a given password file.
    79  Non-interactive mode is only meant for scripted use on test networks or known
    80  safe environments.
    81  
    82  Make sure you remember the password you gave when creating a new account (with
    83  either new or import). Without it you are not able to unlock your account.
    84  
    85  Note that exporting your key in unencrypted format is NOT supported.
    86  
    87  Keys are stored under <DATADIR>/keystore.
    88  It is safe to transfer the entire directory or the individual keys therein
    89  between ethereum nodes by simply copying.
    90  
    91  Make sure you backup your keys regularly.`,
    92  		Subcommands: []cli.Command{
    93  			{
    94  				Name:   "list",
    95  				Usage:  "Print summary of existing accounts",
    96  				Action: utils.MigrateFlags(accountList),
    97  				Flags: []cli.Flag{
    98  					utils.DataDirFlag,
    99  					utils.KeyStoreDirFlag,
   100  				},
   101  				Description: `
   102  Print a short summary of all accounts`,
   103  			},
   104  			{
   105  				Name:   "new",
   106  				Usage:  "Create a new account",
   107  				Action: utils.MigrateFlags(accountCreate),
   108  				Flags: []cli.Flag{
   109  					utils.DataDirFlag,
   110  					utils.KeyStoreDirFlag,
   111  					utils.PasswordFileFlag,
   112  				},
   113  				Description: `
   114      intchain account new
   115  
   116  Creates a new account and prints the address.
   117  
   118  The account is saved in encrypted format, you are prompted for a passphrase.
   119  
   120  You must remember this passphrase to unlock your account in the future.
   121  
   122  For non-interactive use the passphrase can be specified with the --password flag:
   123  
   124  Note, this is meant to be used for testing only, it is a bad idea to save your
   125  password to file or expose in any other way.
   126  `,
   127  			},
   128  			{
   129  				Name:      "update",
   130  				Usage:     "Update an existing account",
   131  				Action:    utils.MigrateFlags(accountUpdate),
   132  				ArgsUsage: "<address>",
   133  				Flags: []cli.Flag{
   134  					utils.DataDirFlag,
   135  					utils.KeyStoreDirFlag,
   136  				},
   137  				Description: `
   138      intchain 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 passphrase 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 passphrase can be specified with the --password flag:
   149  
   150      intchain 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: utils.MigrateFlags(accountImport),
   160  				Flags: []cli.Flag{
   161  					utils.DataDirFlag,
   162  					utils.KeyStoreDirFlag,
   163  					utils.PasswordFileFlag,
   164  				},
   165  				ArgsUsage: "<keyFile>",
   166  				Description: `
   167      intchain account import <keyfile>
   168  
   169  Imports an unencrypted private key from <keyfile> and creates a new account.
   170  Prints the address.
   171  
   172  The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
   173  
   174  The account is saved in encrypted format, you are prompted for a passphrase.
   175  
   176  You must remember this passphrase to unlock your account in the future.
   177  
   178  For non-interactive use the passphrase can be specified with the -password flag:
   179  
   180      intchain account import [options] <keyfile>
   181  
   182  Note:
   183  As you can directly copy your encrypted accounts to another ethereum instance,
   184  this import mechanism is not needed when you transfer an account between
   185  nodes.
   186  `,
   187  			},
   188  		},
   189  	}
   190  )
   191  
   192  func accountList(ctx *cli.Context) error {
   193  	ChainId := params.MainnetChainConfig.IntChainId
   194  
   195  	if ctx.GlobalIsSet(utils.TestnetFlag.Name) {
   196  		fmt.Printf("testnet: %v\n", params.TestnetChainConfig.IntChainId)
   197  		ChainId = params.TestnetChainConfig.IntChainId
   198  	}
   199  
   200  	stack, _ := makeConfigNode(ctx, ChainId)
   201  	var index int
   202  	for _, wallet := range stack.AccountManager().Wallets() {
   203  		for _, account := range wallet.Accounts() {
   204  			fmt.Printf("Account #%d: {%v} %s\n", index, account.Address.String(), &account.URL)
   205  			index++
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  // tries unlocking the specified account a few times.
   212  func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
   213  	account, err := utils.MakeAddress(ks, address)
   214  	if err != nil {
   215  		utils.Fatalf("Could not list accounts: %v", err)
   216  	}
   217  	for trials := 0; trials < 3; trials++ {
   218  		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
   219  		password := getPassPhrase(prompt, false, i, passwords)
   220  		err = ks.Unlock(account, password)
   221  		if err == nil {
   222  			//log.Info("Unlocked account", "address", account.Address.Hex())
   223  			log.Info("Unlocked account", "address", account.Address.String())
   224  			return account, password
   225  		}
   226  		if err, ok := err.(*keystore.AmbiguousAddrError); ok {
   227  			//log.Info("Unlocked account", "address", account.Address.Hex())
   228  			log.Info("Unlocked account", "address", account.Address.String())
   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(prompt 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 prompt != "" {
   254  		fmt.Println(prompt)
   255  	}
   256  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   257  	if err != nil {
   258  		utils.Fatalf("Failed to read passphrase: %v", err)
   259  	}
   260  	if confirmation {
   261  		confirm, err := console.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 := gethConfig{Node: defaultNodeConfig()}
   301  	// Load config file.
   302  	if file := ctx.GlobalString(configFileFlag.Name); file != "" {
   303  		if err := loadConfig(file, &cfg); err != nil {
   304  			utils.Fatalf("%v", err)
   305  		}
   306  	}
   307  
   308  	cfg.Node.ChainId = params.MainnetChainConfig.IntChainId
   309  
   310  	if ctx.GlobalIsSet(utils.TestnetFlag.Name) {
   311  		fmt.Printf("testnet: %v\n", params.TestnetChainConfig.IntChainId)
   312  		cfg.Node.ChainId = params.TestnetChainConfig.IntChainId
   313  	}
   314  
   315  	utils.SetNodeConfig(ctx, &cfg.Node)
   316  	scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()
   317  
   318  	if err != nil {
   319  		utils.Fatalf("Failed to read configuration: %v", err)
   320  	}
   321  
   322  	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
   323  
   324  	address, err := keystore.StoreKey(keydir, password, scryptN, scryptP)
   325  
   326  	if err != nil {
   327  		utils.Fatalf("Failed to create account: %v", err)
   328  	}
   329  	fmt.Printf("Address: %v\n", address.String())
   330  	return nil
   331  }
   332  
   333  // accountUpdate transitions an account from a previous format to the current
   334  // one, also providing the possibility to change the pass-phrase.
   335  func accountUpdate(ctx *cli.Context) error {
   336  	if len(ctx.Args()) == 0 {
   337  		utils.Fatalf("No accounts specified to update")
   338  	}
   339  	stack, _ := makeConfigNode(ctx, clientIdentifier)
   340  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   341  
   342  	for _, addr := range ctx.Args() {
   343  		account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil)
   344  		newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
   345  		if err := ks.Update(account, oldPassword, newPassword); err != nil {
   346  			utils.Fatalf("Could not update the account: %v", err)
   347  		}
   348  	}
   349  	return nil
   350  }
   351  
   352  func importWallet(ctx *cli.Context) error {
   353  	keyfile := ctx.Args().First()
   354  	if len(keyfile) == 0 {
   355  		utils.Fatalf("keyfile must be given as argument")
   356  	}
   357  	keyJson, err := ioutil.ReadFile(keyfile)
   358  	if err != nil {
   359  		utils.Fatalf("Could not read wallet file: %v", err)
   360  	}
   361  
   362  	stack, _ := makeConfigNode(ctx, clientIdentifier)
   363  	passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
   364  
   365  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   366  	acct, err := ks.ImportPreSaleKey(keyJson, passphrase)
   367  	if err != nil {
   368  		utils.Fatalf("%v", err)
   369  	}
   370  	fmt.Printf("Address: {%x}\n", acct.Address)
   371  	return nil
   372  }
   373  
   374  func accountImport(ctx *cli.Context) error {
   375  	keyfile := ctx.Args().First()
   376  	if len(keyfile) == 0 {
   377  		utils.Fatalf("keyfile must be given as argument")
   378  	}
   379  	key, err := crypto.LoadECDSA(keyfile)
   380  	if err != nil {
   381  		utils.Fatalf("Failed to load the private key: %v", err)
   382  	}
   383  	stack, _ := makeConfigNode(ctx, clientIdentifier)
   384  	passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
   385  
   386  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   387  	acct, err := ks.ImportECDSA(key, passphrase)
   388  	if err != nil {
   389  		utils.Fatalf("Could not create the account: %v", err)
   390  	}
   391  	fmt.Printf("Address: {%x}\n", acct.Address)
   392  	return nil
   393  }