github.com/prysmaticlabs/prysm@v1.4.4/validator/accounts/wallet_create.go (about)

     1  package accounts
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/manifoldco/promptui"
    11  	"github.com/pkg/errors"
    12  	"github.com/prysmaticlabs/prysm/cmd/validator/flags"
    13  	"github.com/prysmaticlabs/prysm/shared/promptutil"
    14  	"github.com/prysmaticlabs/prysm/validator/accounts/iface"
    15  	"github.com/prysmaticlabs/prysm/validator/accounts/prompt"
    16  	"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
    17  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    18  	"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
    19  	"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
    20  	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
    21  	"github.com/urfave/cli/v2"
    22  )
    23  
    24  // CreateWalletConfig defines the parameters needed to call the create wallet functions.
    25  type CreateWalletConfig struct {
    26  	SkipMnemonicConfirm  bool
    27  	NumAccounts          int
    28  	RemoteKeymanagerOpts *remote.KeymanagerOpts
    29  	WalletCfg            *wallet.Config
    30  	Mnemonic25thWord     string
    31  }
    32  
    33  // CreateAndSaveWalletCli from user input with a desired keymanager. If a
    34  // wallet already exists in the path, it suggests the user alternatives
    35  // such as how to edit their existing wallet configuration.
    36  func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) {
    37  	keymanagerKind, err := extractKeymanagerKindFromCli(cliCtx)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	createWalletConfig, err := extractWalletCreationConfigFromCli(cliCtx, keymanagerKind)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	dir := createWalletConfig.WalletCfg.WalletDir
    47  	dirExists, err := wallet.Exists(dir)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	if dirExists {
    52  		return nil, errors.New("a wallet already exists at this location. Please input an" +
    53  			" alternative location for the new wallet or remove the current wallet")
    54  	}
    55  
    56  	w, err := CreateWalletWithKeymanager(cliCtx.Context, createWalletConfig)
    57  	if err != nil {
    58  		return nil, errors.Wrap(err, "could not create wallet")
    59  	}
    60  	return w, nil
    61  }
    62  
    63  // CreateWalletWithKeymanager specified by configuration options.
    64  func CreateWalletWithKeymanager(ctx context.Context, cfg *CreateWalletConfig) (*wallet.Wallet, error) {
    65  	w := wallet.New(&wallet.Config{
    66  		WalletDir:      cfg.WalletCfg.WalletDir,
    67  		KeymanagerKind: cfg.WalletCfg.KeymanagerKind,
    68  		WalletPassword: cfg.WalletCfg.WalletPassword,
    69  	})
    70  	var err error
    71  	switch w.KeymanagerKind() {
    72  	case keymanager.Imported:
    73  		if err = createImportedKeymanagerWallet(ctx, w); err != nil {
    74  			return nil, errors.Wrap(err, "could not initialize wallet")
    75  		}
    76  		km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
    77  		if err != nil {
    78  			return nil, errors.Wrap(err, ErrCouldNotInitializeKeymanager)
    79  		}
    80  		importedKm, ok := km.(*imported.Keymanager)
    81  		if !ok {
    82  			return nil, errors.Wrap(err, ErrCouldNotInitializeKeymanager)
    83  		}
    84  		accountsKeystore, err := importedKm.CreateAccountsKeystore(ctx, make([][]byte, 0), make([][]byte, 0))
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		encodedAccounts, err := json.MarshalIndent(accountsKeystore, "", "\t")
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		if err = w.WriteFileAtPath(ctx, imported.AccountsPath, imported.AccountsKeystoreFileName, encodedAccounts); err != nil {
    93  			return nil, err
    94  		}
    95  
    96  		log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info(
    97  			"Successfully created wallet with ability to import keystores",
    98  		)
    99  	case keymanager.Derived:
   100  		if err = createDerivedKeymanagerWallet(
   101  			ctx,
   102  			w,
   103  			cfg.Mnemonic25thWord,
   104  			cfg.SkipMnemonicConfirm,
   105  			cfg.NumAccounts,
   106  		); err != nil {
   107  			return nil, errors.Wrap(err, "could not initialize wallet")
   108  		}
   109  		log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info(
   110  			"Successfully created HD wallet from mnemonic and regenerated accounts",
   111  		)
   112  	case keymanager.Remote:
   113  		if err = createRemoteKeymanagerWallet(ctx, w, cfg.RemoteKeymanagerOpts); err != nil {
   114  			return nil, errors.Wrap(err, "could not initialize wallet")
   115  		}
   116  		log.WithField("--wallet-dir", cfg.WalletCfg.WalletDir).Info(
   117  			"Successfully created wallet with remote keymanager configuration",
   118  		)
   119  	default:
   120  		return nil, errors.Wrapf(err, errKeymanagerNotSupported, w.KeymanagerKind())
   121  	}
   122  	return w, nil
   123  }
   124  
   125  func extractKeymanagerKindFromCli(cliCtx *cli.Context) (keymanager.Kind, error) {
   126  	return inputKeymanagerKind(cliCtx)
   127  }
   128  
   129  func extractWalletCreationConfigFromCli(cliCtx *cli.Context, keymanagerKind keymanager.Kind) (*CreateWalletConfig, error) {
   130  	walletDir, err := prompt.InputDirectory(cliCtx, prompt.WalletDirPromptText, flags.WalletDirFlag)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	walletPassword, err := promptutil.InputPassword(
   135  		cliCtx,
   136  		flags.WalletPasswordFileFlag,
   137  		wallet.NewWalletPasswordPromptText,
   138  		wallet.ConfirmPasswordPromptText,
   139  		true, /* Should confirm password */
   140  		promptutil.ValidatePasswordInput,
   141  	)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	createWalletConfig := &CreateWalletConfig{
   146  		WalletCfg: &wallet.Config{
   147  			WalletDir:      walletDir,
   148  			KeymanagerKind: keymanagerKind,
   149  			WalletPassword: walletPassword,
   150  		},
   151  		SkipMnemonicConfirm: cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name),
   152  	}
   153  	skipMnemonic25thWord := cliCtx.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name)
   154  	has25thWordFile := cliCtx.IsSet(flags.Mnemonic25thWordFileFlag.Name)
   155  	if keymanagerKind == keymanager.Derived {
   156  		numAccounts, err := inputNumAccounts(cliCtx)
   157  		if err != nil {
   158  			return nil, errors.Wrap(err, "could not get number of accounts to generate")
   159  		}
   160  		createWalletConfig.NumAccounts = int(numAccounts)
   161  	}
   162  	if keymanagerKind == keymanager.Derived && !skipMnemonic25thWord && !has25thWordFile {
   163  		resp, err := promptutil.ValidatePrompt(
   164  			os.Stdin, newMnemonicPassphraseYesNoText, promptutil.ValidateYesOrNo,
   165  		)
   166  		if err != nil {
   167  			return nil, errors.Wrap(err, "could not validate choice")
   168  		}
   169  		if strings.EqualFold(resp, "y") {
   170  			mnemonicPassphrase, err := promptutil.InputPassword(
   171  				cliCtx,
   172  				flags.Mnemonic25thWordFileFlag,
   173  				newMnemonicPassphrasePromptText,
   174  				"Confirm mnemonic passphrase",
   175  				true, /* Should confirm password */
   176  				func(input string) error {
   177  					if strings.TrimSpace(input) == "" {
   178  						return errors.New("input cannot be empty")
   179  					}
   180  					return nil
   181  				},
   182  			)
   183  			if err != nil {
   184  				return nil, err
   185  			}
   186  			createWalletConfig.Mnemonic25thWord = mnemonicPassphrase
   187  		}
   188  	}
   189  	if keymanagerKind == keymanager.Remote {
   190  		opts, err := prompt.InputRemoteKeymanagerConfig(cliCtx)
   191  		if err != nil {
   192  			return nil, errors.Wrap(err, "could not input remote keymanager config")
   193  		}
   194  		createWalletConfig.RemoteKeymanagerOpts = opts
   195  	}
   196  	return createWalletConfig, nil
   197  }
   198  
   199  func createImportedKeymanagerWallet(ctx context.Context, wallet *wallet.Wallet) error {
   200  	if wallet == nil {
   201  		return errors.New("nil wallet")
   202  	}
   203  	if err := wallet.SaveWallet(); err != nil {
   204  		return errors.Wrap(err, "could not save wallet to disk")
   205  	}
   206  	return nil
   207  }
   208  
   209  func createDerivedKeymanagerWallet(
   210  	ctx context.Context,
   211  	wallet *wallet.Wallet,
   212  	mnemonicPassphrase string,
   213  	skipMnemonicConfirm bool,
   214  	numAccounts int,
   215  ) error {
   216  	if wallet == nil {
   217  		return errors.New("nil wallet")
   218  	}
   219  	if err := wallet.SaveWallet(); err != nil {
   220  		return errors.Wrap(err, "could not save wallet to disk")
   221  	}
   222  	km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{
   223  		Wallet:           wallet,
   224  		ListenForChanges: true,
   225  	})
   226  	if err != nil {
   227  		return errors.Wrap(err, "could not initialize HD keymanager")
   228  	}
   229  	mnemonic, err := derived.GenerateAndConfirmMnemonic(skipMnemonicConfirm)
   230  	if err != nil {
   231  		return errors.Wrap(err, "could not confirm mnemonic")
   232  	}
   233  	if err := km.RecoverAccountsFromMnemonic(ctx, mnemonic, mnemonicPassphrase, numAccounts); err != nil {
   234  		return errors.Wrap(err, "could not recover accounts from mnemonic")
   235  	}
   236  	return nil
   237  }
   238  
   239  func createRemoteKeymanagerWallet(ctx context.Context, wallet *wallet.Wallet, opts *remote.KeymanagerOpts) error {
   240  	keymanagerConfig, err := remote.MarshalOptionsFile(ctx, opts)
   241  	if err != nil {
   242  		return errors.Wrap(err, "could not marshal config file")
   243  	}
   244  	if err := wallet.SaveWallet(); err != nil {
   245  		return errors.Wrap(err, "could not save wallet to disk")
   246  	}
   247  	if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
   248  		return errors.Wrap(err, "could not write keymanager config to disk")
   249  	}
   250  	return nil
   251  }
   252  
   253  func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) {
   254  	if cliCtx.IsSet(flags.KeymanagerKindFlag.Name) {
   255  		return keymanager.ParseKind(cliCtx.String(flags.KeymanagerKindFlag.Name))
   256  	}
   257  	promptSelect := promptui.Select{
   258  		Label: "Select a type of wallet",
   259  		Items: []string{
   260  			wallet.KeymanagerKindSelections[keymanager.Imported],
   261  			wallet.KeymanagerKindSelections[keymanager.Derived],
   262  			wallet.KeymanagerKindSelections[keymanager.Remote],
   263  		},
   264  	}
   265  	selection, _, err := promptSelect.Run()
   266  	if err != nil {
   267  		return keymanager.Imported, fmt.Errorf("could not select wallet type: %w", prompt.FormatPromptError(err))
   268  	}
   269  	return keymanager.Kind(selection), nil
   270  }