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 }