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  }