mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
Refactor Wallet Password Handling, Preventing Wallet Corruption (#6933)
* wallet should no longer deal with account passwords * ensure tests are fixed * Merge branch 'master' into corrupted-pass * move mnemonic logic into right place * rem fmts * add fileutil * gazelle * imports * move seed logic to derived * fix tests * imports * gaz * Merge refs/heads/master into corrupted-pass * merge confs * Merge refs/heads/master into corrupted-pass * ivan's feedback * Merge branch 'corrupted-pass' of github.com:prysmaticlabs/prysm into corrupted-pass * gaz * fix shared test * Merge refs/heads/master into corrupted-pass * resolve conflicts * fix test build
This commit is contained in:
@@ -5,7 +5,6 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"customflags.go",
|
||||
"defaults.go",
|
||||
"flags.go",
|
||||
"helpers.go",
|
||||
@@ -16,6 +15,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/shared/cmd",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"@com_github_golang_mock//gomock:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
@@ -31,7 +31,6 @@ go_test(
|
||||
size = "small",
|
||||
srcs = [
|
||||
"config_test.go",
|
||||
"customflags_test.go",
|
||||
"helpers_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Expands a file path
|
||||
// 1. replace tilde with users home dir
|
||||
// 2. expands embedded environment variables
|
||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||
func expandPath(p string) string {
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
if home := homeDir(); home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return path.Clean(os.ExpandEnv(p))
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -19,13 +19,15 @@ package cmd
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
)
|
||||
|
||||
// DefaultDataDir is the default data directory to use for the databases and other
|
||||
// persistence requirements.
|
||||
func DefaultDataDir() string {
|
||||
// Try to place the data folder in the user's home dir
|
||||
home := homeDir()
|
||||
home := fileutil.HomeDir()
|
||||
if home != "" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return filepath.Join(home, "Library", "Eth2")
|
||||
|
||||
20
shared/fileutil/BUILD.bazel
Normal file
20
shared/fileutil/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["fileutil.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/shared/fileutil",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_pkg_errors//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["fileutil_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
],
|
||||
)
|
||||
76
shared/fileutil/fileutil.go
Normal file
76
shared/fileutil/fileutil.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ExpandPath given a string which may be a relative path.
|
||||
// 1. replace tilde with users home dir
|
||||
// 2. expands embedded environment variables
|
||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||
func ExpandPath(p string) (string, error) {
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
if home := HomeDir(); home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return filepath.Abs(path.Clean(os.ExpandEnv(p)))
|
||||
}
|
||||
|
||||
// HomeDir for a user.
|
||||
func HomeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HasDir checks if a directory indeed exists at the specified path.
|
||||
func HasDir(dirPath string) (bool, error) {
|
||||
fullPath, err := ExpandPath(dirPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
info, err := os.Stat(fullPath)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if info == nil {
|
||||
return false, err
|
||||
}
|
||||
return info.IsDir(), err
|
||||
}
|
||||
|
||||
// FileExists returns true if a file is not a directory and exists
|
||||
// at the specified path.
|
||||
func FileExists(filename string) bool {
|
||||
filePath, err := ExpandPath(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
info, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// ReadFileAsBytes expands a file name's absolute path and reads it as bytes from disk.
|
||||
func ReadFileAsBytes(filename string) ([]byte, error) {
|
||||
filePath, err := ExpandPath(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not determine absolute path of password file")
|
||||
}
|
||||
return ioutil.ReadFile(filePath)
|
||||
}
|
||||
@@ -13,8 +13,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
)
|
||||
|
||||
func TestPathExpansion(t *testing.T) {
|
||||
@@ -32,7 +32,6 @@ func TestPathExpansion(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"/home/someuser/tmp": "/home/someuser/tmp",
|
||||
"~/tmp": user.HomeDir + "/tmp",
|
||||
"~thisOtherUser/b/": "~thisOtherUser/b",
|
||||
"$DDDXXX/a/b": "/tmp/a/b",
|
||||
"/a/b/": "/a/b",
|
||||
}
|
||||
@@ -41,6 +40,8 @@ func TestPathExpansion(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
for test, expected := range tests {
|
||||
assert.Equal(t, expected, expandPath(test))
|
||||
expanded, err := ExpandPath(test)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, expanded)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -20,6 +18,7 @@ import (
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
@@ -92,7 +91,7 @@ func decrypt(cliCtx *cli.Context) error {
|
||||
if keystorePath == "" {
|
||||
return errors.New("--keystore must be set")
|
||||
}
|
||||
fullPath, err := expandPath(keystorePath)
|
||||
fullPath, err := fileutil.ExpandPath(keystorePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not expand path: %s", keystorePath)
|
||||
}
|
||||
@@ -104,7 +103,7 @@ func decrypt(cliCtx *cli.Context) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
isDir, err := hasDir(fullPath)
|
||||
isDir, err := fileutil.HasDir(fullPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not check if path exists: %s", fullPath)
|
||||
}
|
||||
@@ -149,15 +148,11 @@ func encrypt(cliCtx *cli.Context) error {
|
||||
if outputPath == "" {
|
||||
return errors.New("--output-path must be set")
|
||||
}
|
||||
fullPath, err := expandPath(outputPath)
|
||||
fullPath, err := fileutil.ExpandPath(outputPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not expand path: %s", outputPath)
|
||||
}
|
||||
exists, err := fileExists(fullPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not check if file exists: %s", fullPath)
|
||||
}
|
||||
if exists {
|
||||
if fileutil.FileExists(fullPath) {
|
||||
response, err := promptutil.ValidatePrompt(
|
||||
fmt.Sprintf("file at path %s already exists, are you sure you want to overwrite it? [y/n]", fullPath),
|
||||
func(s string) error {
|
||||
@@ -263,58 +258,3 @@ func readAndDecryptKeystore(fullPath string, password string) error {
|
||||
fmt.Printf("Pubkey: %#x\n", au.BrightGreen(pubKeyBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the item at the specified path exists and is a directory.
|
||||
func hasDir(dirPath string) (bool, error) {
|
||||
fullPath, err := expandPath(dirPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return info.IsDir(), nil
|
||||
}
|
||||
|
||||
// Check if a file at the specified path exists.
|
||||
func fileExists(filePath string) (bool, error) {
|
||||
fullPath, err := expandPath(filePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Expands a file path
|
||||
// 1. replace tilde with users home dir
|
||||
// 2. expands embedded environment variables
|
||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||
func expandPath(p string) (string, error) {
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
if home := homeDir(); home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return filepath.Abs(path.Clean(os.ExpandEnv(p)))
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ go_library(
|
||||
"//contracts/deposit-contract:go_default_library",
|
||||
"//shared/cmd:go_default_library",
|
||||
"//shared/depositutil:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/keystore:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//validator/db/kv:go_default_library",
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
contract "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
|
||||
"github.com/prysmaticlabs/prysm/shared/cmd"
|
||||
"github.com/prysmaticlabs/prysm/shared/depositutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/keystore"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/validator/db/kv"
|
||||
@@ -190,7 +190,7 @@ func PrintPublicAndPrivateKeys(path string, passphrase string) error {
|
||||
// DefaultValidatorDir returns OS-specific default keystore directory.
|
||||
func DefaultValidatorDir() string {
|
||||
// Try to place the data folder in the user's home dir
|
||||
home := homeDir()
|
||||
home := fileutil.HomeDir()
|
||||
if home != "" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return filepath.Join(home, "Library", "Eth2Validators")
|
||||
@@ -336,17 +336,6 @@ func changePasswordForKeyType(keystorePath string, filePrefix string, oldPasswor
|
||||
return nil
|
||||
}
|
||||
|
||||
// homeDir returns home directory path.
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractPublicKeysFromKeyStore extracts only the public keys from the decrypted keys from the keystore.
|
||||
func ExtractPublicKeysFromKeyStore(keystorePath string, passphrase string) ([][]byte, error) {
|
||||
decryptedKeys, err := DecryptKeysFromKeystore(keystorePath, params.BeaconConfig().ValidatorPrivkeyFileName, passphrase)
|
||||
|
||||
@@ -23,8 +23,8 @@ go_library(
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/featureconfig:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
@@ -34,16 +34,13 @@ go_library(
|
||||
"//validator/keymanager/v2/direct:go_default_library",
|
||||
"//validator/keymanager/v2/remote:go_default_library",
|
||||
"@com_github_gofrs_flock//:go_default_library",
|
||||
"@com_github_k0kubun_go_ansi//:go_default_library",
|
||||
"@com_github_logrusorgru_aurora//:go_default_library",
|
||||
"@com_github_manifoldco_promptui//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_schollz_progressbar_v3//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//wordlists:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -53,7 +50,6 @@ go_test(
|
||||
"accounts_create_test.go",
|
||||
"accounts_import_test.go",
|
||||
"accounts_list_test.go",
|
||||
"consts_test.go",
|
||||
"wallet_create_test.go",
|
||||
"wallet_edit_test.go",
|
||||
"wallet_recover_test.go",
|
||||
|
||||
@@ -26,7 +26,7 @@ func CreateAccount(cliCtx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
skipMnemonicConfirm := cliCtx.Bool(flags.SkipMnemonicConfirmFlag.Name)
|
||||
keymanager, err := wallet.InitializeKeymanager(ctx, skipMnemonicConfirm)
|
||||
keymanager, err := wallet.InitializeKeymanager(cliCtx, skipMnemonicConfirm)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return errors.New("wrong wallet password entered")
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestCreateAccount_Derived(t *testing.T) {
|
||||
|
||||
require.NoError(t, CreateAccount(cliCtx))
|
||||
|
||||
keymanager, err := wallet.InitializeKeymanager(ctx, true)
|
||||
keymanager, err := wallet.InitializeKeymanager(cliCtx, true)
|
||||
require.NoError(t, err)
|
||||
km, ok := keymanager.(*derived.Keymanager)
|
||||
if !ok {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -94,7 +95,7 @@ func (w *Wallet) zipAccounts(accounts []string, targetPath string) error {
|
||||
if err := os.MkdirAll(targetPath, params.BeaconIoConfig().ReadWriteExecutePermissions); err != nil {
|
||||
return errors.Wrap(err, "could not create target folder")
|
||||
}
|
||||
if fileExists(archivePath) {
|
||||
if fileutil.FileExists(archivePath) {
|
||||
return errors.Errorf("Zip file already exists in directory: %s", archivePath)
|
||||
}
|
||||
zipfile, err := os.Create(archivePath)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
@@ -91,7 +92,7 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
km, err := direct.NewKeymanager(ctx, wallet, directCfg)
|
||||
km, err := direct.NewKeymanager(cliCtx, wallet, directCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,10 +100,7 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse keys directory")
|
||||
}
|
||||
if err := wallet.SaveWallet(); err != nil {
|
||||
return errors.Wrap(err, "could not save wallet")
|
||||
}
|
||||
isDir, err := hasDir(keysDir)
|
||||
isDir, err := fileutil.HasDir(keysDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not determine if path is a directory")
|
||||
}
|
||||
@@ -131,14 +129,14 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
// specify this value in their filename.
|
||||
sort.Sort(byDerivationPath(keystoreFileNames))
|
||||
for _, name := range keystoreFileNames {
|
||||
keystore, err := wallet.readKeystoreFile(ctx, filepath.Join(keysDir, name))
|
||||
keystore, err := readKeystoreFile(ctx, filepath.Join(keysDir, name))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not import keystore at path: %s", name)
|
||||
}
|
||||
keystoresImported = append(keystoresImported, keystore)
|
||||
}
|
||||
} else {
|
||||
keystore, err := wallet.readKeystoreFile(ctx, keysDir)
|
||||
keystore, err := readKeystoreFile(ctx, keysDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not import keystore")
|
||||
}
|
||||
@@ -156,7 +154,7 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) readKeystoreFile(ctx context.Context, keystoreFilePath string) (*v2keymanager.Keystore, error) {
|
||||
func readKeystoreFile(ctx context.Context, keystoreFilePath string) (*v2keymanager.Keystore, error) {
|
||||
keystoreBytes, err := ioutil.ReadFile(keystoreFilePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read keystore file")
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestImport_Noninteractive(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg))
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
cliCtx,
|
||||
wallet,
|
||||
direct.DefaultConfig(),
|
||||
)
|
||||
@@ -70,7 +70,7 @@ func TestImport_Noninteractive(t *testing.T) {
|
||||
|
||||
wallet, err = OpenWallet(cliCtx)
|
||||
require.NoError(t, err)
|
||||
km, err := wallet.InitializeKeymanager(ctx, true)
|
||||
km, err := wallet.InitializeKeymanager(cliCtx, true)
|
||||
require.NoError(t, err)
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -105,7 +105,7 @@ func TestImport_Noninteractive_Filepath(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg))
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
cliCtx,
|
||||
wallet,
|
||||
keymanagerCfg,
|
||||
)
|
||||
@@ -120,7 +120,7 @@ func TestImport_Noninteractive_Filepath(t *testing.T) {
|
||||
|
||||
wallet, err = OpenWallet(cliCtx)
|
||||
require.NoError(t, err)
|
||||
km, err := wallet.InitializeKeymanager(ctx, true)
|
||||
km, err := wallet.InitializeKeymanager(cliCtx, true)
|
||||
require.NoError(t, err)
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -20,14 +20,13 @@ import (
|
||||
// ListAccounts displays all available validator accounts in a Prysm wallet.
|
||||
func ListAccounts(cliCtx *cli.Context) error {
|
||||
// Read the wallet from the specified path.
|
||||
ctx := context.Background()
|
||||
wallet, err := OpenWallet(cliCtx)
|
||||
if errors.Is(err, ErrNoWalletFound) {
|
||||
return errors.Wrap(err, "no wallet found at path, create a new wallet with wallet-v2 create")
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "could not open wallet")
|
||||
}
|
||||
keymanager, err := wallet.InitializeKeymanager(ctx, true /* skip mnemonic confirm */)
|
||||
keymanager, err := wallet.InitializeKeymanager(cliCtx, true /* skip mnemonic confirm */)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return errors.New("wrong wallet password entered")
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
cliCtx,
|
||||
wallet,
|
||||
direct.DefaultConfig(),
|
||||
)
|
||||
@@ -108,20 +108,11 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
|
||||
require.NoError(t, wallet.SaveWallet())
|
||||
ctx := context.Background()
|
||||
|
||||
seedConfig, err := derived.InitializeWalletSeedFile(ctx, password, true /* skip confirm */)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a new wallet seed file and write it to disk.
|
||||
seedConfigFile, err := derived.MarshalEncryptedSeedFile(ctx, seedConfig)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.WriteFileAtPath(ctx, "", derived.EncryptedSeedFileName, seedConfigFile))
|
||||
|
||||
keymanager, err := derived.NewKeymanager(
|
||||
ctx,
|
||||
cliCtx,
|
||||
wallet,
|
||||
derived.DefaultConfig(),
|
||||
true, /* skip confirm */
|
||||
password,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package v2
|
||||
|
||||
const (
|
||||
walletDirName = "wallet"
|
||||
passwordDirName = "walletpasswords"
|
||||
exportDirName = "export"
|
||||
importDirName = "import"
|
||||
importPasswordDirName = "importpasswords"
|
||||
passwordFileName = "password.txt"
|
||||
password = "OhWOWthisisatest42!$"
|
||||
mnemonicFileName = "mnemonic.txt"
|
||||
mnemonic = "garage car helmet trade salmon embrace market giant movie wet same champion dawn chair shield drill amazing panther accident puzzle garden mosquito kind arena"
|
||||
)
|
||||
@@ -12,7 +12,6 @@ type Wallet interface {
|
||||
// Methods to retrieve wallet and accounts metadata.
|
||||
AccountsDir() string
|
||||
ListDirs() ([]string, error)
|
||||
Password() string
|
||||
// Read methods for important wallet and accounts-related files.
|
||||
ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error)
|
||||
ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error)
|
||||
|
||||
@@ -3,15 +3,12 @@ package v2
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/remote"
|
||||
@@ -19,23 +16,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
importKeysDirPromptText = "Enter the directory or filepath where your keystores to import are located"
|
||||
exportDirPromptText = "Enter a file location to write the exported account(s) to"
|
||||
walletDirPromptText = "Enter a wallet directory"
|
||||
newWalletPasswordPromptText = "New wallet password"
|
||||
confirmPasswordPromptText = "Confirm password"
|
||||
walletPasswordPromptText = "Wallet password"
|
||||
newAccountPasswordPromptText = "New account password"
|
||||
passwordForAccountPromptText = "Enter password for account with public key %#x"
|
||||
)
|
||||
|
||||
type passwordConfirm int
|
||||
|
||||
const (
|
||||
// An enum to indicate to the prompt that confirming the password is not needed.
|
||||
noConfirmPass passwordConfirm = iota
|
||||
// An enum to indicate to the prompt to confirm the password entered.
|
||||
confirmPass
|
||||
importKeysDirPromptText = "Enter the directory or filepath where your keystores to import are located"
|
||||
walletDirPromptText = "Enter a wallet directory"
|
||||
)
|
||||
|
||||
var au = aurora.NewAurora(true)
|
||||
@@ -43,11 +25,11 @@ var au = aurora.NewAurora(true)
|
||||
func inputDirectory(cliCtx *cli.Context, promptText string, flag *cli.StringFlag) (string, error) {
|
||||
directory := cliCtx.String(flag.Name)
|
||||
if cliCtx.IsSet(flag.Name) {
|
||||
return expandPath(directory)
|
||||
return fileutil.ExpandPath(directory)
|
||||
}
|
||||
// Append and log the appropriate directory name depending on the flag used.
|
||||
if flag.Name == flags.WalletDirFlag.Name {
|
||||
ok, err := hasDir(directory)
|
||||
ok, err := fileutil.HasDir(directory)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not check if wallet dir %s exists", directory)
|
||||
}
|
||||
@@ -64,62 +46,13 @@ func inputDirectory(cliCtx *cli.Context, promptText string, flag *cli.StringFlag
|
||||
if inputtedDir == directory {
|
||||
return directory, nil
|
||||
}
|
||||
return expandPath(inputtedDir)
|
||||
}
|
||||
|
||||
func inputPassword(
|
||||
cliCtx *cli.Context,
|
||||
passwordFileFlag *cli.StringFlag,
|
||||
promptText string,
|
||||
confirmPassword passwordConfirm,
|
||||
passwordValidator func(input string) error,
|
||||
) (string, error) {
|
||||
if cliCtx.IsSet(passwordFileFlag.Name) {
|
||||
passwordFilePathInput := cliCtx.String(passwordFileFlag.Name)
|
||||
passwordFilePath, err := expandPath(passwordFilePathInput)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not determine absolute path of password file")
|
||||
}
|
||||
data, err := ioutil.ReadFile(passwordFilePath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not read password file")
|
||||
}
|
||||
enteredPassword := strings.TrimRight(string(data), "\r\n")
|
||||
if err := passwordValidator(enteredPassword); err != nil {
|
||||
return "", errors.Wrap(err, "password did not pass validation")
|
||||
}
|
||||
return enteredPassword, nil
|
||||
}
|
||||
var hasValidPassword bool
|
||||
var walletPassword string
|
||||
var err error
|
||||
for !hasValidPassword {
|
||||
walletPassword, err = promptutil.PasswordPrompt(promptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read account password: %v", err)
|
||||
}
|
||||
|
||||
if confirmPassword == confirmPass {
|
||||
passwordConfirmation, err := promptutil.PasswordPrompt(confirmPasswordPromptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read password confirmation: %v", err)
|
||||
}
|
||||
if walletPassword != passwordConfirmation {
|
||||
log.Error("Passwords do not match")
|
||||
continue
|
||||
}
|
||||
hasValidPassword = true
|
||||
} else {
|
||||
return walletPassword, nil
|
||||
}
|
||||
}
|
||||
return walletPassword, nil
|
||||
return fileutil.ExpandPath(inputtedDir)
|
||||
}
|
||||
|
||||
func inputWeakPassword(cliCtx *cli.Context, passwordFileFlag *cli.StringFlag, promptText string) (string, error) {
|
||||
if cliCtx.IsSet(passwordFileFlag.Name) {
|
||||
passwordFilePathInput := cliCtx.String(passwordFileFlag.Name)
|
||||
passwordFilePath, err := expandPath(passwordFilePathInput)
|
||||
passwordFilePath, err := fileutil.ExpandPath(passwordFilePathInput)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not determine absolute path of password file")
|
||||
}
|
||||
@@ -167,15 +100,15 @@ func inputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
crtPath, err := expandPath(strings.TrimRight(crt, "\r\n"))
|
||||
crtPath, err := fileutil.ExpandPath(strings.TrimRight(crt, "\r\n"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
|
||||
}
|
||||
keyPath, err := expandPath(strings.TrimRight(key, "\r\n"))
|
||||
keyPath, err := fileutil.ExpandPath(strings.TrimRight(key, "\r\n"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
|
||||
}
|
||||
caPath, err := expandPath(strings.TrimRight(ca, "\r\n"))
|
||||
caPath, err := fileutil.ExpandPath(strings.TrimRight(ca, "\r\n"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
|
||||
}
|
||||
@@ -198,7 +131,7 @@ func validateCertPath(input string) error {
|
||||
if !promptutil.IsValidUnicode(input) {
|
||||
return errors.New("not valid unicode")
|
||||
}
|
||||
if !fileExists(input) {
|
||||
if !fileutil.FileExists(input) {
|
||||
return fmt.Errorf("no crt found at path: %s", input)
|
||||
}
|
||||
return nil
|
||||
@@ -216,27 +149,3 @@ func formatPromptError(err error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Expands a file path
|
||||
// 1. replace tilde with users home dir
|
||||
// 2. expands embedded environment variables
|
||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||
func expandPath(p string) (string, error) {
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
if home := homeDir(); home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return filepath.Abs(path.Clean(os.ExpandEnv(p)))
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ type Wallet struct {
|
||||
EncryptedSeedFile []byte
|
||||
AccountPasswords map[string]string
|
||||
UnlockAccounts bool
|
||||
WalletPassword string
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -38,11 +37,6 @@ func (m *Wallet) AccountsDir() string {
|
||||
return m.InnerAccountsDir
|
||||
}
|
||||
|
||||
// Password --
|
||||
func (m *Wallet) Password() string {
|
||||
return m.WalletPassword
|
||||
}
|
||||
|
||||
// ListDirs --
|
||||
func (m *Wallet) ListDirs() ([]string, error) {
|
||||
return m.Directories, nil
|
||||
|
||||
@@ -2,32 +2,23 @@ package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/k0kubun/go-ansi"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"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/schollz/progressbar/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -63,7 +54,6 @@ type Wallet struct {
|
||||
configFilePath string
|
||||
walletFileLock *flock.Flock
|
||||
keymanagerKind v2keymanager.Kind
|
||||
walletPassword string
|
||||
}
|
||||
|
||||
// NewWallet given a set of configuration options, will leverage
|
||||
@@ -76,7 +66,7 @@ func NewWallet(
|
||||
// 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)
|
||||
walletExists, err := fileutil.HasDir(walletDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if wallet exists")
|
||||
}
|
||||
@@ -90,25 +80,11 @@ func NewWallet(
|
||||
}
|
||||
}
|
||||
accountsPath := filepath.Join(walletDir, keymanagerKind.String())
|
||||
w := &Wallet{
|
||||
return &Wallet{
|
||||
accountsPath: accountsPath,
|
||||
keymanagerKind: keymanagerKind,
|
||||
walletDir: walletDir,
|
||||
}
|
||||
if keymanagerKind == v2keymanager.Derived || keymanagerKind == v2keymanager.Direct {
|
||||
walletPassword, err := inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
newWalletPasswordPromptText,
|
||||
confirmPass,
|
||||
promptutil.ValidatePasswordInput,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get password")
|
||||
}
|
||||
w.walletPassword = walletPassword
|
||||
}
|
||||
return w, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpenWallet instantiates a wallet from a specified path. It checks the
|
||||
@@ -120,7 +96,7 @@ func OpenWallet(cliCtx *cli.Context) (*Wallet, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok, err := hasDir(walletDir)
|
||||
ok, err := fileutil.HasDir(walletDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse wallet directory")
|
||||
}
|
||||
@@ -140,71 +116,12 @@ func OpenWallet(cliCtx *cli.Context) (*Wallet, error) {
|
||||
return nil, errors.Wrap(err, "could not read keymanager kind for wallet")
|
||||
}
|
||||
walletPath := filepath.Join(walletDir, keymanagerKind.String())
|
||||
w := &Wallet{
|
||||
log.Infof("%s %s", au.BrightMagenta("(wallet directory)"), walletDir)
|
||||
return &Wallet{
|
||||
walletDir: walletDir,
|
||||
accountsPath: walletPath,
|
||||
keymanagerKind: keymanagerKind,
|
||||
}
|
||||
// Check if the wallet is using the new, fast keystore format.
|
||||
hasNewFormat, err := hasDir(filepath.Join(walletPath, direct.AccountsPath))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read wallet dir")
|
||||
}
|
||||
log.Infof("%s %s", au.BrightMagenta("(wallet directory)"), w.walletDir)
|
||||
if keymanagerKind == v2keymanager.Derived {
|
||||
validateExistingPass := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("password input cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
walletPassword, err := inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
walletPasswordPromptText,
|
||||
noConfirmPass,
|
||||
validateExistingPass,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.walletPassword = walletPassword
|
||||
}
|
||||
if keymanagerKind == v2keymanager.Direct {
|
||||
var walletPassword string
|
||||
if hasNewFormat {
|
||||
validateExistingPass := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("password input cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
walletPassword, err = inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
walletPasswordPromptText,
|
||||
noConfirmPass,
|
||||
validateExistingPass,
|
||||
)
|
||||
} else {
|
||||
fmt.Println("\nWe have revamped how imported accounts work, improving speed significantly for your " +
|
||||
"validators as well as reducing memory and CPU requirements. This unifies all your existing accounts " +
|
||||
"into a single format protected by a strong password. You'll need to set a new password for this " +
|
||||
"updated wallet format")
|
||||
walletPassword, err = inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
newWalletPasswordPromptText,
|
||||
confirmPass,
|
||||
promptutil.ValidatePasswordInput,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.walletPassword = walletPassword
|
||||
}
|
||||
return w, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SaveWallet persists the wallet's directories to disk.
|
||||
@@ -225,17 +142,13 @@ func (w *Wallet) AccountsDir() string {
|
||||
return w.accountsPath
|
||||
}
|
||||
|
||||
// Password for the wallet.
|
||||
func (w *Wallet) Password() string {
|
||||
return w.walletPassword
|
||||
}
|
||||
|
||||
// InitializeKeymanager reads a keymanager config from disk at the wallet path,
|
||||
// unmarshals it based on the wallet's keymanager kind, and returns its value.
|
||||
func (w *Wallet) InitializeKeymanager(
|
||||
ctx context.Context,
|
||||
cliCtx *cli.Context,
|
||||
skipMnemonicConfirm bool,
|
||||
) (v2keymanager.IKeymanager, error) {
|
||||
ctx := context.Background()
|
||||
configFile, err := w.ReadKeymanagerConfigFromDisk(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read keymanager config")
|
||||
@@ -247,7 +160,7 @@ func (w *Wallet) InitializeKeymanager(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
|
||||
}
|
||||
keymanager, err = direct.NewKeymanager(ctx, w, cfg)
|
||||
keymanager, err = direct.NewKeymanager(cliCtx, w, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize direct keymanager")
|
||||
}
|
||||
@@ -256,7 +169,7 @@ func (w *Wallet) InitializeKeymanager(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
|
||||
}
|
||||
keymanager, err = derived.NewKeymanager(ctx, w, cfg, skipMnemonicConfirm, w.walletPassword)
|
||||
keymanager, err = derived.NewKeymanager(cliCtx, w, cfg, skipMnemonicConfirm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize derived keymanager")
|
||||
}
|
||||
@@ -265,7 +178,7 @@ func (w *Wallet) InitializeKeymanager(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
|
||||
}
|
||||
keymanager, err = remote.NewKeymanager(ctx, 100000000, cfg)
|
||||
keymanager, err = remote.NewKeymanager(cliCtx, 100000000, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize remote keymanager")
|
||||
}
|
||||
@@ -295,7 +208,7 @@ func (w *Wallet) ListDirs() ([]string, error) {
|
||||
}
|
||||
dirNames := make([]string, 0)
|
||||
for _, item := range list {
|
||||
ok, err := hasDir(filepath.Join(w.AccountsDir(), item))
|
||||
ok, err := fileutil.HasDir(filepath.Join(w.AccountsDir(), item))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse directory: %v", err)
|
||||
}
|
||||
@@ -363,27 +276,11 @@ func (w *Wallet) FileNameAtPath(ctx context.Context, filePath string, fileName s
|
||||
return fullFileName, nil
|
||||
}
|
||||
|
||||
// AccountTimestamp retrieves the timestamp from a given keystore file name.
|
||||
func AccountTimestamp(fileName string) (time.Time, error) {
|
||||
timestampStart := strings.LastIndex(fileName, "-") + 1
|
||||
timestampEnd := strings.LastIndex(fileName, ".")
|
||||
// Return an error if the text we expect cannot be found.
|
||||
if timestampStart == -1 || timestampEnd == -1 {
|
||||
return time.Unix(0, 0), fmt.Errorf("could not find timestamp in file name %s", fileName)
|
||||
}
|
||||
unixTimestampStr, err := strconv.ParseInt(fileName[timestampStart:timestampEnd], 10, 64)
|
||||
if err != nil {
|
||||
return time.Unix(0, 0), errors.Wrapf(err, "could not parse account created at timestamp: %s", fileName)
|
||||
}
|
||||
unixTimestamp := time.Unix(unixTimestampStr, 0)
|
||||
return unixTimestamp, nil
|
||||
}
|
||||
|
||||
// ReadKeymanagerConfigFromDisk opens a keymanager config file
|
||||
// for reading if it exists at the wallet path.
|
||||
func (w *Wallet) ReadKeymanagerConfigFromDisk(ctx context.Context) (io.ReadCloser, error) {
|
||||
configFilePath := filepath.Join(w.accountsPath, KeymanagerConfigFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
if !fileutil.FileExists(configFilePath) {
|
||||
return nil, fmt.Errorf("no keymanager config file found at path: %s", w.accountsPath)
|
||||
}
|
||||
w.configFilePath = configFilePath
|
||||
@@ -431,7 +328,7 @@ func (w *Wallet) WriteKeymanagerConfigToDisk(ctx context.Context, encoded []byte
|
||||
// within the wallet path.
|
||||
func (w *Wallet) ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error) {
|
||||
configFilePath := filepath.Join(w.accountsPath, derived.EncryptedSeedFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
if !fileutil.FileExists(configFilePath) {
|
||||
return nil, fmt.Errorf("no encrypted seed file found at path: %s", w.accountsPath)
|
||||
}
|
||||
return os.Open(configFilePath)
|
||||
@@ -449,168 +346,6 @@ func (w *Wallet) WriteEncryptedSeedToDisk(ctx context.Context, encoded []byte) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// enterPasswordForAccount checks if a user has a password specified for the new account
|
||||
// either from a file or from stdin. Then, it saves the password to the wallet.
|
||||
func (w *Wallet) enterPasswordForAccount(cliCtx *cli.Context, accountName string, pubKey []byte) error {
|
||||
au := aurora.NewAurora(true)
|
||||
var password string
|
||||
var err error
|
||||
if cliCtx.IsSet(flags.AccountPasswordFileFlag.Name) {
|
||||
passwordFilePath := cliCtx.String(flags.AccountPasswordFileFlag.Name)
|
||||
data, err := ioutil.ReadFile(passwordFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password = string(data)
|
||||
err = w.checkPasswordForAccount(accountName, password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return fmt.Errorf("invalid password entered for account with public key %#x", pubKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
pubKeyStr := fmt.Sprintf("%#x", bytesutil.Trunc(pubKey))
|
||||
attemptingPassword := true
|
||||
// Loop asking for the password until the user enters it correctly.
|
||||
for attemptingPassword {
|
||||
// Ask the user for the password to their account.
|
||||
password, err = inputWeakPassword(
|
||||
cliCtx,
|
||||
flags.AccountPasswordFileFlag,
|
||||
fmt.Sprintf(passwordForAccountPromptText, au.BrightGreen(pubKeyStr)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not input password")
|
||||
}
|
||||
err = w.checkPasswordForAccount(accountName, password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
fmt.Print(au.Red("X").Bold())
|
||||
fmt.Print(au.Red("\nIncorrect password entered, please try again"))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attemptingPassword = false
|
||||
fmt.Print(au.Green("✔️\n").Bold())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) enterPasswordForAllAccounts(cliCtx *cli.Context, accountNames []string, pubKeys [][]byte) error {
|
||||
au := aurora.NewAurora(true)
|
||||
var password string
|
||||
var err error
|
||||
if cliCtx.IsSet(flags.AccountPasswordFileFlag.Name) {
|
||||
passwordFilePath := cliCtx.String(flags.AccountPasswordFileFlag.Name)
|
||||
data, err := ioutil.ReadFile(passwordFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password = string(data)
|
||||
for i := 0; i < len(accountNames); i++ {
|
||||
err = w.checkPasswordForAccount(accountNames[i], password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return fmt.Errorf("invalid password for account with public key %#x", pubKeys[i])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
password, err = inputWeakPassword(
|
||||
cliCtx,
|
||||
flags.AccountPasswordFileFlag,
|
||||
"Enter the password for your imported accounts",
|
||||
)
|
||||
fmt.Println("Importing accounts, this may take a while...")
|
||||
bar := progressbar.NewOptions(
|
||||
len(accountNames),
|
||||
progressbar.OptionFullWidth(),
|
||||
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
|
||||
progressbar.OptionEnableColorCodes(true),
|
||||
progressbar.OptionSetTheme(progressbar.Theme{
|
||||
Saucer: "[green]=[reset]",
|
||||
SaucerHead: "[green]>[reset]",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "[",
|
||||
BarEnd: "]",
|
||||
}),
|
||||
progressbar.OptionOnCompletion(func() { fmt.Println() }),
|
||||
progressbar.OptionSetDescription("Importing accounts"),
|
||||
)
|
||||
for i := 0; i < len(accountNames); i++ {
|
||||
// We check if the individual account unlocks with the global password.
|
||||
err = w.checkPasswordForAccount(accountNames[i], password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
// If the password fails for an individual account, we ask the user to input
|
||||
// that individual account's password until it succeeds.
|
||||
_, err := w.askUntilPasswordConfirms(cliCtx, accountNames[i], pubKeys[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bar.Add(1); err != nil {
|
||||
return errors.Wrap(err, "could not add to progress bar")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Finished importing %#x\n", au.BrightMagenta(bytesutil.Trunc(pubKeys[i])))
|
||||
if err := bar.Add(1); err != nil {
|
||||
return errors.Wrap(err, "could not add to progress bar")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) askUntilPasswordConfirms(cliCtx *cli.Context, accountName string, pubKey []byte) (string, error) {
|
||||
// Loop asking for the password until the user enters it correctly.
|
||||
var password string
|
||||
var err error
|
||||
for {
|
||||
password, err = inputWeakPassword(
|
||||
cliCtx,
|
||||
flags.AccountPasswordFileFlag,
|
||||
fmt.Sprintf(passwordForAccountPromptText, bytesutil.Trunc(pubKey)),
|
||||
)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not input password")
|
||||
}
|
||||
err = w.checkPasswordForAccount(accountName, password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
fmt.Println(au.Red("Incorrect password entered, please try again"))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
break
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (w *Wallet) checkPasswordForAccount(accountName string, password string) error {
|
||||
encoded, err := w.ReadFileAtPath(context.Background(), accountName, direct.KeystoreFileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read keystore file")
|
||||
}
|
||||
keystoreJSON := &v2keymanager.Keystore{}
|
||||
if err := json.Unmarshal(encoded, &keystoreJSON); err != nil {
|
||||
return errors.Wrap(err, "could not decode json")
|
||||
}
|
||||
decryptor := keystorev4.New()
|
||||
_, err = decryptor.Decrypt(keystoreJSON.Crypto, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not decrypt keystore")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readKeymanagerKindFromWalletPath(walletPath string) (v2keymanager.Kind, error) {
|
||||
walletItem, err := os.Open(walletPath)
|
||||
if err != nil {
|
||||
@@ -638,7 +373,7 @@ func readKeymanagerKindFromWalletPath(walletPath string) (v2keymanager.Kind, err
|
||||
|
||||
func createOrOpenWallet(cliCtx *cli.Context, creationFunc func(cliCtx *cli.Context) (*Wallet, error)) (*Wallet, error) {
|
||||
directory := cliCtx.String(flags.WalletDirFlag.Name)
|
||||
ok, err := hasDir(directory)
|
||||
ok, err := fileutil.HasDir(directory)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not check if wallet dir %s exists", directory)
|
||||
}
|
||||
@@ -654,40 +389,10 @@ func createOrOpenWallet(cliCtx *cli.Context, creationFunc func(cliCtx *cli.Conte
|
||||
return creationFunc(cliCtx)
|
||||
}
|
||||
|
||||
// Returns true if a file is not a directory and exists
|
||||
// at the specified path.
|
||||
func fileExists(filename string) bool {
|
||||
filePath, err := expandPath(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
info, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// Checks if a directory indeed exists at the specified path.
|
||||
func hasDir(dirPath string) (bool, error) {
|
||||
fullPath, err := expandPath(dirPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
info, err := os.Stat(fullPath)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if info == nil {
|
||||
return false, err
|
||||
}
|
||||
return info.IsDir(), err
|
||||
}
|
||||
|
||||
// isEmptyWallet checks if a folder consists key directory such as `derived`, `remote` or `direct`.
|
||||
// Returns true if exists, false otherwise.
|
||||
func isEmptyWallet(name string) (bool, error) {
|
||||
expanded, err := expandPath(name)
|
||||
expanded, err := fileutil.ExpandPath(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
@@ -76,16 +75,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()
|
||||
seedConfig, err := derived.InitializeWalletSeedFile(ctx, wallet.walletPassword, skipMnemonicConfirm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
seedConfigFile, err := derived.MarshalEncryptedSeedFile(ctx, seedConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
|
||||
}
|
||||
keymanagerConfig, err := derived.MarshalConfigFile(ctx, derived.DefaultConfig())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal keymanager config file")
|
||||
@@ -96,9 +86,6 @@ func createDerivedKeymanagerWallet(cliCtx *cli.Context, wallet *Wallet) error {
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
return errors.Wrap(err, "could not write keymanager config to disk")
|
||||
}
|
||||
if err := wallet.WriteEncryptedSeedToDisk(ctx, seedConfigFile); err != nil {
|
||||
return errors.Wrap(err, "could not write encrypted wallet seed config to disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ func RecoverWallet(cliCtx *cli.Context) error {
|
||||
return errors.Wrap(err, "could not create new wallet")
|
||||
}
|
||||
ctx := context.Background()
|
||||
seedConfig, err := derived.SeedFileFromMnemonic(ctx, mnemonic, wallet.walletPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
seedConfigFile, err := derived.MarshalEncryptedSeedFile(ctx, seedConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
|
||||
}
|
||||
keymanagerConfig, err := derived.MarshalConfigFile(ctx, derived.DefaultConfig())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal keymanager config file")
|
||||
@@ -49,10 +41,7 @@ func RecoverWallet(cliCtx *cli.Context) error {
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
return errors.Wrap(err, "could not write keymanager config to disk")
|
||||
}
|
||||
if err := wallet.WriteEncryptedSeedToDisk(ctx, seedConfigFile); err != nil {
|
||||
return errors.Wrap(err, "could not write encrypted wallet seed config to disk")
|
||||
}
|
||||
keymanager, err := wallet.InitializeKeymanager(ctx, true)
|
||||
keymanager, err := wallet.InitializeKeymanager(cliCtx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,7 +49,9 @@ func RecoverWallet(cliCtx *cli.Context) error {
|
||||
if !ok {
|
||||
return errors.New("not a derived keymanager")
|
||||
}
|
||||
|
||||
if err := km.WriteEncryptedSeedToWallet(ctx, mnemonic); err != nil {
|
||||
return err
|
||||
}
|
||||
numAccounts, err := inputNumAccounts(cliCtx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get number of accounts to recover")
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestRecoverDerivedWallet(t *testing.T) {
|
||||
wantCfg := derived.DefaultConfig()
|
||||
assert.DeepEqual(t, wantCfg, cfg)
|
||||
|
||||
keymanager, err := wallet.InitializeKeymanager(ctx, true)
|
||||
keymanager, err := wallet.InitializeKeymanager(cliCtx, true)
|
||||
require.NoError(t, err)
|
||||
km, ok := keymanager.(*derived.Keymanager)
|
||||
if !ok {
|
||||
|
||||
@@ -9,10 +9,8 @@ import (
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
@@ -25,6 +23,16 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
walletDirName = "wallet"
|
||||
passwordDirName = "walletpasswords"
|
||||
exportDirName = "export"
|
||||
passwordFileName = "password.txt"
|
||||
password = "OhWOWthisisatest42!$"
|
||||
mnemonicFileName = "mnemonic.txt"
|
||||
mnemonic = "garage car helmet trade salmon embrace market giant movie wet same champion dawn chair shield drill amazing panther accident puzzle garden mosquito kind arena"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
@@ -90,44 +98,6 @@ func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) {
|
||||
return walletDir, passwordsDir, passwordFilePath
|
||||
}
|
||||
|
||||
func TestAccountTimestamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fileName string
|
||||
want time.Time
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "keystore with timestamp",
|
||||
fileName: "keystore-1234567.json",
|
||||
want: time.Unix(1234567, 0),
|
||||
},
|
||||
{
|
||||
name: "keystore with deriv path and timestamp",
|
||||
fileName: "keystore-12313-313-00-0-5500550.json",
|
||||
want: time.Unix(5500550, 0),
|
||||
},
|
||||
{
|
||||
name: "keystore with no timestamp",
|
||||
fileName: "keystore.json",
|
||||
want: time.Unix(0, 0),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := AccountTimestamp(tt.fileName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AccountTimestamp() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("AccountTimestamp() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsEmptyWallet_RandomFiles(t *testing.T) {
|
||||
path := testutil.TempDir()
|
||||
walletDir := filepath.Join(path, "test")
|
||||
@@ -164,7 +134,7 @@ func Test_LockUnlockFile(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wallet, err := OpenWallet(cliCtx)
|
||||
defer unlock(t, wallet)
|
||||
_, err = wallet.InitializeKeymanager(ctx, true)
|
||||
_, err = wallet.InitializeKeymanager(cliCtx, true)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
err = wallet.LockConfigFile(ctx)
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/flags",
|
||||
visibility = ["//validator:__subpackages__"],
|
||||
deps = [
|
||||
"//shared/fileutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -16,8 +15,6 @@ import (
|
||||
const (
|
||||
// WalletDefaultDirName for accounts-v2.
|
||||
WalletDefaultDirName = "prysm-wallet-v2"
|
||||
// PasswordsDefaultDirName where account-v2 passwords are stored.
|
||||
PasswordsDefaultDirName = "prysm-wallet-v2-passwords"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("prefix", "flags")
|
||||
@@ -248,7 +245,7 @@ func ComplainOnDeprecatedFlags(ctx *cli.Context) {
|
||||
// DefaultValidatorDir returns OS-specific default validator directory.
|
||||
func DefaultValidatorDir() string {
|
||||
// Try to place the data folder in the user's home dir
|
||||
home := homeDir()
|
||||
home := fileutil.HomeDir()
|
||||
if home != "" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return filepath.Join(home, "Library", "Eth2Validators")
|
||||
@@ -261,14 +258,3 @@ func DefaultValidatorDir() string {
|
||||
// As we cannot guess a stable location, return empty and handle later
|
||||
return ""
|
||||
}
|
||||
|
||||
// homeDir returns home directory path.
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -17,14 +17,17 @@ go_library(
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/depositutil:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
"//shared/rand:go_default_library",
|
||||
"//validator/accounts/v2/iface:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_util//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -15,11 +17,15 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/depositutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/petnames"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/rand"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/v2/iface"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"github.com/urfave/cli/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
@@ -38,7 +44,19 @@ const (
|
||||
// m / purpose / coin_type / account_index / withdrawal_key / validating_key
|
||||
ValidatingKeyDerivationPathTemplate = "m/12381/3600/%d/0/0"
|
||||
// EncryptedSeedFileName for persisting a wallet's seed when using a derived keymanager.
|
||||
EncryptedSeedFileName = "seed.encrypted.json"
|
||||
EncryptedSeedFileName = "seed.encrypted.json"
|
||||
newWalletPasswordPromptText = "New wallet password"
|
||||
walletPasswordPromptText = "Wallet password"
|
||||
confirmPasswordPromptText = "Confirm password"
|
||||
)
|
||||
|
||||
type passwordConfirm int
|
||||
|
||||
const (
|
||||
// An enum to indicate to the prompt that confirming the password is not needed.
|
||||
noConfirmPass passwordConfirm = iota
|
||||
// An enum to indicate to the prompt to confirm the password entered.
|
||||
confirmPass
|
||||
)
|
||||
|
||||
// Config for a derived keymanager.
|
||||
@@ -56,7 +74,7 @@ type Keymanager struct {
|
||||
lock sync.RWMutex
|
||||
seedCfg *SeedConfig
|
||||
seed []byte
|
||||
walletPassword string
|
||||
accountsPassword string
|
||||
}
|
||||
|
||||
// SeedConfig json file representation as a Go struct.
|
||||
@@ -78,31 +96,80 @@ func DefaultConfig() *Config {
|
||||
|
||||
// NewKeymanager instantiates a new derived keymanager from configuration options.
|
||||
func NewKeymanager(
|
||||
ctx context.Context,
|
||||
cliCtx *cli.Context,
|
||||
wallet iface.Wallet,
|
||||
cfg *Config,
|
||||
skipMnemonicConfirm bool,
|
||||
password string,
|
||||
) (*Keymanager, error) {
|
||||
seedConfigFile, err := wallet.ReadEncryptedSeedFromDisk(ctx)
|
||||
walletFiles, err := wallet.ListDirs()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read encrypted seed file from disk")
|
||||
return nil, err
|
||||
}
|
||||
enc, err := ioutil.ReadAll(seedConfigFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read seed configuration file contents")
|
||||
}
|
||||
defer func() {
|
||||
if err := seedConfigFile.Close(); err != nil {
|
||||
log.Errorf("Could not close keymanager config file: %v", err)
|
||||
var accountsPassword string
|
||||
// If the user does not have any accounts in their wallet, we ask them to
|
||||
// set a new wallet password, which will be used for encrypting/decrypting
|
||||
// their wallet secret to and from disk.
|
||||
if len(walletFiles) == 0 {
|
||||
accountsPassword, err = inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
newWalletPasswordPromptText,
|
||||
confirmPass,
|
||||
promptutil.ValidatePasswordInput,
|
||||
)
|
||||
} else {
|
||||
validateExistingPass := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("password input cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
accountsPassword, err = inputPassword(
|
||||
cliCtx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
walletPasswordPromptText,
|
||||
noConfirmPass,
|
||||
validateExistingPass,
|
||||
)
|
||||
}
|
||||
|
||||
// Check if the wallet seed file exists. If it does not, we initialize one
|
||||
// by creating a new mnemonic and writing the encrypted file to disk.
|
||||
ctx := context.Background()
|
||||
var encodedSeedFile []byte
|
||||
if !fileutil.FileExists(filepath.Join(wallet.AccountsDir(), EncryptedSeedFileName)) {
|
||||
seedConfig, err := initializeWalletSeedFile(accountsPassword, skipMnemonicConfirm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
encodedSeedFile, err = marshalEncryptedSeedFile(seedConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal encrypted wallet seed file")
|
||||
}
|
||||
if err = wallet.WriteEncryptedSeedToDisk(ctx, encodedSeedFile); err != nil {
|
||||
return nil, errors.Wrap(err, "could not write encrypted wallet seed config to disk")
|
||||
}
|
||||
} else {
|
||||
seedConfigFile, err := wallet.ReadEncryptedSeedFromDisk(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read encrypted seed file from disk")
|
||||
}
|
||||
encodedSeedFile, err = ioutil.ReadAll(seedConfigFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read seed configuration file contents")
|
||||
}
|
||||
defer func() {
|
||||
if err := seedConfigFile.Close(); err != nil {
|
||||
log.Errorf("Could not close keymanager config file: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
seedConfig := &SeedConfig{}
|
||||
if err := json.Unmarshal(enc, seedConfig); err != nil {
|
||||
if err := json.Unmarshal(encodedSeedFile, seedConfig); err != nil {
|
||||
return nil, errors.Wrap(err, "could not unmarshal seed configuration")
|
||||
}
|
||||
decryptor := keystorev4.New()
|
||||
seed, err := decryptor.Decrypt(seedConfig.Crypto, password)
|
||||
seed, err := decryptor.Decrypt(seedConfig.Crypto, accountsPassword)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not decrypt seed configuration with password")
|
||||
}
|
||||
@@ -112,10 +179,10 @@ func NewKeymanager(
|
||||
mnemonicGenerator: &EnglishMnemonicGenerator{
|
||||
skipMnemonicConfirm: skipMnemonicConfirm,
|
||||
},
|
||||
seedCfg: seedConfig,
|
||||
seed: seed,
|
||||
walletPassword: password,
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
seedCfg: seedConfig,
|
||||
seed: seed,
|
||||
accountsPassword: accountsPassword,
|
||||
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.
|
||||
@@ -150,72 +217,6 @@ func MarshalConfigFile(ctx context.Context, cfg *Config) ([]byte, error) {
|
||||
return json.MarshalIndent(cfg, "", "\t")
|
||||
}
|
||||
|
||||
// InitializeWalletSeedFile creates a new, encrypted seed using a password input
|
||||
// and persists its encrypted file metadata to disk under the wallet path.
|
||||
func InitializeWalletSeedFile(ctx context.Context, password string, skipMnemonicConfirm bool) (*SeedConfig, error) {
|
||||
mnemonicRandomness := make([]byte, 32)
|
||||
if _, err := rand.NewGenerator().Read(mnemonicRandomness); err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize mnemonic source of randomness")
|
||||
}
|
||||
m := &EnglishMnemonicGenerator{
|
||||
skipMnemonicConfirm: skipMnemonicConfirm,
|
||||
}
|
||||
phrase, err := m.Generate(mnemonicRandomness)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate wallet seed")
|
||||
}
|
||||
if err := m.ConfirmAcknowledgement(phrase); err != nil {
|
||||
return nil, errors.Wrap(err, "could not confirm mnemonic acknowledgement")
|
||||
}
|
||||
walletSeed := bip39.NewSeed(phrase, "")
|
||||
encryptor := keystorev4.New()
|
||||
cryptoFields, err := encryptor.Encrypt(walletSeed, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not encrypt seed phrase into keystore")
|
||||
}
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate unique UUID")
|
||||
}
|
||||
return &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if ok := bip39.IsMnemonicValid(mnemonic); !ok {
|
||||
return nil, bip39.ErrInvalidMnemonic
|
||||
}
|
||||
walletSeed := bip39.NewSeed(mnemonic, "")
|
||||
encryptor := keystorev4.New()
|
||||
cryptoFields, err := encryptor.Encrypt(walletSeed, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not encrypt seed phrase into keystore")
|
||||
}
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate unique UUID")
|
||||
}
|
||||
return &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalEncryptedSeedFile json encodes the seed configuration for a derived keymanager.
|
||||
func MarshalEncryptedSeedFile(ctx context.Context, seedCfg *SeedConfig) ([]byte, error) {
|
||||
return json.MarshalIndent(seedCfg, "", "\t")
|
||||
}
|
||||
|
||||
// Config returns the derived keymanager configuration.
|
||||
func (dr *Keymanager) Config() *Config {
|
||||
return dr.cfg
|
||||
@@ -226,6 +227,23 @@ func (dr *Keymanager) NextAccountNumber(ctx context.Context) uint64 {
|
||||
return dr.seedCfg.NextAccount
|
||||
}
|
||||
|
||||
// WriteEncryptedSeedToWallet given a mnemonic phrase, is able to regenerate a wallet seed
|
||||
// encrypt it, and write it to the wallet's path.
|
||||
func (dr *Keymanager) WriteEncryptedSeedToWallet(ctx context.Context, mnemonic string) error {
|
||||
seedConfig, err := seedFileFromMnemonic(mnemonic, dr.accountsPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize new wallet seed file")
|
||||
}
|
||||
seedConfigFile, err := marshalEncryptedSeedFile(seedConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal encrypted wallet seed file")
|
||||
}
|
||||
if err := dr.wallet.WriteEncryptedSeedToDisk(ctx, seedConfigFile); err != nil {
|
||||
return errors.Wrap(err, "could not write encrypted wallet seed config to disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatingAccountNames for the derived keymanager.
|
||||
func (dr *Keymanager) ValidatingAccountNames(ctx context.Context) ([]string, error) {
|
||||
names := make([]string, 0)
|
||||
@@ -293,7 +311,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, logAccountInfo bool) (s
|
||||
}).Info("Successfully created new validator account")
|
||||
}
|
||||
dr.seedCfg.NextAccount++
|
||||
encodedCfg, err := MarshalEncryptedSeedFile(ctx, dr.seedCfg)
|
||||
encodedCfg, err := marshalEncryptedSeedFile(dr.seedCfg)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not marshal encrypted seed file")
|
||||
}
|
||||
@@ -412,3 +430,114 @@ func (dr *Keymanager) initializeSecretKeysCache() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inputPassword(
|
||||
cliCtx *cli.Context,
|
||||
passwordFileFlag *cli.StringFlag,
|
||||
promptText string,
|
||||
confirmPassword passwordConfirm,
|
||||
passwordValidator func(input string) error,
|
||||
) (string, error) {
|
||||
if cliCtx.IsSet(passwordFileFlag.Name) {
|
||||
passwordFilePathInput := cliCtx.String(passwordFileFlag.Name)
|
||||
data, err := fileutil.ReadFileAsBytes(passwordFilePathInput)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not read file as bytes")
|
||||
}
|
||||
enteredPassword := strings.TrimRight(string(data), "\r\n")
|
||||
if err := passwordValidator(enteredPassword); err != nil {
|
||||
return "", errors.Wrap(err, "password did not pass validation")
|
||||
}
|
||||
return enteredPassword, nil
|
||||
}
|
||||
var hasValidPassword bool
|
||||
var walletPassword string
|
||||
var err error
|
||||
for !hasValidPassword {
|
||||
walletPassword, err = promptutil.PasswordPrompt(promptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read account password: %v", err)
|
||||
}
|
||||
|
||||
if confirmPassword == confirmPass {
|
||||
passwordConfirmation, err := promptutil.PasswordPrompt(confirmPasswordPromptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read password confirmation: %v", err)
|
||||
}
|
||||
if walletPassword != passwordConfirmation {
|
||||
log.Error("Passwords do not match")
|
||||
continue
|
||||
}
|
||||
hasValidPassword = true
|
||||
} else {
|
||||
return walletPassword, nil
|
||||
}
|
||||
}
|
||||
return walletPassword, nil
|
||||
}
|
||||
|
||||
// Creates a new, encrypted seed using a password input
|
||||
// and persists its encrypted file metadata to disk under the wallet path.
|
||||
func initializeWalletSeedFile(password string, skipMnemonicConfirm bool) (*SeedConfig, error) {
|
||||
mnemonicRandomness := make([]byte, 32)
|
||||
if _, err := rand.NewGenerator().Read(mnemonicRandomness); err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize mnemonic source of randomness")
|
||||
}
|
||||
m := &EnglishMnemonicGenerator{
|
||||
skipMnemonicConfirm: skipMnemonicConfirm,
|
||||
}
|
||||
phrase, err := m.Generate(mnemonicRandomness)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate wallet seed")
|
||||
}
|
||||
if err := m.ConfirmAcknowledgement(phrase); err != nil {
|
||||
return nil, errors.Wrap(err, "could not confirm mnemonic acknowledgement")
|
||||
}
|
||||
walletSeed := bip39.NewSeed(phrase, "")
|
||||
encryptor := keystorev4.New()
|
||||
cryptoFields, err := encryptor.Encrypt(walletSeed, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not encrypt seed phrase into keystore")
|
||||
}
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate unique UUID")
|
||||
}
|
||||
return &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Uses the provided mnemonic seed phrase to generate the
|
||||
// appropriate seed file for recovering a derived wallets.
|
||||
func seedFileFromMnemonic(mnemonic string, password string) (*SeedConfig, error) {
|
||||
if ok := bip39.IsMnemonicValid(mnemonic); !ok {
|
||||
return nil, bip39.ErrInvalidMnemonic
|
||||
}
|
||||
walletSeed := bip39.NewSeed(mnemonic, "")
|
||||
encryptor := keystorev4.New()
|
||||
cryptoFields, err := encryptor.Encrypt(walletSeed, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not encrypt seed phrase into keystore")
|
||||
}
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate unique UUID")
|
||||
}
|
||||
return &SeedConfig{
|
||||
Crypto: cryptoFields,
|
||||
ID: id.String(),
|
||||
NextAccount: 0,
|
||||
Version: encryptor.Version(),
|
||||
Name: encryptor.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// marshalEncryptedSeedFile json encodes the seed configuration for a derived keymanager.
|
||||
func marshalEncryptedSeedFile(seedCfg *SeedConfig) ([]byte, error) {
|
||||
return json.MarshalIndent(seedCfg, "", "\t")
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestDerivedKeymanager_CreateAccount(t *testing.T) {
|
||||
seedCfg: &SeedConfig{
|
||||
NextAccount: 0,
|
||||
},
|
||||
walletPassword: password,
|
||||
accountsPassword: password,
|
||||
}
|
||||
ctx := context.Background()
|
||||
accountName, err := dr.CreateAccount(ctx, true /*logAccountInfo*/)
|
||||
@@ -105,8 +105,8 @@ func TestDerivedKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
seedCfg: &SeedConfig{
|
||||
NextAccount: 0,
|
||||
},
|
||||
seed: make([]byte, 32),
|
||||
walletPassword: "hello world",
|
||||
seed: make([]byte, 32),
|
||||
accountsPassword: "hello world",
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
@@ -154,7 +154,7 @@ func TestDerivedKeymanager_Sign(t *testing.T) {
|
||||
seedCfg: &SeedConfig{
|
||||
NextAccount: 0,
|
||||
},
|
||||
walletPassword: "hello world",
|
||||
accountsPassword: "hello world",
|
||||
}
|
||||
|
||||
// First, generate some accounts.
|
||||
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/depositutil:go_default_library",
|
||||
"//shared/fileutil:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/promptutil:go_default_library",
|
||||
"//validator/accounts/v2/iface:go_default_library",
|
||||
|
||||
@@ -16,11 +16,14 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/depositutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/fileutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/petnames"
|
||||
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||||
"github.com/prysmaticlabs/prysm/validator/accounts/v2/iface"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
@@ -31,13 +34,22 @@ const (
|
||||
KeystoreFileName = "keystore-*.json"
|
||||
// KeystoreFileNameFormat exposes the filename the keystore should be formatted in.
|
||||
KeystoreFileNameFormat = "keystore-%d.json"
|
||||
// PasswordFileSuffix for passwords persisted as text to disk.
|
||||
PasswordFileSuffix = ".pass"
|
||||
// AccountsPath where all direct keymanager keystores are kept.
|
||||
AccountsPath = "accounts"
|
||||
accountsKeystoreFileName = "all-accounts.keystore.json"
|
||||
accountsKeystoreFileNameFormat = "all-accounts.keystore.json"
|
||||
eipVersion = "EIP-2335"
|
||||
AccountsPath = "accounts"
|
||||
accountsKeystoreFileName = "all-accounts.keystore.json"
|
||||
eipVersion = "EIP-2335"
|
||||
newWalletPasswordPromptText = "New wallet password"
|
||||
walletPasswordPromptText = "Wallet password"
|
||||
confirmPasswordPromptText = "Confirm password"
|
||||
)
|
||||
|
||||
type passwordConfirm int
|
||||
|
||||
const (
|
||||
// An enum to indicate to the prompt that confirming the password is not needed.
|
||||
noConfirmPass passwordConfirm = iota
|
||||
// An enum to indicate to the prompt to confirm the password entered.
|
||||
confirmPass
|
||||
)
|
||||
|
||||
// Config for a direct keymanager.
|
||||
@@ -47,11 +59,12 @@ type Config struct {
|
||||
|
||||
// Keymanager implementation for direct keystores utilizing EIP-2335.
|
||||
type Keymanager struct {
|
||||
wallet iface.Wallet
|
||||
cfg *Config
|
||||
keysCache map[[48]byte]bls.SecretKey
|
||||
accountsStore *AccountStore
|
||||
lock sync.RWMutex
|
||||
wallet iface.Wallet
|
||||
cfg *Config
|
||||
keysCache map[[48]byte]bls.SecretKey
|
||||
accountsStore *AccountStore
|
||||
lock sync.RWMutex
|
||||
accountsPassword string
|
||||
}
|
||||
|
||||
// AccountStore --
|
||||
@@ -68,7 +81,7 @@ func DefaultConfig() *Config {
|
||||
}
|
||||
|
||||
// NewKeymanager instantiates a new direct keymanager from configuration options.
|
||||
func NewKeymanager(ctx context.Context, wallet iface.Wallet, cfg *Config) (*Keymanager, error) {
|
||||
func NewKeymanager(ctx *cli.Context, wallet iface.Wallet, cfg *Config) (*Keymanager, error) {
|
||||
k := &Keymanager{
|
||||
wallet: wallet,
|
||||
cfg: cfg,
|
||||
@@ -76,6 +89,39 @@ func NewKeymanager(ctx context.Context, wallet iface.Wallet, cfg *Config) (*Keym
|
||||
accountsStore: &AccountStore{},
|
||||
}
|
||||
|
||||
walletFiles, err := wallet.ListDirs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accountsPassword string
|
||||
if len(walletFiles) == 0 {
|
||||
accountsPassword, err = inputPassword(
|
||||
ctx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
newWalletPasswordPromptText,
|
||||
confirmPass,
|
||||
promptutil.ValidatePasswordInput,
|
||||
)
|
||||
} else {
|
||||
validateExistingPass := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("password input cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
accountsPassword, err = inputPassword(
|
||||
ctx,
|
||||
flags.WalletPasswordFileFlag,
|
||||
walletPasswordPromptText,
|
||||
noConfirmPass,
|
||||
validateExistingPass,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.accountsPassword = accountsPassword
|
||||
|
||||
// If the wallet has the capability of unlocking accounts using
|
||||
// passphrases, then we initialize a cache of public key -> secret keys
|
||||
// used to retrieve secrets keys for the accounts via password unlock.
|
||||
@@ -238,8 +284,8 @@ func (dr *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (b
|
||||
return secretKey.Sign(req.SigningRoot), nil
|
||||
}
|
||||
|
||||
func (dr *Keymanager) initializeSecretKeysCache(ctx context.Context) error {
|
||||
encoded, err := dr.wallet.ReadFileAtPath(ctx, AccountsPath, accountsKeystoreFileName)
|
||||
func (dr *Keymanager) initializeSecretKeysCache(cliCtx *cli.Context) error {
|
||||
encoded, err := dr.wallet.ReadFileAtPath(context.Background(), AccountsPath, accountsKeystoreFileName)
|
||||
if err != nil && strings.Contains(err.Error(), "no files found") {
|
||||
// If there are no keys to initialize at all, just exit.
|
||||
return nil
|
||||
@@ -254,11 +300,11 @@ func (dr *Keymanager) initializeSecretKeysCache(ctx context.Context) error {
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.accountsPassword)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
// If the password fails for an individual account, we ask the user to input
|
||||
// that individual account's password until it succeeds.
|
||||
enc, _, err = dr.askUntilPasswordConfirms(decryptor, keystoreFile)
|
||||
enc, dr.accountsPassword, err = dr.askUntilPasswordConfirms(decryptor, keystoreFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not confirm password via prompt")
|
||||
}
|
||||
@@ -336,7 +382,7 @@ func (dr *Keymanager) createAccountsKeystore(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cryptoFields, err := encryptor.Encrypt(encodedStore, dr.wallet.Password())
|
||||
cryptoFields, err := encryptor.Encrypt(encodedStore, dr.accountsPassword)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not encrypt accounts")
|
||||
}
|
||||
@@ -375,3 +421,52 @@ func (dr *Keymanager) askUntilPasswordConfirms(
|
||||
}
|
||||
return secretKey, password, nil
|
||||
}
|
||||
|
||||
func inputPassword(
|
||||
cliCtx *cli.Context,
|
||||
passwordFileFlag *cli.StringFlag,
|
||||
promptText string,
|
||||
confirmPassword passwordConfirm,
|
||||
passwordValidator func(input string) error,
|
||||
) (string, error) {
|
||||
if cliCtx.IsSet(passwordFileFlag.Name) {
|
||||
passwordFilePathInput := cliCtx.String(passwordFileFlag.Name)
|
||||
passwordFilePath, err := fileutil.ExpandPath(passwordFilePathInput)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not determine absolute path of password file")
|
||||
}
|
||||
data, err := ioutil.ReadFile(passwordFilePath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not read password file")
|
||||
}
|
||||
enteredPassword := strings.TrimRight(string(data), "\r\n")
|
||||
if err := passwordValidator(enteredPassword); err != nil {
|
||||
return "", errors.Wrap(err, "password did not pass validation")
|
||||
}
|
||||
return enteredPassword, nil
|
||||
}
|
||||
var hasValidPassword bool
|
||||
var walletPassword string
|
||||
var err error
|
||||
for !hasValidPassword {
|
||||
walletPassword, err = promptutil.PasswordPrompt(promptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read account password: %v", err)
|
||||
}
|
||||
|
||||
if confirmPassword == confirmPass {
|
||||
passwordConfirmation, err := promptutil.PasswordPrompt(confirmPasswordPromptText, passwordValidator)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read password confirmation: %v", err)
|
||||
}
|
||||
if walletPassword != passwordConfirmation {
|
||||
log.Error("Passwords do not match")
|
||||
continue
|
||||
}
|
||||
hasValidPassword = true
|
||||
} else {
|
||||
return walletPassword, nil
|
||||
}
|
||||
}
|
||||
return walletPassword, nil
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ func TestDirectKeymanager_CreateAccount(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
password := "secretPassw0rd$1999"
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
Files: make(map[string]map[string][]byte),
|
||||
}
|
||||
dr := &Keymanager{
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
accountsPassword: password,
|
||||
}
|
||||
ctx := context.Background()
|
||||
accountName, err := dr.CreateAccount(ctx)
|
||||
@@ -66,13 +66,13 @@ func TestDirectKeymanager_CreateAccount(t *testing.T) {
|
||||
func TestDirectKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
password := "secretPassw0rd$1999"
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
Files: make(map[string]map[string][]byte),
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
accountsStore: &AccountStore{},
|
||||
wallet: wallet,
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
accountsStore: &AccountStore{},
|
||||
accountsPassword: password,
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
@@ -107,12 +107,12 @@ func TestDirectKeymanager_Sign(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
WalletPassword: password,
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
keysCache: make(map[[48]byte]bls.SecretKey),
|
||||
accountsPassword: password,
|
||||
}
|
||||
|
||||
// First, generate accounts and their keystore.json files.
|
||||
@@ -136,7 +136,7 @@ func TestDirectKeymanager_Sign(t *testing.T) {
|
||||
// by utilizing the password and initialize a new BLS secret key from
|
||||
// its raw bytes.
|
||||
decryptor := keystorev4.New()
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.wallet.Password())
|
||||
enc, err := decryptor.Decrypt(keystoreFile.Crypto, dr.accountsPassword)
|
||||
require.NoError(t, err)
|
||||
store := &AccountStore{}
|
||||
require.NoError(t, json.Unmarshal(enc, store))
|
||||
|
||||
@@ -62,11 +62,10 @@ func TestDirectKeymanager_CreateAccountsKeystore_NoDuplicates(t *testing.T) {
|
||||
privKeys[i] = priv.Marshal()
|
||||
pubKeys[i] = priv.PublicKey().Marshal()
|
||||
}
|
||||
wallet := &mock.Wallet{
|
||||
WalletPassword: "Passw0rdz$%@49",
|
||||
}
|
||||
wallet := &mock.Wallet{}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
wallet: wallet,
|
||||
accountsPassword: "Mypassw0rdz932",
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, err := dr.createAccountsKeystore(ctx, privKeys, pubKeys)
|
||||
@@ -122,12 +121,12 @@ func TestDirectKeymanager_ImportKeystores(t *testing.T) {
|
||||
|
||||
// Setup the keymanager.
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
WalletPassword: password,
|
||||
Files: make(map[string]map[string][]byte),
|
||||
}
|
||||
dr := &Keymanager{
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
wallet: wallet,
|
||||
accountsStore: &AccountStore{},
|
||||
accountsPassword: password,
|
||||
}
|
||||
|
||||
// Create several keystores and attempt to import them.
|
||||
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"@com_github_logrusorgru_aurora//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//credentials:go_default_library",
|
||||
],
|
||||
@@ -37,5 +38,6 @@ go_test(
|
||||
"//shared/testutil:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"@com_github_golang_mock//gomock:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
@@ -54,7 +55,7 @@ type Keymanager struct {
|
||||
}
|
||||
|
||||
// NewKeymanager instantiates a new direct keymanager from configuration options.
|
||||
func NewKeymanager(ctx context.Context, maxMessageSize int, cfg *Config) (*Keymanager, error) {
|
||||
func NewKeymanager(cliCtx *cli.Context, maxMessageSize int, cfg *Config) (*Keymanager, error) {
|
||||
// Load the client certificates.
|
||||
if cfg.RemoteCertificate == nil {
|
||||
return nil, errors.New("certificates are required")
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var validClientCert = `-----BEGIN CERTIFICATE-----
|
||||
@@ -173,7 +174,7 @@ func TestNewRemoteKeymanager(t *testing.T) {
|
||||
test.opts.RemoteCertificate.ClientKeyPath = clientKeyPath
|
||||
}
|
||||
}
|
||||
_, err := NewKeymanager(context.Background(), 1, test.opts)
|
||||
_, err := NewKeymanager(&cli.Context{}, 1, test.opts)
|
||||
if test.err == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
|
||||
@@ -91,7 +91,7 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
|
||||
ValidatorClient.wallet = wallet
|
||||
ctx := context.Background()
|
||||
keyManagerV2, err = wallet.InitializeKeymanager(
|
||||
ctx, false, /* skipMnemonicConfirm */
|
||||
cliCtx, false, /* skipMnemonicConfirm */
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not read existing keymanager for wallet: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user