mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 13:58:09 -05:00
Accounts V2: Simplify Wallet Save/Read To and From Disk Functions (#6686)
* simplify wallet functions * fix build * futher simplify wallet * simplify read/write methods * move direct functions to direct keymanager * further move direct km specific funcs * cleanup * simplify the direct tests * fixed tests * lint * further simplify * tidy * fix config write * fixed test Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,6 @@ go_library(
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
@@ -37,7 +36,6 @@ go_library(
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package v2
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -31,8 +33,15 @@ func ExportAccount(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not open wallet")
|
||||
}
|
||||
|
||||
allAccounts, err := wallet.AccountNames()
|
||||
keymanager, err := wallet.InitializeKeymanager(context.Background(), true /* skip mnemonic confirm */)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize keymanager")
|
||||
}
|
||||
km, ok := keymanager.(*direct.Keymanager)
|
||||
if !ok {
|
||||
return errors.New("can only export accounts for a non-HD wallet")
|
||||
}
|
||||
allAccounts, err := km.ValidatingAccountNames()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get account names")
|
||||
}
|
||||
@@ -48,7 +57,7 @@ func ExportAccount(cliCtx *cli.Context) error {
|
||||
return errors.Wrap(err, "could not export accounts")
|
||||
}
|
||||
|
||||
if err := logAccountsExported(wallet, accounts); err != nil {
|
||||
if err := logAccountsExported(wallet, km, accounts); err != nil {
|
||||
return errors.Wrap(err, "could not log out exported accounts")
|
||||
}
|
||||
|
||||
@@ -196,7 +205,7 @@ func copyFileFromZip(archive *zip.Writer, sourcePath string, info os.FileInfo, p
|
||||
return err
|
||||
}
|
||||
|
||||
func logAccountsExported(wallet *Wallet, accountNames []string) error {
|
||||
func logAccountsExported(wallet *Wallet, keymanager *direct.Keymanager, accountNames []string) error {
|
||||
au := aurora.NewAurora(true)
|
||||
|
||||
numAccounts := au.BrightYellow(len(accountNames))
|
||||
@@ -210,7 +219,7 @@ func logAccountsExported(wallet *Wallet, accountNames []string) error {
|
||||
fmt.Println("")
|
||||
fmt.Printf("%s\n", au.BrightGreen(accountName).Bold())
|
||||
|
||||
publicKey, err := wallet.publicKeyForAccount(accountName)
|
||||
publicKey, err := keymanager.PublicKeyForAccount(accountName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get public key")
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestZipAndUnzip(t *testing.T) {
|
||||
_, err = keymanager.CreateAccount(ctx, password)
|
||||
require.NoError(t, err)
|
||||
|
||||
accounts, err := wallet.AccountNames()
|
||||
accounts, err := keymanager.ValidatingAccountNames()
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(accounts) == 0 {
|
||||
@@ -88,10 +88,14 @@ func TestExport_Noninteractive(t *testing.T) {
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
keymanagerCfg := direct.DefaultConfig()
|
||||
encodedCfg, err := direct.MarshalConfigFile(ctx, keymanagerCfg)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg))
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
wallet,
|
||||
direct.DefaultConfig(),
|
||||
keymanagerCfg,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = keymanager.CreateAccount(ctx, password)
|
||||
|
||||
@@ -10,16 +10,14 @@ import (
|
||||
// Useful for keymanagers to have persistent capabilities for accounts on-disk.
|
||||
type Wallet interface {
|
||||
// Methods to retrieve wallet and accounts metadata.
|
||||
AccountNames() ([]string, error)
|
||||
AccountsDir() string
|
||||
ListDirs() ([]string, error)
|
||||
// Read methods for important wallet and accounts-related files.
|
||||
ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error)
|
||||
ReadPasswordForAccount(accountName string) (string, error)
|
||||
ReadFileForAccount(accountName string, fileName string) ([]byte, error)
|
||||
ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error)
|
||||
ReadPasswordFromDisk(ctx context.Context, passwordFileName string) (string, error)
|
||||
// Write methods to persist important wallet and accounts-related files to disk.
|
||||
WriteFileAtPath(ctx context.Context, pathName string, fileName string, data []byte) error
|
||||
WritePasswordToDisk(ctx context.Context, passwordFileName string, password string) error
|
||||
WriteEncryptedSeedToDisk(ctx context.Context, encoded []byte) error
|
||||
WriteAccountToDisk(ctx context.Context, password string) (string, error)
|
||||
WriteFileForAccount(ctx context.Context, accountName string, fileName string, data []byte) error
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package v2
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -10,10 +11,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -24,6 +25,14 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not open wallet")
|
||||
}
|
||||
keymanager, err := wallet.InitializeKeymanager(context.Background(), true /* skip mnemonic confirm */)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize keymanager")
|
||||
}
|
||||
km, ok := keymanager.(*direct.Keymanager)
|
||||
if !ok {
|
||||
return errors.New("can only export accounts for a non-HD wallet")
|
||||
}
|
||||
|
||||
backupDir, err := inputImportDir(cliCtx)
|
||||
if err != nil {
|
||||
@@ -43,11 +52,11 @@ func ImportAccount(cliCtx *cli.Context) error {
|
||||
fmt.Printf("Importing accounts: %s\n", strings.Join(loggedAccounts, ", "))
|
||||
|
||||
for _, accountName := range accountsImported {
|
||||
if err := wallet.enterPasswordForAccount(cliCtx, accountName); err != nil {
|
||||
if err := km.EnterPasswordForAccount(cliCtx, accountName); err != nil {
|
||||
return errors.Wrap(err, "could not set account password")
|
||||
}
|
||||
}
|
||||
if err := logAccountsImported(wallet, accountsImported); err != nil {
|
||||
if err := logAccountsImported(wallet, km, accountsImported); err != nil {
|
||||
return errors.Wrap(err, "could not log accounts imported")
|
||||
}
|
||||
|
||||
@@ -136,7 +145,7 @@ func copyFileFromZipToPath(file *zip.File, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func logAccountsImported(wallet *Wallet, accountNames []string) error {
|
||||
func logAccountsImported(wallet *Wallet, keymanager *direct.Keymanager, accountNames []string) error {
|
||||
au := aurora.NewAurora(true)
|
||||
|
||||
numAccounts := au.BrightYellow(len(accountNames))
|
||||
@@ -150,7 +159,7 @@ func logAccountsImported(wallet *Wallet, accountNames []string) error {
|
||||
fmt.Println("")
|
||||
fmt.Printf("%s\n", au.BrightGreen(accountName).Bold())
|
||||
|
||||
publicKey, err := wallet.publicKeyForAccount(accountName)
|
||||
publicKey, err := keymanager.PublicKeyForAccount(accountName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get public key")
|
||||
}
|
||||
|
||||
@@ -43,18 +43,21 @@ func TestImport_Noninteractive(t *testing.T) {
|
||||
})
|
||||
wallet, err := NewWallet(cliCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
keymanagerCfg := direct.DefaultConfig()
|
||||
encodedCfg, err := direct.MarshalConfigFile(ctx, keymanagerCfg)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg))
|
||||
keymanager, err := direct.NewKeymanager(
|
||||
ctx,
|
||||
wallet,
|
||||
direct.DefaultConfig(),
|
||||
keymanagerCfg,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = keymanager.CreateAccount(ctx, password)
|
||||
require.NoError(t, err)
|
||||
|
||||
accounts, err := wallet.AccountNames()
|
||||
accounts, err := keymanager.ValidatingAccountNames()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(accounts), 1)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func listDirectKeymanagerAccounts(
|
||||
keymanager *direct.Keymanager,
|
||||
) error {
|
||||
// We initialize the wallet's keymanager.
|
||||
accountNames, err := wallet.AccountNames()
|
||||
accountNames, err := keymanager.ValidatingAccountNames()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch account names")
|
||||
}
|
||||
@@ -77,7 +77,8 @@ func listDirectKeymanagerAccounts(
|
||||
)
|
||||
fmt.Printf("Keymanager kind: %s\n", au.BrightGreen(wallet.KeymanagerKind().String()).Bold())
|
||||
|
||||
pubKeys, err := keymanager.FetchValidatingPublicKeys(context.Background())
|
||||
ctx := context.Background()
|
||||
pubKeys, err := keymanager.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch validating public keys")
|
||||
}
|
||||
@@ -87,7 +88,7 @@ func listDirectKeymanagerAccounts(
|
||||
fmt.Printf("%s %#x\n", au.BrightMagenta("[public key]").Bold(), pubKeys[i])
|
||||
|
||||
// Retrieve the account creation timestamp.
|
||||
createdAtBytes, err := wallet.ReadFileForAccount(accountNames[i], direct.TimestampFileName)
|
||||
createdAtBytes, err := wallet.ReadFileAtPath(ctx, accountNames[i], direct.TimestampFileName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read file for account: %s", direct.TimestampFileName)
|
||||
}
|
||||
@@ -100,7 +101,7 @@ func listDirectKeymanagerAccounts(
|
||||
if !showDepositData {
|
||||
continue
|
||||
}
|
||||
enc, err := wallet.ReadFileForAccount(accountNames[i], direct.DepositTransactionFileName)
|
||||
enc, err := wallet.ReadFileAtPath(ctx, accountNames[i], direct.DepositTransactionFileName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read file for account: %s", direct.DepositTransactionFileName)
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
accountName, err := keymanager.CreateAccount(ctx, "hello world")
|
||||
require.NoError(t, err)
|
||||
depositData, err := wallet.ReadFileForAccount(accountName, direct.DepositTransactionFileName)
|
||||
depositData, err := wallet.ReadFileAtPath(ctx, accountName, direct.DepositTransactionFileName)
|
||||
require.NoError(t, err)
|
||||
depositDataForAccounts[i] = depositData
|
||||
unixTimestamp, err := wallet.ReadFileForAccount(accountName, direct.TimestampFileName)
|
||||
unixTimestamp, err := wallet.ReadFileAtPath(ctx, accountName, direct.TimestampFileName)
|
||||
require.NoError(t, err)
|
||||
accountCreationTimestamps[i] = unixTimestamp
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func TestListAccounts_DirectKeymanager(t *testing.T) {
|
||||
t.Errorf("Did not find accounts path %s in output", wallet.accountsPath)
|
||||
}
|
||||
|
||||
accountNames, err := wallet.AccountNames()
|
||||
accountNames, err := keymanager.ValidatingAccountNames()
|
||||
require.NoError(t, err)
|
||||
pubKeys, err := keymanager.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -122,7 +122,7 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
|
||||
// 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.WriteEncryptedSeedToDisk(ctx, seedConfigFile))
|
||||
require.NoError(t, wallet.WriteFileAtPath(ctx, "", derived.EncryptedSeedFileName, seedConfigFile))
|
||||
|
||||
keymanager, err := derived.NewKeymanager(
|
||||
ctx,
|
||||
|
||||
@@ -9,5 +9,4 @@ go_library(
|
||||
"//validator:__pkg__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = ["@com_github_dustinkirkland_golang_petname//:go_default_library"],
|
||||
)
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
petname "github.com/dustinkirkland/golang-petname"
|
||||
)
|
||||
|
||||
// Wallet contains an in-memory, simulated wallet implementation.
|
||||
type Wallet struct {
|
||||
InnerAccountsDir string
|
||||
Directories []string
|
||||
Files map[string]map[string][]byte
|
||||
EncryptedSeedFile []byte
|
||||
AccountPasswords map[string]string
|
||||
@@ -33,58 +33,34 @@ func (m *Wallet) AccountNames() ([]string, error) {
|
||||
|
||||
// AccountsDir --
|
||||
func (m *Wallet) AccountsDir() string {
|
||||
return ""
|
||||
return m.InnerAccountsDir
|
||||
}
|
||||
|
||||
// WriteAccountToDisk --
|
||||
func (m *Wallet) WriteAccountToDisk(ctx context.Context, password string) (string, error) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
accountName := petname.Generate(3, "-")
|
||||
m.AccountPasswords[accountName] = password
|
||||
return accountName, nil
|
||||
// ListDirs --
|
||||
func (m *Wallet) ListDirs() ([]string, error) {
|
||||
return m.Directories, nil
|
||||
}
|
||||
|
||||
// WriteFileForAccount --
|
||||
func (m *Wallet) WriteFileForAccount(
|
||||
ctx context.Context,
|
||||
accountName string,
|
||||
fileName string,
|
||||
data []byte,
|
||||
) error {
|
||||
// WritePasswordToDisk --
|
||||
func (m *Wallet) WritePasswordToDisk(ctx context.Context, passwordFileName string, password string) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if m.Files[accountName] == nil {
|
||||
m.Files[accountName] = make(map[string][]byte)
|
||||
}
|
||||
m.Files[accountName][fileName] = data
|
||||
m.AccountPasswords[passwordFileName] = password
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPasswordForAccount --
|
||||
func (m *Wallet) ReadPasswordForAccount(accountName string) (string, error) {
|
||||
// ReadPasswordFromDisk --
|
||||
func (m *Wallet) ReadPasswordFromDisk(ctx context.Context, passwordFileName string) (string, error) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
for name, password := range m.AccountPasswords {
|
||||
if name == accountName {
|
||||
if name == passwordFileName {
|
||||
return password, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("account not found")
|
||||
}
|
||||
|
||||
// ReadFileForAccount --
|
||||
func (m *Wallet) ReadFileForAccount(accountName string, fileName string) ([]byte, error) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
for f, v := range m.Files[accountName] {
|
||||
if f == fileName {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
}
|
||||
|
||||
// WriteFileAtPath --
|
||||
func (m *Wallet) WriteFileAtPath(ctx context.Context, pathName string, fileName string, data []byte) error {
|
||||
m.lock.Lock()
|
||||
|
||||
@@ -2,27 +2,20 @@ package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
|
||||
petname "github.com/dustinkirkland/golang-petname"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/derived"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,14 +25,6 @@ const (
|
||||
PasswordsDefaultDirName = ".prysm-wallet-v2-passwords"
|
||||
// KeymanagerConfigFileName for the keymanager used by the wallet: direct, derived, or remote.
|
||||
KeymanagerConfigFileName = "keymanageropts.json"
|
||||
// EncryptedSeedFileName for persisting a wallet's seed when using a derived keymanager.
|
||||
EncryptedSeedFileName = "seed.encrypted.json"
|
||||
// PasswordFileSuffix for passwords persisted as text to disk.
|
||||
PasswordFileSuffix = ".pass"
|
||||
// NumAccountWords for human-readable names in wallets using a direct keymanager.
|
||||
NumAccountWords = 3 // Number of words in account human-readable names.
|
||||
// AccountFilePermissions for accounts saved to disk.
|
||||
AccountFilePermissions = os.O_CREATE | os.O_RDWR
|
||||
// DirectoryPermissions for directories created under the wallet path.
|
||||
DirectoryPermissions = os.ModePerm
|
||||
)
|
||||
@@ -155,16 +140,6 @@ func OpenWallet(cliCtx *cli.Context) (*Wallet, error) {
|
||||
return w, 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 := path.Join(w.accountsPath, KeymanagerConfigFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
return nil, fmt.Errorf("no keymanager config file found at path: %s", w.accountsPath)
|
||||
}
|
||||
return os.Open(configFilePath)
|
||||
}
|
||||
|
||||
// KeymanagerKind used by the wallet.
|
||||
func (w *Wallet) KeymanagerKind() v2keymanager.Kind {
|
||||
return w.keymanagerKind
|
||||
@@ -175,37 +150,6 @@ func (w *Wallet) AccountsDir() string {
|
||||
return w.accountsPath
|
||||
}
|
||||
|
||||
// AccountNames reads all account names at the wallet's path.
|
||||
func (w *Wallet) AccountNames() ([]string, error) {
|
||||
accountsDir, err := os.Open(w.accountsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := accountsDir.Close(); err != nil {
|
||||
log.WithField(
|
||||
"directory", w.accountsPath,
|
||||
).Errorf("Could not close accounts directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
list, err := accountsDir.Readdirnames(0) // 0 to read all files and folders.
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read files in directory: %s", w.accountsPath)
|
||||
}
|
||||
accountNames := make([]string, 0)
|
||||
for _, item := range list {
|
||||
ok, err := hasDir(path.Join(w.accountsPath, item))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse directory: %v", err)
|
||||
}
|
||||
if ok {
|
||||
accountNames = append(accountNames, item)
|
||||
}
|
||||
}
|
||||
return accountNames, err
|
||||
}
|
||||
|
||||
// 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(
|
||||
@@ -242,24 +186,35 @@ func (w *Wallet) InitializeKeymanager(
|
||||
return keymanager, nil
|
||||
}
|
||||
|
||||
// WriteAccountToDisk creates an account directory under a unique namespace
|
||||
// within the wallet's path. It additionally writes the account's password to the
|
||||
// wallet's passwords directory. Returns the unique account name.
|
||||
func (w *Wallet) WriteAccountToDisk(ctx context.Context, password string) (string, error) {
|
||||
accountName, err := w.generateAccountName()
|
||||
// ListDirs in wallet accounts path.
|
||||
func (w *Wallet) ListDirs() ([]string, error) {
|
||||
accountsDir, err := os.Open(w.AccountsDir())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not generate unique account name")
|
||||
return nil, err
|
||||
}
|
||||
// Generate a directory for the new account name and
|
||||
// write its associated password to disk.
|
||||
accountPath := path.Join(w.accountsPath, accountName)
|
||||
if err := os.MkdirAll(accountPath, DirectoryPermissions); err != nil {
|
||||
return "", errors.Wrap(err, "could not create account directory")
|
||||
defer func() {
|
||||
if err := accountsDir.Close(); err != nil {
|
||||
log.WithField(
|
||||
"directory", w.AccountsDir(),
|
||||
).Errorf("Could not close accounts directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
list, err := accountsDir.Readdirnames(0) // 0 to read all files and folders.
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read files in directory: %s", w.AccountsDir())
|
||||
}
|
||||
if err := w.writePasswordToFile(accountName, password); err != nil {
|
||||
return "", errors.Wrap(err, "could not write password to disk")
|
||||
dirNames := make([]string, 0)
|
||||
for _, item := range list {
|
||||
ok, err := hasDir(filepath.Join(w.AccountsDir(), item))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse directory: %v", err)
|
||||
}
|
||||
if ok {
|
||||
dirNames = append(dirNames, item)
|
||||
}
|
||||
}
|
||||
return accountName, nil
|
||||
return dirNames, nil
|
||||
}
|
||||
|
||||
// WriteFileAtPath within the wallet directory given the desired path, filename, and raw data.
|
||||
@@ -282,9 +237,6 @@ func (w *Wallet) WriteFileAtPath(ctx context.Context, filePath string, fileName
|
||||
// ReadFileAtPath within the wallet directory given the desired path and filename.
|
||||
func (w *Wallet) ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error) {
|
||||
accountPath := path.Join(w.accountsPath, filePath)
|
||||
if err := os.MkdirAll(accountPath, os.ModePerm); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not create path: %s", accountPath)
|
||||
}
|
||||
fullPath := path.Join(accountPath, fileName)
|
||||
rawData, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
@@ -293,27 +245,14 @@ func (w *Wallet) ReadFileAtPath(ctx context.Context, filePath string, fileName s
|
||||
return rawData, nil
|
||||
}
|
||||
|
||||
// WriteFileForAccount stores a unique file and its data under an account namespace
|
||||
// in the wallet's directory on-disk. Creates the file if it does not exist
|
||||
// and writes over it otherwise.
|
||||
func (w *Wallet) WriteFileForAccount(ctx context.Context, accountName string, fileName string, data []byte) error {
|
||||
accountPath := path.Join(w.accountsPath, accountName)
|
||||
exists, err := hasDir(accountPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not check if account exists in directory: %s", w.accountsPath)
|
||||
// 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 := path.Join(w.accountsPath, KeymanagerConfigFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
return nil, fmt.Errorf("no keymanager config file found at path: %s", w.accountsPath)
|
||||
}
|
||||
if !exists {
|
||||
return errors.Wrapf(err, "account does not exist in wallet directory: %s", w.accountsPath)
|
||||
}
|
||||
filePath := path.Join(accountPath, fileName)
|
||||
if err := ioutil.WriteFile(filePath, data, os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "could not write %s", filePath)
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"name": accountName,
|
||||
"path": filePath,
|
||||
}).Debug("Wrote new file for account")
|
||||
return nil
|
||||
return os.Open(configFilePath)
|
||||
}
|
||||
|
||||
// WriteKeymanagerConfigToDisk takes an encoded keymanager config file
|
||||
@@ -328,10 +267,20 @@ func (w *Wallet) WriteKeymanagerConfigToDisk(ctx context.Context, encoded []byte
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadEncryptedSeedFromDisk reads the encrypted wallet seed configuration from
|
||||
// within the wallet path.
|
||||
func (w *Wallet) ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error) {
|
||||
configFilePath := path.Join(w.accountsPath, derived.EncryptedSeedFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
return nil, fmt.Errorf("no encrypted seed file found at path: %s", w.accountsPath)
|
||||
}
|
||||
return os.Open(configFilePath)
|
||||
}
|
||||
|
||||
// WriteEncryptedSeedToDisk writes the encrypted wallet seed configuration
|
||||
// within the wallet path.
|
||||
func (w *Wallet) WriteEncryptedSeedToDisk(ctx context.Context, encoded []byte) error {
|
||||
seedFilePath := path.Join(w.accountsPath, EncryptedSeedFileName)
|
||||
seedFilePath := path.Join(w.accountsPath, derived.EncryptedSeedFileName)
|
||||
// Write the config file to disk.
|
||||
if err := ioutil.WriteFile(seedFilePath, encoded, os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "could not write %s", seedFilePath)
|
||||
@@ -340,190 +289,25 @@ func (w *Wallet) WriteEncryptedSeedToDisk(ctx context.Context, encoded []byte) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadEncryptedSeedFromDisk reads the encrypted wallet seed configuration from
|
||||
// within the wallet path.
|
||||
func (w *Wallet) ReadEncryptedSeedFromDisk(ctx context.Context) (io.ReadCloser, error) {
|
||||
if !fileExists(path.Join(w.accountsPath, EncryptedSeedFileName)) {
|
||||
return nil, fmt.Errorf("no encrypted seed file found at path: %s", w.accountsPath)
|
||||
// ReadPasswordFromDisk --
|
||||
func (w *Wallet) ReadPasswordFromDisk(ctx context.Context, passwordFileName string) (string, error) {
|
||||
fullPath := filepath.Join(w.passwordsDir, passwordFileName)
|
||||
rawData, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not read %s", fullPath)
|
||||
}
|
||||
configFilePath := path.Join(w.accountsPath, EncryptedSeedFileName)
|
||||
return os.Open(configFilePath)
|
||||
return string(rawData), nil
|
||||
}
|
||||
|
||||
// ReadPasswordForAccount when given an account name from the wallet's passwords' path.
|
||||
func (w *Wallet) ReadPasswordForAccount(accountName string) (string, error) {
|
||||
passwordFilePath := path.Join(w.passwordsDir, accountName+PasswordFileSuffix)
|
||||
passwordFile, err := os.Open(passwordFilePath)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not read password file from directory: %s", w.passwordsDir)
|
||||
}
|
||||
defer func() {
|
||||
if err := passwordFile.Close(); err != nil {
|
||||
log.Errorf("Could not close password file: %s", passwordFilePath)
|
||||
}
|
||||
}()
|
||||
password, err := ioutil.ReadAll(passwordFile)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not read data from password file: %s", passwordFilePath)
|
||||
}
|
||||
return string(password), nil
|
||||
}
|
||||
|
||||
// ReadFileForAccount from the wallet's accounts directory.
|
||||
func (w *Wallet) ReadFileForAccount(accountName string, fileName string) ([]byte, error) {
|
||||
accountPath := path.Join(w.accountsPath, accountName)
|
||||
exists, err := hasDir(accountPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not check if account exists in directory: %s", w.accountsPath)
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.Wrapf(err, "account does not exist in wallet directory: %s", w.accountsPath)
|
||||
}
|
||||
filePath := path.Join(accountPath, fileName)
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read file for account: %s", filePath)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Errorf("Could not close file after writing: %s", filePath)
|
||||
}
|
||||
}()
|
||||
return ioutil.ReadAll(f)
|
||||
}
|
||||
|
||||
func (w *Wallet) enterPasswordForAccount(cliCtx *cli.Context, accountName string) error {
|
||||
au := aurora.NewAurora(true)
|
||||
|
||||
var password string
|
||||
var err error
|
||||
if cliCtx.IsSet(flags.PasswordFileFlag.Name) {
|
||||
passwordFilePath := cliCtx.String(flags.PasswordFileFlag.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 %s", accountName)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
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 = inputPasswordForAccount(cliCtx, accountName)
|
||||
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
|
||||
}
|
||||
|
||||
attemptingPassword = false
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(w.passwordsDir, params.BeaconIoConfig().ReadWriteExecutePermissions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.writePasswordToFile(accountName, password); err != nil {
|
||||
return errors.Wrap(err, "could not write password to disk")
|
||||
// WritePasswordToDisk --
|
||||
func (w *Wallet) WritePasswordToDisk(ctx context.Context, passwordFileName string, password string) error {
|
||||
passwordPath := filepath.Join(w.passwordsDir, passwordFileName)
|
||||
if err := ioutil.WriteFile(passwordPath, []byte(password), os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "could not write %s", passwordPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) checkPasswordForAccount(accountName string, password string) error {
|
||||
accountKeystore, err := w.keystoreForAccount(accountName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get keystore")
|
||||
}
|
||||
decryptor := keystorev4.New()
|
||||
_, err = decryptor.Decrypt(accountKeystore.Crypto, []byte(password))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not decrypt keystore")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) publicKeyForAccount(accountName string) ([48]byte, error) {
|
||||
accountKeystore, err := w.keystoreForAccount(accountName)
|
||||
if err != nil {
|
||||
return [48]byte{}, errors.Wrap(err, "could not get keystore")
|
||||
}
|
||||
pubKey, err := hex.DecodeString(accountKeystore.Pubkey)
|
||||
if err != nil {
|
||||
return [48]byte{}, errors.Wrap(err, "could decode pubkey string")
|
||||
}
|
||||
return bytesutil.ToBytes48(pubKey), nil
|
||||
}
|
||||
|
||||
func (w *Wallet) keystoreForAccount(accountName string) (*v2keymanager.Keystore, error) {
|
||||
encoded, err := w.ReadFileForAccount(accountName, direct.KeystoreFileName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read keystore file")
|
||||
}
|
||||
keystoreJSON := &v2keymanager.Keystore{}
|
||||
if err := json.Unmarshal(encoded, &keystoreJSON); err != nil {
|
||||
return nil, errors.Wrap(err, "could not decode json")
|
||||
}
|
||||
return keystoreJSON, nil
|
||||
}
|
||||
|
||||
// Writes the password file for an account namespace in the wallet's passwords directory.
|
||||
func (w *Wallet) writePasswordToFile(accountName string, password string) error {
|
||||
passwordFilePath := path.Join(w.passwordsDir, accountName+PasswordFileSuffix)
|
||||
// Removing any file that exists to make sure the existing is overwritten.
|
||||
if _, err := os.Stat(passwordFilePath); os.IsExist(err) {
|
||||
if err := os.Remove(passwordFilePath); err != nil {
|
||||
return errors.Wrap(err, "could not rewrite password file")
|
||||
}
|
||||
}
|
||||
passwordFile, err := os.Create(passwordFilePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create password file in directory: %s", w.passwordsDir)
|
||||
}
|
||||
defer func() {
|
||||
if err := passwordFile.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close password file")
|
||||
}
|
||||
}()
|
||||
n, err := passwordFile.WriteString(password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not write account password to disk")
|
||||
}
|
||||
if n != len(password) {
|
||||
return fmt.Errorf("could only write %d/%d password bytes to disk", n, len(password))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generates a human-readable name for an account. Checks for uniqueness in the accounts path.
|
||||
func (w *Wallet) generateAccountName() (string, error) {
|
||||
var accountExists bool
|
||||
var accountName string
|
||||
for !accountExists {
|
||||
accountName = petname.Generate(NumAccountWords, "-" /* separator */)
|
||||
exists, err := hasDir(path.Join(w.accountsPath, accountName))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not check if account exists in dir: %s", w.accountsPath)
|
||||
}
|
||||
if !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
return accountName, nil
|
||||
}
|
||||
|
||||
func readKeymanagerKindFromWalletPath(walletPath string) (v2keymanager.Kind, error) {
|
||||
walletItem, err := os.Open(walletPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -52,6 +52,8 @@ const (
|
||||
DepositTransactionFileName = "deposit_transaction.rlp"
|
||||
// DepositDataFileName for the raw, ssz-encoded deposit data object.
|
||||
DepositDataFileName = "deposit_data.ssz"
|
||||
// EncryptedSeedFileName for persisting a wallet's seed when using a derived keymanager.
|
||||
EncryptedSeedFileName = "seed.encrypted.json"
|
||||
)
|
||||
|
||||
// Config for a derived keymanager.
|
||||
@@ -99,7 +101,7 @@ func NewKeymanager(
|
||||
) (*Keymanager, error) {
|
||||
seedConfigFile, err := wallet.ReadEncryptedSeedFromDisk(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read encrypted seed configuration file from disk")
|
||||
return nil, errors.Wrap(err, "could not read encrypted seed file from disk")
|
||||
}
|
||||
enc, err := ioutil.ReadAll(seedConfigFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,13 +17,18 @@ go_library(
|
||||
"//shared/bls:go_default_library",
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/depositutil:go_default_library",
|
||||
"//shared/petnames:go_default_library",
|
||||
"//shared/roughtime:go_default_library",
|
||||
"//validator/accounts/v2/iface:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"@com_github_google_uuid//: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_prysmaticlabs_go_ssz//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,20 +7,28 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
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/depositutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/petnames"
|
||||
"github.com/prysmaticlabs/prysm/shared/roughtime"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -34,7 +42,9 @@ const (
|
||||
// file for a direct keymanager account.
|
||||
TimestampFileName = "created_at.txt"
|
||||
// KeystoreFileName exposes the expected filename for the keystore file for an account.
|
||||
KeystoreFileName = "keystore.json"
|
||||
KeystoreFileName = "keystore.json"
|
||||
// PasswordFileSuffix for passwords persisted as text to disk.
|
||||
PasswordFileSuffix = ".pass"
|
||||
depositDataFileName = "deposit_data.ssz"
|
||||
eipVersion = "EIP-2335"
|
||||
)
|
||||
@@ -70,7 +80,7 @@ func NewKeymanager(ctx context.Context, wallet iface.Wallet, cfg *Config) (*Keym
|
||||
// passphrases, then we initialize a cache of public key -> secret keys
|
||||
// used to retrieve secrets keys for the accounts via password unlock.
|
||||
// This cache is needed to process Sign requests using a public key.
|
||||
if err := k.initializeSecretKeysCache(); err != nil {
|
||||
if err := k.initializeSecretKeysCache(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize keys cache")
|
||||
}
|
||||
return k, nil
|
||||
@@ -100,20 +110,28 @@ func MarshalConfigFile(ctx context.Context, cfg *Config) ([]byte, error) {
|
||||
return json.MarshalIndent(cfg, "", "\t")
|
||||
}
|
||||
|
||||
// ValidatingAccountNames for a direct keymanager.
|
||||
func (dr *Keymanager) ValidatingAccountNames() ([]string, error) {
|
||||
return dr.wallet.ListDirs()
|
||||
}
|
||||
|
||||
// CreateAccount for a direct keymanager implementation. This utilizes
|
||||
// the EIP-2335 keystore standard for BLS12-381 keystores. It
|
||||
// stores the generated keystore.json file in the wallet and additionally
|
||||
// generates withdrawal credentials. At the end, it logs
|
||||
// the raw deposit data hex string for users to copy.
|
||||
func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (string, error) {
|
||||
// Create a new, unique account name and write its password + directory to disk.
|
||||
accountName, err := dr.wallet.WriteAccountToDisk(ctx, password)
|
||||
// Create a petname for an account from its public key and write its password to disk.
|
||||
validatingKey := bls.RandKey()
|
||||
accountName, err := dr.generateAccountName(validatingKey.PublicKey().Marshal())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not write account to disk")
|
||||
return "", errors.Wrap(err, "could not generate unique account name")
|
||||
}
|
||||
if err := dr.wallet.WritePasswordToDisk(ctx, accountName+".pass", password); err != nil {
|
||||
return "", errors.Wrap(err, "could not write password to disk")
|
||||
}
|
||||
// Generates a new EIP-2335 compliant keystore file
|
||||
// from a BLS private key and marshals it as JSON.
|
||||
validatingKey := bls.RandKey()
|
||||
encoded, err := dr.generateKeystoreFile(validatingKey, password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -145,7 +163,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
|
||||
depositutil.LogDepositTransaction(log, tx)
|
||||
|
||||
// We write the raw deposit transaction as an .rlp encoded file.
|
||||
if err := dr.wallet.WriteFileForAccount(ctx, accountName, DepositTransactionFileName, tx.Data()); err != nil {
|
||||
if err := dr.wallet.WriteFileAtPath(ctx, accountName, DepositTransactionFileName, tx.Data()); err != nil {
|
||||
return "", errors.Wrapf(err, "could not write for account %s: %s", accountName, DepositTransactionFileName)
|
||||
}
|
||||
|
||||
@@ -154,19 +172,19 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not marshal deposit data")
|
||||
}
|
||||
if err := dr.wallet.WriteFileForAccount(ctx, accountName, depositDataFileName, encodedDepositData); err != nil {
|
||||
if err := dr.wallet.WriteFileAtPath(ctx, accountName, depositDataFileName, encodedDepositData); err != nil {
|
||||
return "", errors.Wrapf(err, "could not write for account %s: %s", accountName, encodedDepositData)
|
||||
}
|
||||
|
||||
// Write the encoded keystore to disk.
|
||||
if err := dr.wallet.WriteFileForAccount(ctx, accountName, KeystoreFileName, encoded); err != nil {
|
||||
if err := dr.wallet.WriteFileAtPath(ctx, accountName, KeystoreFileName, encoded); err != nil {
|
||||
return "", errors.Wrapf(err, "could not write keystore file for account %s", accountName)
|
||||
}
|
||||
|
||||
// Finally, write the account creation timestamp as a file.
|
||||
createdAt := roughtime.Now().Unix()
|
||||
createdAtStr := strconv.FormatInt(createdAt, 10)
|
||||
if err := dr.wallet.WriteFileForAccount(ctx, accountName, TimestampFileName, []byte(createdAtStr)); err != nil {
|
||||
if err := dr.wallet.WriteFileAtPath(ctx, accountName, TimestampFileName, []byte(createdAtStr)); err != nil {
|
||||
return "", errors.Wrapf(err, "could not write timestamp file for account %s", accountName)
|
||||
}
|
||||
|
||||
@@ -179,7 +197,7 @@ func (dr *Keymanager) CreateAccount(ctx context.Context, password string) (strin
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of public keys from the direct account keystores.
|
||||
func (dr *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
|
||||
accountNames, err := dr.wallet.AccountNames()
|
||||
accountNames, err := dr.ValidatingAccountNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,7 +217,7 @@ func (dr *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte
|
||||
}
|
||||
|
||||
for i, name := range accountNames {
|
||||
encoded, err := dr.wallet.ReadFileForAccount(name, KeystoreFileName)
|
||||
encoded, err := dr.wallet.ReadFileAtPath(ctx, name, KeystoreFileName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read keystore file for account %s", name)
|
||||
}
|
||||
@@ -231,18 +249,91 @@ func (dr *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (b
|
||||
return secretKey.Sign(req.SigningRoot), nil
|
||||
}
|
||||
|
||||
func (dr *Keymanager) initializeSecretKeysCache() error {
|
||||
accountNames, err := dr.wallet.AccountNames()
|
||||
// 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 (dr *Keymanager) EnterPasswordForAccount(cliCtx *cli.Context, accountName string) error {
|
||||
au := aurora.NewAurora(true)
|
||||
var password string
|
||||
var err error
|
||||
if cliCtx.IsSet(flags.PasswordFileFlag.Name) {
|
||||
passwordFilePath := cliCtx.String(flags.PasswordFileFlag.Name)
|
||||
data, err := ioutil.ReadFile(passwordFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password = string(data)
|
||||
err = dr.checkPasswordForAccount(accountName, password)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid checksum") {
|
||||
return fmt.Errorf("invalid password entered for account %s", accountName)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
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 = inputPasswordForAccount(cliCtx, accountName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not input password")
|
||||
}
|
||||
err = dr.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
|
||||
}
|
||||
|
||||
attemptingPassword = false
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := dr.wallet.WritePasswordToDisk(ctx, accountName+PasswordFileSuffix, password); err != nil {
|
||||
return errors.Wrap(err, "could not write password to disk")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublicKeyForAccount returns the associated public key for an account name.
|
||||
func (dr *Keymanager) PublicKeyForAccount(accountName string) ([48]byte, error) {
|
||||
accountKeystore, err := dr.keystoreForAccount(accountName)
|
||||
if err != nil {
|
||||
return [48]byte{}, errors.Wrap(err, "could not get keystore")
|
||||
}
|
||||
pubKey, err := hex.DecodeString(accountKeystore.Pubkey)
|
||||
if err != nil {
|
||||
return [48]byte{}, errors.Wrap(err, "could decode pubkey string")
|
||||
}
|
||||
return bytesutil.ToBytes48(pubKey), nil
|
||||
}
|
||||
|
||||
func (dr *Keymanager) keystoreForAccount(accountName string) (*v2keymanager.Keystore, error) {
|
||||
encoded, err := dr.wallet.ReadFileAtPath(context.Background(), accountName, KeystoreFileName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read keystore file")
|
||||
}
|
||||
keystoreJSON := &v2keymanager.Keystore{}
|
||||
if err := json.Unmarshal(encoded, &keystoreJSON); err != nil {
|
||||
return nil, errors.Wrap(err, "could not decode json")
|
||||
}
|
||||
return keystoreJSON, nil
|
||||
}
|
||||
|
||||
func (dr *Keymanager) initializeSecretKeysCache(ctx context.Context) error {
|
||||
accountNames, err := dr.ValidatingAccountNames()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range accountNames {
|
||||
password, err := dr.wallet.ReadPasswordForAccount(name)
|
||||
password, err := dr.wallet.ReadPasswordFromDisk(ctx, name+PasswordFileSuffix)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read password for account %s", name)
|
||||
}
|
||||
encoded, err := dr.wallet.ReadFileForAccount(name, KeystoreFileName)
|
||||
encoded, err := dr.wallet.ReadFileAtPath(ctx, name, KeystoreFileName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read keystore file for account %s", name)
|
||||
}
|
||||
@@ -289,3 +380,53 @@ func (dr *Keymanager) generateKeystoreFile(validatingKey bls.SecretKey, password
|
||||
}
|
||||
return json.MarshalIndent(keystoreFile, "", "\t")
|
||||
}
|
||||
|
||||
func (dr *Keymanager) generateAccountName(pubKey []byte) (string, error) {
|
||||
var accountExists bool
|
||||
var accountName string
|
||||
for !accountExists {
|
||||
accountName = petnames.DeterministicName(pubKey, "-")
|
||||
exists, err := hasDir(filepath.Join(dr.wallet.AccountsDir(), accountName))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not check if account exists in dir: %s", dr.wallet.AccountsDir())
|
||||
}
|
||||
if !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
return accountName, nil
|
||||
}
|
||||
|
||||
func (dr *Keymanager) checkPasswordForAccount(accountName string, password string) error {
|
||||
accountKeystore, err := dr.keystoreForAccount(accountName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get keystore")
|
||||
}
|
||||
decryptor := keystorev4.New()
|
||||
_, err = decryptor.Decrypt(accountKeystore.Crypto, []byte(password))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not decrypt keystore")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a directory indeed exists at the specified path.
|
||||
func hasDir(dirPath string) (bool, error) {
|
||||
info, err := os.Stat(dirPath)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return info.IsDir(), err
|
||||
}
|
||||
|
||||
func inputPasswordForAccount(_ *cli.Context, accountName string) (string, error) {
|
||||
prompt := promptui.Prompt{
|
||||
Label: fmt.Sprintf("Enter password for account %s", accountName),
|
||||
Mask: '*',
|
||||
}
|
||||
walletPassword, err := prompt.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read wallet password: %v", err)
|
||||
}
|
||||
return walletPassword, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestKeymanager_CreateAccount(t *testing.T) {
|
||||
func TestDirectKeymanager_CreateAccount(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
@@ -72,7 +72,7 @@ func TestKeymanager_CreateAccount(t *testing.T) {
|
||||
testutil.AssertLogsContain(t, hook, "Successfully created new validator account")
|
||||
}
|
||||
|
||||
func TestKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
func TestDirectKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
@@ -83,8 +83,9 @@ func TestKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
}
|
||||
// First, generate accounts and their keystore.json files.
|
||||
ctx := context.Background()
|
||||
numAccounts := 20
|
||||
wantedPublicKeys := generateAccounts(t, numAccounts, dr)
|
||||
numAccounts := 1
|
||||
accountNames, wantedPublicKeys := generateAccounts(t, numAccounts, dr)
|
||||
wallet.Directories = accountNames
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
// The results are not guaranteed to be ordered, so we ensure each
|
||||
@@ -100,7 +101,7 @@ func TestKeymanager_FetchValidatingPublicKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeymanager_Sign(t *testing.T) {
|
||||
func TestDirectKeymanager_Sign(t *testing.T) {
|
||||
wallet := &mock.Wallet{
|
||||
Files: make(map[string]map[string][]byte),
|
||||
AccountPasswords: make(map[string]string),
|
||||
@@ -112,9 +113,11 @@ func TestKeymanager_Sign(t *testing.T) {
|
||||
|
||||
// First, generate accounts and their keystore.json files.
|
||||
numAccounts := 2
|
||||
generateAccounts(t, numAccounts, dr)
|
||||
accountNames, _ := generateAccounts(t, numAccounts, dr)
|
||||
wallet.Directories = accountNames
|
||||
|
||||
ctx := context.Background()
|
||||
require.NoError(t, dr.initializeSecretKeysCache())
|
||||
require.NoError(t, dr.initializeSecretKeysCache(ctx))
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -137,7 +140,7 @@ func TestKeymanager_Sign(t *testing.T) {
|
||||
t.Fatalf("Expected sig not to verify for pubkey %#x and data %v", wrongPubKey.Marshal(), data)
|
||||
}
|
||||
}
|
||||
func TestKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
func TestDirectKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: nil,
|
||||
}
|
||||
@@ -146,7 +149,7 @@ func TestKeymanager_Sign_NoPublicKeySpecified(t *testing.T) {
|
||||
assert.ErrorContains(t, "nil public key", err)
|
||||
}
|
||||
|
||||
func TestKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
|
||||
func TestDirectKeymanager_Sign_NoPublicKeyInCache(t *testing.T) {
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: []byte("hello world"),
|
||||
}
|
||||
@@ -178,8 +181,9 @@ func BenchmarkKeymanager_FetchValidatingPublicKeys(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func generateAccounts(t testing.TB, numAccounts int, dr *Keymanager) [][48]byte {
|
||||
func generateAccounts(t testing.TB, numAccounts int, dr *Keymanager) ([]string, [][48]byte) {
|
||||
ctx := context.Background()
|
||||
accountNames := make([]string, numAccounts)
|
||||
wantedPublicKeys := make([][48]byte, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
validatingKey := bls.RandKey()
|
||||
@@ -187,9 +191,11 @@ func generateAccounts(t testing.TB, numAccounts int, dr *Keymanager) [][48]byte
|
||||
password := strconv.Itoa(i)
|
||||
encoded, err := dr.generateKeystoreFile(validatingKey, password)
|
||||
require.NoError(t, err)
|
||||
accountName, err := dr.wallet.WriteAccountToDisk(ctx, password)
|
||||
accountName, err := dr.generateAccountName(validatingKey.PublicKey().Marshal())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, dr.wallet.WriteFileForAccount(ctx, accountName, KeystoreFileName, encoded))
|
||||
assert.NoError(t, err, dr.wallet.WriteFileAtPath(ctx, accountName, KeystoreFileName, encoded))
|
||||
assert.NoError(t, err, dr.wallet.WritePasswordToDisk(ctx, accountName+PasswordFileSuffix, password))
|
||||
accountNames[i] = accountName
|
||||
}
|
||||
return wantedPublicKeys
|
||||
return accountNames, wantedPublicKeys
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user