github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/chain/neatio/accountcmd.go (about)

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