github.com/status-im/status-go@v1.1.0/account/accounts_test.go (about) 1 package account 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "testing" 10 11 "github.com/status-im/status-go/eth-node/crypto" 12 "github.com/status-im/status-go/eth-node/keystore" 13 "github.com/status-im/status-go/eth-node/types" 14 "github.com/status-im/status-go/t/utils" 15 16 "github.com/stretchr/testify/require" 17 "github.com/stretchr/testify/suite" 18 ) 19 20 const testPassword = "test-password" 21 const newTestPassword = "new-test-password" 22 23 func TestVerifyAccountPassword(t *testing.T) { 24 accManager := NewGethManager() 25 keyStoreDir := t.TempDir() 26 emptyKeyStoreDir := t.TempDir() 27 28 // import account keys 29 utils.Init() 30 require.NoError(t, utils.ImportTestAccount(keyStoreDir, utils.GetAccount1PKFile())) 31 require.NoError(t, utils.ImportTestAccount(keyStoreDir, utils.GetAccount2PKFile())) 32 33 account1Address := types.BytesToAddress(types.FromHex(utils.TestConfig.Account1.WalletAddress)) 34 35 testCases := []struct { 36 name string 37 keyPath string 38 address string 39 password string 40 expectedError error 41 }{ 42 { 43 "correct address, correct password (decrypt should succeed)", 44 keyStoreDir, 45 utils.TestConfig.Account1.WalletAddress, 46 utils.TestConfig.Account1.Password, 47 nil, 48 }, 49 { 50 "correct address, correct password, non-existent key store", 51 filepath.Join(keyStoreDir, "non-existent-folder"), 52 utils.TestConfig.Account1.WalletAddress, 53 utils.TestConfig.Account1.Password, 54 fmt.Errorf("cannot traverse key store folder: lstat %s/non-existent-folder: no such file or directory", keyStoreDir), 55 }, 56 { 57 "correct address, correct password, empty key store (pk is not there)", 58 emptyKeyStoreDir, 59 utils.TestConfig.Account1.WalletAddress, 60 utils.TestConfig.Account1.Password, 61 ErrCannotLocateKeyFile{fmt.Sprintf("cannot locate account for address: %s", account1Address.Hex())}, 62 }, 63 { 64 "wrong address, correct password", 65 keyStoreDir, 66 "0x79791d3e8f2daa1f7fec29649d152c0ada3cc535", 67 utils.TestConfig.Account1.Password, 68 ErrCannotLocateKeyFile{fmt.Sprintf("cannot locate account for address: %s", "0x79791d3E8F2dAa1F7FeC29649d152c0aDA3cc535")}, 69 }, 70 { 71 "correct address, wrong password", 72 keyStoreDir, 73 utils.TestConfig.Account1.WalletAddress, 74 "wrong password", // wrong password 75 errors.New("could not decrypt key with given password"), 76 }, 77 } 78 for _, testCase := range testCases { 79 accountKey, err := accManager.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password) 80 if testCase.expectedError != nil && err != nil && testCase.expectedError.Error() != err.Error() || 81 ((testCase.expectedError == nil || err == nil) && testCase.expectedError != err) { 82 require.FailNow(t, fmt.Sprintf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err)) 83 } 84 if err == nil { 85 if accountKey == nil { // nolint: staticcheck 86 require.Fail(t, "no error reported, but account key is missing") 87 } 88 accountAddress := types.BytesToAddress(types.FromHex(testCase.address)) 89 if accountKey.Address != accountAddress { // nolint: staticcheck 90 require.Fail(t, "account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex()) 91 } 92 } 93 } 94 } 95 96 // TestVerifyAccountPasswordWithAccountBeforeEIP55 verifies if VerifyAccountPassword 97 // can handle accounts before introduction of EIP55. 98 func TestVerifyAccountPasswordWithAccountBeforeEIP55(t *testing.T) { 99 keyStoreDir := t.TempDir() 100 101 // Import keys and make sure one was created before EIP55 introduction. 102 utils.Init() 103 err := utils.ImportTestAccount(keyStoreDir, "test-account3-before-eip55.pk") 104 require.NoError(t, err) 105 106 accManager := NewGethManager() 107 108 address := types.HexToAddress(utils.TestConfig.Account3.WalletAddress) 109 _, err = accManager.VerifyAccountPassword(keyStoreDir, address.Hex(), utils.TestConfig.Account3.Password) 110 require.NoError(t, err) 111 } 112 113 func TestManagerTestSuite(t *testing.T) { 114 suite.Run(t, new(ManagerTestSuite)) 115 } 116 117 type ManagerTestSuite struct { 118 suite.Suite 119 testAccount 120 accManager *GethManager 121 keydir string 122 } 123 124 type testAccount struct { 125 password string 126 walletAddress string 127 walletPubKey string 128 chatAddress string 129 chatPubKey string 130 mnemonic string 131 } 132 133 // SetupTest is used here for reinitializing the mock before every 134 // test function to avoid faulty execution. 135 func (s *ManagerTestSuite) SetupTest() { 136 s.accManager = NewGethManager() 137 138 keyStoreDir := s.T().TempDir() 139 s.Require().NoError(s.accManager.InitKeystore(keyStoreDir)) 140 s.keydir = keyStoreDir 141 142 // Initial test - create test account 143 _, accountInfo, mnemonic, err := s.accManager.CreateAccount(testPassword) 144 s.Require().NoError(err) 145 s.Require().NotEmpty(accountInfo.WalletAddress) 146 s.Require().NotEmpty(accountInfo.WalletPubKey) 147 s.Require().NotEmpty(accountInfo.ChatAddress) 148 s.Require().NotEmpty(accountInfo.ChatPubKey) 149 s.Require().NotEmpty(mnemonic) 150 151 // Before the complete decoupling of the keys, wallet and chat keys are the same 152 s.Equal(accountInfo.WalletAddress, accountInfo.ChatAddress) 153 s.Equal(accountInfo.WalletPubKey, accountInfo.ChatPubKey) 154 155 s.testAccount = testAccount{ 156 testPassword, 157 accountInfo.WalletAddress, 158 accountInfo.WalletPubKey, 159 accountInfo.ChatAddress, 160 accountInfo.ChatPubKey, 161 mnemonic, 162 } 163 } 164 165 func (s *ManagerTestSuite) TestRecoverAccount() { 166 accountInfo, err := s.accManager.RecoverAccount(s.password, s.mnemonic) 167 s.NoError(err) 168 s.Equal(s.walletAddress, accountInfo.WalletAddress) 169 s.Equal(s.walletPubKey, accountInfo.WalletPubKey) 170 s.Equal(s.chatAddress, accountInfo.ChatAddress) 171 s.Equal(s.chatPubKey, accountInfo.ChatPubKey) 172 } 173 174 func (s *ManagerTestSuite) TestOnboarding() { 175 // try to choose an account before starting onboarding 176 _, _, err := s.accManager.ImportOnboardingAccount("test-id", "test-password") 177 s.Equal(ErrOnboardingNotStarted, err) 178 179 // generates 5 random accounts 180 count := 5 181 accounts, err := s.accManager.StartOnboarding(count, 24) 182 s.Require().NoError(err) 183 s.Equal(count, len(accounts)) 184 185 // try to choose an account with an undefined id 186 _, _, err = s.accManager.ImportOnboardingAccount("test-id", "test-password") 187 s.Equal(ErrOnboardingAccountNotFound, err) 188 189 // choose one account and encrypt it with password 190 password := "test-onboarding-account" 191 account := accounts[0] 192 info, mnemonic, err := s.accManager.ImportOnboardingAccount(account.ID, password) 193 s.Require().NoError(err) 194 s.Equal(account.Info, info) 195 s.Equal(account.mnemonic, mnemonic) 196 s.Nil(s.accManager.onboarding) 197 198 // try to decrypt it with password to check if it's been imported correctly 199 decAccount, _, err := s.accManager.AddressToDecryptedAccount(info.WalletAddress, password) 200 s.Require().NoError(err) 201 s.Equal(info.WalletAddress, decAccount.Address.Hex()) 202 203 // try resetting onboarding 204 _, err = s.accManager.StartOnboarding(count, 24) 205 s.Require().NoError(err) 206 s.NotNil(s.accManager.onboarding) 207 208 s.accManager.RemoveOnboarding() 209 s.Nil(s.accManager.onboarding) 210 } 211 212 func (s *ManagerTestSuite) TestSelectAccountSuccess() { 213 s.testSelectAccount(types.HexToAddress(s.testAccount.chatAddress), types.HexToAddress(s.testAccount.walletAddress), s.testAccount.password, nil) 214 } 215 216 func (s *ManagerTestSuite) TestSelectAccountWrongAddress() { 217 s.testSelectAccount(types.HexToAddress("0x0000000000000000000000000000000000000001"), types.HexToAddress(s.testAccount.walletAddress), s.testAccount.password, errors.New("cannot retrieve a valid key for a given account: no key for given address or file")) 218 } 219 220 func (s *ManagerTestSuite) TestSelectAccountWrongPassword() { 221 s.testSelectAccount(types.HexToAddress(s.testAccount.chatAddress), types.HexToAddress(s.testAccount.walletAddress), "wrong", errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given password")) 222 } 223 224 func (s *ManagerTestSuite) testSelectAccount(chat, wallet types.Address, password string, expErr error) { 225 loginParams := LoginParams{ 226 ChatAddress: chat, 227 MainAccount: wallet, 228 Password: password, 229 } 230 err := s.accManager.SelectAccount(loginParams) 231 s.Require().Equal(expErr, err) 232 233 selectedMainAccountAddress, walletErr := s.accManager.MainAccountAddress() 234 selectedChatAccount, chatErr := s.accManager.SelectedChatAccount() 235 236 if expErr == nil { 237 s.Require().NoError(walletErr) 238 s.Require().NoError(chatErr) 239 s.Equal(wallet, selectedMainAccountAddress) 240 s.Equal(chat, crypto.PubkeyToAddress(selectedChatAccount.AccountKey.PrivateKey.PublicKey)) 241 } else { 242 s.Equal(types.Address{}, selectedMainAccountAddress) 243 s.Nil(selectedChatAccount) 244 s.Equal(walletErr, ErrNoAccountSelected) 245 s.Equal(chatErr, ErrNoAccountSelected) 246 } 247 248 s.accManager.Logout() 249 } 250 251 func (s *ManagerTestSuite) TestSetChatAccount() { 252 s.accManager.Logout() 253 254 privKey, err := crypto.GenerateKey() 255 s.Require().NoError(err) 256 257 address := crypto.PubkeyToAddress(privKey.PublicKey) 258 259 s.Require().NoError(s.accManager.SetChatAccount(privKey)) 260 selectedChatAccount, err := s.accManager.SelectedChatAccount() 261 s.Require().NoError(err) 262 s.Require().NotNil(selectedChatAccount) 263 s.Equal(privKey, selectedChatAccount.AccountKey.PrivateKey) 264 s.Equal(address, selectedChatAccount.Address) 265 266 selectedMainAccountAddress, err := s.accManager.MainAccountAddress() 267 s.Error(err) 268 s.Equal(types.Address{}, selectedMainAccountAddress) 269 } 270 271 func (s *ManagerTestSuite) TestLogout() { 272 s.accManager.Logout() 273 s.Equal(types.Address{}, s.accManager.mainAccountAddress) 274 s.Nil(s.accManager.selectedChatAccount) 275 s.Len(s.accManager.watchAddresses, 0) 276 } 277 278 // TestAccounts tests cases for (*Manager).Accounts. 279 func (s *ManagerTestSuite) TestAccounts() { 280 // Select the test account 281 loginParams := LoginParams{ 282 MainAccount: types.HexToAddress(s.walletAddress), 283 ChatAddress: types.HexToAddress(s.chatAddress), 284 Password: s.password, 285 } 286 err := s.accManager.SelectAccount(loginParams) 287 s.NoError(err) 288 289 // Success 290 accs, err := s.accManager.Accounts() 291 s.NoError(err) 292 s.NotNil(accs) 293 // Selected main account address is zero address but doesn't fail 294 s.accManager.mainAccountAddress = types.Address{} 295 accs, err = s.accManager.Accounts() 296 s.NoError(err) 297 s.NotNil(accs) 298 } 299 300 func (s *ManagerTestSuite) TestAddressToDecryptedAccountSuccess() { 301 s.testAddressToDecryptedAccount(s.walletAddress, s.password, nil) 302 } 303 304 func (s *ManagerTestSuite) TestAddressToDecryptedAccountWrongAddress() { 305 s.testAddressToDecryptedAccount("0x0001", s.password, ErrAddressToAccountMappingFailure) 306 } 307 308 func (s *ManagerTestSuite) TestAddressToDecryptedAccountWrongPassword() { 309 s.testAddressToDecryptedAccount(s.walletAddress, "wrong", errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given password")) 310 } 311 312 func (s *ManagerTestSuite) testAddressToDecryptedAccount(wallet, password string, expErr error) { 313 acc, key, err := s.accManager.AddressToDecryptedAccount(wallet, password) 314 if expErr != nil { 315 s.Equal(expErr, err) 316 } else { 317 s.Require().NoError(err) 318 s.Require().NotNil(acc) 319 s.Require().NotNil(key) 320 s.Equal(acc.Address, key.Address) 321 } 322 } 323 324 func (s *ManagerTestSuite) TestMigrateKeyStoreDir() { 325 oldKeyDir := s.keydir 326 newKeyDir := filepath.Join(oldKeyDir, "new_dir") 327 err := os.Mkdir(newKeyDir, 0777) 328 s.Require().NoError(err) 329 330 files, _ := os.ReadDir(newKeyDir) 331 s.Equal(0, len(files)) 332 333 address := types.HexToAddress(s.walletAddress).Hex() 334 addresses := []string{address} 335 err = s.accManager.MigrateKeyStoreDir(oldKeyDir, newKeyDir, addresses) 336 s.Require().NoError(err) 337 338 files, _ = os.ReadDir(newKeyDir) 339 s.Equal(1, len(files)) 340 } 341 342 func (s *ManagerTestSuite) TestReEncryptKey() { 343 var firstKeyPath string 344 files, _ := os.ReadDir(s.keydir) 345 346 // there is only one file in this dir, 347 // is there a better way to reference it? 348 for _, f := range files { 349 firstKeyPath = filepath.Join(s.keydir, f.Name()) 350 } 351 352 rawKey, _ := os.ReadFile(firstKeyPath) 353 reEncryptedKey, _ := s.accManager.ReEncryptKey(rawKey, testPassword, newTestPassword) 354 355 type Key struct { 356 Address string `json:"address"` 357 } 358 359 var unmarshaledRaw, unmarshaledReEncrypted Key 360 _ = json.Unmarshal(rawKey, &unmarshaledRaw) 361 _ = json.Unmarshal(reEncryptedKey, &unmarshaledReEncrypted) 362 363 oldCrypto, _ := keystore.RawKeyToCryptoJSON(rawKey) 364 newCrypto, _ := keystore.RawKeyToCryptoJSON(reEncryptedKey) 365 366 // Test address is same post re-encryption 367 s.Equal(unmarshaledRaw.Address, unmarshaledReEncrypted.Address) 368 369 // Test cipher changes after re-encryption 370 s.NotEqual(oldCrypto.CipherText, newCrypto.CipherText) 371 372 // Test re-encrypted key cannot be decrypted using old testPasswordword 373 _, decryptOldError := keystore.DecryptKey(reEncryptedKey, testPassword) 374 s.Require().Error(decryptOldError) 375 376 // Test re-encrypted key can be decrypted using new testPassword 377 _, decryptNewError := keystore.DecryptKey(reEncryptedKey, newTestPassword) 378 s.Require().NoError(decryptNewError) 379 } 380 381 func (s *ManagerTestSuite) TestReEncryptKeyStoreDir() { 382 383 err := s.accManager.ReEncryptKeyStoreDir(s.keydir, testPassword, newTestPassword) 384 s.Require().NoError(err) 385 386 err = filepath.Walk(s.keydir, func(path string, fileInfo os.FileInfo, err error) error { 387 if fileInfo.IsDir() { 388 return nil 389 } 390 391 // walk should not throw callback errors 392 s.Require().NoError(err) 393 394 rawKeyFile, err := os.ReadFile(path) 395 s.Require().NoError(err) 396 397 // should not decrypt with old password 398 _, decryptError := keystore.DecryptKey(rawKeyFile, testPassword) 399 s.Require().Error(decryptError) 400 401 // should decrypt with new password 402 _, decryptError = keystore.DecryptKey(rawKeyFile, newTestPassword) 403 s.Require().NoError(decryptError) 404 405 return nil 406 }) 407 408 s.Require().NoError(err) 409 }