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