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 }