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  }