gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/encrypt_test.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "os" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "gitlab.com/SiaPrime/SiaPrime/build" 11 "gitlab.com/SiaPrime/SiaPrime/crypto" 12 "gitlab.com/SiaPrime/SiaPrime/modules" 13 "gitlab.com/SiaPrime/SiaPrime/modules/miner" 14 "gitlab.com/SiaPrime/SiaPrime/types" 15 ) 16 17 // postEncryptionTesting runs a series of checks on the wallet after it has 18 // been encrypted, to make sure that locking, unlocking, and spending after 19 // unlocking are all happening in the correct order and returning the correct 20 // errors. 21 func postEncryptionTesting(m modules.TestMiner, w *Wallet, masterKey crypto.CipherKey) { 22 encrypted, err := w.Encrypted() 23 if err != nil { 24 panic(err) 25 } 26 unlocked, err := w.Unlocked() 27 if err != nil { 28 panic(err) 29 } 30 if !encrypted { 31 panic("wallet is not encrypted when starting postEncryptionTesting") 32 } 33 if unlocked { 34 panic("wallet is unlocked when starting postEncryptionTesting") 35 } 36 if len(w.seeds) != 0 { 37 panic("wallet has seeds in it when startin postEncryptionTesting") 38 } 39 40 // Try unlocking and using the wallet. 41 err = w.Unlock(masterKey) 42 if err != nil { 43 panic(err) 44 } 45 err = w.Unlock(masterKey) 46 if err != errAlreadyUnlocked { 47 panic(err) 48 } 49 // Mine enough coins so that a balance appears (and some buffer for the 50 // send later). 51 for i := types.BlockHeight(0); i <= types.MaturityDelay+1; i++ { 52 _, err := m.AddBlock() 53 if err != nil { 54 panic(err) 55 } 56 } 57 siacoinBal, _, _, err := w.ConfirmedBalance() 58 if err != nil { 59 panic(err) 60 } 61 if siacoinBal.IsZero() { 62 panic("wallet balance reported as 0 after maturing some mined blocks") 63 } 64 err = w.Unlock(masterKey) 65 if err != errAlreadyUnlocked { 66 panic(err) 67 } 68 69 // Lock, unlock, and trying using the wallet some more. 70 err = w.Lock() 71 if err != nil { 72 panic(err) 73 } 74 err = w.Lock() 75 if err != modules.ErrLockedWallet { 76 panic(err) 77 } 78 err = w.Unlock(nil) 79 if err != modules.ErrBadEncryptionKey { 80 panic(err) 81 } 82 err = w.Unlock(masterKey) 83 if err != nil { 84 panic(err) 85 } 86 // Verify that the secret keys have been restored by sending coins to the 87 // void. Send more coins than are received by mining a block. 88 _, err = w.SendSiacoins(types.CalculateCoinbase(0), types.UnlockHash{}) 89 if err != nil { 90 panic(err) 91 } 92 _, err = m.AddBlock() 93 if err != nil { 94 panic(err) 95 } 96 siacoinBal2, _, _, err := w.ConfirmedBalance() 97 if err != nil { 98 panic(err) 99 } 100 if siacoinBal2.Cmp(siacoinBal) >= 0 { 101 panic("balance did not increase") 102 } 103 } 104 105 // TestIntegrationPreEncryption checks that the wallet operates as expected 106 // prior to encryption. 107 func TestIntegrationPreEncryption(t *testing.T) { 108 if testing.Short() { 109 t.SkipNow() 110 } 111 wt, err := createBlankWalletTester(t.Name()) 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 // Check that the wallet knows it's not encrypted. 117 encrypted, err := wt.wallet.Encrypted() 118 if err != nil { 119 t.Fatal(err) 120 } 121 if encrypted { 122 t.Error("wallet is reporting that it has been encrypted") 123 } 124 err = wt.wallet.Lock() 125 if err != modules.ErrLockedWallet { 126 t.Fatal(err) 127 } 128 err = wt.wallet.Unlock(nil) 129 if err != errUnencryptedWallet { 130 t.Fatal(err) 131 } 132 wt.closeWt() 133 134 // Create a second wallet using the same directory - make sure that if any 135 // files have been created, the wallet is still being treated as new. 136 w1, err := New(wt.cs, wt.tpool, filepath.Join(wt.persistDir, modules.WalletDir)) 137 if err != nil { 138 t.Fatal(err) 139 } 140 encrypted, err = w1.Encrypted() 141 if encrypted { 142 t.Error("wallet is reporting that it has been encrypted when no such action has occurred") 143 } 144 unlocked, err := w1.Unlocked() 145 if err != nil { 146 t.Fatal(err) 147 } 148 unlocked, err = w1.Unlocked() 149 if err != nil { 150 t.Fatal(err) 151 } 152 if unlocked { 153 t.Error("new wallet is not being treated as locked") 154 } 155 w1.Close() 156 } 157 158 // TestIntegrationUserSuppliedEncryption probes the encryption process when the 159 // user manually supplies an encryption key. 160 func TestIntegrationUserSuppliedEncryption(t *testing.T) { 161 if testing.Short() { 162 t.SkipNow() 163 } 164 165 // Create and wallet and user-specified key, then encrypt the wallet and 166 // run post-encryption tests on it. 167 wt, err := createBlankWalletTester(t.Name()) 168 if err != nil { 169 t.Fatal(err) 170 } 171 defer wt.closeWt() 172 masterKey := crypto.NewWalletKey(crypto.HashObject([]byte{})) 173 _, err = wt.wallet.Encrypt(masterKey) 174 if err != nil { 175 t.Error(err) 176 } 177 postEncryptionTesting(wt.miner, wt.wallet, masterKey) 178 } 179 180 // TestIntegrationBlankEncryption probes the encryption process when the user 181 // supplies a blank encryption key during the encryption process. 182 func TestIntegrationBlankEncryption(t *testing.T) { 183 if testing.Short() { 184 t.SkipNow() 185 } 186 187 // Create the wallet. 188 wt, err := createBlankWalletTester(t.Name()) 189 if err != nil { 190 t.Fatal(err) 191 } 192 defer wt.closeWt() 193 // Encrypt the wallet using a blank key. 194 seed, err := wt.wallet.Encrypt(nil) 195 if err != nil { 196 t.Error(err) 197 } 198 199 // Try unlocking the wallet using a blank key. 200 err = wt.wallet.Unlock(nil) 201 if err != modules.ErrBadEncryptionKey { 202 t.Fatal(err) 203 } 204 // Try unlocking the wallet using the correct key. 205 sk := crypto.NewWalletKey(crypto.HashObject(seed)) 206 err = wt.wallet.Unlock(sk) 207 if err != nil { 208 t.Fatal(err) 209 } 210 err = wt.wallet.Lock() 211 if err != nil { 212 t.Fatal(err) 213 } 214 sk = crypto.NewWalletKey(crypto.HashObject(seed)) 215 postEncryptionTesting(wt.miner, wt.wallet, sk) 216 } 217 218 // TestLock checks that lock correctly wipes keys when locking the wallet, 219 // while still being able to track the balance of the wallet. 220 func TestLock(t *testing.T) { 221 if testing.Short() { 222 t.SkipNow() 223 } 224 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer wt.closeWt() 229 230 // Grab a block for work - miner will not supply blocks after the wallet 231 // has been locked, and the test needs to mine a block after locking the 232 // wallet to verify that the balance reporting of a locked wallet is 233 // correct. 234 block, target, err := wt.miner.BlockForWork() 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 // Lock the wallet. 240 siacoinBalance, _, _, err := wt.wallet.ConfirmedBalance() 241 if err != nil { 242 t.Error(err) 243 } 244 err = wt.wallet.Lock() 245 if err != nil { 246 t.Error(err) 247 } 248 // Compare to the original balance. 249 siacoinBalance2, _, _, err := wt.wallet.ConfirmedBalance() 250 if err != nil { 251 t.Error(err) 252 } 253 if !siacoinBalance2.Equals(siacoinBalance) { 254 t.Error("siacoin balance reporting changed upon closing the wallet") 255 } 256 // Check that the keys and seeds were wiped. 257 wipedKey := make([]byte, crypto.SecretKeySize) 258 for _, key := range wt.wallet.keys { 259 for i := range key.SecretKeys { 260 if !bytes.Equal(wipedKey, key.SecretKeys[i][:]) { 261 t.Error("Key was not wiped after closing the wallet") 262 } 263 } 264 } 265 if len(wt.wallet.seeds) != 0 { 266 t.Error("seeds not wiped from wallet") 267 } 268 if !bytes.Equal(wipedKey[:crypto.EntropySize], wt.wallet.primarySeed[:]) { 269 t.Error("primary seed not wiped from memory") 270 } 271 272 // Solve the block generated earlier and add it to the consensus set, this 273 // should boost the balance of the wallet. 274 solvedBlock, _ := wt.miner.SolveBlock(block, target) 275 err = wt.cs.AcceptBlock(solvedBlock) 276 if err != nil { 277 t.Fatal(err) 278 } 279 siacoinBalance3, _, _, err := wt.wallet.ConfirmedBalance() 280 if err != nil { 281 t.Error(err) 282 } 283 if siacoinBalance3.Cmp(siacoinBalance2) <= 0 { 284 t.Error("balance should increase after a block was mined") 285 } 286 } 287 288 // TestInitFromSeedConcurrentUnlock verifies that calling InitFromSeed and 289 // then Unlock() concurrently results in the correct balance. 290 func TestInitFromSeedConcurrentUnlock(t *testing.T) { 291 t.Skip("Test has poor concurrency design") 292 if testing.Short() { 293 t.SkipNow() 294 } 295 // create a wallet with some money 296 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 297 if err != nil { 298 t.Fatal(err) 299 } 300 defer wt.closeWt() 301 seed, _, err := wt.wallet.PrimarySeed() 302 if err != nil { 303 t.Fatal(err) 304 } 305 origBal, _, _, err := wt.wallet.ConfirmedBalance() 306 if err != nil { 307 t.Fatal(err) 308 } 309 310 // create a blank wallet 311 dir := filepath.Join(build.TempDir(modules.WalletDir, t.Name()+"-new"), modules.WalletDir) 312 w, err := New(wt.cs, wt.tpool, dir) 313 if err != nil { 314 t.Fatal(err) 315 } 316 317 // spawn an initfromseed goroutine 318 go w.InitFromSeed(nil, seed) 319 320 // pause for 10ms to allow the seed sweeper to start 321 time.Sleep(time.Millisecond * 10) 322 323 // unlock should now return an error 324 sk := crypto.NewWalletKey(crypto.HashObject(seed)) 325 err = w.Unlock(sk) 326 if err != errScanInProgress { 327 t.Fatal("expected errScanInProgress, got", err) 328 } 329 // wait for init to finish 330 for i := 0; i < 100; i++ { 331 time.Sleep(time.Millisecond * 10) 332 err = w.Unlock(sk) 333 if err == nil { 334 break 335 } 336 } 337 338 // starting balance should match the original wallet 339 newBal, _, _, err := w.ConfirmedBalance() 340 if err != nil { 341 t.Fatal(err) 342 } 343 if newBal.Cmp(origBal) != 0 { 344 t.Log(w.UnconfirmedBalance()) 345 t.Fatalf("wallet should have correct balance after loading seed: wanted %v, got %v", origBal, newBal) 346 } 347 } 348 349 // TestUnlockConcurrent verifies that calling unlock multiple times 350 // concurrently results in only one unlock operation. 351 func TestUnlockConcurrent(t *testing.T) { 352 if testing.Short() { 353 t.SkipNow() 354 } 355 // create a wallet with some money 356 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 357 if err != nil { 358 t.Fatal(err) 359 } 360 defer wt.closeWt() 361 362 // lock the wallet 363 wt.wallet.Lock() 364 365 // spawn an unlock goroutine 366 errChan := make(chan error) 367 go func() { 368 // acquire the write lock so that Unlock acquires the trymutex, but 369 // cannot proceed further 370 wt.wallet.mu.Lock() 371 errChan <- wt.wallet.Unlock(wt.walletMasterKey) 372 }() 373 374 // wait for goroutine to start 375 time.Sleep(time.Millisecond * 10) 376 377 // unlock should now return an error 378 err = wt.wallet.Unlock(wt.walletMasterKey) 379 if err != errScanInProgress { 380 t.Fatal("expected errScanInProgress, got", err) 381 } 382 383 wt.wallet.mu.Unlock() 384 if err := <-errChan; err != nil { 385 t.Fatal("first unlock failed:", err) 386 } 387 } 388 389 // TestInitFromSeed tests creating a wallet from a preexisting seed. 390 func TestInitFromSeed(t *testing.T) { 391 if testing.Short() { 392 t.SkipNow() 393 } 394 // create a wallet with some money 395 wt, err := createWalletTester("TestInitFromSeed0", modules.ProdDependencies) 396 if err != nil { 397 t.Fatal(err) 398 } 399 defer wt.closeWt() 400 seed, _, err := wt.wallet.PrimarySeed() 401 if err != nil { 402 t.Fatal(err) 403 } 404 origBal, _, _, err := wt.wallet.ConfirmedBalance() 405 if err != nil { 406 t.Fatal(err) 407 } 408 409 // create a blank wallet 410 dir := filepath.Join(build.TempDir(modules.WalletDir, "TestInitFromSeed1"), modules.WalletDir) 411 w, err := New(wt.cs, wt.tpool, dir) 412 if err != nil { 413 t.Fatal(err) 414 } 415 err = w.InitFromSeed(nil, seed) 416 if err != nil { 417 t.Fatal(err) 418 } 419 sk := crypto.NewWalletKey(crypto.HashObject(seed)) 420 err = w.Unlock(sk) 421 if err != nil { 422 t.Fatal(err) 423 } 424 // starting balance should match the original wallet 425 newBal, _, _, err := w.ConfirmedBalance() 426 if err != nil { 427 t.Fatal(err) 428 } 429 if newBal.Cmp(origBal) != 0 { 430 t.Log(w.UnconfirmedBalance()) 431 t.Fatalf("wallet should have correct balance after loading seed: wanted %v, got %v", origBal, newBal) 432 } 433 } 434 435 // TestReset tests that Reset resets a wallet correctly. 436 func TestReset(t *testing.T) { 437 if testing.Short() { 438 t.SkipNow() 439 } 440 441 wt, err := createBlankWalletTester(t.Name()) 442 if err != nil { 443 t.Fatal(err) 444 } 445 defer wt.closeWt() 446 447 originalKey := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 448 _, err = wt.wallet.Encrypt(originalKey) 449 if err != nil { 450 t.Fatal(err) 451 } 452 postEncryptionTesting(wt.miner, wt.wallet, originalKey) 453 454 err = wt.wallet.Reset() 455 if err != nil { 456 t.Fatal(err) 457 } 458 459 // reinitialize the miner so it mines into the new seed 460 err = wt.miner.Close() 461 if err != nil { 462 t.Fatal(err) 463 } 464 minerData := filepath.Join(wt.persistDir, modules.MinerDir) 465 err = os.RemoveAll(minerData) 466 if err != nil { 467 t.Fatal(err) 468 } 469 newminer, err := miner.New(wt.cs, wt.tpool, wt.wallet, filepath.Join(wt.persistDir, modules.MinerDir)) 470 if err != nil { 471 t.Fatal(err) 472 } 473 wt.miner = newminer 474 475 newKey := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 476 _, err = wt.wallet.Encrypt(newKey) 477 if err != nil { 478 t.Fatal(err) 479 } 480 postEncryptionTesting(wt.miner, wt.wallet, newKey) 481 } 482 483 // TestChangeKey tests that a wallet can only be unlocked with the new key 484 // after changing it and that it shows the same balance as before 485 func TestChangeKey(t *testing.T) { 486 if testing.Short() { 487 t.SkipNow() 488 } 489 490 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 491 if err != nil { 492 t.Fatal(err) 493 } 494 defer wt.closeWt() 495 496 newKey := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 497 origBal, _, _, err := wt.wallet.ConfirmedBalance() 498 if err != nil { 499 t.Fatal(err) 500 } 501 502 err = wt.wallet.ChangeKey(wt.walletMasterKey, newKey) 503 if err != nil { 504 t.Fatal(err) 505 } 506 507 err = wt.wallet.Lock() 508 if err != nil { 509 t.Fatal(err) 510 } 511 512 err = wt.wallet.Unlock(wt.walletMasterKey) 513 if err == nil { 514 t.Fatal("expected unlock to fail with the original key") 515 } 516 517 err = wt.wallet.Unlock(newKey) 518 if err != nil { 519 t.Fatal(err) 520 } 521 newBal, _, _, err := wt.wallet.ConfirmedBalance() 522 if err != nil { 523 t.Fatal(err) 524 } 525 if newBal.Cmp(origBal) != 0 { 526 t.Fatal("wallet with changed key did not have the same balance") 527 } 528 529 err = wt.wallet.Lock() 530 if err != nil { 531 t.Fatal(err) 532 } 533 postEncryptionTesting(wt.miner, wt.wallet, newKey) 534 }