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  }