package geth_test import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "reflect" "testing" "github.com/ethereum/go-ethereum/common" "github.com/status-im/status-go/geth" ) func TestVerifyAccountPassword(t *testing.T) { keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts") if err != nil { t.Fatal(err) } defer os.RemoveAll(keyStoreDir) // nolint: errcheck emptyKeyStoreDir, err := ioutil.TempDir(os.TempDir(), "empty") if err != nil { t.Fatal(err) } defer os.RemoveAll(emptyKeyStoreDir) // nolint: errcheck // import account keys if err = geth.ImportTestAccount(keyStoreDir, "test-account1.pk"); err != nil { t.Fatal(err) } if err = geth.ImportTestAccount(keyStoreDir, "test-account2.pk"); err != nil { t.Fatal(err) } account1Address := common.BytesToAddress(common.FromHex(testConfig.Account1.Address)) testCases := []struct { name string keyPath string address string password string expectedError error }{ { "correct address, correct password (decrypt should succeed)", keyStoreDir, testConfig.Account1.Address, testConfig.Account1.Password, nil, }, { "correct address, correct password, non-existent key store", filepath.Join(keyStoreDir, "non-existent-folder"), testConfig.Account1.Address, testConfig.Account1.Password, fmt.Errorf("cannot traverse key store folder: lstat %s/non-existent-folder: no such file or directory", keyStoreDir), }, { "correct address, correct password, empty key store (pk is not there)", emptyKeyStoreDir, testConfig.Account1.Address, testConfig.Account1.Password, fmt.Errorf("cannot locate account for address: %x", account1Address), }, { "wrong address, correct password", keyStoreDir, "0x79791d3e8f2daa1f7fec29649d152c0ada3cc535", testConfig.Account1.Password, fmt.Errorf("cannot locate account for address: %s", "79791d3e8f2daa1f7fec29649d152c0ada3cc535"), }, { "correct address, wrong password", keyStoreDir, testConfig.Account1.Address, "wrong password", // wrong password errors.New("could not decrypt key with given passphrase"), }, } for _, testCase := range testCases { t.Log(testCase.name) accountKey, err := geth.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password) if !reflect.DeepEqual(err, testCase.expectedError) { t.Fatalf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err) } if err == nil { if accountKey == nil { t.Error("no error reported, but account key is missing") } accountAddress := common.BytesToAddress(common.FromHex(testCase.address)) if accountKey.Address != accountAddress { t.Fatalf("account mismatch: have %x, want %x", accountKey.Address, accountAddress) } } } } func TestAccountsList(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) return } les, err := geth.NodeManagerInstance().LightEthereumService() if err != nil { t.Errorf("expected LES service: %v", err) } accounts := les.StatusBackend.AccountManager().Accounts() if err = geth.Logout(); err != nil { t.Fatal(err) } // make sure that we start with empty accounts list (nobody has logged in yet) if len(accounts) != 0 { t.Error("accounts returned, while there should be none (we haven't logged in yet)") return } // create an account address, _, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } // ensure that there is still no accounts returned accounts = les.StatusBackend.AccountManager().Accounts() if len(accounts) != 0 { t.Error("accounts returned, while there should be none (we haven't logged in yet)") return } // select account (sub-accounts will be created for this key) err = geth.SelectAccount(address, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } // at this point main account should show up accounts = les.StatusBackend.AccountManager().Accounts() if len(accounts) != 1 { t.Error("exactly single account is expected (main account)") return } if string(accounts[0].Hex()) != "0x"+address { t.Errorf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address) return } // create sub-account 1 subAccount1, subPubKey1, err := geth.CreateChildAccount("", testConfig.Account1.Password) if err != nil { t.Errorf("cannot create sub-account: %v", err) return } // now we expect to see both main account and sub-account 1 accounts = les.StatusBackend.AccountManager().Accounts() if len(accounts) != 2 { t.Error("exactly 2 accounts are expected (main + sub-account 1)") return } if string(accounts[0].Hex()) != "0x"+address { t.Errorf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address) return } if string(accounts[1].Hex()) != "0x"+subAccount1 { t.Errorf("subAcount1 not returned: got %s, expected %s", accounts[1].Hex(), "0x"+subAccount1) return } // create sub-account 2, index automatically progresses subAccount2, subPubKey2, err := geth.CreateChildAccount("", testConfig.Account1.Password) if err != nil { t.Errorf("cannot create sub-account: %v", err) } if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 { t.Error("sub-account index auto-increament failed") return } // finally, all 3 accounts should show up (main account, sub-accounts 1 and 2) accounts = les.StatusBackend.AccountManager().Accounts() if len(accounts) != 3 { t.Errorf("unexpected number of accounts: expected %d, got %d", 3, len(accounts)) return } if string(accounts[0].Hex()) != "0x"+address { t.Errorf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address) return } subAccount1MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount1 subAccount1MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount1 if !subAccount1MatchesKey1 && !subAccount1MatchesKey2 { t.Errorf("subAcount1 not returned: got %s, expected %s", accounts[1].Hex(), "0x"+subAccount1) return } subAccount2MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount2 subAccount2MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount2 if !subAccount2MatchesKey1 && !subAccount2MatchesKey2 { t.Errorf("subAcount2 not returned: got %s, expected %s", accounts[2].Hex(), "0x"+subAccount1) return } } func TestAccountsCreateChildAccount(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } // to make sure that we start with empty account (which might get populated during previous tests) if err = geth.Logout(); err != nil { t.Fatal(err) } keyStore, err := geth.NodeManagerInstance().AccountKeyStore() if err != nil { t.Fatal(err) } // create an account address, pubKey, mnemonic, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic) account, err := geth.ParseAccountString(address) if err != nil { t.Errorf("can not get account from address: %v", err) return } // obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present _, key, err := keyStore.AccountDecryptedKey(account, testConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return } if key.ExtendedKey == nil { t.Error("CKD#2 has not been generated for new account") return } // try creating sub-account, w/o selecting main account i.e. w/o login to main account _, _, err = geth.CreateChildAccount("", testConfig.Account1.Password) if !reflect.DeepEqual(err, geth.ErrNoAccountSelected) { t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", err) return } err = geth.SelectAccount(address, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } // try to create sub-account with wrong password _, _, err = geth.CreateChildAccount("", "wrong password") if !reflect.DeepEqual(err, errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")) { t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", err) return } // create sub-account (from implicit parent) subAccount1, subPubKey1, err := geth.CreateChildAccount("", testConfig.Account1.Password) if err != nil { t.Errorf("cannot create sub-account: %v", err) return } // make sure that sub-account index automatically progresses subAccount2, subPubKey2, err := geth.CreateChildAccount("", testConfig.Account1.Password) if err != nil { t.Errorf("cannot create sub-account: %v", err) } if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 { t.Error("sub-account index auto-increament failed") return } // create sub-account (from explicit parent) subAccount3, subPubKey3, err := geth.CreateChildAccount(subAccount2, testConfig.Account1.Password) if err != nil { t.Errorf("cannot create sub-account: %v", err) } if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 { t.Error("sub-account index auto-increament failed") return } } func TestAccountsRecoverAccount(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } keyStore, _ := geth.NodeManagerInstance().AccountKeyStore() // create an account address, pubKey, mnemonic, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic) // try recovering using password + mnemonic addressCheck, pubKeyCheck, err := geth.RecoverAccount(testConfig.Account1.Password, mnemonic) if err != nil { t.Errorf("recover account failed: %v", err) return } if address != addressCheck || pubKey != pubKeyCheck { t.Error("recover account details failed to pull the correct details") } // now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device account, err := geth.ParseAccountString(address) if err != nil { t.Errorf("can not get account from address: %v", err) } account, key, err := keyStore.AccountDecryptedKey(account, testConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return } extChild2String := key.ExtendedKey.String() if err := keyStore.Delete(account, testConfig.Account1.Password); err != nil { t.Errorf("cannot remove account: %v", err) } addressCheck, pubKeyCheck, err = geth.RecoverAccount(testConfig.Account1.Password, mnemonic) if err != nil { t.Errorf("recover account failed (for non-cached account): %v", err) return } if address != addressCheck || pubKey != pubKeyCheck { t.Error("recover account details failed to pull the correct details (for non-cached account)") } // make sure that extended key exists and is imported ok too _, key, err = keyStore.AccountDecryptedKey(account, testConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return } if extChild2String != key.ExtendedKey.String() { t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String()) } // make sure that calling import several times, just returns from cache (no error is expected) addressCheck, pubKeyCheck, err = geth.RecoverAccount(testConfig.Account1.Password, mnemonic) if err != nil { t.Errorf("recover account failed (for non-cached account): %v", err) return } if address != addressCheck || pubKey != pubKeyCheck { t.Error("recover account details failed to pull the correct details (for non-cached account)") } // time to login with recovered data whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // make sure that identity is not (yet injected) if whisperService.HasKeyPair(pubKeyCheck) { t.Error("identity already present in whisper") } err = geth.SelectAccount(addressCheck, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKeyCheck) { t.Errorf("identity not injected into whisper: %v", err) } } func TestAccountSelect(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } // test to see if the account was injected in whisper whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create an account address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("Account created: {address: %s, key: %s}", address1, pubKey1) address2, pubKey2, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Error("Test failed: could not create account") return } t.Logf("Account created: {address: %s, key: %s}", address2, pubKey2) // make sure that identity is not (yet injected) if whisperService.HasKeyPair(pubKey1) { t.Error("identity already present in whisper") } // try selecting with wrong password err = geth.SelectAccount(address1, "wrongPassword") if err == nil { t.Error("select account is expected to throw error: wrong password used") return } err = geth.SelectAccount(address1, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKey1) { t.Errorf("identity not injected into whisper: %v", err) } // select another account, make sure that previous account is wiped out from Whisper cache if whisperService.HasKeyPair(pubKey2) { t.Error("identity already present in whisper") } err = geth.SelectAccount(address2, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKey2) { t.Errorf("identity not injected into whisper: %v", err) } if whisperService.HasKeyPair(pubKey1) { t.Error("identity should be removed, but it is still present in whisper") } } func TestAccountsLogout(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create an account address, pubKey, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } // make sure that identity doesn't exist (yet) in Whisper if whisperService.HasKeyPair(pubKey) { t.Error("identity already present in whisper") } // select/login err = geth.SelectAccount(address, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKey) { t.Error("identity not injected into whisper") } if err = geth.Logout(); err != nil { t.Errorf("cannot logout: %v", err) } // now, logout and check if identity is removed indeed if whisperService.HasKeyPair(pubKey) { t.Error("identity not cleared from whisper") } } func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } // we need to make sure that selected account is injected as identity into Whisper whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create test accounts address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("account1 created: {address: %s, key: %s}", address1, pubKey1) address2, pubKey2, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("account2 created: {address: %s, key: %s}", address2, pubKey2) // make sure that identity is not (yet injected) if whisperService.HasKeyPair(pubKey1) { t.Error("identity already present in whisper") } // make sure that no account is selected by default if geth.NodeManagerInstance().SelectedAccount != nil { t.Error("account selected, but should not be") return } // select account err = geth.SelectAccount(address1, "wrongPassword") if err == nil { t.Error("select account is expected to throw error: wrong password used") return } err = geth.SelectAccount(address1, testConfig.Account1.Password) if err != nil { t.Errorf("could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKey1) { t.Errorf("identity not injected into whisper: %v", err) } // select another account, make sure that previous account is wiped out from Whisper cache if whisperService.HasKeyPair(pubKey2) { t.Error("identity already present in whisper") } err = geth.SelectAccount(address2, testConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return } if !whisperService.HasKeyPair(pubKey2) { t.Errorf("identity not injected into whisper: %v", err) } if whisperService.HasKeyPair(pubKey1) { t.Error("identity should be removed, but it is still present in whisper") } // stop node (and all of its sub-protocols) if err := geth.NodeManagerInstance().StopNode(); err != nil { t.Fatal(err) } // make sure that account is still selected if geth.NodeManagerInstance().SelectedAccount == nil { t.Error("no selected account") return } if geth.NodeManagerInstance().SelectedAccount.Address.Hex() != "0x"+address2 { t.Error("incorrect address selected") return } // resume node if err := geth.NodeManagerInstance().ResumeNode(); err != nil { t.Fatal(err) } // re-check selected account (account2 MUST be selected) if geth.NodeManagerInstance().SelectedAccount == nil { t.Error("no selected account") return } if geth.NodeManagerInstance().SelectedAccount.Address.Hex() != "0x"+address2 { t.Error("incorrect address selected") return } // make sure that Whisper gets identity re-injected whisperService, err = geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } if !whisperService.HasKeyPair(pubKey2) { t.Errorf("identity not injected into whisper: %v", err) } if whisperService.HasKeyPair(pubKey1) { t.Error("identity should not be present, but it is still present in whisper") } } func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Fatal(err) } if err = geth.Logout(); err != nil { t.Fatal(err) } // we need to make sure that selected account is injected as identity into Whisper whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create test accounts address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return } t.Logf("account1 created: {address: %s, key: %s}", address1, pubKey1) // make sure that identity is not present if whisperService.HasKeyPair(pubKey1) { t.Error("identity already present in whisper") } // make sure that no account is selected if geth.NodeManagerInstance().SelectedAccount != nil { t.Error("account selected, but should not be") return } // stop node (and all of its sub-protocols) if err := geth.NodeManagerInstance().StopNode(); err != nil { t.Fatal(err) } // make sure that no account is selected if geth.NodeManagerInstance().SelectedAccount != nil { t.Error("account selected, but should not be") return } // resume node if err := geth.NodeManagerInstance().ResumeNode(); err != nil { t.Fatal(err) } // make sure that no account is selected if geth.NodeManagerInstance().SelectedAccount != nil { t.Error("account selected, but should not be") return } // make sure that Whisper gets identity re-injected whisperService, err = geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } if whisperService.HasKeyPair(pubKey1) { t.Error("identity should not be present, but it is present in whisper") } }