Accounts V2: Refactor Wallet Open / Close and Keymanager Initialization (#6668)

* refactoring create account

* dep

* much easier, create a derived account by simply unlocking wallet

* revert changes to new

* make open wallet smarter and utilize cli ctx

* remove the wallet config

* successfully build

* simplify ctx creation for tests

* tests should pass individually

* tests pass

* fixed up to allow for wallet password file input

* fix broken tests

* formatting

* fmt

* simplify recover

* fixed up tests

* implicit use of default wallet path working

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
Raul Jordan
2020-07-21 23:49:04 -05:00
committed by GitHub
parent 28096a846e
commit 4017743f7f
24 changed files with 391 additions and 551 deletions

2
go.sum
View File

@@ -1003,6 +1003,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/src-d/envconfig v1.0.0 h1:/AJi6DtjFhZKNx3OB2qMsq7y4yT5//AeSZIe7rk+PX8=
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E=
@@ -1464,6 +1465,7 @@ gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 h1:ITeyKbRetr
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
gopkg.in/src-d/go-log.v1 v1.0.1 h1:heWvX7J6qbGWbeFS/aRmiy1eYaT+QMV6wNvHDyMjQV4=
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

View File

@@ -64,7 +64,6 @@ go_test(
"//validator/keymanager/v2/derived:go_default_library",
"//validator/keymanager/v2/direct:go_default_library",
"//validator/keymanager/v2/remote:go_default_library",
"//validator/keymanager/v2/testing:go_default_library",
"@com_github_dustin_go_humanize//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",

View File

@@ -23,6 +23,7 @@ var WalletCommands = &cli.Command{
flags.RemoteSignerCertPathFlag,
flags.RemoteSignerKeyPathFlag,
flags.RemoteSignerCACertPathFlag,
flags.PasswordFileFlag,
},
Action: func(cliCtx *cli.Context) error {
if err := CreateWallet(cliCtx); err != nil {

View File

@@ -2,7 +2,6 @@ package v2
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
@@ -23,23 +22,12 @@ const archiveFilename = "backup.zip"
// ExportAccount creates a zip archive of the selected accounts to be used in the future for importing accounts.
func ExportAccount(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if errors.Is(err, ErrNoWalletFound) {
return errors.New("no wallet found, create a new one with ./prysm.sh validator wallet-v2 create")
} else if err != nil {
return errors.Wrap(err, "could not parse wallet directory")
}
outputDir, err := inputExportDir(cliCtx)
if err != nil {
return errors.Wrap(err, "could not parse output directory")
}
wallet, err := OpenWallet(context.Background(), &WalletConfig{
CanUnlockAccounts: false,
WalletDir: walletDir,
})
wallet, err := OpenWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
@@ -57,7 +45,7 @@ func ExportAccount(cliCtx *cli.Context) error {
}
if err := wallet.zipAccounts(accounts, outputDir); err != nil {
log.WithError(err).Error("Could not export accounts")
return errors.Wrap(err, "could not export accounts")
}
if err := logAccountsExported(wallet, accounts); err != nil {

View File

@@ -2,61 +2,49 @@ package v2
import (
"context"
"flag"
"crypto/rand"
"fmt"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/validator/flags"
v2 "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/urfave/cli/v2"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
)
func setupWallet(t *testing.T, testDir string) *Wallet {
walletDir := filepath.Join(testDir, walletDirName)
passwordsDir := filepath.Join(testDir, passwordDirName)
func TestZipAndUnzip(t *testing.T) {
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err, "Could not generate random file path")
exportDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "export")
importDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "import")
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(exportDir), "Failed to remove directory")
require.NoError(t, os.RemoveAll(importDir), "Failed to remove directory")
})
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
exportDir: exportDir,
keymanagerKind: v2keymanager.Direct,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
ctx := context.Background()
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletPasswordsDirFlag.Name, passwordsDir, "")
assert.NoError(t, set.Set(flags.WalletPasswordsDirFlag.Name, passwordsDir))
cliCtx := cli.NewContext(&app, set, nil)
assert.NoError(t, createDirectWallet(cliCtx, walletDir))
cfg := &WalletConfig{
WalletDir: walletDir,
PasswordsDir: passwordsDir,
KeymanagerKind: v2.Direct,
}
w, err := NewWallet(ctx, cfg)
keymanager, err := direct.NewKeymanager(
ctx,
wallet,
direct.DefaultConfig(),
true, /* skip mnemonic */
)
require.NoError(t, err)
keymanager, err := w.InitializeKeymanager(ctx, true)
require.NoError(t, err)
_, err = keymanager.CreateAccount(ctx, password)
require.NoError(t, err)
return w
}
func TestZipAndUnzip(t *testing.T) {
testDir := testutil.TempDir()
walletDir := filepath.Join(testDir, walletDirName)
passwordsDir := filepath.Join(testDir, passwordDirName)
exportDir := filepath.Join(testDir, exportDirName)
importDir := filepath.Join(testDir, importDirName)
defer func() {
assert.NoError(t, os.RemoveAll(walletDir))
assert.NoError(t, os.RemoveAll(passwordsDir))
assert.NoError(t, os.RemoveAll(exportDir))
assert.NoError(t, os.RemoveAll(importDir))
}()
wallet := setupWallet(t, testDir)
accounts, err := wallet.AccountNames()
require.NoError(t, err)
@@ -83,29 +71,33 @@ func TestZipAndUnzip(t *testing.T) {
}
func TestExport_Noninteractive(t *testing.T) {
testDir := testutil.TempDir()
walletDir := filepath.Join(testDir, walletDirName)
passwordsDir := filepath.Join(testDir, passwordDirName)
exportDir := filepath.Join(testDir, exportDirName)
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err, "Could not generate random file path")
exportDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "export")
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(exportDir), "Failed to remove directory")
})
accounts := "all"
defer func() {
assert.NoError(t, os.RemoveAll(walletDir))
assert.NoError(t, os.RemoveAll(passwordsDir))
assert.NoError(t, os.RemoveAll(exportDir))
}()
setupWallet(t, testDir)
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, walletDir, "")
set.String(flags.WalletPasswordsDirFlag.Name, passwordsDir, "")
set.String(flags.BackupPathFlag.Name, exportDir, "")
set.String(flags.AccountsFlag.Name, accounts, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, walletDir))
assert.NoError(t, set.Set(flags.WalletPasswordsDirFlag.Name, passwordsDir))
assert.NoError(t, set.Set(flags.BackupPathFlag.Name, exportDir))
assert.NoError(t, set.Set(flags.AccountsFlag.Name, accounts))
cliCtx := cli.NewContext(&app, set, nil)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
exportDir: exportDir,
accountsToExport: accounts,
keymanagerKind: v2keymanager.Direct,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
ctx := context.Background()
keymanager, err := direct.NewKeymanager(
ctx,
wallet,
direct.DefaultConfig(),
true, /* skip mnemonic */
)
require.NoError(t, err)
_, err = keymanager.CreateAccount(ctx, password)
require.NoError(t, err)
require.NoError(t, ExportAccount(cliCtx))
if _, err := os.Stat(filepath.Join(exportDir, archiveFilename)); os.IsNotExist(err) {
t.Fatal("Expected file to exist")

View File

@@ -2,7 +2,6 @@ package v2
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
@@ -21,10 +20,9 @@ import (
// ImportAccount uses the archived account made from ExportAccount to import an account and
// asks the users for account passwords.
func ImportAccount(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
wallet, err := OpenWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not parse wallet directory")
return errors.Wrap(err, "could not open wallet")
}
backupDir, err := inputImportDir(cliCtx)
@@ -32,7 +30,7 @@ func ImportAccount(cliCtx *cli.Context) error {
return errors.Wrap(err, "could not parse output directory")
}
accountsImported, err := unzipArchiveToTarget(backupDir, walletDir)
accountsImported, err := unzipArchiveToTarget(backupDir, wallet.AccountsDir())
if err != nil {
return errors.Wrap(err, "could not unzip archive")
}
@@ -44,18 +42,6 @@ func ImportAccount(cliCtx *cli.Context) error {
}
fmt.Printf("Importing accounts: %s\n", strings.Join(loggedAccounts, ", "))
// Read the directory for password storage from user input.
passwordsDirPath := inputPasswordsDirectory(cliCtx)
wallet, err := OpenWallet(context.Background(), &WalletConfig{
CanUnlockAccounts: true,
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
})
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
for _, accountName := range accountsImported {
if err := wallet.enterPasswordForAccount(cliCtx, accountName); err != nil {
return errors.Wrap(err, "could not set account password")

View File

@@ -1,38 +1,59 @@
package v2
import (
"flag"
"context"
"crypto/rand"
"fmt"
"io/ioutil"
"math/big"
"os"
"path"
"path/filepath"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/validator/flags"
"github.com/urfave/cli/v2"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
)
func TestImport_Noninteractive(t *testing.T) {
testDir := testutil.TempDir()
walletDir := filepath.Join(testDir, walletDirName)
passwordsDir := filepath.Join(testDir, passwordDirName)
exportDir := filepath.Join(testDir, exportDirName)
importDir := filepath.Join(testDir, importDirName)
importPasswordDir := filepath.Join(testDir, importPasswordDirName)
passwordFilePath := filepath.Join(testDir, passwordFileName)
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err, "Could not generate random file path")
exportDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "export")
importDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "import")
importPasswordDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "importpassword")
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(exportDir), "Failed to remove directory")
require.NoError(t, os.RemoveAll(importDir), "Failed to remove directory")
require.NoError(t, os.RemoveAll(importPasswordDir), "Failed to remove directory")
})
require.NoError(t, os.MkdirAll(importPasswordDir, os.ModePerm))
passwordFilePath := filepath.Join(importPasswordDir, passwordFileName)
require.NoError(t, ioutil.WriteFile(passwordFilePath, []byte(password), os.ModePerm))
defer func() {
assert.NoError(t, os.RemoveAll(walletDir))
assert.NoError(t, os.RemoveAll(passwordsDir))
assert.NoError(t, os.RemoveAll(exportDir))
assert.NoError(t, os.RemoveAll(importDir))
assert.NoError(t, os.RemoveAll(importPasswordDir))
}()
wallet := setupWallet(t, testDir)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
exportDir: exportDir,
keymanagerKind: v2keymanager.Direct,
passwordFile: passwordFilePath,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
ctx := context.Background()
keymanager, err := direct.NewKeymanager(
ctx,
wallet,
direct.DefaultConfig(),
true, /* skip mnemonic */
)
require.NoError(t, err)
_, err = keymanager.CreateAccount(ctx, password)
require.NoError(t, err)
accounts, err := wallet.AccountNames()
require.NoError(t, err)
@@ -42,18 +63,5 @@ func TestImport_Noninteractive(t *testing.T) {
if _, err := os.Stat(filepath.Join(exportDir, archiveFilename)); os.IsNotExist(err) {
t.Fatal("Expected file to exist")
}
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, importDir, "")
set.String(flags.WalletPasswordsDirFlag.Name, importPasswordDir, "")
set.String(flags.BackupPathFlag.Name, exportDir, "")
set.String(flags.PasswordFileFlag.Name, passwordFilePath, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, importDir))
assert.NoError(t, set.Set(flags.WalletPasswordsDirFlag.Name, importPasswordDir))
assert.NoError(t, set.Set(flags.BackupPathFlag.Name, exportDir))
assert.NoError(t, set.Set(flags.PasswordFileFlag.Name, passwordFilePath))
cliCtx := cli.NewContext(&app, set, nil)
require.NoError(t, ImportAccount(cliCtx))
}

