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