github.com/prysmaticlabs/prysm@v1.4.4/validator/accounts/accounts_import_test.go (about) 1 package accounts 2 3 import ( 4 "crypto/rand" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "math/big" 9 "os" 10 "path/filepath" 11 "sort" 12 "testing" 13 "time" 14 15 "github.com/google/uuid" 16 "github.com/prysmaticlabs/prysm/shared/bls" 17 "github.com/prysmaticlabs/prysm/shared/bytesutil" 18 "github.com/prysmaticlabs/prysm/shared/params" 19 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 20 "github.com/prysmaticlabs/prysm/shared/testutil/require" 21 "github.com/prysmaticlabs/prysm/shared/timeutils" 22 "github.com/prysmaticlabs/prysm/validator/accounts/iface" 23 "github.com/prysmaticlabs/prysm/validator/accounts/wallet" 24 "github.com/prysmaticlabs/prysm/validator/keymanager" 25 "github.com/prysmaticlabs/prysm/validator/keymanager/imported" 26 keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" 27 ) 28 29 func TestImport_Noninteractive(t *testing.T) { 30 imported.ResetCaches() 31 walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t) 32 keysDir := filepath.Join(t.TempDir(), "keysDir") 33 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 34 35 cliCtx := setupWalletCtx(t, &testWalletConfig{ 36 walletDir: walletDir, 37 passwordsDir: passwordsDir, 38 keysDir: keysDir, 39 keymanagerKind: keymanager.Imported, 40 walletPasswordFile: passwordFilePath, 41 accountPasswordFile: passwordFilePath, 42 }) 43 w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 44 WalletCfg: &wallet.Config{ 45 WalletDir: walletDir, 46 KeymanagerKind: keymanager.Imported, 47 WalletPassword: password, 48 }, 49 }) 50 require.NoError(t, err) 51 keymanager, err := imported.NewKeymanager( 52 cliCtx.Context, 53 &imported.SetupConfig{ 54 Wallet: w, 55 ListenForChanges: false, 56 }, 57 ) 58 require.NoError(t, err) 59 60 // Make sure there are no accounts at the start. 61 accounts, err := keymanager.ValidatingAccountNames() 62 require.NoError(t, err) 63 assert.Equal(t, len(accounts), 0) 64 65 // Create 2 keys. 66 createKeystore(t, keysDir) 67 time.Sleep(time.Second) 68 createKeystore(t, keysDir) 69 70 require.NoError(t, ImportAccountsCli(cliCtx)) 71 72 w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{ 73 WalletDir: walletDir, 74 WalletPassword: password, 75 }) 76 require.NoError(t, err) 77 km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) 78 require.NoError(t, err) 79 keys, err := km.FetchValidatingPublicKeys(cliCtx.Context) 80 require.NoError(t, err) 81 82 assert.Equal(t, 2, len(keys)) 83 } 84 85 // TestImport_DuplicateKeys is a regression test that ensures correction function if duplicate keys are being imported 86 func TestImport_DuplicateKeys(t *testing.T) { 87 imported.ResetCaches() 88 walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t) 89 keysDir := filepath.Join(t.TempDir(), "keysDir") 90 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 91 92 cliCtx := setupWalletCtx(t, &testWalletConfig{ 93 walletDir: walletDir, 94 passwordsDir: passwordsDir, 95 keysDir: keysDir, 96 keymanagerKind: keymanager.Imported, 97 walletPasswordFile: passwordFilePath, 98 accountPasswordFile: passwordFilePath, 99 }) 100 w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 101 WalletCfg: &wallet.Config{ 102 WalletDir: walletDir, 103 KeymanagerKind: keymanager.Imported, 104 WalletPassword: password, 105 }, 106 }) 107 require.NoError(t, err) 108 109 // Create a key and then copy it to create a duplicate 110 _, keystorePath := createKeystore(t, keysDir) 111 time.Sleep(time.Second) 112 input, err := ioutil.ReadFile(keystorePath) 113 require.NoError(t, err) 114 keystorePath2 := filepath.Join(keysDir, "copyOfKeystore.json") 115 err = ioutil.WriteFile(keystorePath2, input, os.ModePerm) 116 require.NoError(t, err) 117 118 require.NoError(t, ImportAccountsCli(cliCtx)) 119 120 _, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{ 121 WalletDir: walletDir, 122 WalletPassword: password, 123 }) 124 require.NoError(t, err) 125 km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) 126 require.NoError(t, err) 127 keys, err := km.FetchValidatingPublicKeys(cliCtx.Context) 128 require.NoError(t, err) 129 130 // There should only be 1 account as the duplicate keystore was ignored 131 assert.Equal(t, 1, len(keys)) 132 } 133 134 // TestImport_NonImportedWallet is a regression test that ensures non-silent failure when importing to non-imported wallets 135 func TestImport_NonImportedWallet(t *testing.T) { 136 walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t) 137 keysDir := filepath.Join(t.TempDir(), "keysDir") 138 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 139 140 cliCtx := setupWalletCtx(t, &testWalletConfig{ 141 walletDir: walletDir, 142 passwordsDir: passwordsDir, 143 keysDir: keysDir, 144 keymanagerKind: keymanager.Derived, 145 walletPasswordFile: passwordFilePath, 146 }) 147 _, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 148 WalletCfg: &wallet.Config{ 149 WalletDir: walletDir, 150 KeymanagerKind: keymanager.Derived, 151 WalletPassword: password, 152 }, 153 }) 154 require.NoError(t, err) 155 156 // Create a key 157 createKeystore(t, keysDir) 158 require.ErrorContains(t, "only imported wallets", ImportAccountsCli(cliCtx)) 159 } 160 161 func TestImport_Noninteractive_RandomName(t *testing.T) { 162 imported.ResetCaches() 163 walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t) 164 keysDir := filepath.Join(t.TempDir(), "keysDir") 165 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 166 167 cliCtx := setupWalletCtx(t, &testWalletConfig{ 168 walletDir: walletDir, 169 passwordsDir: passwordsDir, 170 keysDir: keysDir, 171 keymanagerKind: keymanager.Imported, 172 walletPasswordFile: passwordFilePath, 173 accountPasswordFile: passwordFilePath, 174 }) 175 w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 176 WalletCfg: &wallet.Config{ 177 WalletDir: walletDir, 178 KeymanagerKind: keymanager.Imported, 179 WalletPassword: password, 180 }, 181 }) 182 require.NoError(t, err) 183 keymanager, err := imported.NewKeymanager( 184 cliCtx.Context, 185 &imported.SetupConfig{ 186 Wallet: w, 187 ListenForChanges: false, 188 }, 189 ) 190 require.NoError(t, err) 191 192 // Make sure there are no accounts at the start. 193 accounts, err := keymanager.ValidatingAccountNames() 194 require.NoError(t, err) 195 assert.Equal(t, len(accounts), 0) 196 197 // Create 2 keys. 198 createRandomNameKeystore(t, keysDir) 199 time.Sleep(time.Second) 200 createRandomNameKeystore(t, keysDir) 201 202 require.NoError(t, ImportAccountsCli(cliCtx)) 203 204 w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{ 205 WalletDir: walletDir, 206 WalletPassword: password, 207 }) 208 require.NoError(t, err) 209 km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) 210 require.NoError(t, err) 211 keys, err := km.FetchValidatingPublicKeys(cliCtx.Context) 212 require.NoError(t, err) 213 214 assert.Equal(t, 2, len(keys)) 215 } 216 217 func TestImport_Noninteractive_Filepath(t *testing.T) { 218 imported.ResetCaches() 219 walletDir, passwordsDir, passwordFilePath := setupWalletAndPasswordsDir(t) 220 keysDir := filepath.Join(t.TempDir(), "keysDir") 221 require.NoError(t, os.MkdirAll(keysDir, os.ModePerm)) 222 223 _, keystorePath := createKeystore(t, keysDir) 224 cliCtx := setupWalletCtx(t, &testWalletConfig{ 225 walletDir: walletDir, 226 passwordsDir: passwordsDir, 227 keysDir: keystorePath, 228 keymanagerKind: keymanager.Imported, 229 walletPasswordFile: passwordFilePath, 230 accountPasswordFile: passwordFilePath, 231 }) 232 w, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 233 WalletCfg: &wallet.Config{ 234 WalletDir: walletDir, 235 KeymanagerKind: keymanager.Imported, 236 WalletPassword: password, 237 }, 238 }) 239 require.NoError(t, err) 240 keymanager, err := imported.NewKeymanager( 241 cliCtx.Context, 242 &imported.SetupConfig{ 243 Wallet: w, 244 ListenForChanges: false, 245 }, 246 ) 247 require.NoError(t, err) 248 249 // Make sure there are no accounts at the start. 250 accounts, err := keymanager.ValidatingAccountNames() 251 require.NoError(t, err) 252 assert.Equal(t, len(accounts), 0) 253 254 require.NoError(t, ImportAccountsCli(cliCtx)) 255 256 w, err = wallet.OpenWallet(cliCtx.Context, &wallet.Config{ 257 WalletDir: walletDir, 258 WalletPassword: password, 259 }) 260 require.NoError(t, err) 261 km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) 262 require.NoError(t, err) 263 keys, err := km.FetchValidatingPublicKeys(cliCtx.Context) 264 require.NoError(t, err) 265 266 assert.Equal(t, 1, len(keys)) 267 } 268 269 func TestImport_SortByDerivationPath(t *testing.T) { 270 imported.ResetCaches() 271 type test struct { 272 name string 273 input []string 274 want []string 275 } 276 tests := []test{ 277 { 278 name: "Basic sort", 279 input: []string{ 280 "keystore_m_12381_3600_2_0_0.json", 281 "keystore_m_12381_3600_1_0_0.json", 282 "keystore_m_12381_3600_0_0_0.json", 283 }, 284 want: []string{ 285 "keystore_m_12381_3600_0_0_0.json", 286 "keystore_m_12381_3600_1_0_0.json", 287 "keystore_m_12381_3600_2_0_0.json", 288 }, 289 }, 290 { 291 name: "Large digit accounts", 292 input: []string{ 293 "keystore_m_12381_3600_30020330_0_0.json", 294 "keystore_m_12381_3600_430490934_0_0.json", 295 "keystore_m_12381_3600_0_0_0.json", 296 "keystore_m_12381_3600_333_0_0.json", 297 }, 298 want: []string{ 299 "keystore_m_12381_3600_0_0_0.json", 300 "keystore_m_12381_3600_333_0_0.json", 301 "keystore_m_12381_3600_30020330_0_0.json", 302 "keystore_m_12381_3600_430490934_0_0.json", 303 }, 304 }, 305 { 306 name: "Some filenames with derivation path, others without", 307 input: []string{ 308 "keystore_m_12381_3600_4_0_0.json", 309 "keystore.json", 310 "keystore-2309023.json", 311 "keystore_m_12381_3600_1_0_0.json", 312 "keystore_m_12381_3600_3_0_0.json", 313 }, 314 want: []string{ 315 "keystore_m_12381_3600_1_0_0.json", 316 "keystore_m_12381_3600_3_0_0.json", 317 "keystore_m_12381_3600_4_0_0.json", 318 "keystore.json", 319 "keystore-2309023.json", 320 }, 321 }, 322 } 323 for _, tt := range tests { 324 t.Run(tt.name, func(t *testing.T) { 325 sort.Sort(byDerivationPath(tt.input)) 326 assert.DeepEqual(t, tt.want, tt.input) 327 }) 328 } 329 } 330 331 func Test_importPrivateKeyAsAccount(t *testing.T) { 332 walletDir, _, passwordFilePath := setupWalletAndPasswordsDir(t) 333 privKeyDir := filepath.Join(t.TempDir(), "privKeys") 334 require.NoError(t, os.MkdirAll(privKeyDir, os.ModePerm)) 335 privKeyFileName := filepath.Join(privKeyDir, "privatekey.txt") 336 337 // We create a new private key and save it to a file on disk. 338 privKey, err := bls.RandKey() 339 require.NoError(t, err) 340 privKeyHex := fmt.Sprintf("%x", privKey.Marshal()) 341 require.NoError( 342 t, 343 ioutil.WriteFile(privKeyFileName, []byte(privKeyHex), params.BeaconIoConfig().ReadWritePermissions), 344 ) 345 346 // We instantiate a new wallet from a cli context. 347 cliCtx := setupWalletCtx(t, &testWalletConfig{ 348 walletDir: walletDir, 349 keymanagerKind: keymanager.Imported, 350 walletPasswordFile: passwordFilePath, 351 privateKeyFile: privKeyFileName, 352 }) 353 walletPass := "Passwordz0320$" 354 wallet, err := CreateWalletWithKeymanager(cliCtx.Context, &CreateWalletConfig{ 355 WalletCfg: &wallet.Config{ 356 WalletDir: walletDir, 357 KeymanagerKind: keymanager.Imported, 358 WalletPassword: walletPass, 359 }, 360 }) 361 require.NoError(t, err) 362 keymanager, err := imported.NewKeymanager( 363 cliCtx.Context, 364 &imported.SetupConfig{ 365 Wallet: wallet, 366 ListenForChanges: false, 367 }, 368 ) 369 require.NoError(t, err) 370 assert.NoError(t, importPrivateKeyAsAccount(cliCtx, wallet, keymanager)) 371 372 // We re-instantiate the keymanager and check we now have 1 public key. 373 keymanager, err = imported.NewKeymanager( 374 cliCtx.Context, 375 &imported.SetupConfig{ 376 Wallet: wallet, 377 ListenForChanges: false, 378 }, 379 ) 380 require.NoError(t, err) 381 pubKeys, err := keymanager.FetchValidatingPublicKeys(cliCtx.Context) 382 require.NoError(t, err) 383 require.Equal(t, 1, len(pubKeys)) 384 assert.DeepEqual(t, pubKeys[0], bytesutil.ToBytes48(privKey.PublicKey().Marshal())) 385 } 386 387 // Returns the fullPath to the newly created keystore file. 388 func createKeystore(t *testing.T, path string) (*keymanager.Keystore, string) { 389 validatingKey, err := bls.RandKey() 390 require.NoError(t, err) 391 encryptor := keystorev4.New() 392 cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password) 393 require.NoError(t, err) 394 id, err := uuid.NewRandom() 395 require.NoError(t, err) 396 keystoreFile := &keymanager.Keystore{ 397 Crypto: cryptoFields, 398 ID: id.String(), 399 Pubkey: fmt.Sprintf("%x", validatingKey.PublicKey().Marshal()), 400 Version: encryptor.Version(), 401 Name: encryptor.Name(), 402 } 403 encoded, err := json.MarshalIndent(keystoreFile, "", "\t") 404 require.NoError(t, err) 405 // Write the encoded keystore to disk with the timestamp appended 406 createdAt := timeutils.Now().Unix() 407 fullPath := filepath.Join(path, fmt.Sprintf(imported.KeystoreFileNameFormat, createdAt)) 408 require.NoError(t, ioutil.WriteFile(fullPath, encoded, os.ModePerm)) 409 return keystoreFile, fullPath 410 } 411 412 // Returns the fullPath to the newly created keystore file. 413 func createRandomNameKeystore(t *testing.T, path string) (*keymanager.Keystore, string) { 414 validatingKey, err := bls.RandKey() 415 require.NoError(t, err) 416 encryptor := keystorev4.New() 417 cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password) 418 require.NoError(t, err) 419 id, err := uuid.NewRandom() 420 require.NoError(t, err) 421 keystoreFile := &keymanager.Keystore{ 422 Crypto: cryptoFields, 423 ID: id.String(), 424 Pubkey: fmt.Sprintf("%x", validatingKey.PublicKey().Marshal()), 425 Version: encryptor.Version(), 426 Name: encryptor.Name(), 427 } 428 encoded, err := json.MarshalIndent(keystoreFile, "", "\t") 429 require.NoError(t, err) 430 // Write the encoded keystore to disk with the timestamp appended 431 random, err := rand.Int(rand.Reader, big.NewInt(1000000)) 432 require.NoError(t, err) 433 fullPath := filepath.Join(path, fmt.Sprintf("test-%d-keystore", random.Int64())) 434 require.NoError(t, ioutil.WriteFile(fullPath, encoded, os.ModePerm)) 435 return keystoreFile, fullPath 436 }