mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
Accounts V2: Resolve Remaining Keymanager Bugs (#6706)
* v2 fix bugs * better doc * include wallet build fix * fixed broken list test * add round trip recover seed unit test * imports * implement list with tests * add altona flags * tests for unicode * added is valid unicode tests * fixed up tests to ensure wallet is persisted after everything works * resolve confs and integrate medalla testnet * fix build * add medalla * fixed import spacing
This commit is contained in:
@@ -115,13 +115,13 @@ func ConfigureBeaconChain(ctx *cli.Context) {
|
||||
if ctx.Bool(devModeFlag.Name) {
|
||||
enableDevModeFlags(ctx)
|
||||
}
|
||||
if ctx.Bool(altonaTestnet.Name) {
|
||||
if ctx.Bool(AltonaTestnet.Name) {
|
||||
log.Warn("Running Node on Altona Testnet")
|
||||
params.UseAltonaConfig()
|
||||
params.UseAltonaNetworkConfig()
|
||||
cfg.AltonaTestnet = true
|
||||
}
|
||||
if ctx.Bool(medallaTestnet.Name) {
|
||||
if ctx.Bool(MedallaTestnet.Name) {
|
||||
log.Warn("Running Node on Medalla Testnet")
|
||||
params.UseMedallaConfig()
|
||||
params.UseMedallaNetworkConfig()
|
||||
@@ -264,13 +264,13 @@ func ConfigureSlasher(ctx *cli.Context) {
|
||||
func ConfigureValidator(ctx *cli.Context) {
|
||||
complainOnDeprecatedFlags(ctx)
|
||||
cfg := &Flags{}
|
||||
if ctx.Bool(altonaTestnet.Name) {
|
||||
if ctx.Bool(AltonaTestnet.Name) {
|
||||
log.Warn("Running Validator on Altona Testnet")
|
||||
params.UseAltonaConfig()
|
||||
params.UseAltonaNetworkConfig()
|
||||
cfg.AltonaTestnet = true
|
||||
}
|
||||
if ctx.Bool(medallaTestnet.Name) {
|
||||
if ctx.Bool(MedallaTestnet.Name) {
|
||||
log.Warn("Running Validator on Medalla Testnet")
|
||||
params.UseMedallaConfig()
|
||||
params.UseMedallaNetworkConfig()
|
||||
|
||||
@@ -5,6 +5,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// AltonaTestnet flag for the multiclient eth2 testnet configuration.
|
||||
AltonaTestnet = &cli.BoolFlag{
|
||||
Name: "altona",
|
||||
Usage: "This defines the flag through which we can run on the Altona Multiclient Testnet",
|
||||
}
|
||||
// MedallaTestnet flag for the multiclient eth2 testnet configuration.
|
||||
MedallaTestnet = &cli.BoolFlag{
|
||||
Name: "medalla",
|
||||
Usage: "This defines the flag through which we can run on the Medalla Multiclient Testnet",
|
||||
}
|
||||
devModeFlag = &cli.BoolFlag{
|
||||
Name: "dev",
|
||||
Usage: "Enable experimental features still in development. These features may not be stable.",
|
||||
@@ -136,14 +146,6 @@ var (
|
||||
Name: "attestation-aggregation-force-maxcover",
|
||||
Usage: "When enabled, forces --attestation-aggregation-strategy=max_cover setting.",
|
||||
}
|
||||
altonaTestnet = &cli.BoolFlag{
|
||||
Name: "altona",
|
||||
Usage: "This defines the flag through which we can run on the Altona Multiclient Testnet",
|
||||
}
|
||||
medallaTestnet = &cli.BoolFlag{
|
||||
Name: "medalla",
|
||||
Usage: "This defines the flag through which we can run on the Medalla Multiclient Testnet",
|
||||
}
|
||||
enableAccountsV2 = &cli.BoolFlag{
|
||||
Name: "enable-accounts-v2",
|
||||
Usage: "Enables usage of v2 for Prysm validator accounts",
|
||||
@@ -569,8 +571,8 @@ var ValidatorFlags = append(deprecatedFlags, []cli.Flag{
|
||||
enableExternalSlasherProtectionFlag,
|
||||
disableDomainDataCacheFlag,
|
||||
waitForSyncedFlag,
|
||||
altonaTestnet,
|
||||
medallaTestnet,
|
||||
AltonaTestnet,
|
||||
MedallaTestnet,
|
||||
enableAccountsV2,
|
||||
}...)
|
||||
|
||||
@@ -612,8 +614,8 @@ var BeaconChainFlags = append(deprecatedFlags, []cli.Flag{
|
||||
attestationAggregationStrategy,
|
||||
newBeaconStateLocks,
|
||||
forceMaxCoverAttestationAggregation,
|
||||
altonaTestnet,
|
||||
medallaTestnet,
|
||||
AltonaTestnet,
|
||||
MedallaTestnet,
|
||||
batchBlockVerify,
|
||||
initSyncVerbose,
|
||||
enableFinalizedDepositsCache,
|
||||
|
||||
@@ -23,7 +23,9 @@ go_library(
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//shared/featureconfig:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"//validator/keymanager/v2/derived:go_default_library",
|
||||
@@ -55,6 +57,10 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/testutil:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -22,6 +23,8 @@ this command outputs a deposit data string which is required to become a validat
|
||||
flags.WalletPasswordsDirFlag,
|
||||
flags.PasswordFileFlag,
|
||||
flags.SkipMnemonicConfirmFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := NewAccount(cliCtx); err != nil {
|
||||
@@ -38,6 +41,8 @@ this command outputs a deposit data string which is required to become a validat
|
||||
flags.WalletPasswordsDirFlag,
|
||||
flags.PasswordFileFlag,
|
||||
flags.ShowDepositDataFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := ListAccounts(cliCtx); err != nil {
|
||||
@@ -54,6 +59,8 @@ this command outputs a deposit data string which is required to become a validat
|
||||
flags.WalletPasswordsDirFlag,
|
||||
flags.BackupDirFlag,
|
||||
flags.AccountsFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := ExportAccount(cliCtx); err != nil {
|
||||
@@ -70,6 +77,8 @@ this command outputs a deposit data string which is required to become a validat
|
||||
flags.WalletPasswordsDirFlag,
|
||||
flags.BackupDirFlag,
|
||||
flags.PasswordFileFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := ImportAccount(cliCtx); err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -24,6 +25,8 @@ var WalletCommands = &cli.Command{
|
||||
flags.RemoteSignerKeyPathFlag,
|
||||
flags.RemoteSignerCACertPathFlag,
|
||||
flags.PasswordFileFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := CreateWallet(cliCtx); err != nil {
|
||||
@@ -41,6 +44,8 @@ var WalletCommands = &cli.Command{
|
||||
flags.RemoteSignerCertPathFlag,
|
||||
flags.RemoteSignerKeyPathFlag,
|
||||
flags.RemoteSignerCACertPathFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := EditWalletConfiguration(cliCtx); err != nil {
|
||||
@@ -57,6 +62,8 @@ var WalletCommands = &cli.Command{
|
||||
flags.WalletPasswordsDirFlag,
|
||||
flags.MnemonicFileFlag,
|
||||
flags.PasswordFileFlag,
|
||||
featureconfig.AltonaTestnet,
|
||||
featureconfig.MedallaTestnet,
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := RecoverWallet(cliCtx); err != nil {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Package v2 defines a new model for accounts management in Prysm, using best
|
||||
// practices for user security, UX, and extensibility via different wallet types
|
||||
// including derived, HD wallets and remote-signing capable configurations.
|
||||
// including HD wallets, non-HD wallets, and remote-signing capable configurations. This model
|
||||
// is compliant with the EIP-2333, EIP-2334, and EIP-2335 standards for key management in eth2.
|
||||
package v2
|
||||
|
||||
@@ -32,8 +32,9 @@ func TestZipAndUnzip(t *testing.T) {
|
||||
exportDir: exportDir,
|
||||
keymanagerKind: v2keymanager.Direct,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Direct)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
@@ -84,8 +85,9 @@ func TestExport_Noninteractive(t *testing.T) {
|
||||
accountsToExport: accounts,
|
||||
keymanagerKind: v2keymanager.Direct,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Direct)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
keymanagerCfg := direct.DefaultConfig()
|
||||
encodedCfg, err := direct.MarshalConfigFile(ctx, keymanagerCfg)
|
||||
|
||||
@@ -40,8 +40,9 @@ func TestImport_Noninteractive(t *testing.T) {
|
||||
keymanagerKind: v2keymanager.Direct,
|
||||
passwordFile: passwordFilePath,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Direct)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
keymanagerCfg := direct.DefaultConfig()
|
||||
encodedCfg, err := direct.MarshalConfigFile(ctx, keymanagerCfg)
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/petnames"
|
||||
"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/prysmaticlabs/prysm/validator/keymanager/v2/remote"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -47,6 +49,14 @@ func ListAccounts(cliCtx *cli.Context) error {
|
||||
if err := listDerivedKeymanagerAccounts(showDepositData, wallet, km); err != nil {
|
||||
return errors.Wrap(err, "could not list validator accounts with derived keymanager")
|
||||
}
|
||||
case v2keymanager.Remote:
|
||||
km, ok := keymanager.(*remote.Keymanager)
|
||||
if !ok {
|
||||
return errors.New("could not assert keymanager interface to concrete type")
|
||||
}
|
||||
if err := listRemoteKeymanagerAccounts(wallet, km, km.Config()); err != nil {
|
||||
return errors.Wrap(err, "could not list validator accounts with remote keymanager")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("keymanager kind %s not yet supported", wallet.KeymanagerKind().String())
|
||||
}
|
||||
@@ -75,7 +85,6 @@ func listDirectKeymanagerAccounts(
|
||||
au.BrightRed("View the eth1 deposit transaction data for your accounts " +
|
||||
"by running `validator accounts-v2 list --show-deposit-data"),
|
||||
)
|
||||
fmt.Printf("Keymanager kind: %s\n", au.BrightGreen(wallet.KeymanagerKind().String()).Bold())
|
||||
|
||||
ctx := context.Background()
|
||||
pubKeys, err := keymanager.FetchValidatingPublicKeys(ctx)
|
||||
@@ -152,6 +161,14 @@ func listDerivedKeymanagerAccounts(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(accountNames) == 1 {
|
||||
fmt.Print("Showing 1 validator account\n")
|
||||
} else if len(accountNames) == 0 {
|
||||
fmt.Print("No accounts found\n")
|
||||
return nil
|
||||
} else {
|
||||
fmt.Printf("Showing %d validator accounts\n", len(accountNames))
|
||||
}
|
||||
for i := uint64(0); i <= currentAccountNumber; i++ {
|
||||
fmt.Println("")
|
||||
validatingKeyPath := fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
|
||||
@@ -197,3 +214,42 @@ func listDerivedKeymanagerAccounts(
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func listRemoteKeymanagerAccounts(
|
||||
wallet *Wallet,
|
||||
keymanager v2keymanager.IKeymanager,
|
||||
cfg *remote.Config,
|
||||
) error {
|
||||
au := aurora.NewAurora(true)
|
||||
fmt.Printf("(keymanager kind) %s\n", au.BrightGreen("remote signer").Bold())
|
||||
fmt.Printf(
|
||||
"(configuration file path) %s\n",
|
||||
au.BrightGreen(filepath.Join(wallet.AccountsDir(), KeymanagerConfigFileName)).Bold(),
|
||||
)
|
||||
ctx := context.Background()
|
||||
fmt.Println(" ")
|
||||
fmt.Printf("%s\n", au.BrightGreen("Configuration options").Bold())
|
||||
fmt.Println(cfg)
|
||||
validatingPubKeys, err := keymanager.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch validating public keys")
|
||||
}
|
||||
if len(validatingPubKeys) == 1 {
|
||||
fmt.Print("Showing 1 validator account\n")
|
||||
} else if len(validatingPubKeys) == 0 {
|
||||
fmt.Print("No accounts found\n")
|
||||
return nil
|
||||
} else {
|
||||
fmt.Printf("Showing %d validator accounts\n", len(validatingPubKeys))
|
||||
}
|
||||
for i := 0; i < len(validatingPubKeys); i++ {
|
||||
fmt.Println("")
|
||||
fmt.Printf(
|
||||
"%s\n", au.BrightGreen(petnames.DeterministicName(validatingPubKeys[i][:], "-")).Bold(),
|
||||
)
|
||||
// Retrieve the validating key account metadata.
|
||||
fmt.Printf("%s %#x\n", au.BrightCyan("[validating public key]").Bold(), validatingPubKeys[i])
|
||||
fmt.Println(" ")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,22 +2,43 @@ package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/petnames"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
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/prysmaticlabs/prysm/validator/keymanager/v2/remote"
|
||||
)
|
||||
|
||||
type mockKeymanager struct {
|
||||
publicKeys [][48]byte
|
||||
}
|
||||
|
||||
func (m *mockKeymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
|
||||
return m.publicKeys, nil
|
||||
}
|
||||
|
||||
func (m *mockKeymanager) Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
|
||||
cliCtx := setupWalletCtx(t, &testWalletConfig{
|
||||
@@ -25,8 +46,9 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
passwordsDir: passwordsDir,
|
||||
keymanagerKind: v2keymanager.Direct,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Direct)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
@@ -106,15 +128,29 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
|
||||
func TestListAccounts_DerivedKeymanager(t *testing.T) {
|
||||
walletDir, passwordsDir := setupWalletAndPasswordsDir(t)
|
||||
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
require.NoError(t, err)
|
||||
passwordFileDir := filepath.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath), "passwords-file")
|
||||
require.NoError(t, os.MkdirAll(passwordFileDir, os.ModePerm))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.RemoveAll(passwordFileDir), "Failed to remove directory")
|
||||
})
|
||||
passwordFilePath := filepath.Join(passwordFileDir, passwordFileName)
|
||||
password := "PasszW0rdzzz2%"
|
||||
require.NoError(
|
||||
t,
|
||||
ioutil.WriteFile(passwordFilePath, []byte(password), os.ModePerm),
|
||||
)
|
||||
cliCtx := setupWalletCtx(t, &testWalletConfig{
|
||||
walletDir: walletDir,
|
||||
passwordsDir: passwordsDir,
|
||||
keymanagerKind: v2keymanager.Derived,
|
||||
passwordFile: passwordFilePath,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Derived)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
password := "hello world"
|
||||
|
||||
seedConfig, err := derived.InitializeWalletSeedFile(ctx, password, true /* skip confirm */)
|
||||
require.NoError(t, err)
|
||||
@@ -167,7 +203,7 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
|
||||
t.Error("Did not find Keymanager kind in output")
|
||||
}
|
||||
|
||||
// Assert the wallet and passwords paths are in stdout.
|
||||
// Assert the wallet accounts path is in stdout.
|
||||
if !strings.Contains(stringOutput, wallet.accountsPath) {
|
||||
t.Errorf("Did not find accounts path %s in output", wallet.accountsPath)
|
||||
}
|
||||
@@ -203,3 +239,75 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
|
||||
assert.Equal(t, strings.Contains(stringOutput, humanize.Time(unixTimestamp)), true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAccounts_RemoteKeymanager(t *testing.T) {
|
||||
walletDir, _ := setupWalletAndPasswordsDir(t)
|
||||
cliCtx := setupWalletCtx(t, &testWalletConfig{
|
||||
walletDir: walletDir,
|
||||
keymanagerKind: v2keymanager.Remote,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Remote)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
|
||||
rescueStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
os.Stdout = w
|
||||
|
||||
numAccounts := 3
|
||||
pubKeys := make([][48]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
key := make([]byte, 48)
|
||||
copy(key, strconv.Itoa(i))
|
||||
pubKeys[i] = bytesutil.ToBytes48(key)
|
||||
}
|
||||
km := &mockKeymanager{
|
||||
publicKeys: pubKeys,
|
||||
}
|
||||
// We call the list remote keymanager accounts function.
|
||||
cfg := &remote.Config{
|
||||
RemoteCertificate: &remote.CertificateConfig{
|
||||
ClientCertPath: "/tmp/client.crt",
|
||||
ClientKeyPath: "/tmp/client.key",
|
||||
CACertPath: "/tmp/ca.crt",
|
||||
},
|
||||
RemoteAddr: "localhost:4000",
|
||||
}
|
||||
require.NoError(t, listRemoteKeymanagerAccounts(wallet, km, cfg))
|
||||
|
||||
require.NoError(t, w.Close())
|
||||
out, err := ioutil.ReadAll(r)
|
||||
require.NoError(t, err)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
// Assert the keymanager kind is printed to stdout.
|
||||
stringOutput := string(out)
|
||||
if !strings.Contains(stringOutput, wallet.KeymanagerKind().String()) {
|
||||
t.Error("Did not find keymanager kind in output")
|
||||
}
|
||||
|
||||
// Assert the keymanager configuration is printed to stdout.
|
||||
if !strings.Contains(stringOutput, cfg.String()) {
|
||||
t.Error("Did not find remote config in output")
|
||||
}
|
||||
|
||||
// Assert the wallet accounts path is in stdout.
|
||||
if !strings.Contains(stringOutput, wallet.accountsPath) {
|
||||
t.Errorf("Did not find accounts path %s in output", wallet.accountsPath)
|
||||
}
|
||||
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
accountName := petnames.DeterministicName(pubKeys[i][:], "-")
|
||||
// Assert the account name is printed to stdout.
|
||||
if !strings.Contains(stringOutput, accountName) {
|
||||
t.Errorf("Did not find account %s in output", accountName)
|
||||
}
|
||||
key := pubKeys[i]
|
||||
|
||||
// Assert every public key is printed to stdout.
|
||||
if !strings.Contains(stringOutput, fmt.Sprintf("%#x", key)) {
|
||||
t.Errorf("Did not find pubkey %#x in output", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ func Test_validatePasswordInput(t *testing.T) {
|
||||
input: "aaaaaaa1$",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Unicode strings separated by a space character",
|
||||
input: "x*329293@aAJSD i22903saj",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -44,3 +49,39 @@ func Test_validatePasswordInput(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isValidUnicode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Regular alphanumeric",
|
||||
input: "Someone23xx",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Unicode strings separated by a space character",
|
||||
input: "x*329293@aAJSD i22903saj",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Japanese",
|
||||
input: "僕は絵お見るのが好きです",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Other foreign",
|
||||
input: "Etérium",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isValidUnicode(tt.input); got != tt.want {
|
||||
t.Errorf("isValidUnicode() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ func validatePasswordInput(input string) error {
|
||||
}
|
||||
for _, char := range input {
|
||||
switch {
|
||||
case !(unicode.IsLetter(char) || unicode.IsNumber(char) || unicode.IsPunct(char) || unicode.IsSymbol(char)):
|
||||
return errors.New("password must only contain alphanumeric characters, punctuation, or symbols")
|
||||
case unicode.IsLetter(char):
|
||||
hasLetter = true
|
||||
case unicode.IsNumber(char):
|
||||
@@ -195,7 +197,11 @@ func inputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.Config, error) {
|
||||
crt := cliCtx.String(flags.RemoteSignerCertPathFlag.Name)
|
||||
key := cliCtx.String(flags.RemoteSignerKeyPathFlag.Name)
|
||||
ca := cliCtx.String(flags.RemoteSignerCACertPathFlag.Name)
|
||||
|
||||
if addr != "" && crt != "" && key != "" && ca != "" {
|
||||
if !(isValidUnicode(addr) && isValidUnicode(crt) && isValidUnicode(key) && isValidUnicode(ca)) {
|
||||
return nil, errors.New("flag inputs contain non-unicode characters")
|
||||
}
|
||||
newCfg := &remote.Config{
|
||||
RemoteCertificate: &remote.CertificateConfig{
|
||||
ClientCertPath: strings.TrimRight(crt, "\r\n"),
|
||||
@@ -215,6 +221,9 @@ func inputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.Config, error) {
|
||||
if input == "" {
|
||||
return errors.New("remote host address cannot be empty")
|
||||
}
|
||||
if !isValidUnicode(input) {
|
||||
return errors.New("not valid unicode")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -240,7 +249,7 @@ func inputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.Config, error) {
|
||||
}
|
||||
prompt = promptui.Prompt{
|
||||
Label: "(Optional) Path to certificate authority (CA) crt (such as /path/to/ca.crt)",
|
||||
Validate: validateCertPath,
|
||||
Validate: validateCACertPath,
|
||||
}
|
||||
caCrtPath, err := prompt.Run()
|
||||
if err != nil {
|
||||
@@ -262,12 +271,25 @@ func validateCertPath(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("crt path cannot be empty")
|
||||
}
|
||||
if !isValidUnicode(input) {
|
||||
return errors.New("not valid unicode")
|
||||
}
|
||||
if !fileExists(input) {
|
||||
return fmt.Errorf("no crt found at path: %s", input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCACertPath(input string) error {
|
||||
if input != "" && !fileExists(input) {
|
||||
return fmt.Errorf("no crt found at path: %s", input)
|
||||
}
|
||||
if !isValidUnicode(input) {
|
||||
return errors.New("not valid unicode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatPromptError(err error) error {
|
||||
switch err {
|
||||
case promptui.ErrAbort:
|
||||
@@ -280,3 +302,18 @@ func formatPromptError(err error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if an input string is a valid unicode string comprised of only
|
||||
// letters, numbers, punctuation, or symbols.
|
||||
func isValidUnicode(input string) bool {
|
||||
for _, char := range input {
|
||||
if !(unicode.IsLetter(char) ||
|
||||
unicode.IsNumber(char) ||
|
||||
unicode.IsPunct(char) ||
|
||||
unicode.IsSymbol(char)) {
|
||||
log.Info(char)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
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/prysmaticlabs/prysm/validator/keymanager/v2/remote"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -60,6 +61,7 @@ func init() {
|
||||
// create and write a new wallet to disk for a Prysm validator.
|
||||
func NewWallet(
|
||||
cliCtx *cli.Context,
|
||||
keymanagerKind v2keymanager.Kind,
|
||||
) (*Wallet, error) {
|
||||
walletDir, err := inputDirectory(cliCtx, walletDirPromptText, flags.WalletDirFlag)
|
||||
if err != nil && !errors.Is(err, ErrNoWalletFound) {
|
||||
@@ -78,18 +80,18 @@ func NewWallet(
|
||||
"edit your wallet configuration by running ./prysm.sh validator wallet-v2 edit",
|
||||
)
|
||||
}
|
||||
keymanagerKind, err := inputKeymanagerKind(cliCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountsPath := filepath.Join(walletDir, keymanagerKind.String())
|
||||
if err := os.MkdirAll(accountsPath, DirectoryPermissions); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create wallet directory")
|
||||
}
|
||||
w := &Wallet{
|
||||
accountsPath: accountsPath,
|
||||
keymanagerKind: keymanagerKind,
|
||||
}
|
||||
if keymanagerKind == v2keymanager.Derived {
|
||||
walletPassword, err := inputPassword(cliCtx, newWalletPasswordPromptText, confirmPass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.walletPassword = walletPassword
|
||||
}
|
||||
if keymanagerKind == v2keymanager.Direct {
|
||||
passwordsDir, err := inputDirectory(cliCtx, passwordsDirPromptText, flags.WalletPasswordsDirFlag)
|
||||
if err != nil {
|
||||
@@ -140,6 +142,19 @@ func OpenWallet(cliCtx *cli.Context) (*Wallet, error) {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// SaveWallet persists the wallet's directories to disk.
|
||||
func (w *Wallet) SaveWallet() error {
|
||||
if err := os.MkdirAll(w.accountsPath, DirectoryPermissions); err != nil {
|
||||
return errors.Wrap(err, "could not create wallet directory")
|
||||
}
|
||||
if w.keymanagerKind == v2keymanager.Direct {
|
||||
if err := os.MkdirAll(w.passwordsDir, DirectoryPermissions); err != nil {
|
||||
return errors.Wrap(err, "could not create passwords directory")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeymanagerKind used by the wallet.
|
||||
func (w *Wallet) KeymanagerKind() v2keymanager.Kind {
|
||||
return w.keymanagerKind
|
||||
@@ -180,6 +195,15 @@ func (w *Wallet) InitializeKeymanager(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize derived keymanager")
|
||||
}
|
||||
case v2keymanager.Remote:
|
||||
cfg, err := remote.UnmarshalConfigFile(configFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
|
||||
}
|
||||
keymanager, err = remote.NewKeymanager(ctx, 100000000, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize remote keymanager")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("keymanager kind not supported: %s", w.keymanagerKind)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ 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 {
|
||||
w, err := NewWallet(cliCtx)
|
||||
keymanagerKind, err := inputKeymanagerKind(cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := NewWallet(cliCtx, keymanagerKind)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if wallet directory exists")
|
||||
}
|
||||
@@ -51,6 +55,9 @@ func CreateWallet(cliCtx *cli.Context) error {
|
||||
}
|
||||
|
||||
func createDirectKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
if err := wallet.SaveWallet(); err != nil {
|
||||
return errors.Wrap(err, "could not save wallet to disk")
|
||||
}
|
||||
keymanagerConfig, err := direct.MarshalConfigFile(context.Background(), direct.DefaultConfig())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal keymanager config file")
|
||||
@@ -64,11 +71,7 @@ func createDirectKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
func createDerivedKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
skipMnemonicConfirm := cliCtx.Bool(flags.SkipMnemonicConfirmFlag.Name)
|
||||
ctx := context.Background()
|
||||
walletPassword, err := inputPassword(cliCtx, newWalletPasswordPromptText, confirmPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seedConfig, err := derived.InitializeWalletSeedFile(ctx, walletPassword, skipMnemonicConfirm)
|
||||
seedConfig, err := derived.InitializeWalletSeedFile(ctx, wallet.walletPassword, skipMnemonicConfirm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
@@ -80,6 +83,9 @@ func createDerivedKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal keymanager config file")
|
||||
}
|
||||
if err := wallet.SaveWallet(); err != nil {
|
||||
return errors.Wrap(err, "could not save wallet to disk")
|
||||
}
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
return errors.Wrap(err, "could not write keymanager config to disk")
|
||||
}
|
||||
@@ -99,6 +105,9 @@ func createRemoteKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal config file")
|
||||
}
|
||||
if err := wallet.SaveWallet(); err != nil {
|
||||
return errors.Wrap(err, "could not save wallet to disk")
|
||||
}
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
return errors.Wrap(err, "could not write keymanager config to disk")
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ func TestEditWalletConfiguration(t *testing.T) {
|
||||
walletDir: walletDir,
|
||||
keymanagerKind: v2keymanager.Remote,
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Remote)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
|
||||
originalCfg := &remote.Config{
|
||||
|
||||
@@ -9,6 +9,7 @@ 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"
|
||||
)
|
||||
@@ -21,7 +22,7 @@ func RecoverWallet(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get mnemonic phrase")
|
||||
}
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Derived)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create new wallet")
|
||||
}
|
||||
@@ -38,6 +39,9 @@ func RecoverWallet(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal keymanager config file")
|
||||
}
|
||||
if err := wallet.SaveWallet(); err != nil {
|
||||
return errors.Wrap(err, "could not save wallet to disk")
|
||||
}
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
return errors.Wrap(err, "could not write keymanager config to disk")
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ func TestCreateAndReadWallet(t *testing.T) {
|
||||
passwordsDir: passwordsDir,
|
||||
keymanagerKind: v2keymanager.Direct,
|
||||
})
|
||||
_, err := NewWallet(cliCtx)
|
||||
wallet, err := NewWallet(cliCtx, v2keymanager.Direct)
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
require.NoError(t, err)
|
||||
// We should be able to now read the wallet as well.
|
||||
_, err = OpenWallet(cliCtx)
|
||||
|
||||
@@ -44,11 +44,13 @@ go_test(
|
||||
"//proto/validator/accounts/v2:go_default_library",
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/rand:go_default_library",
|
||||
"//shared/testutil:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"//validator/accounts/v2/testing:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
|
||||
@@ -207,7 +207,7 @@ func InitializeWalletSeedFile(ctx context.Context, password string, skipMnemonic
|
||||
// SeedFileFromMnemonic uses the provided mnemonic seed phrase to generate the
|
||||
// appropriate seed file for recovering a derived wallets.
|
||||
func SeedFileFromMnemonic(ctx context.Context, mnemonic string, password string) (*SeedConfig, error) {
|
||||
walletSeed, err := bip39.MnemonicToByteArray(mnemonic)
|
||||
walletSeed, err := bip39.EntropyFromMnemonic(mnemonic)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert mnemonic to wallet seed")
|
||||
}
|
||||
|
||||
@@ -9,18 +9,68 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/rand"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
mock "github.com/prysmaticlabs/prysm/validator/accounts/v2/testing"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestDerivedKeymanager_RecoverSeedRoundTrip(t *testing.T) {
|
||||
walletSeed := make([]byte, 32)
|
||||
n, err := rand.NewGenerator().Read(walletSeed)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, len(walletSeed))
|
||||
encryptor := keystorev4.New()
|
||||
password := "Passwz0rdz2020%"
|
||||
cryptoFields, err := encryptor.Encrypt(walletSeed, []byte(password))
|
||||
require.NoError(t, err)
|
||||
id, err := uuid.NewRandom()
|
||||
require.NoError(t, err)
|
||||
cfg := &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}
|
||||
|
||||
phrase, err := bip39.NewMnemonic(walletSeed)
|
||||
require.NoError(t, err)
|
||||
recoveredSeed, err := bip39.EntropyFromMnemonic(phrase)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure the recovered seed matches the old wallet seed.
|
||||
assert.DeepEqual(t, walletSeed, recoveredSeed)
|
||||
|
||||
cryptoFields, err = encryptor.Encrypt(recoveredSeed, []byte(password))
|
||||
require.NoError(t, err)
|
||||
newCfg := &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: cfg.ID,
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}
|
||||
|
||||
// Ensure we can decrypt the newly recovered config.
|
||||
decryptor := keystorev4.New()
|
||||
seed, err := decryptor.Decrypt(newCfg.Crypto, []byte(password))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Ensure the decrypted seed matches the old wallet seed and the new wallet seed.
|
||||
assert.DeepEqual(t, walletSeed, seed)
|
||||
assert.DeepEqual(t, recoveredSeed, seed)
|
||||
}
|
||||
|
||||
func TestDerivedKeymanager_CreateAccount(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
wallet := &mock.Wallet{
|
||||
|
||||
@@ -151,6 +151,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
|
||||
|
||||
===================================================================
|
||||
`, withdrawalKey.Marshal())
|
||||
fmt.Println(" ")
|
||||
|
||||
// Upon confirmation of the withdrawal key, proceed to display
|
||||
// and write associated deposit data to disk.
|
||||
|
||||
@@ -164,9 +164,9 @@ func (c *Config) String() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// CreateAccount based on the keymanager's logic. Returns the account name.
|
||||
func (k *Keymanager) CreateAccount(ctx context.Context, password string) (string, error) {
|
||||
return "", errors.New("a remote validator account cannot be created from the client")
|
||||
// Config for the remote keymanager.
|
||||
func (k *Keymanager) Config() *Config {
|
||||
return k.cfg
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of public keys that should be used to validate with.
|
||||
|
||||
Reference in New Issue
Block a user