github.com/ZuluSpl0it/Sia@v1.3.7/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 "github.com/NebulousLabs/Sia/build" 11 "github.com/NebulousLabs/Sia/crypto" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/modules/miner" 14 "github.com/NebulousLabs/Sia/types" 15 "github.com/NebulousLabs/fastrand" 16 ) 17 18 // postEncryptionTesting runs a series of checks on the wallet after it has 19 // been encrypted, to make sure that locking, unlocking, and spending after 20 // unlocking are all happening in the correct order and returning the correct 21 // errors. 22 func postEncryptionTesting(m modules.TestMiner, w *Wallet, masterKey crypto.TwofishKey) { 23 encrypted, err := w.Encrypted() 24 if err != nil { 25 panic(err) 26 } 27 unlocked, err := w.Unlocked() 28 if err != nil { 29 panic(err) 30 } 31 if !encrypted { 32 panic("wallet is not encrypted when starting postEncryptionTesting") 33 } 34 if unlocked { 35 panic("wallet is unlocked when starting postEncryptionTesting") 36 } 37 if len(w.seeds) != 0 { 38 panic("wallet has seeds in it when startin postEncryptionTesting") 39 } 40 41 // Try unlocking and using the wallet. 42 err = w.Unlock(masterKey) 43 if err != nil { 44 panic(err) 45 } 46 err = w.Unlock(masterKey) 47 if err != errAlreadyUnlocked { 48 panic(err) 49 } 50 // Mine enough coins so that a balance appears (and some buffer for the 51 // send later). 52 for i := types.BlockHeight(0); i <= types.MaturityDelay+1; i++ { 53 _, err := m.AddBlock() 54 if err != nil { 55 panic(err) 56 } 57 } 58 siacoinBal, _, _, err := w.ConfirmedBalance() 59 if err != nil { 60 panic(err) 61 } 62 if siacoinBal.IsZero() { 63 panic("wallet balance reported as 0 after maturing some mined blocks") 64 } 65 err = w.Unlock(masterKey) 66 if err != errAlreadyUnlocked { 67 panic(err) 68 } 69 70 // Lock, unlock, and trying using the wallet some more. 71 err = w.Lock() 72 if err != nil { 73 panic(err) 74 } 75 err = w.Lock() 76 if err != modules.ErrLockedWallet { 77 panic(err) 78 } 79 err = w.Unlock(crypto.TwofishKey{}) 80 if err != modules.ErrBadEncryptionKey { 81 panic(err) 82 } 83 err = w.Unlock(masterKey) 84 if err != nil { 85 panic(err) 86 } 87 // Verify that the secret keys have been restored by sending coins to the 88 // void. Send more coins than are received by mining a block. 89 _, err = w.SendSiacoins(types.CalculateCoinbase(0), types.UnlockHash{}) 90 if err != nil { 91 panic(err) 92 } 93 _, err = m.AddBlock() 94 if err != nil { 95 panic(err) 96 } 97 siacoinBal2, _, _, err := w.ConfirmedBalance() 98 if err != nil { 99 panic(err) 100 } 101 if siacoinBal2.Cmp(siacoinBal) >= 0 { 102 panic("balance did not increase") 103 } 104 } 105 106 // TestIntegrationPreEncryption checks that the wallet operates as expected 107 // prior to encryption. 108 func TestIntegrationPreEncryption(t *testing.T) { 109 if testing.Short() { 110 t.SkipNow() 111 } 112 wt, err := createBlankWalletTester(t.Name()) 113 if err != nil { 114 t.Fatal(err) 115 } 116 117 // Check that the wallet knows it's not encrypted. 118 encrypted, err := wt.wallet.Encrypted() 119 if err != nil { 120 t.Fatal(err) 121 } 122 if encrypted { 123 t.Error("wallet is reporting that it has been encrypted") 124 } 125 err = wt.wallet.Lock() 126 if err != modules.ErrLockedWallet { 127 t.Fatal(err) 128 } 129 err = wt.wallet.Unlock(crypto.TwofishKey{}) 130 if err != errUnencryptedWallet { 131 t.Fatal(err) 132 } 133 wt.closeWt() 134 135 // Create a second wallet using the same directory - make sure that if any 136 // files have been created, the wallet is still being treated as new. 137 w1, err := New(wt.cs, wt.tpool, filepath.Join(wt.persistDir, modules.WalletDir)) 138 if err != nil { 139 t.Fatal(err) 140 } 141 encrypted, err = w1.Encrypted() 142 if encrypted { 143 t.Error("wallet is reporting that it has been encrypted when no such action has occurred") 144 } 145 unlocked, err := w1.Unlocked() 146 if err != nil { 147 t.Fatal(err) 148 } 149 unlocked, err = w1.Unlocked() 150 if err != nil { 151 t.Fatal(err) 152 } 153 if unlocked { 154 t.Error("new wallet is not being treated as locked") 155 } 156 w1.Close() 157 } 158 159 // TestIntegrationUserSuppliedEncryption probes the encryption process when the 160 // user manually supplies an encryption key. 161 func TestIntegrationUserSuppliedEncryption(t *testing.T) { 162 if testing.Short() { 163 t.SkipNow() 164 } 165 166 // Create and wallet and user-specified key, then encrypt the wallet and 167 // run post-encryption tests on it. 168 wt, err := createBlankWalletTester(t.Name()) 169 if err != nil { 170 t.Fatal(err) 171 } 172 defer wt.closeWt() 173 var masterKey crypto.TwofishKey 174 fastrand.Read(masterKey[:]) 175 _, err = wt.wallet.Encrypt(masterKey) 176 if err != nil { 177 t.Error(err) 178 } 179 postEncryptionTesting(wt.miner, wt.wallet, masterKey) 180 } 181 182 // TestIntegrationBlankEncryption probes the encryption process when the user 183 // supplies a blank encryption key during the encryption process. 184 func TestIntegrationBlankEncryption(t *testing.T) { 185 if testing.Short() { 186 t.SkipNow() 187 } 188 189 // Create the wallet. 190 wt, err := createBlankWalletTester(t.Name()) 191 if err != nil { 192 t.Fatal(err) 193 } 194 defer wt.closeWt() 195 // Encrypt the wallet using a blank key. 196 seed, err := wt.wallet.Encrypt(crypto.TwofishKey{}) 197 if err != nil { 198 t.Error(err) 199 } 200 201 // Try unlocking the wallet using a blank key. 202 err = wt.wallet.Unlock(crypto.TwofishKey{}) 203 if err != modules.ErrBadEncryptionKey { 204 t.Fatal(err) 205 } 206 // Try unlocking the wallet using the correct key. 207 err = wt.wallet.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) 208 if err != nil { 209 t.Fatal(err) 210 } 211 err = wt.wallet.Lock() 212 if err != nil { 213 t.Fatal(err) 214 } 215 postEncryptionTesting(wt.miner, wt.wallet, crypto.TwofishKey(crypto.HashObject(seed))) 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(crypto.TwofishKey{}, 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 err = w.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) 325 if err != errScanInProgress { 326 t.Fatal("expected errScanInProgress, got", err) 327 } 328 // wait for init to finish 329 for i := 0; i < 100; i++ { 330 time.Sleep(time.Millisecond * 10) 331 err = w.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) 332 if err == nil { 333 break 334 } 335 } 336 337 // starting balance should match the original wallet 338 newBal, _, _, err := w.ConfirmedBalance() 339 if err != nil { 340 t.Fatal(err) 341 } 342 if newBal.Cmp(origBal) != 0 { 343 t.Log(w.UnconfirmedBalance()) 344 t.Fatalf("wallet should have correct balance after loading seed: wanted %v, got %v", origBal, newBal) 345 } 346 } 347 348 // TestUnlockConcurrent verifies that calling unlock multiple times 349 // concurrently results in only one unlock operation. 350 func TestUnlockConcurrent(t *testing.T) { 351 if testing.Short() { 352 t.SkipNow() 353 } 354 // create a wallet with some money 355 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 356 if err != nil { 357 t.Fatal(err) 358 } 359 defer wt.closeWt() 360 361 // lock the wallet 362 wt.wallet.Lock() 363 364 // spawn an unlock goroutine 365 errChan := make(chan error) 366 go func() { 367 // acquire the write lock so that Unlock acquires the trymutex, but 368 // cannot proceed further 369 wt.wallet.mu.Lock() 370 errChan <- wt.wallet.Unlock(wt.walletMasterKey) 371 }() 372 373 // wait for goroutine to start 374 time.Sleep(time.Millisecond * 10) 375 376 // unlock should now return an error 377 err = wt.wallet.Unlock(wt.walletMasterKey) 378 if err != errScanInProgress { 379 t.Fatal("expected errScanInProgress, got", err) 380 } 381 382 wt.wallet.mu.Unlock() 383 if err := <-errChan; err != nil { 384 t.Fatal("first unlock failed:", err) 385 } 386 } 387 388 // TestInitFromSeed tests creating a wallet from a preexisting seed. 389 func TestInitFromSeed(t *testing.T) { 390 if testing.Short() { 391 t.SkipNow() 392 } 393 // create a wallet with some money 394 wt, err := createWalletTester("TestInitFromSeed0", modules.ProdDependencies) 395 if err != nil { 396 t.Fatal(err) 397 } 398 defer wt.closeWt() 399 seed, _, err := wt.wallet.PrimarySeed() 400 if err != nil { 401 t.Fatal(err) 402 } 403 origBal, _, _, err := wt.wallet.ConfirmedBalance() 404 if err != nil { 405 t.Fatal(err) 406 } 407 408 // create a blank wallet 409 dir := filepath.Join(build.TempDir(modules.WalletDir, "TestInitFromSeed1"), modules.WalletDir) 410 w, err := New(wt.cs, wt.tpool, dir) 411 if err != nil { 412 t.Fatal(err) 413 } 414 err = w.InitFromSeed(crypto.TwofishKey{}, seed) 415 if err != nil { 416 t.Fatal(err) 417 } 418 err = w.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) 419 if err != nil { 420 t.Fatal(err) 421 } 422 // starting balance should match the original wallet 423 newBal, _, _, err := w.ConfirmedBalance() 424 if err != nil { 425 t.Fatal(err) 426 } 427 if newBal.Cmp(origBal) != 0 { 428 t.Log(w.UnconfirmedBalance()) 429 t.Fatalf("wallet should have correct balance after loading seed: wanted %v, got %v", origBal, newBal) 430 } 431 } 432 433 // TestReset tests that Reset resets a wallet correctly. 434 func TestReset(t *testing.T) { 435 if testing.Short() { 436 t.SkipNow() 437 } 438 439 wt, err := createBlankWalletTester(t.Name()) 440 if err != nil { 441 t.Fatal(err) 442 } 443 defer wt.closeWt() 444 445 var originalKey crypto.TwofishKey 446 fastrand.Read(originalKey[:]) 447 _, err = wt.wallet.Encrypt(originalKey) 448 if err != nil { 449 t.Fatal(err) 450 } 451 postEncryptionTesting(wt.miner, wt.wallet, originalKey) 452 453 err = wt.wallet.Reset() 454 if err != nil { 455 t.Fatal(err) 456 } 457 458 // reinitialize the miner so it mines into the new seed 459 err = wt.miner.Close() 460 if err != nil { 461 t.Fatal(err) 462 } 463 minerData := filepath.Join(wt.persistDir, modules.MinerDir) 464 err = os.RemoveAll(minerData) 465 if err != nil { 466 t.Fatal(err) 467 } 468 newminer, err := miner.New(wt.cs, wt.tpool, wt.wallet, filepath.Join(wt.persistDir, modules.MinerDir)) 469 if err != nil { 470 t.Fatal(err) 471 } 472 wt.miner = newminer 473 474 var newKey crypto.TwofishKey 475 fastrand.Read(newKey[:]) 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 var newKey crypto.TwofishKey 497 fastrand.Read(newKey[:]) 498 origBal, _, _, err := wt.wallet.ConfirmedBalance() 499 if err != nil { 500 t.Fatal(err) 501 } 502 503 err = wt.wallet.ChangeKey(wt.walletMasterKey, newKey) 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 err = wt.wallet.Lock() 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 err = wt.wallet.Unlock(wt.walletMasterKey) 514 if err == nil { 515 t.Fatal("expected unlock to fail with the original key") 516 } 517 518 err = wt.wallet.Unlock(newKey) 519 if err != nil { 520 t.Fatal(err) 521 } 522 newBal, _, _, err := wt.wallet.ConfirmedBalance() 523 if err != nil { 524 t.Fatal(err) 525 } 526 if newBal.Cmp(origBal) != 0 { 527 t.Fatal("wallet with changed key did not have the same balance") 528 } 529 530 err = wt.wallet.Lock() 531 if err != nil { 532 t.Fatal(err) 533 } 534 postEncryptionTesting(wt.miner, wt.wallet, newKey) 535 }