2020-07-22 02:41:39 +00:00
|
|
|
package v2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2020-07-27 14:03:30 +00:00
|
|
|
"strconv"
|
2020-07-22 02:41:39 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/manifoldco/promptui"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/prysmaticlabs/prysm/validator/flags"
|
2020-07-24 00:43:01 +00:00
|
|
|
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
2020-07-22 02:41:39 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const phraseWordCount = 24
|
|
|
|
|
|
|
|
// RecoverWallet uses a menmonic seed phrase to recover a wallet into the path provided.
|
|
|
|
func RecoverWallet(cliCtx *cli.Context) error {
|
|
|
|
mnemonic, err := inputMnemonic(cliCtx)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not get mnemonic phrase")
|
|
|
|
}
|
2020-07-24 00:43:01 +00:00
|
|
|
wallet, err := NewWallet(cliCtx, v2keymanager.Derived)
|
2020-07-22 02:41:39 +00:00
|
|
|
if err != nil {
|
2020-07-22 04:49:04 +00:00
|
|
|
return errors.Wrap(err, "could not create new wallet")
|
2020-07-22 02:41:39 +00:00
|
|
|
}
|
2020-07-22 04:49:04 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
seedConfig, err := derived.SeedFileFromMnemonic(ctx, mnemonic, wallet.walletPassword)
|
2020-07-22 02:41:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not initialize new wallet seed file")
|
|
|
|
}
|
|
|
|
seedConfigFile, err := derived.MarshalEncryptedSeedFile(ctx, seedConfig)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
|
|
|
|
}
|
|
|
|
keymanagerConfig, err := derived.MarshalConfigFile(ctx, derived.DefaultConfig())
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not marshal keymanager config file")
|
|
|
|
}
|
2020-07-24 00:43:01 +00:00
|
|
|
if err := wallet.SaveWallet(); err != nil {
|
|
|
|
return errors.Wrap(err, "could not save wallet to disk")
|
|
|
|
}
|
2020-07-22 02:41:39 +00:00
|
|
|
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
|
|
|
return errors.Wrap(err, "could not write keymanager config to disk")
|
|
|
|
}
|
|
|
|
if err := wallet.WriteEncryptedSeedToDisk(ctx, seedConfigFile); err != nil {
|
|
|
|
return errors.Wrap(err, "could not write encrypted wallet seed config to disk")
|
|
|
|
}
|
2020-07-27 14:03:30 +00:00
|
|
|
keymanager, err := wallet.InitializeKeymanager(ctx, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
km, ok := keymanager.(*derived.Keymanager)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("not a derived keymanager")
|
|
|
|
}
|
|
|
|
|
|
|
|
numAccounts, err := inputNumAccounts(cliCtx)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not get number of accounts to recover")
|
|
|
|
}
|
|
|
|
if numAccounts == 1 {
|
|
|
|
if _, err := km.CreateAccount(ctx, true /*logAccountInfo*/); err != nil {
|
|
|
|
return errors.Wrap(err, "could not create account in wallet")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for i := 0; i < int(numAccounts); i++ {
|
|
|
|
if _, err := km.CreateAccount(ctx, false /*logAccountInfo*/); err != nil {
|
|
|
|
return errors.Wrap(err, "could not create account in wallet")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.WithField("wallet-path", wallet.AccountsDir()).Infof(
|
|
|
|
"Successfully recovered HD wallet with %d accounts. Please use accounts-v2 list to view details for your accounts.",
|
|
|
|
numAccounts,
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof(
|
2020-07-22 04:49:04 +00:00
|
|
|
"Successfully recovered HD wallet and saved configuration to disk. " +
|
2020-07-23 03:10:23 +00:00
|
|
|
"Make a new validator account with ./prysm.sh validator accounts-v2 create",
|
2020-07-22 04:49:04 +00:00
|
|
|
)
|
2020-07-22 02:41:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func inputMnemonic(cliCtx *cli.Context) (string, error) {
|
|
|
|
if cliCtx.IsSet(flags.MnemonicFileFlag.Name) {
|
|
|
|
mnemonicFilePath := cliCtx.String(flags.MnemonicFileFlag.Name)
|
|
|
|
data, err := ioutil.ReadFile(mnemonicFilePath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
enteredMnemonic := string(data)
|
|
|
|
if err := validateMnemonic(enteredMnemonic); err != nil {
|
|
|
|
return "", errors.Wrap(err, "mnemonic phrase did not pass validation")
|
|
|
|
}
|
|
|
|
return enteredMnemonic, nil
|
|
|
|
}
|
|
|
|
prompt := promptui.Prompt{
|
2020-07-28 19:23:53 +00:00
|
|
|
Label: "Enter the seed phrase for the wallet you would like to recover",
|
2020-07-22 02:41:39 +00:00
|
|
|
Validate: validateMnemonic,
|
|
|
|
}
|
|
|
|
menmonicPhrase, err := prompt.Run()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not determine wallet directory: %v", formatPromptError(err))
|
|
|
|
}
|
|
|
|
return menmonicPhrase, nil
|
|
|
|
}
|
|
|
|
|
2020-07-27 14:03:30 +00:00
|
|
|
func inputNumAccounts(cliCtx *cli.Context) (int64, error) {
|
|
|
|
if cliCtx.IsSet(flags.NumAccountsFlag.Name) {
|
|
|
|
numAccounts := cliCtx.Int64(flags.NumAccountsFlag.Name)
|
|
|
|
return numAccounts, nil
|
|
|
|
}
|
|
|
|
prompt := promptui.Prompt{
|
|
|
|
Label: "Enter how many accounts you would like to recover",
|
|
|
|
Validate: func(input string) error {
|
|
|
|
_, err := strconv.Atoi(input)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
Default: "0",
|
|
|
|
}
|
|
|
|
numAccounts, err := prompt.Run()
|
|
|
|
if err != nil {
|
|
|
|
return 0, formatPromptError(err)
|
|
|
|
}
|
|
|
|
numAccountsInt, err := strconv.Atoi(numAccounts)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return int64(numAccountsInt), nil
|
|
|
|
}
|
|
|
|
|
2020-07-22 02:41:39 +00:00
|
|
|
func validateMnemonic(mnemonic string) error {
|
|
|
|
if strings.Trim(mnemonic, " ") == "" {
|
|
|
|
return errors.New("phrase cannot be empty")
|
|
|
|
}
|
|
|
|
words := strings.Split(mnemonic, " ")
|
|
|
|
for i, word := range words {
|
|
|
|
if strings.Trim(word, " ") == "" {
|
|
|
|
words = append(words[:i], words[i+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(words) != phraseWordCount {
|
|
|
|
return fmt.Errorf("phrase must be %d words, entered %d", phraseWordCount, len(words))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|