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