View File

@@ -19,22 +19,13 @@ import (
// ListAccounts displays all available validator accounts in a Prysm wallet.
func ListAccounts(cliCtx *cli.Context) error {
walletDir, err := inputWalletDir(cliCtx)
if errors.Is(err, ErrNoWalletFound) {
return errors.New("no wallet found, create a new one with ./prysm.sh validator wallet-v2 create")
} else if err != nil {
return errors.Wrap(err, "could not parse wallet directory")
}
// Read the wallet from the specified path.
ctx := context.Background()
wallet, err := OpenWallet(ctx, &WalletConfig{
WalletDir: walletDir,
CanUnlockAccounts: false,
})
wallet, err := OpenWallet(cliCtx)
if err != nil {
return errors.Wrapf(err, "could not read wallet at specified path %s", walletDir)
return errors.Wrapf(err, "could not read wallet at specified path %s", wallet.AccountsDir())
}
keymanager, err := wallet.InitializeKeymanager(ctx, false /* skipMnemonicConfirm */)
keymanager, err := wallet.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
if err != nil {
return errors.Wrap(err, "could not initialize keymanager")
}
@@ -84,8 +75,6 @@ func listDirectKeymanagerAccounts(
au.BrightRed("View the eth1 deposit transaction data for your accounts " +
"by running `validator accounts-v2 list --show-deposit-data"),
)
dirPath := au.BrightCyan("(wallet dir)")
fmt.Printf("%s %s\n", dirPath, wallet.AccountsDir())
fmt.Printf("Keymanager kind: %s\n", au.BrightGreen(wallet.KeymanagerKind().String()).Bold())
pubKeys, err := keymanager.FetchValidatingPublicKeys(context.Background())
@@ -142,8 +131,6 @@ func listDerivedKeymanagerAccounts(
au.BrightRed("View the eth1 deposit transaction data for your accounts " +
"by running `validator accounts-v2 list --show-deposit-data"),
)
dirPath := au.BrightCyan("(wallet dir)")
fmt.Printf("%s %s\n", dirPath, wallet.AccountsDir())
fmt.Printf("(keymanager kind) %s\n", au.BrightGreen("derived, (HD) hierarchical-deterministic").Bold())
fmt.Printf("(derivation format) %s\n", au.BrightGreen(keymanager.Config().DerivedPathStructure).Bold())
ctx := context.Background()
@@ -164,7 +151,6 @@ func listDerivedKeymanagerAccounts(
if err != nil {
return err
}
log.Info(currentAccountNumber)
for i := uint64(0); i <= currentAccountNumber; i++ {
fmt.Println("")
validatingKeyPath := fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)

View File

@@ -19,17 +19,23 @@ import (
)
func TestListAccounts_DirectKeymanager(t *testing.T) {
walletDir, passwordsDir := setupWalletDir(t)
keymanagerKind := v2keymanager.Direct
ctx := context.Background()
wallet, err := NewWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
KeymanagerKind: keymanagerKind,
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keymanagerKind: v2keymanager.Direct,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
keymanager, err := direct.NewKeymanager(ctx, wallet, direct.DefaultConfig(), true /* skip confirm */)
ctx := context.Background()
keymanager, err := direct.NewKeymanager(
ctx,
wallet,
direct.DefaultConfig(),
true, /* skip confirm */
)
require.NoError(t, err)
numAccounts := 5
depositDataForAccounts := make([][]byte, numAccounts)
accountCreationTimestamps := make([][]byte, numAccounts)
@@ -100,17 +106,17 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
}
func TestListAccounts_DerivedKeymanager(t *testing.T) {
walletDir, passwordsDir := setupWalletDir(t)
keymanagerKind := v2keymanager.Derived
ctx := context.Background()
wallet, err := NewWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
KeymanagerKind: keymanagerKind,
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keymanagerKind: v2keymanager.Derived,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
ctx := context.Background()
password := "hello world"
seedConfig, err := derived.InitializeWalletSeedFile(ctx, password, true /* skip confirm */)
require.NoError(t, err)
@@ -127,11 +133,12 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
password,
)
require.NoError(t, err)
numAccounts := 5
depositDataForAccounts := make([][]byte, numAccounts)
accountCreationTimestamps := make([][]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
_, err := keymanager.CreateAccount(ctx, password)
_, err := keymanager.CreateAccount(ctx)
require.NoError(t, err)
withdrawalKeyPath := fmt.Sprintf(derived.WithdrawalKeyDerivationPathTemplate, i)
depositData, err := wallet.ReadFileAtPath(ctx, withdrawalKeyPath, direct.DepositTransactionFileName)

View File

@@ -7,11 +7,14 @@ import (
"path"
"unicode"
"github.com/logrusorgru/aurora"
"github.com/manifoldco/promptui"
strongPasswords "github.com/nbutton23/zxcvbn-go"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
@@ -25,60 +28,45 @@ const (
minPasswordScore = 3
)
var keymanagerKindSelections = map[v2keymanager.Kind]string{
v2keymanager.Derived: "HD Wallet (Recommended)",
v2keymanager.Direct: "Non-HD Wallet (Most Basic)",
v2keymanager.Remote: "Remote Signing Wallet (Advanced)",
}
// NewAccount creates a new validator account from user input by opening
// a wallet from the user's specified path.
func NewAccount(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if errors.Is(err, ErrNoWalletFound) {
return errors.New("no wallet found, create a new one with ./prysm.sh validator wallet-v2 create")
} else if err != nil {
return errors.Wrap(err, "could not get wallet directory")
}
ctx := context.Background()
keymanagerKind, err := readKeymanagerKindFromWalletPath(walletDir)
if err != nil {
return errors.Wrap(err, "could not get keymanager kind")
}
// Only direct keymanagers can create accounts for now.
if keymanagerKind == v2keymanager.Remote {
return errors.New("cannot create a new account for a remote keymanager")
}
// Read the directory for password storage from user input.
passwordsDirPath := inputPasswordsDirectory(cliCtx)
wallet, err := OpenWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
CanUnlockAccounts: true,
KeymanagerKind: keymanagerKind,
})
wallet, err := OpenWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
// We initialize a new keymanager depending on the wallet's keymanager kind.
skipMnemonicConfirm := cliCtx.Bool(flags.SkipMnemonicConfirmFlag.Name)
keymanager, err := wallet.InitializeKeymanager(ctx, skipMnemonicConfirm)
if err != nil {
return errors.Wrap(err, "could not initialize keymanager")
}
// Read the new account's password from user input.
password, err := inputNewAccountPassword(cliCtx)
if err != nil {
return errors.Wrap(err, "could not read password")
}
// Create a new validator account using the specified keymanager.
if _, err := keymanager.CreateAccount(ctx, password); err != nil {
return errors.Wrap(err, "could not create account in wallet")
switch wallet.KeymanagerKind() {
case v2keymanager.Remote:
return errors.New("cannot create a new account for a remote keymanager")
case v2keymanager.Direct:
km, ok := keymanager.(*direct.Keymanager)
if !ok {
return errors.New("not a direct keymanager")
}
password, err := inputNewAccountPassword(cliCtx)
if err != nil {
return errors.Wrap(err, "could not input new account password")
}
// Create a new validator account using the specified keymanager.
if _, err := km.CreateAccount(ctx, password); err != nil {
return errors.Wrap(err, "could not create account in wallet")
}
case v2keymanager.Derived:
km, ok := keymanager.(*derived.Keymanager)
if !ok {
return errors.New("not a derived keymanager")
}
if _, err := km.CreateAccount(ctx); err != nil {
return errors.Wrap(err, "could not create account in wallet")
}
default:
return fmt.Errorf("keymanager kind %s not supported", wallet.KeymanagerKind())
}
return nil
}
@@ -91,6 +79,15 @@ func inputWalletDir(cliCtx *cli.Context) (string, error) {
if walletDir == flags.DefaultValidatorDir() {
walletDir = path.Join(walletDir, WalletDefaultDirName)
ok, err := hasDir(walletDir)
if err != nil {
return "", errors.Wrapf(err, "could not check if wallet dir %s exists", walletDir)
}
if ok {
au := aurora.NewAurora(true)
log.Infof("%s %s", au.BrightMagenta("(wallet path)"), walletDir)
return walletDir, nil
}
}
prompt := promptui.Prompt{
Label: "Enter a wallet directory",
@@ -118,8 +115,8 @@ func inputKeymanagerKind(cliCtx *cli.Context) (v2keymanager.Kind, error) {
promptSelect := promptui.Select{
Label: "Select a type of wallet",
Items: []string{
keymanagerKindSelections[v2keymanager.Direct],
keymanagerKindSelections[v2keymanager.Derived],
keymanagerKindSelections[v2keymanager.Direct],
keymanagerKindSelections[v2keymanager.Remote],
},
}
@@ -176,7 +173,15 @@ func inputNewWalletPassword(cliCtx *cli.Context) (string, error) {
return walletPassword, nil
}
func inputExistingWalletPassword() (string, error) {
func inputExistingWalletPassword(cliCtx *cli.Context) (string, error) {
if cliCtx.IsSet(flags.PasswordFileFlag.Name) {
passwordFilePath := cliCtx.String(flags.PasswordFileFlag.Name)
data, err := ioutil.ReadFile(passwordFilePath)
if err != nil {
return "", err
}
return string(data), nil
}
prompt := promptui.Prompt{
Label: "Wallet password",
Validate: validatePasswordInput,
@@ -249,14 +254,23 @@ func inputPasswordForAccount(_ *cli.Context, accountName string) (string, error)
return walletPassword, nil
}
func inputPasswordsDirectory(cliCtx *cli.Context) string {
func inputPasswordsDirectory(cliCtx *cli.Context) (string, error) {
passwordsDir := cliCtx.String(flags.WalletPasswordsDirFlag.Name)
if cliCtx.IsSet(flags.WalletPasswordsDirFlag.Name) {
return passwordsDir
return passwordsDir, nil
}
if passwordsDir == flags.DefaultValidatorDir() {
passwordsDir = path.Join(passwordsDir, PasswordsDefaultDirName)
ok, err := hasDir(passwordsDir)
if err != nil {
return "", errors.Wrap(err, "could not check if passwords directory exists")
}
if ok {
au := aurora.NewAurora(true)
log.Infof("%s %s", au.BrightMagenta("(account passwords path)"), passwordsDir)
return passwordsDir, nil
}
}
prompt := promptui.Prompt{
Label: "Directory where passwords will be stored",
@@ -265,9 +279,9 @@ func inputPasswordsDirectory(cliCtx *cli.Context) string {
}
passwordsPath, err := prompt.Run()
if err != nil {
log.Fatalf("Could not determine passwords directory: %v", formatPromptError(err))
return "", fmt.Errorf("could not determine passwords directory: %v", formatPromptError(err))
}
return passwordsPath
return passwordsPath, nil
}
// Validate a strong password input for new accounts,

View File

@@ -49,17 +49,13 @@ var (
ErrNoWalletFound = errors.New(
"no wallet found at path, please create a new wallet using `./prysm.sh validator wallet-v2 create`",
)
keymanagerKindSelections = map[v2keymanager.Kind]string{
v2keymanager.Derived: "HD Wallet (Recommended)",
v2keymanager.Direct: "Non-HD Wallet (Most Basic)",
v2keymanager.Remote: "Remote Signing Wallet (Advanced)",
}
)
// WalletConfig for a wallet struct, containing important information
// such as the passwords directory, the wallet's directory, and keymanager.
type WalletConfig struct {
WalletDir string
PasswordsDir string
KeymanagerKind v2keymanager.Kind
CanUnlockAccounts bool
}
// Wallet is a primitive in Prysm's v2 account management which
// has the capability of creating new accounts, reading existing accounts,
// and providing secure access to eth2 secrets depending on an
@@ -69,6 +65,7 @@ type Wallet struct {
passwordsDir string
canUnlockAccounts bool
keymanagerKind v2keymanager.Kind
walletPassword string
}
func init() {
@@ -77,24 +74,48 @@ func init() {
// NewWallet given a set of configuration options, will leverage
// create and write a new wallet to disk for a Prysm validator.
func NewWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
if cfg.WalletDir == "" || (cfg.CanUnlockAccounts && cfg.PasswordsDir == "") {
return nil, errors.New("wallet dir and passwords dir cannot be nil")
func NewWallet(
cliCtx *cli.Context,
) (*Wallet, error) {
walletDir, err := inputWalletDir(cliCtx)
if err != nil && !errors.Is(err, ErrNoWalletFound) {
return nil, errors.Wrap(err, "could not parse wallet directory")
}
accountsPath := path.Join(cfg.WalletDir, cfg.KeymanagerKind.String())
// Check if the user has a wallet at the specified path.
// If a user does not have a wallet, we instantiate one
// based on specified options.
walletExists, err := hasDir(walletDir)
if err != nil {
return nil, errors.Wrap(err, "could not check if wallet exists")
}
if walletExists {
return nil, errors.New(
"you already have a wallet at the specified path. You can " +
"edit your wallet configuration by running ./prysm.sh validator wallet-v2 edit",
)
}
keymanagerKind, err := inputKeymanagerKind(cliCtx)
if err != nil {
return nil, err
}
accountsPath := path.Join(walletDir, keymanagerKind.String())
if err := os.MkdirAll(accountsPath, DirectoryPermissions); err != nil {
return nil, errors.Wrap(err, "could not create wallet directory")
}
if cfg.PasswordsDir != "" {
if err := os.MkdirAll(cfg.PasswordsDir, DirectoryPermissions); err != nil {
w := &Wallet{
accountsPath: accountsPath,
keymanagerKind: keymanagerKind,
}
if keymanagerKind == v2keymanager.Direct {
passwordsDir, err := inputPasswordsDirectory(cliCtx)
if err != nil {
return nil, err
}
if err := os.MkdirAll(passwordsDir, DirectoryPermissions); err != nil {
return nil, errors.Wrap(err, "could not create passwords directory")
}
}
w := &Wallet{
accountsPath: accountsPath,
passwordsDir: cfg.PasswordsDir,
keymanagerKind: cfg.KeymanagerKind,
canUnlockAccounts: cfg.CanUnlockAccounts,
w.passwordsDir = passwordsDir
w.canUnlockAccounts = true
}
return w, nil
}
@@ -102,18 +123,39 @@ func NewWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
// OpenWallet instantiates a wallet from a specified path. It checks the
// type of keymanager associated with the wallet by reading files in the wallet
// path, if applicable. If a wallet does not exist, returns an appropriate error.
func OpenWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
keymanagerKind, err := readKeymanagerKindFromWalletPath(cfg.WalletDir)
func OpenWallet(cliCtx *cli.Context) (*Wallet, error) {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if errors.Is(err, ErrNoWalletFound) {
return nil, errors.New("no wallet found, create a new one with ./prysm.sh validator wallet-v2 create")
} else if err != nil {
return nil, err
}
keymanagerKind, err := readKeymanagerKindFromWalletPath(walletDir)
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager kind for wallet")
}
walletPath := path.Join(cfg.WalletDir, keymanagerKind.String())
return &Wallet{
accountsPath: walletPath,
passwordsDir: cfg.PasswordsDir,
keymanagerKind: keymanagerKind,
canUnlockAccounts: cfg.CanUnlockAccounts,
}, nil
walletPath := path.Join(walletDir, keymanagerKind.String())
w := &Wallet{
accountsPath: walletPath,
keymanagerKind: keymanagerKind,
}
if keymanagerKind == v2keymanager.Derived {
walletPassword, err := inputExistingWalletPassword(cliCtx)
if err != nil {
return nil, err
}
w.walletPassword = walletPassword
}
if keymanagerKind == v2keymanager.Direct {
passwordsDir, err := inputPasswordsDirectory(cliCtx)
if err != nil {
return nil, err
}
w.passwordsDir = passwordsDir
w.canUnlockAccounts = true
}
return w, nil
}
// ReadKeymanagerConfigFromDisk opens a keymanager config file
@@ -195,15 +237,11 @@ func (w *Wallet) InitializeKeymanager(
return nil, errors.Wrap(err, "could not initialize direct keymanager")
}
case v2keymanager.Derived:
seedPassword, err := inputExistingWalletPassword()
if err != nil {
return nil, err
}
cfg, err := derived.UnmarshalConfigFile(configFile)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
}
keymanager, err = derived.NewKeymanager(ctx, w, cfg, skipMnemonicConfirm, seedPassword)
keymanager, err = derived.NewKeymanager(ctx, w, cfg, skipMnemonicConfirm, w.walletPassword)
if err != nil {
return nil, errors.Wrap(err, "could not initialize derived keymanager")
}

View File

@@ -19,96 +19,58 @@ import (
// wallet already exists in the path, it suggests the user alternatives
// such as how to edit their existing wallet configuration.
func CreateWallet(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if err != nil && !errors.Is(err, ErrNoWalletFound) {
return errors.Wrap(err, "could not parse wallet directory")
}
// Check if the user has a wallet at the specified path.
// If a user does not have a wallet, we instantiate one
// based on specified options.
walletExists, err := hasDir(walletDir)
w, err := NewWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not check if wallet directory exists")
}
if walletExists {
return errors.New(
"You already have a wallet at the specified path. You can " +
"edit your wallet configuration by running ./prysm.sh validator wallet-v2 edit",
)
}
// Determine the desired keymanager kind for the wallet from user input.
keymanagerKind, err := inputKeymanagerKind(cliCtx)
if err != nil {
return errors.Wrap(err, "could not select keymanager kind")
}
switch keymanagerKind {
switch w.KeymanagerKind() {
case v2keymanager.Direct:
if err = createDirectWallet(cliCtx, walletDir); err != nil {
if err = createDirectKeymanagerWallet(cliCtx, w); err != nil {
return errors.Wrap(err, "could not initialize wallet with direct keymanager")
}
log.WithField("wallet-path", walletDir).Infof(
log.WithField("wallet-path", w.accountsPath).Infof(
"Successfully created wallet with on-disk keymanager configuration. " +
"Make a new validator account with ./prysm.sh validator accounts-2 new",
)
case v2keymanager.Derived:
if err = createDerivedWallet(cliCtx, walletDir); err != nil {
if err = createDerivedKeymanagerWallet(cliCtx, w); err != nil {
return errors.Wrap(err, "could not initialize wallet with derived keymanager")
}
log.WithField("wallet-path", walletDir).Infof(
log.WithField("wallet-path", w.accountsPath).Infof(
"Successfully created HD wallet and saved configuration to disk. " +
"Make a new validator account with ./prysm.sh validator accounts-2 new",
)
case v2keymanager.Remote:
if err = createRemoteWallet(cliCtx, walletDir); err != nil {
if err = createRemoteKeymanagerWallet(cliCtx, w); err != nil {
return errors.Wrap(err, "could not initialize wallet with remote keymanager")
}
log.WithField("wallet-path", walletDir).Infof(
log.WithField("wallet-path", w.accountsPath).Infof(
"Successfully created wallet with remote keymanager configuration",
)
default:
return errors.Wrap(err, "keymanager type %s is not supported")
return errors.Wrapf(err, "keymanager type %s is not supported", w.KeymanagerKind())
}
return nil
}
func createDirectWallet(cliCtx *cli.Context, walletDir string) error {
passwordsDirPath := inputPasswordsDirectory(cliCtx)
walletConfig := &WalletConfig{
WalletDir: walletDir,
PasswordsDir: passwordsDirPath,
KeymanagerKind: v2keymanager.Direct,
CanUnlockAccounts: true,
}
ctx := context.Background()
wallet, err := NewWallet(ctx, walletConfig)
if err != nil {
return errors.Wrap(err, "could not create new wallet")
}
keymanagerConfig, err := direct.MarshalConfigFile(ctx, direct.DefaultConfig())
func createDirectKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
keymanagerConfig, err := direct.MarshalConfigFile(context.Background(), direct.DefaultConfig())
if err != nil {
return errors.Wrap(err, "could not marshal keymanager config file")
}
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
if err := wallet.WriteKeymanagerConfigToDisk(context.Background(), keymanagerConfig); err != nil {
return errors.Wrap(err, "could not write keymanager config to disk")
}
return nil
}
func createDerivedWallet(cliCtx *cli.Context, walletDir string) error {
passwordsDirPath := inputPasswordsDirectory(cliCtx)
walletConfig := &WalletConfig{
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Derived,
CanUnlockAccounts: true,
}
func createDerivedKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
skipMnemonicConfirm := cliCtx.Bool(flags.SkipMnemonicConfirmFlag.Name)
ctx := context.Background()
walletPassword, err := inputNewWalletPassword(cliCtx)
if err != nil {
return errors.Wrap(err, "could not input new wallet password")
return err
}
skipMnemonicConfirm := cliCtx.Bool(flags.SkipMnemonicConfirmFlag.Name)
seedConfig, err := derived.InitializeWalletSeedFile(ctx, walletPassword, skipMnemonicConfirm)
if err != nil {
return errors.Wrap(err, "could not initialize new wallet seed file")
@@ -117,10 +79,6 @@ func createDerivedWallet(cliCtx *cli.Context, walletDir string) error {
if err != nil {
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
}
wallet, err := NewWallet(ctx, walletConfig)
if err != nil {
return errors.Wrap(err, "could not create new wallet")
}
keymanagerConfig, err := derived.MarshalConfigFile(ctx, derived.DefaultConfig())
if err != nil {
return errors.Wrap(err, "could not marshal keymanager config file")
@@ -134,7 +92,7 @@ func createDerivedWallet(cliCtx *cli.Context, walletDir string) error {
return nil
}
func createRemoteWallet(cliCtx *cli.Context, walletDir string) error {
func createRemoteKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
conf, err := inputRemoteKeymanagerConfig(cliCtx)
if err != nil {
return errors.Wrap(err, "could not input remote keymanager config")
@@ -144,14 +102,6 @@ func createRemoteWallet(cliCtx *cli.Context, walletDir string) error {
if err != nil {
return errors.Wrap(err, "could not marshal config file")
}
walletConfig := &WalletConfig{
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Remote,
}
wallet, err := NewWallet(ctx, walletConfig)
if err != nil {
return errors.Wrap(err, "could not create new wallet")
}
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
return errors.Wrap(err, "could not write keymanager config to disk")
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil"
@@ -18,34 +17,19 @@ import (
)
func TestCreateWallet_Direct(t *testing.T) {
walletDir := filepath.Join(testutil.TempDir(), walletDirName)
passwordsDir := filepath.Join(testutil.TempDir(), passwordDirName)
defer func() {
assert.NoError(t, os.RemoveAll(walletDir))
assert.NoError(t, os.RemoveAll(passwordsDir))
}()
wantCfg := direct.DefaultConfig()
app := cli.App{}
set := flag.NewFlagSet("test", 0)
keymanagerKind := "direct"
set.String(flags.WalletDirFlag.Name, walletDir, "")
set.String(flags.KeymanagerKindFlag.Name, keymanagerKind, "")
set.String(flags.WalletPasswordsDirFlag.Name, passwordsDir, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, walletDir))
assert.NoError(t, set.Set(flags.KeymanagerKindFlag.Name, keymanagerKind))
assert.NoError(t, set.Set(flags.WalletPasswordsDirFlag.Name, passwordsDir))
cliCtx := cli.NewContext(&app, set, nil)
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keymanagerKind: v2keymanager.Direct,
})
// We attempt to create the wallet.
require.NoError(t, CreateWallet(cliCtx))
// We attempt to open the newly created wallet.
ctx := context.Background()
wallet, err := OpenWallet(ctx, &WalletConfig{
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Direct,
CanUnlockAccounts: false,
})
wallet, err := OpenWallet(cliCtx)
assert.NoError(t, err)
// We read the keymanager config for the newly created wallet.
@@ -55,7 +39,7 @@ func TestCreateWallet_Direct(t *testing.T) {
assert.NoError(t, err)
// We assert the created configuration was as desired.
assert.DeepEqual(t, wantCfg, cfg)
assert.DeepEqual(t, direct.DefaultConfig(), cfg)
}
func TestCreateWallet_Remote(t *testing.T) {
@@ -93,11 +77,7 @@ func TestCreateWallet_Remote(t *testing.T) {
// We attempt to open the newly created wallet.
ctx := context.Background()
wallet, err := OpenWallet(ctx, &WalletConfig{
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Remote,
CanUnlockAccounts: false,
})
wallet, err := OpenWallet(cliCtx)
assert.NoError(t, err)
// We read the keymanager config for the newly created wallet.

View File

@@ -14,28 +14,12 @@ import (
// things such as remote gRPC credentials for remote signing, derivation paths
// for HD wallets, and more.
func EditWalletConfiguration(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if errors.Is(err, ErrNoWalletFound) {
return errors.New("no wallet found, create a new one with ./prysm.sh validator wallet-v2 create")
} else if err != nil {
return errors.Wrap(err, "could not parse wallet directory")
}
// Determine the keymanager kind for the wallet.
keymanagerKind, err := readKeymanagerKindFromWalletPath(walletDir)
if err != nil {
return errors.Wrap(err, "could not select keymanager kind")
}
ctx := context.Background()
wallet, err := OpenWallet(ctx, &WalletConfig{
CanUnlockAccounts: false,
WalletDir: walletDir,
KeymanagerKind: keymanagerKind,
})
wallet, err := OpenWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
switch keymanagerKind {
switch wallet.KeymanagerKind() {
case v2keymanager.Direct:
return errors.New("no configuration options available to edit for direct keymanager")
case v2keymanager.Derived:
@@ -63,7 +47,7 @@ func EditWalletConfiguration(cliCtx *cli.Context) error {
return errors.Wrap(err, "could not write config to disk")
}
default:
return fmt.Errorf("keymanager type %s is not supported", keymanagerKind)
return fmt.Errorf("keymanager type %s is not supported", wallet.KeymanagerKind())
}
return nil
}

View File

@@ -3,12 +3,10 @@ package v2
import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/remote"
@@ -16,11 +14,15 @@ import (
)
func TestEditWalletConfiguration(t *testing.T) {
walletDir := filepath.Join(testutil.TempDir(), walletDirName)
defer func() {
assert.NoError(t, os.RemoveAll(walletDir))
}()
walletDir, _ := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
keymanagerKind: v2keymanager.Remote,
})
wallet, err := NewWallet(cliCtx)
require.NoError(t, err)
ctx := context.Background()
originalCfg := &remote.Config{
RemoteCertificate: &remote.CertificateConfig{
ClientCertPath: "/tmp/a.crt",
@@ -31,12 +33,6 @@ func TestEditWalletConfiguration(t *testing.T) {
}
encodedCfg, err := remote.MarshalConfigFile(ctx, originalCfg)
assert.NoError(t, err)
walletConfig := &WalletConfig{
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Remote,
}
wallet, err := NewWallet(ctx, walletConfig)
assert.NoError(t, err)
assert.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg))
wantCfg := &remote.Config{
@@ -59,7 +55,7 @@ func TestEditWalletConfiguration(t *testing.T) {
assert.NoError(t, set.Set(flags.RemoteSignerCertPathFlag.Name, wantCfg.RemoteCertificate.ClientCertPath))
assert.NoError(t, set.Set(flags.RemoteSignerKeyPathFlag.Name, wantCfg.RemoteCertificate.ClientKeyPath))
assert.NoError(t, set.Set(flags.RemoteSignerCACertPathFlag.Name, wantCfg.RemoteCertificate.CACertPath))
cliCtx := cli.NewContext(&app, set, nil)
cliCtx = cli.NewContext(&app, set, nil)
assert.NoError(t, EditWalletConfiguration(cliCtx))
encoded, err := wallet.ReadKeymanagerConfigFromDisk(ctx)

View File

@@ -9,7 +9,6 @@ import (
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
"github.com/urfave/cli/v2"
)
@@ -18,54 +17,16 @@ const phraseWordCount = 24
// RecoverWallet uses a menmonic seed phrase to recover a wallet into the path provided.
func RecoverWallet(cliCtx *cli.Context) error {
// Read a wallet's directory from user input.
walletDir, err := inputWalletDir(cliCtx)
if err != nil && !errors.Is(err, ErrNoWalletFound) {
log.Fatalf("Could not parse wallet directory: %v", err)
}
// Check if the user has a wallet at the specified path.
// If a user does not have a wallet, we instantiate one
// based on specified options.
walletExists, err := hasDir(walletDir)
if err != nil {
log.Fatal(err)
}
if walletExists {
log.Fatal(
"You already have a wallet at the specified path. You can " +
"edit your wallet configuration by running ./prysm.sh validator wallet-v2 edit",
)
}
if err = recoverDerivedWallet(cliCtx, walletDir); err != nil {
log.Fatalf("Could not initialize wallet with derived keymanager: %v", err)
}
log.WithField("wallet-path", walletDir).Infof(
"Successfully recovered HD wallet and saved configuration to disk. " +
"Make a new validator account with ./prysm.sh validator accounts-2 new",
)
return nil
}
func recoverDerivedWallet(cliCtx *cli.Context, walletDir string) error {
mnemonic, err := inputMnemonic(cliCtx)
if err != nil {
return errors.Wrap(err, "could not get mnemonic phrase")
}
passwordsDirPath := inputPasswordsDirectory(cliCtx)
walletConfig := &WalletConfig{
PasswordsDir: passwordsDirPath,
WalletDir: walletDir,
KeymanagerKind: v2keymanager.Derived,
CanUnlockAccounts: true,
wallet, err := NewWallet(cliCtx)
if err != nil {
return errors.Wrap(err, "could not create new wallet")
}
ctx := context.Background()
walletPassword, err := inputNewWalletPassword(cliCtx)
if err != nil {
return errors.Wrap(err, "could not input new wallet password")
}
seedConfig, err := derived.SeedFileFromMnemonic(ctx, mnemonic, walletPassword)
seedConfig, err := derived.SeedFileFromMnemonic(ctx, mnemonic, wallet.walletPassword)
if err != nil {
return errors.Wrap(err, "could not initialize new wallet seed file")
}
@@ -73,11 +34,6 @@ func recoverDerivedWallet(cliCtx *cli.Context, walletDir string) error {
if err != nil {
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
}
wallet, err := NewWallet(ctx, walletConfig)
if err != nil {
return errors.Wrap(err, "could not create new wallet")
}
keymanagerConfig, err := derived.MarshalConfigFile(ctx, derived.DefaultConfig())
if err != nil {
return errors.Wrap(err, "could not marshal keymanager config file")
@@ -88,6 +44,10 @@ func recoverDerivedWallet(cliCtx *cli.Context, walletDir string) error {
if err := wallet.WriteEncryptedSeedToDisk(ctx, seedConfigFile); err != nil {
return errors.Wrap(err, "could not write encrypted wallet seed config to disk")
}
log.WithField("wallet-path", wallet.AccountsDir()).Infof(
"Successfully recovered HD wallet and saved configuration to disk. " +
"Make a new validator account with ./prysm.sh validator accounts-2 new",
)
return nil
}

View File

@@ -38,24 +38,21 @@ func TestRecoverDerivedWallet(t *testing.T) {
set.String(flags.WalletDirFlag.Name, walletDir, "")
set.String(flags.WalletPasswordsDirFlag.Name, passwordsDir, "")
set.String(flags.PasswordFileFlag.Name, passwordFilePath, "")
set.String(flags.KeymanagerKindFlag.Name, v2keymanager.Derived.String(), "")
set.String(flags.MnemonicFileFlag.Name, mnemonicFilePath, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, walletDir))
assert.NoError(t, set.Set(flags.WalletPasswordsDirFlag.Name, passwordsDir))
assert.NoError(t, set.Set(flags.PasswordFileFlag.Name, passwordFilePath))
assert.NoError(t, set.Set(flags.KeymanagerKindFlag.Name, v2keymanager.Derived.String()))
assert.NoError(t, set.Set(flags.MnemonicFileFlag.Name, mnemonicFilePath))
cliCtx := cli.NewContext(&app, set, nil)
if err := recoverDerivedWallet(cliCtx, walletDir); err != nil {
if err := RecoverWallet(cliCtx); err != nil {
t.Fatal(err)
}
ctx := context.Background()
wallet, err := OpenWallet(ctx, &WalletConfig{
WalletDir: walletDir,
PasswordsDir: passwordsDir,
KeymanagerKind: v2keymanager.Derived,
CanUnlockAccounts: false,
})
wallet, err := OpenWallet(cliCtx)
assert.NoError(t, err)
encoded, err := wallet.ReadKeymanagerConfigFromDisk(ctx)

View File

@@ -1,8 +1,8 @@
package v2
import (
"context"
"crypto/rand"
"flag"
"fmt"
"io/ioutil"
"math/big"
@@ -11,10 +11,12 @@ import (
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/validator/flags"
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
mock "github.com/prysmaticlabs/prysm/validator/keymanager/v2/testing"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
func init() {
@@ -22,12 +24,42 @@ func init() {
logrus.SetOutput(ioutil.Discard)
}
func setupWalletDir(t testing.TB) (string, string) {
type testWalletConfig struct {
walletDir string
passwordsDir string
exportDir string
accountsToExport string
passwordFile string
keymanagerKind v2keymanager.Kind
}
func setupWalletCtx(
tb testing.TB,
cfg *testWalletConfig,
) *cli.Context {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, cfg.walletDir, "")
set.String(flags.WalletPasswordsDirFlag.Name, cfg.passwordsDir, "")
set.String(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String(), "")
set.String(flags.BackupPathFlag.Name, cfg.exportDir, "")
set.String(flags.AccountsFlag.Name, cfg.accountsToExport, "")
set.String(flags.PasswordFileFlag.Name, cfg.passwordFile, "")
assert.NoError(tb, set.Set(flags.WalletDirFlag.Name, cfg.walletDir))
assert.NoError(tb, set.Set(flags.WalletPasswordsDirFlag.Name, cfg.passwordsDir))
assert.NoError(tb, set.Set(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String()))
assert.NoError(tb, set.Set(flags.BackupPathFlag.Name, cfg.exportDir))
assert.NoError(tb, set.Set(flags.AccountsFlag.Name, cfg.accountsToExport))
assert.NoError(tb, set.Set(flags.PasswordFileFlag.Name, cfg.passwordFile))
return cli.NewContext(&app, set, nil)
}
func setupWalletAndPasswordsDir(t testing.TB) (string, string) {
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
require.NoError(t, err, "Could not generate random file path")
walletDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath))
walletDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "wallet")
require.NoError(t, os.RemoveAll(walletDir), "Failed to remove directory")
passwordsDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath))
passwordsDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "passwords")
require.NoError(t, os.RemoveAll(passwordsDir), "Failed to remove directory")
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(walletDir), "Failed to remove directory")
@@ -37,37 +69,15 @@ func setupWalletDir(t testing.TB) (string, string) {
}
func TestCreateAndReadWallet(t *testing.T) {
ctx := context.Background()
if _, err := NewWallet(ctx, &WalletConfig{
PasswordsDir: "",
WalletDir: "",
}); err == nil {
t.Error("Expected error when passing in empty directories, received nil")
}
walletDir, passwordsDir := setupWalletDir(t)
keymanagerKind := v2keymanager.Direct
wallet, err := NewWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
KeymanagerKind: keymanagerKind,
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
passwordsDir: passwordsDir,
keymanagerKind: v2keymanager.Direct,
})
_, err := NewWallet(cliCtx)
require.NoError(t, err)
keymanager := &mock.MockKeymanager{
ConfigFileContents: []byte("hello-world"),
}
keymanagerConfig, err := keymanager.MarshalConfigFile(ctx)
require.NoError(t, err, "Could not marshal keymanager config file")
require.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig), "Could not write keymanager config file to disk")
walletPath := path.Join(walletDir, keymanagerKind.String())
configFilePath := path.Join(walletPath, KeymanagerConfigFileName)
require.Equal(t, true, fileExists(configFilePath), "Expected config file to have been created at path: %s", configFilePath)
// We should be able to now read the wallet as well.
_, err = NewWallet(ctx, &WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
})
_, err = OpenWallet(cliCtx)
require.NoError(t, err)
}

View File

@@ -73,6 +73,7 @@ type Keymanager struct {
lock sync.RWMutex
seedCfg *SeedConfig
seed []byte
walletPassword string
}
// SeedConfig json file representation as a Go struct.
@@ -128,9 +129,10 @@ func NewKeymanager(
mnemonicGenerator: &EnglishMnemonicGenerator{
skipMnemonicConfirm: skipMnemonicConfirm,
},
seedCfg: seedConfig,
seed: seed,
keysCache: make(map[[48]byte]bls.SecretKey),
seedCfg: seedConfig,
seed: seed,
walletPassword: password,
keysCache: make(map[[48]byte]bls.SecretKey),
}
// We initialize a cache of public key -> secret keys
// used to retrieve secrets keys for the accounts via the unlocked wallet.
@@ -248,8 +250,12 @@ func (dr *Keymanager) NextAccountNumber(ctx context.Context) uint64 {
func (dr *Keymanager) ValidatingAccountNames(ctx context.Context) ([]string, error) {
names := make([]string, 0)
for i := uint64(0); i < dr.seedCfg.NextAccount; i++ {
withdrawalKeyPath := fmt.Sprintf(WithdrawalKeyDerivationPathTemplate, i)
names = append(names, petnames.DeterministicName([]byte(withdrawalKeyPath), "-"))
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i)
validatingKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, validatingKeyPath)
if err != nil {
return nil, errors.Wrap(err, "could not derive validating key")
}
names = append(names, petnames.DeterministicName(validatingKey.Marshal(), "-"))
}
return names, nil
}
@@ -259,7 +265,7 @@ func (dr *Keymanager) ValidatingAccountNames(ctx context.Context) ([]string, err
// for hierarchical derivation of BLS secret keys and a common derivation path structure for
// persisting accounts to disk. Each account stores the generated keystore.json file.
// The entire derived wallet seed phrase can be recovered from a BIP-39 english mnemonic.
func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (string, error) {
func (dr *Keymanager) CreateAccount(ctx context.Context) (string, error) {
withdrawalKeyPath := fmt.Sprintf(WithdrawalKeyDerivationPathTemplate, dr.seedCfg.NextAccount)
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, dr.seedCfg.NextAccount)
withdrawalKey, err := util.PrivateKeyFromSeedAndPath(dr.seed, withdrawalKeyPath)
@@ -275,7 +281,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
encodedWithdrawalKeystore, err := dr.generateKeystoreFile(
withdrawalKey.Marshal(),
withdrawalKey.PublicKey().Marshal(),
password,
dr.walletPassword,
)
if err != nil {
return "", errors.Wrap(err, "could not generate keystore file for withdrawal account")
@@ -283,7 +289,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
encodedValidatingKeystore, err := dr.generateKeystoreFile(
validatingKey.Marshal(),
validatingKey.PublicKey().Marshal(),
password,
dr.walletPassword,
)
if err != nil {
return "", errors.Wrap(err, "could not generate keystore file for validating account")

View File

@@ -29,16 +29,17 @@ func TestDerivedKeymanager_CreateAccount(t *testing.T) {
}
seed := make([]byte, 32)
copy(seed, "hello world")
password := "secretPassw0rd$1999"
dr := &Keymanager{
wallet: wallet,
seed: seed,
seedCfg: &SeedConfig{
NextAccount: 0,
},
walletPassword: password,
}
ctx := context.Background()
password := "secretPassw0rd$1999"
accountName, err := dr.CreateAccount(ctx, password)
accountName, err := dr.CreateAccount(ctx)
require.NoError(t, err)
assert.Equal(t, "0", accountName)
@@ -108,17 +109,17 @@ func TestDerivedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
seedCfg: &SeedConfig{
NextAccount: 0,
},
seed: make([]byte, 32),
seed: make([]byte, 32),
walletPassword: "hello world",
}
// First, generate accounts and their keystore.json files.
ctx := context.Background()
numAccounts := 20
password := "hello world"
wantedPublicKeys := make([][48]byte, numAccounts)
var err error
var accountName string
for i := 0; i < numAccounts; i++ {
accountName, err = dr.CreateAccount(ctx, password)
accountName, err = dr.CreateAccount(ctx)
require.NoError(t, err)
validatingKeyPath := fmt.Sprintf(ValidatingKeyDerivationPathTemplate, i)
enc, err := wallet.ReadFileAtPath(ctx, validatingKeyPath, KeystoreFileName)
@@ -161,16 +162,16 @@ func TestDerivedKeymanager_Sign(t *testing.T) {
seedCfg: &SeedConfig{
NextAccount: 0,
},
walletPassword: "hello world",
}
// First, generate some accounts.
numAccounts := 2
ctx := context.Background()
password := "hello world"
var err error
var accountName string
for i := 0; i < numAccounts; i++ {
accountName, err = dr.CreateAccount(ctx, password)
accountName, err = dr.CreateAccount(ctx)
require.NoError(t, err)
}
assert.Equal(t, fmt.Sprintf("%d", numAccounts-1), accountName)

View File

@@ -1,17 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
testonly = 1,
srcs = ["mock.go"],
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/v2/testing",
visibility = [
"//validator:__pkg__",
"//validator:__subpackages__",
],
deps = [
"//proto/validator/accounts/v2:go_default_library",
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
],
)

View File

@@ -1,42 +0,0 @@
package testing
import (
"context"
"errors"
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
)
// MockKeymanager --
type MockKeymanager struct {
ConfigFileContents []byte
PublicKeys [][48]byte
PubkeystoSecretKeys map[[48]byte]bls.SecretKey
}
// CreateAccount --
func (m *MockKeymanager) CreateAccount(ctx context.Context, password string) (string, error) {
return "", nil
}
// MarshalConfigFile --
func (m *MockKeymanager) MarshalConfigFile(ctx context.Context) ([]byte, error) {
return m.ConfigFileContents, nil
}
// FetchValidatingPublicKeys --
func (m *MockKeymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
return m.PublicKeys, nil
}
// Sign --
func (m *MockKeymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
pubKey := bytesutil.ToBytes48(req.PublicKey)
secretKey, ok := m.PubkeystoSecretKeys[pubKey]
if !ok {
return nil, errors.New("no secret key found")
}
return secretKey.Sign(req.SigningRoot), nil
}

View File

@@ -10,8 +10,6 @@ import (
// IKeymanager defines a general keymanager-v2 interface for Prysm wallets.
type IKeymanager interface {
// CreateAccount based on the keymanager's logic. Returns the account name.
CreateAccount(ctx context.Context, password string) (string, error)
// FetchValidatingKeys fetches the list of public keys that should be used to validate with.
FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error)
// Sign signs a message using a validator key.
@@ -32,10 +30,10 @@ type Keystore struct {
type Kind int
const (
// Direct keymanager defines an on-disk, encrypted keystore-capable store.
Direct Kind = iota
// Derived keymanager using a hierarchical-deterministic algorithm.
Derived
Derived Kind = iota
// Direct keymanager defines an on-disk, encrypted keystore-capable store.
Direct
// Remote keymanager capable of remote-signing data.
Remote
)
@@ -43,10 +41,10 @@ const (
// String marshals a keymanager kind to a string value.
func (k Kind) String() string {
switch k {
case Direct:
return "direct"
case Derived:
return "derived"
case Direct:
return "direct"
case Remote:
return "remote"
default:
@@ -57,10 +55,10 @@ func (k Kind) String() string {
// ParseKind from a raw string, returning a keymanager kind.
func ParseKind(k string) (Kind, error) {
switch k {
case "direct":
return Direct, nil
case "derived":
return Derived, nil
case "direct":
return Direct, nil
case "remote":
return Remote, nil
default:

View File

@@ -95,11 +95,7 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
passwordsDir = path.Join(passwordsDir, accountsv2.PasswordsDefaultDirName)
}
// Read the wallet from the specified path.
wallet, err := accountsv2.OpenWallet(context.Background(), &accountsv2.WalletConfig{
PasswordsDir: passwordsDir,
WalletDir: walletDir,
CanUnlockAccounts: true,
})
wallet, err := accountsv2.OpenWallet(cliCtx)
if err != nil {
log.Fatalf("Could not open wallet: %v", err)
}