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  }