github.com/prysmaticlabs/prysm@v1.4.4/validator/rpc/wallet_test.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/golang/protobuf/ptypes/empty"
    12  	"github.com/google/uuid"
    13  	pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    14  	"github.com/prysmaticlabs/prysm/shared/bls"
    15  	"github.com/prysmaticlabs/prysm/shared/event"
    16  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    17  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    18  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    19  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    20  	"github.com/prysmaticlabs/prysm/validator/accounts"
    21  	"github.com/prysmaticlabs/prysm/validator/accounts/iface"
    22  	"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
    23  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    24  	"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
    25  	keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
    26  )
    27  
    28  const strongPass = "29384283xasjasd32%%&*@*#*"
    29  
    30  func TestServer_CreateWallet_Imported(t *testing.T) {
    31  	localWalletDir := setupWalletDir(t)
    32  	defaultWalletPath = localWalletDir
    33  	ctx := context.Background()
    34  	s := &Server{
    35  		walletInitializedFeed: new(event.Feed),
    36  		walletDir:             defaultWalletPath,
    37  	}
    38  	_, err := s.Signup(ctx, &pb.AuthRequest{
    39  		Password:             strongPass,
    40  		PasswordConfirmation: strongPass,
    41  	})
    42  	require.NoError(t, err)
    43  	req := &pb.CreateWalletRequest{
    44  		Keymanager:     pb.KeymanagerKind_IMPORTED,
    45  		WalletPassword: strongPass,
    46  	}
    47  	// We delete the directory at defaultWalletPath as CreateWallet will return an error if it tries to create a wallet
    48  	// where a directory already exists
    49  	require.NoError(t, os.RemoveAll(defaultWalletPath))
    50  	_, err = s.CreateWallet(ctx, req)
    51  	require.NoError(t, err)
    52  
    53  	importReq := &pb.ImportKeystoresRequest{
    54  		KeystoresPassword: strongPass,
    55  		KeystoresImported: []string{"badjson"},
    56  	}
    57  	_, err = s.ImportKeystores(ctx, importReq)
    58  	require.ErrorContains(t, "Not a valid EIP-2335 keystore", err)
    59  
    60  	encryptor := keystorev4.New()
    61  	keystores := make([]string, 3)
    62  	for i := 0; i < len(keystores); i++ {
    63  		privKey, err := bls.RandKey()
    64  		require.NoError(t, err)
    65  		pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal())
    66  		id, err := uuid.NewRandom()
    67  		require.NoError(t, err)
    68  		cryptoFields, err := encryptor.Encrypt(privKey.Marshal(), strongPass)
    69  		require.NoError(t, err)
    70  		item := &keymanager.Keystore{
    71  			Crypto:  cryptoFields,
    72  			ID:      id.String(),
    73  			Version: encryptor.Version(),
    74  			Pubkey:  pubKey,
    75  			Name:    encryptor.Name(),
    76  		}
    77  		encodedFile, err := json.MarshalIndent(item, "", "\t")
    78  		require.NoError(t, err)
    79  		keystores[i] = string(encodedFile)
    80  	}
    81  	importReq.KeystoresImported = keystores
    82  	_, err = s.ImportKeystores(ctx, importReq)
    83  	require.NoError(t, err)
    84  }
    85  
    86  func TestServer_RecoverWallet_Derived(t *testing.T) {
    87  	localWalletDir := setupWalletDir(t)
    88  	ctx := context.Background()
    89  	s := &Server{
    90  		walletInitializedFeed: new(event.Feed),
    91  		walletDir:             localWalletDir,
    92  	}
    93  	req := &pb.RecoverWalletRequest{
    94  		WalletPassword: strongPass,
    95  		NumAccounts:    0,
    96  	}
    97  	// We delete the directory at defaultWalletPath as RecoverWallet will return an error if it tries to create a wallet
    98  	// where a directory already exists
    99  	require.NoError(t, os.RemoveAll(localWalletDir))
   100  	_, err := s.RecoverWallet(ctx, req)
   101  	require.ErrorContains(t, "Must create at least 1 validator account", err)
   102  
   103  	req.NumAccounts = 2
   104  	req.Language = "Swahili"
   105  	_, err = s.RecoverWallet(ctx, req)
   106  	require.ErrorContains(t, "input not in the list of supported languages", err)
   107  
   108  	req.Language = "ENglish"
   109  	_, err = s.RecoverWallet(ctx, req)
   110  	require.ErrorContains(t, "invalid mnemonic in request", err)
   111  
   112  	mnemonicResp, err := s.GenerateMnemonic(ctx, &empty.Empty{})
   113  	require.NoError(t, err)
   114  	req.Mnemonic = mnemonicResp.Mnemonic
   115  
   116  	req.Mnemonic25ThWord = " "
   117  	_, err = s.RecoverWallet(ctx, req)
   118  	require.ErrorContains(t, "mnemonic 25th word cannot be empty", err)
   119  	req.Mnemonic25ThWord = "outer"
   120  
   121  	// Test weak password.
   122  	req.WalletPassword = "123qwe"
   123  	_, err = s.RecoverWallet(ctx, req)
   124  	require.ErrorContains(t, "password did not pass validation", err)
   125  
   126  	req.WalletPassword = strongPass
   127  	// Create(derived) should fail then test recover.
   128  	reqCreate := &pb.CreateWalletRequest{
   129  		Keymanager:     pb.KeymanagerKind_DERIVED,
   130  		WalletPassword: strongPass,
   131  		NumAccounts:    2,
   132  		Mnemonic:       mnemonicResp.Mnemonic,
   133  	}
   134  	_, err = s.CreateWallet(ctx, reqCreate)
   135  	require.ErrorContains(t, "create wallet not supported through web", err, "Create wallet for DERIVED or REMOTE types not supported through web, either import keystore or recover")
   136  
   137  	// This defer will be the last to execute in this func.
   138  	resetCfgFalse := featureconfig.InitWithReset(&featureconfig.Flags{
   139  		WriteWalletPasswordOnWebOnboarding: false,
   140  	})
   141  	defer resetCfgFalse()
   142  
   143  	resetCfgTrue := featureconfig.InitWithReset(&featureconfig.Flags{
   144  		WriteWalletPasswordOnWebOnboarding: true,
   145  	})
   146  	defer resetCfgTrue()
   147  
   148  	// Finally test recover.
   149  	_, err = s.RecoverWallet(ctx, req)
   150  	require.NoError(t, err)
   151  
   152  	// Password File should have been written.
   153  	passwordFilePath := filepath.Join(localWalletDir, wallet.DefaultWalletPasswordFile)
   154  	assert.Equal(t, true, fileutil.FileExists(passwordFilePath))
   155  
   156  	// Attempting to write again should trigger an error.
   157  	err = writeWalletPasswordToDisk(localWalletDir, "somepassword")
   158  	require.ErrorContains(t, "cannot write wallet password file as it already exists", err)
   159  
   160  }
   161  
   162  func TestServer_WalletConfig_NoWalletFound(t *testing.T) {
   163  	s := &Server{}
   164  	resp, err := s.WalletConfig(context.Background(), &empty.Empty{})
   165  	require.NoError(t, err)
   166  	assert.DeepEqual(t, resp, &pb.WalletResponse{})
   167  }
   168  
   169  func TestServer_WalletConfig(t *testing.T) {
   170  	localWalletDir := setupWalletDir(t)
   171  	defaultWalletPath = localWalletDir
   172  	ctx := context.Background()
   173  	s := &Server{
   174  		walletInitializedFeed: new(event.Feed),
   175  		walletDir:             defaultWalletPath,
   176  	}
   177  	// We attempt to create the wallet.
   178  	w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
   179  		WalletCfg: &wallet.Config{
   180  			WalletDir:      defaultWalletPath,
   181  			KeymanagerKind: keymanager.Imported,
   182  			WalletPassword: strongPass,
   183  		},
   184  		SkipMnemonicConfirm: true,
   185  	})
   186  	require.NoError(t, err)
   187  	km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   188  	require.NoError(t, err)
   189  	s.wallet = w
   190  	s.keymanager = km
   191  	resp, err := s.WalletConfig(ctx, &empty.Empty{})
   192  	require.NoError(t, err)
   193  
   194  	assert.DeepEqual(t, resp, &pb.WalletResponse{
   195  		WalletPath:     localWalletDir,
   196  		KeymanagerKind: pb.KeymanagerKind_IMPORTED,
   197  	})
   198  }
   199  
   200  func TestServer_ImportKeystores_FailedPreconditions_WrongKeymanagerKind(t *testing.T) {
   201  	localWalletDir := setupWalletDir(t)
   202  	defaultWalletPath = localWalletDir
   203  	ctx := context.Background()
   204  	w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
   205  		WalletCfg: &wallet.Config{
   206  			WalletDir:      defaultWalletPath,
   207  			KeymanagerKind: keymanager.Derived,
   208  			WalletPassword: strongPass,
   209  		},
   210  		SkipMnemonicConfirm: true,
   211  	})
   212  	require.NoError(t, err)
   213  	km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   214  	require.NoError(t, err)
   215  	ss := &Server{
   216  		wallet:     w,
   217  		keymanager: km,
   218  	}
   219  	_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
   220  	assert.ErrorContains(t, "Only imported wallets can import more", err)
   221  }
   222  
   223  func TestServer_ImportKeystores_FailedPreconditions(t *testing.T) {
   224  	localWalletDir := setupWalletDir(t)
   225  	defaultWalletPath = localWalletDir
   226  	ctx := context.Background()
   227  	w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
   228  		WalletCfg: &wallet.Config{
   229  			WalletDir:      defaultWalletPath,
   230  			KeymanagerKind: keymanager.Imported,
   231  			WalletPassword: strongPass,
   232  		},
   233  		SkipMnemonicConfirm: true,
   234  	})
   235  	require.NoError(t, err)
   236  	km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   237  	require.NoError(t, err)
   238  	ss := &Server{
   239  		keymanager: km,
   240  	}
   241  	_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
   242  	assert.ErrorContains(t, "No wallet initialized", err)
   243  	ss.wallet = w
   244  	_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{})
   245  	assert.ErrorContains(t, "Password required for keystores", err)
   246  	_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
   247  		KeystoresPassword: strongPass,
   248  	})
   249  	assert.ErrorContains(t, "No keystores included for import", err)
   250  	_, err = ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
   251  		KeystoresPassword: strongPass,
   252  		KeystoresImported: []string{"badjson"},
   253  	})
   254  	assert.ErrorContains(t, "Not a valid EIP-2335 keystore", err)
   255  }
   256  
   257  func TestServer_ImportKeystores_OK(t *testing.T) {
   258  	imported.ResetCaches()
   259  	localWalletDir := setupWalletDir(t)
   260  	defaultWalletPath = localWalletDir
   261  	ctx := context.Background()
   262  	w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
   263  		WalletCfg: &wallet.Config{
   264  			WalletDir:      defaultWalletPath,
   265  			KeymanagerKind: keymanager.Imported,
   266  			WalletPassword: strongPass,
   267  		},
   268  		SkipMnemonicConfirm: true,
   269  	})
   270  	require.NoError(t, err)
   271  	km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   272  	require.NoError(t, err)
   273  	ss := &Server{
   274  		keymanager:            km,
   275  		wallet:                w,
   276  		walletInitializedFeed: new(event.Feed),
   277  	}
   278  
   279  	// Create 3 keystores.
   280  	encryptor := keystorev4.New()
   281  	keystores := make([]string, 3)
   282  	pubKeys := make([][]byte, 3)
   283  	for i := 0; i < len(keystores); i++ {
   284  		privKey, err := bls.RandKey()
   285  		require.NoError(t, err)
   286  		pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal())
   287  		id, err := uuid.NewRandom()
   288  		require.NoError(t, err)
   289  		cryptoFields, err := encryptor.Encrypt(privKey.Marshal(), strongPass)
   290  		require.NoError(t, err)
   291  		item := &keymanager.Keystore{
   292  			Crypto:  cryptoFields,
   293  			ID:      id.String(),
   294  			Version: encryptor.Version(),
   295  			Pubkey:  pubKey,
   296  			Name:    encryptor.Name(),
   297  		}
   298  		encodedFile, err := json.MarshalIndent(item, "", "\t")
   299  		require.NoError(t, err)
   300  		keystores[i] = string(encodedFile)
   301  		pubKeys[i] = privKey.PublicKey().Marshal()
   302  	}
   303  
   304  	// Check the wallet has no accounts to start with.
   305  	keys, err := km.FetchValidatingPublicKeys(ctx)
   306  	require.NoError(t, err)
   307  	assert.Equal(t, 0, len(keys))
   308  
   309  	// Import the 3 keystores and verify the wallet has 3 new accounts.
   310  	res, err := ss.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
   311  		KeystoresPassword: strongPass,
   312  		KeystoresImported: keystores,
   313  	})
   314  	require.NoError(t, err)
   315  	assert.DeepEqual(t, &pb.ImportKeystoresResponse{
   316  		ImportedPublicKeys: pubKeys,
   317  	}, res)
   318  
   319  	km, err = w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   320  	require.NoError(t, err)
   321  	keys, err = km.FetchValidatingPublicKeys(ctx)
   322  	require.NoError(t, err)
   323  	assert.Equal(t, 3, len(keys))
   324  }
   325  
   326  func Test_writeWalletPasswordToDisk(t *testing.T) {
   327  	walletDir := setupWalletDir(t)
   328  	resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
   329  		WriteWalletPasswordOnWebOnboarding: false,
   330  	})
   331  	defer resetCfg()
   332  	err := writeWalletPasswordToDisk(walletDir, "somepassword")
   333  	require.NoError(t, err)
   334  
   335  	// Expected a silent failure if the feature flag is not enabled.
   336  	passwordFilePath := filepath.Join(walletDir, wallet.DefaultWalletPasswordFile)
   337  	assert.Equal(t, false, fileutil.FileExists(passwordFilePath))
   338  	resetCfg = featureconfig.InitWithReset(&featureconfig.Flags{
   339  		WriteWalletPasswordOnWebOnboarding: true,
   340  	})
   341  	defer resetCfg()
   342  	err = writeWalletPasswordToDisk(walletDir, "somepassword")
   343  	require.NoError(t, err)
   344  
   345  	// File should have been written.
   346  	assert.Equal(t, true, fileutil.FileExists(passwordFilePath))
   347  
   348  	// Attempting to write again should trigger an error.
   349  	err = writeWalletPasswordToDisk(walletDir, "somepassword")
   350  	require.NotNil(t, err)
   351  }
   352  
   353  func createImportedWalletWithAccounts(t testing.TB, numAccounts int) (*Server, [][]byte) {
   354  	localWalletDir := setupWalletDir(t)
   355  	defaultWalletPath = localWalletDir
   356  	ctx := context.Background()
   357  	w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
   358  		WalletCfg: &wallet.Config{
   359  			WalletDir:      defaultWalletPath,
   360  			KeymanagerKind: keymanager.Imported,
   361  			WalletPassword: strongPass,
   362  		},
   363  		SkipMnemonicConfirm: true,
   364  	})
   365  	require.NoError(t, err)
   366  
   367  	km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   368  	require.NoError(t, err)
   369  	s := &Server{
   370  		keymanager:            km,
   371  		wallet:                w,
   372  		walletDir:             defaultWalletPath,
   373  		walletInitializedFeed: new(event.Feed),
   374  	}
   375  	// First we import accounts into the wallet.
   376  	encryptor := keystorev4.New()
   377  	keystores := make([]string, numAccounts)
   378  	pubKeys := make([][]byte, len(keystores))
   379  	for i := 0; i < len(keystores); i++ {
   380  		privKey, err := bls.RandKey()
   381  		require.NoError(t, err)
   382  		pubKey := fmt.Sprintf("%x", privKey.PublicKey().Marshal())
   383  		id, err := uuid.NewRandom()
   384  		require.NoError(t, err)
   385  		cryptoFields, err := encryptor.Encrypt(privKey.Marshal(), strongPass)
   386  		require.NoError(t, err)
   387  		item := &keymanager.Keystore{
   388  			Crypto:  cryptoFields,
   389  			ID:      id.String(),
   390  			Version: encryptor.Version(),
   391  			Pubkey:  pubKey,
   392  			Name:    encryptor.Name(),
   393  		}
   394  		encodedFile, err := json.MarshalIndent(item, "", "\t")
   395  		require.NoError(t, err)
   396  		keystores[i] = string(encodedFile)
   397  		pubKeys[i] = privKey.PublicKey().Marshal()
   398  	}
   399  	_, err = s.ImportKeystores(ctx, &pb.ImportKeystoresRequest{
   400  		KeystoresImported: keystores,
   401  		KeystoresPassword: strongPass,
   402  	})
   403  	require.NoError(t, err)
   404  	s.keymanager, err = s.wallet.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
   405  	require.NoError(t, err)
   406  	return s, pubKeys
   407  }