github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/keystore_test.go (about)

     1  // Copyright 2017 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package keystore
    18  
    19  import (
    20  	crand "crypto/rand"
    21  	"io/ioutil"
    22  	"math/rand"
    23  	"os"
    24  	"runtime"
    25  	"sort"
    26  	"strings"
    27  	"sync"
    28  	"sync/atomic"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/core-coin/go-core/v2/accounts"
    33  	"github.com/core-coin/go-core/v2/common"
    34  	"github.com/core-coin/go-core/v2/crypto"
    35  	"github.com/core-coin/go-core/v2/event"
    36  )
    37  
    38  var testSigData = make([]byte, 32)
    39  
    40  func TestKeyStore(t *testing.T) {
    41  	dir, ks := tmpKeyStore(t, true)
    42  	defer os.RemoveAll(dir)
    43  
    44  	a, err := ks.NewAccount("foo")
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  	if !strings.HasPrefix(a.URL.Path, dir) {
    49  		t.Errorf("account file %s doesn't have dir prefix", a.URL)
    50  	}
    51  	stat, err := os.Stat(a.URL.Path)
    52  	if err != nil {
    53  		t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
    54  	}
    55  	if runtime.GOOS != "windows" && stat.Mode() != 0600 {
    56  		t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
    57  	}
    58  	if !ks.HasAddress(a.Address) {
    59  		t.Errorf("HasAccount(%x) should've returned true", a.Address)
    60  	}
    61  	if err := ks.Update(a, "foo", "bar"); err != nil {
    62  		t.Errorf("Update error: %v", err)
    63  	}
    64  	if err := ks.Delete(a, "bar"); err != nil {
    65  		t.Errorf("Delete error: %v", err)
    66  	}
    67  	if common.FileExist(a.URL.Path) {
    68  		t.Errorf("account file %s should be gone after Delete", a.URL)
    69  	}
    70  	if ks.HasAddress(a.Address) {
    71  		t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
    72  	}
    73  }
    74  
    75  func TestSign(t *testing.T) {
    76  	dir, ks := tmpKeyStore(t, true)
    77  	defer os.RemoveAll(dir)
    78  
    79  	pass := "" // not used but required by API
    80  	a1, err := ks.NewAccount(pass)
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  	if err := ks.Unlock(a1, ""); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
    88  		t.Fatal(err)
    89  	}
    90  }
    91  
    92  func TestSignWithPassphrase(t *testing.T) {
    93  	dir, ks := tmpKeyStore(t, true)
    94  	defer os.RemoveAll(dir)
    95  
    96  	pass := "passwd"
    97  	acc, err := ks.NewAccount(pass)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  
   102  	if _, unlocked := ks.unlocked[acc.Address]; unlocked {
   103  		t.Fatal("expected account to be locked")
   104  	}
   105  
   106  	_, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	if _, unlocked := ks.unlocked[acc.Address]; unlocked {
   112  		t.Fatal("expected account to be locked")
   113  	}
   114  
   115  	if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
   116  		t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
   117  	}
   118  }
   119  
   120  func TestTimedUnlock(t *testing.T) {
   121  	dir, ks := tmpKeyStore(t, true)
   122  	defer os.RemoveAll(dir)
   123  
   124  	pass := "foo"
   125  	a1, err := ks.NewAccount(pass)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	// Signing without passphrase fails because account is locked
   131  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   132  	if err != ErrLocked {
   133  		t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
   134  	}
   135  
   136  	// Signing with passphrase works
   137  	if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
   138  		t.Fatal(err)
   139  	}
   140  
   141  	// Signing without passphrase works because account is temp unlocked
   142  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   143  	if err != nil {
   144  		t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
   145  	}
   146  
   147  	// Signing fails again after automatic locking
   148  	time.Sleep(250 * time.Millisecond)
   149  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   150  	if err != ErrLocked {
   151  		t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
   152  	}
   153  }
   154  
   155  func TestOverrideUnlock(t *testing.T) {
   156  	dir, ks := tmpKeyStore(t, false)
   157  	defer os.RemoveAll(dir)
   158  
   159  	pass := "foo"
   160  	a1, err := ks.NewAccount(pass)
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	// Unlock indefinitely.
   166  	if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	// Signing without passphrase works because account is temp unlocked
   171  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   172  	if err != nil {
   173  		t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
   174  	}
   175  
   176  	// reset unlock to a shorter period, invalidates the previous unlock
   177  	if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	// Signing without passphrase still works because account is temp unlocked
   182  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   183  	if err != nil {
   184  		t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
   185  	}
   186  
   187  	// Signing fails again after automatic locking
   188  	time.Sleep(250 * time.Millisecond)
   189  	_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
   190  	if err != ErrLocked {
   191  		t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
   192  	}
   193  }
   194  
   195  // This test should fail under -race if signing races the expiration goroutine.
   196  func TestSignRace(t *testing.T) {
   197  	dir, ks := tmpKeyStore(t, false)
   198  	defer os.RemoveAll(dir)
   199  
   200  	// Create a test account.
   201  	a1, err := ks.NewAccount("")
   202  	if err != nil {
   203  		t.Fatal("could not create the test account", err)
   204  	}
   205  
   206  	if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
   207  		t.Fatal("could not unlock the test account", err)
   208  	}
   209  	end := time.Now().Add(500 * time.Millisecond)
   210  	for time.Now().Before(end) {
   211  		if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
   212  			return
   213  		} else if err != nil {
   214  			t.Errorf("Sign error: %v", err)
   215  			return
   216  		}
   217  		time.Sleep(1 * time.Millisecond)
   218  	}
   219  	t.Errorf("Account did not lock within the timeout")
   220  }
   221  
   222  // Tests that the wallet notifier loop starts and stops correctly based on the
   223  // addition and removal of wallet event subscriptions.
   224  func TestWalletNotifierLifecycle(t *testing.T) {
   225  	// Create a temporary kesytore to test with
   226  	dir, ks := tmpKeyStore(t, false)
   227  	defer os.RemoveAll(dir)
   228  
   229  	// Ensure that the notification updater is not running yet
   230  	time.Sleep(250 * time.Millisecond)
   231  	ks.mu.RLock()
   232  	updating := ks.updating
   233  	ks.mu.RUnlock()
   234  
   235  	if updating {
   236  		t.Errorf("wallet notifier running without subscribers")
   237  	}
   238  	// Subscribe to the wallet feed and ensure the updater boots up
   239  	updates := make(chan accounts.WalletEvent)
   240  
   241  	subs := make([]event.Subscription, 2)
   242  	for i := 0; i < len(subs); i++ {
   243  		// Create a new subscription
   244  		subs[i] = ks.Subscribe(updates)
   245  
   246  		// Ensure the notifier comes online
   247  		time.Sleep(250 * time.Millisecond)
   248  		ks.mu.RLock()
   249  		updating = ks.updating
   250  		ks.mu.RUnlock()
   251  
   252  		if !updating {
   253  			t.Errorf("sub %d: wallet notifier not running after subscription", i)
   254  		}
   255  	}
   256  	// Unsubscribe and ensure the updater terminates eventually
   257  	for i := 0; i < len(subs); i++ {
   258  		// Close an existing subscription
   259  		subs[i].Unsubscribe()
   260  
   261  		// Ensure the notifier shuts down at and only at the last close
   262  		for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
   263  			ks.mu.RLock()
   264  			updating = ks.updating
   265  			ks.mu.RUnlock()
   266  
   267  			if i < len(subs)-1 && !updating {
   268  				t.Fatalf("sub %d: event notifier stopped prematurely", i)
   269  			}
   270  			if i == len(subs)-1 && !updating {
   271  				return
   272  			}
   273  			time.Sleep(250 * time.Millisecond)
   274  		}
   275  	}
   276  	t.Errorf("wallet notifier didn't terminate after unsubscribe")
   277  }
   278  
   279  type walletEvent struct {
   280  	accounts.WalletEvent
   281  	a accounts.Account
   282  }
   283  
   284  // Tests that wallet notifications and correctly fired when accounts are added
   285  // or deleted from the keystore.
   286  func TestWalletNotifications(t *testing.T) {
   287  	dir, ks := tmpKeyStore(t, false)
   288  	defer os.RemoveAll(dir)
   289  
   290  	// Subscribe to the wallet feed and collect events.
   291  	var (
   292  		events  []walletEvent
   293  		updates = make(chan accounts.WalletEvent)
   294  		sub     = ks.Subscribe(updates)
   295  	)
   296  	defer sub.Unsubscribe()
   297  	go func() {
   298  		for {
   299  			select {
   300  			case ev := <-updates:
   301  				events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
   302  			case <-sub.Err():
   303  				close(updates)
   304  				return
   305  			}
   306  		}
   307  	}()
   308  
   309  	// Randomly add and remove accounts.
   310  	var (
   311  		live       = make(map[common.Address]accounts.Account)
   312  		wantEvents []walletEvent
   313  	)
   314  	for i := 0; i < 1024; i++ {
   315  		if create := len(live) == 0 || rand.Int()%4 > 0; create {
   316  			// Add a new account and ensure wallet notifications arrives
   317  			account, err := ks.NewAccount("")
   318  			if err != nil {
   319  				t.Fatalf("failed to create test account: %v", err)
   320  			}
   321  			live[account.Address] = account
   322  			wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account})
   323  		} else {
   324  			// Delete a random account.
   325  			var account accounts.Account
   326  			for _, a := range live {
   327  				account = a
   328  				break
   329  			}
   330  			if err := ks.Delete(account, ""); err != nil {
   331  				t.Fatalf("failed to delete test account: %v", err)
   332  			}
   333  			delete(live, account.Address)
   334  			wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account})
   335  		}
   336  	}
   337  
   338  	// Shut down the event collector and check events.
   339  	sub.Unsubscribe()
   340  	for ev := range updates {
   341  		events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
   342  	}
   343  	checkAccounts(t, live, ks.Wallets())
   344  	checkEvents(t, wantEvents, events)
   345  }
   346  
   347  // TestImportExport tests the import functionality of a keystore.
   348  func TestImportEDDSA(t *testing.T) {
   349  	dir, ks := tmpKeyStore(t, true)
   350  	defer os.RemoveAll(dir)
   351  	key, err := crypto.GenerateKey(crand.Reader)
   352  	if err != nil {
   353  		t.Fatalf("failed to generate key: %v", key)
   354  	}
   355  	if _, err = ks.ImportEDDSA(key, "old"); err != nil {
   356  		t.Errorf("importing failed: %v", err)
   357  	}
   358  	if _, err = ks.ImportEDDSA(key, "old"); err == nil {
   359  		t.Errorf("importing same key twice succeeded")
   360  	}
   361  	if _, err = ks.ImportEDDSA(key, "new"); err == nil {
   362  		t.Errorf("importing same key twice succeeded")
   363  	}
   364  }
   365  
   366  // TestImportEDDSA tests the import and export functionality of a keystore.
   367  func TestImportExport(t *testing.T) {
   368  	dir, ks := tmpKeyStore(t, true)
   369  	defer os.RemoveAll(dir)
   370  	acc, err := ks.NewAccount("old")
   371  	if err != nil {
   372  		t.Fatalf("failed to create account: %v", acc)
   373  	}
   374  	json, err := ks.Export(acc, "old", "new")
   375  	if err != nil {
   376  		t.Fatalf("failed to export account: %v", acc)
   377  	}
   378  	dir2, ks2 := tmpKeyStore(t, true)
   379  	defer os.RemoveAll(dir2)
   380  	if _, err = ks2.Import(json, "old", "old"); err == nil {
   381  		t.Errorf("importing with invalid password succeeded")
   382  	}
   383  	acc2, err := ks2.Import(json, "new", "new")
   384  	if err != nil {
   385  		t.Errorf("importing failed: %v", err)
   386  	}
   387  	if acc.Address != acc2.Address {
   388  		t.Error("imported account does not match exported account")
   389  	}
   390  	if _, err = ks2.Import(json, "new", "new"); err == nil {
   391  		t.Errorf("importing a key twice succeeded")
   392  	}
   393  
   394  }
   395  
   396  // TestImportRace tests the keystore on races.
   397  // This test should fail under -race if importing races.
   398  func TestImportRace(t *testing.T) {
   399  	dir, ks := tmpKeyStore(t, true)
   400  	defer os.RemoveAll(dir)
   401  	acc, err := ks.NewAccount("old")
   402  	if err != nil {
   403  		t.Fatalf("failed to create account: %v", acc)
   404  	}
   405  	json, err := ks.Export(acc, "old", "new")
   406  	if err != nil {
   407  		t.Fatalf("failed to export account: %v", acc)
   408  	}
   409  	dir2, ks2 := tmpKeyStore(t, true)
   410  	defer os.RemoveAll(dir2)
   411  	var atom uint32
   412  	var wg sync.WaitGroup
   413  	wg.Add(2)
   414  	for i := 0; i < 2; i++ {
   415  		go func() {
   416  			defer wg.Done()
   417  			if _, err := ks2.Import(json, "new", "new"); err != nil {
   418  				atomic.AddUint32(&atom, 1)
   419  			}
   420  
   421  		}()
   422  	}
   423  	wg.Wait()
   424  	if atom != 1 {
   425  		t.Errorf("Import is racy")
   426  	}
   427  }
   428  
   429  // checkAccounts checks that all known live accounts are present in the wallet list.
   430  func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
   431  	if len(live) != len(wallets) {
   432  		t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live))
   433  		return
   434  	}
   435  	liveList := make([]accounts.Account, 0, len(live))
   436  	for _, account := range live {
   437  		liveList = append(liveList, account)
   438  	}
   439  	sort.Sort(accountsByURL(liveList))
   440  	for j, wallet := range wallets {
   441  		if accs := wallet.Accounts(); len(accs) != 1 {
   442  			t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
   443  		} else if accs[0] != liveList[j] {
   444  			t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
   445  		}
   446  	}
   447  }
   448  
   449  // checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times.
   450  func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
   451  	for _, wantEv := range want {
   452  		nmatch := 0
   453  		for ; len(have) > 0; nmatch++ {
   454  			if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a {
   455  				break
   456  			}
   457  			have = have[1:]
   458  		}
   459  		if nmatch == 0 {
   460  			t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address)
   461  		}
   462  	}
   463  }
   464  
   465  func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
   466  	d, err := ioutil.TempDir("", "xcb-keystore-test")
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	return d, NewKeyStore(d, veryLightScryptN, veryLightScryptP)
   471  }