Files
Sale Djenic 0b8fa059fe feat(accounts): fast password change via DEK envelope scheme
New profiles use the DEK scheme from day one (kdf_iter 3200).

Password change now auto-detects the profile's encryption scheme:
- profile on the DEK scheme, rekey=false → fast path: only the wrapped-DEK file is re-wrapped (no new DEK is generated)
- profile on the DEK scheme, rekey=true → deep rekey: fresh DEK, databases and keystore re-encrypted (new DEK is generated)
- legacy profile → one-time migration to the DEK scheme (full re-encryption, new DEK is generated)

Api changes:
- GetProfileEncryptionInfo endpoint added
- a rekey flag on ChangeDatabasePasswordV2
2026-08-21 21:19:21 +02:00

1616 lines
50 KiB
Go

package backend
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"os"
"path"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/brianvoe/gofakeit/v6"
accsmanagement "github.com/status-im/status-go/internal/accounts-management"
accsmanagementcommon "github.com/status-im/status-go/internal/accounts-management/common"
"github.com/status-im/status-go/internal/accounts-management/generator"
"github.com/status-im/status-go/internal/accounts-management/keystore"
accsmanagementtypes "github.com/status-im/status-go/internal/accounts-management/types"
"github.com/status-im/status-go/internal/connection"
"github.com/status-im/status-go/internal/crypto"
types "github.com/status-im/status-go/internal/crypto/types"
"github.com/status-im/status-go/internal/db/appdatabase"
"github.com/status-im/status-go/internal/db/multiaccounts"
"github.com/status-im/status-go/internal/db/multiaccounts/accounts"
settings "github.com/status-im/status-go/internal/db/multiaccounts/settings"
"github.com/status-im/status-go/internal/db/walletdb"
"github.com/status-im/status-go/internal/protocol/requests"
"github.com/status-im/status-go/internal/signal"
"github.com/status-im/status-go/internal/testutils"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/pkg/backend/node"
"github.com/status-im/status-go/pkg/security"
"github.com/status-im/status-go/pkg/services/typeddata"
"github.com/status-im/status-go/pkg/services/wallet"
walletservice "github.com/status-im/status-go/pkg/services/wallet"
walletcommon "github.com/status-im/status-go/pkg/services/wallet/common"
)
func setupTestDB() (*sql.DB, func() error, error) {
return testutils.SetupTestSQLDB(appdatabase.DbInitializer{}, "tests")
}
func setupTestWalletDB() (*sql.DB, func() error, error) {
return testutils.SetupTestSQLDB(walletdb.DbInitializer{}, "tests")
}
func setupTestMultiDB() (*multiaccounts.Database, func() error, error) {
tmpfile, err := os.CreateTemp("", "tests")
if err != nil {
return nil, nil, err
}
db, err := multiaccounts.InitializeDB(tmpfile.Name())
if err != nil {
return nil, nil, err
}
return db, func() error {
err := db.Close()
if err != nil {
return err
}
return os.Remove(tmpfile.Name())
}, nil
}
func setupGethStatusBackend() (*StatusBackend, func() error, func() error, func() error, error) {
db, stop1, err := setupTestDB()
if err != nil {
return nil, nil, nil, nil, err
}
backend := NewStatusBackend(testutils.MustCreateTestLogger())
if err != nil {
return nil, nil, nil, nil, err
}
backend.StatusNode().SetAppDB(db)
ma, stop2, err := setupTestMultiDB()
if err != nil {
return nil, nil, nil, nil, err
}
backend.StatusNode().SetMultiaccountsDB(ma)
walletDb, stop3, err := setupTestWalletDB()
if err != nil {
return nil, nil, nil, nil, err
}
backend.StatusNode().SetWalletDB(walletDb)
return backend, stop1, stop2, stop3, err
}
func TestEnsureDBsOpenedReopensEstablishedDatabases(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, true)
backend := testContext.backend
require.NoError(t, backend.closeDBs())
require.NoError(t, backend.ensureDBsOpened(*testContext.multiAcc, testPassword))
t.Cleanup(func() {
require.NoError(t, backend.closeDBs())
})
require.NotNil(t, backend.appDB)
require.NotNil(t, backend.walletDB)
require.NoError(t, backend.appDB.Ping())
require.NoError(t, backend.walletDB.Ping())
require.Same(t, backend.appDB, backend.statusNode.GetAppDB())
require.Same(t, backend.walletDB, backend.statusNode.GetWalletDB())
accountsDB, err := accounts.NewDB(backend.appDB)
require.NoError(t, err)
profileKeypair, err := accountsDB.GetProfileKeypair()
require.NoError(t, err)
require.Equal(t, testContext.profileKeypair.KeyUID, profileKeypair.KeyUID)
}
func TestEnsureDBsOpenedEstablishedDatabasesRejectsWrongPassword(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, true)
backend := testContext.backend
require.NoError(t, backend.closeDBs())
err := backend.ensureDBsOpened(*testContext.multiAcc, "wrong password")
require.Error(t, err)
require.Nil(t, backend.appDB)
require.Nil(t, backend.walletDB)
}
func TestEnsureDBsOpenedEstablishedDatabasesDoesNotRegisterDBsOnAccountsDBFailure(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, true)
backend := testContext.backend
require.NoError(t, backend.closeDBs())
backend.statusNode.SetAppDB(nil)
backend.statusNode.SetWalletDB(nil)
previousNewAccountsDB := newAccountsDB
newAccountsDB = func(*sql.DB) (*accounts.Database, error) {
return nil, fmt.Errorf("failed to create accounts db")
}
t.Cleanup(func() {
newAccountsDB = previousNewAccountsDB
})
err := backend.ensureDBsOpened(*testContext.multiAcc, testPassword)
require.EqualError(t, err, "failed to create accounts db")
require.Nil(t, backend.appDB)
require.Nil(t, backend.walletDB)
require.Nil(t, backend.statusNode.GetAppDB())
require.Nil(t, backend.statusNode.GetWalletDB())
}
func handleError(t *testing.T, err error) {
if err != nil {
t.Logf("deferred function error: '%s'", err)
}
}
func TestBackendStartNodeConcurrently(t *testing.T) {
backend, stop1, stop2, stop3, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop3()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
count := 2
resultCh := make(chan error)
var wg sync.WaitGroup
wg.Add(count)
for i := 0; i < count; i++ {
go func() {
resultCh <- backend.StartNode(config)
wg.Done()
}()
}
// close channel as otherwise for loop never finishes
go func() { wg.Wait(); close(resultCh) }()
var results []error
for err := range resultCh {
results = append(results, err)
}
require.Contains(t, results, nil)
require.Contains(t, results, node.ErrNodeRunning)
err = backend.StopNode()
require.NoError(t, err)
}
func TestBackendRestartNodeConcurrently(t *testing.T) {
backend, stop1, stop2, stopWallet, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stopWallet()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
const count = 3
require.NoError(t, backend.StartNode(config))
defer func() {
require.NoError(t, backend.StopNode())
}()
var wg sync.WaitGroup
wg.Add(count)
for i := 0; i < count; i++ {
go func(idx int) {
assert.NoError(t, backend.RestartNode())
wg.Done()
}(i)
}
wg.Wait()
}
func TestBackendGettersConcurrently(t *testing.T) {
backend, stop1, stop2, stopWallet, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stopWallet()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
err = backend.StartNode(config)
require.NoError(t, err)
defer func() {
require.NoError(t, backend.StopNode())
}()
var wg sync.WaitGroup
wg.Add(1)
go func() {
assert.NotNil(t, backend.StatusNode())
wg.Done()
}()
wg.Add(1)
go func() {
assert.NotNil(t, backend.AccountsManager())
wg.Done()
}()
wg.Add(1)
go func() {
assert.NotNil(t, backend.signer)
wg.Done()
}()
wg.Add(1)
go func() {
assert.NotNil(t, backend.Transactor())
wg.Done()
}()
wg.Add(1)
go func() {
assert.True(t, backend.IsNodeRunning())
wg.Done()
}()
wg.Add(1)
go func() {
assert.True(t, backend.IsNodeRunning())
wg.Done()
}()
wg.Wait()
}
func TestBackendCallRPCConcurrently(t *testing.T) {
backend, stop1, stop2, stopWallet, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stopWallet()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
err = backend.StartNode(config)
require.NoError(t, err)
defer func() {
require.NoError(t, backend.StopNode())
}()
var wg sync.WaitGroup
const count = 3
for i := 0; i < count; i++ {
wg.Add(1)
go func(idx int) {
result := backend.CallInProcessRPC(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"appgeneral_version","params":[],"id":%d}`,
idx+1,
))
assert.NotContains(t, result, "error")
wg.Done()
}(i)
wg.Add(1)
go func(idx int) {
result := backend.CallInProcessRPC(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"appgeneral_version","params":[],"id":%d}`,
idx+1,
))
assert.NotContains(t, result, "error")
wg.Done()
}(i)
}
wg.Wait()
}
func TestCallRPCWithStoppedNode(t *testing.T) {
backend := NewStatusBackend(testutils.MustCreateTestLogger())
resp := backend.CallInProcessRPC(
`{"jsonrpc":"2.0","method":"appgeneral_version","params":[],"id":1}`,
)
assert.Contains(t, resp, "error")
resp = backend.CallInProcessRPC(
`{"jsonrpc":"2.0","method":"appgeneral_version","params":[],"id":1}`,
)
assert.Contains(t, resp, "error")
}
// TODO(adam): add concurrent tests for: SendTransaction
func TestStartStopMultipleTimes(t *testing.T) {
backend, stop1, stop2, stopWallet, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stopWallet()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
require.NoError(t, backend.StartNode(config))
require.NoError(t, backend.StopNode())
require.NoError(t, backend.StartNode(config))
require.NoError(t, backend.StopNode())
}
func TestHashTypedData(t *testing.T) {
backend, stop1, stop2, stopWallet, err := setupGethStatusBackend()
defer func() {
err := stop1()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stop2()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
defer func() {
err := stopWallet()
if err != nil {
require.NoError(t, backend.StopNode())
}
}()
require.NoError(t, err)
config, err := makeTestNodeConfig(t)
require.NoError(t, err)
err = backend.StartNode(config)
require.NoError(t, err)
defer func() {
require.NoError(t, backend.StopNode())
}()
eip712Domain := "EIP712Domain"
mytypes := typeddata.Types{
eip712Domain: []typeddata.Field{
{Name: "name", Type: "string"},
{Name: "version", Type: "string"},
{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"Text": []typeddata.Field{
{Name: "body", Type: "string"},
},
}
domain := map[string]json.RawMessage{
"name": json.RawMessage(`"Ether Text"`),
"version": json.RawMessage(`"1"`),
"chainId": json.RawMessage(fmt.Sprintf("%d", walletcommon.EthereumSepolia)),
"verifyingContract": json.RawMessage(`"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"`),
}
msg := map[string]json.RawMessage{
"body": json.RawMessage(`"Hello, Bob!"`),
}
typed := typeddata.TypedData{
Types: mytypes,
PrimaryType: "Text",
Domain: domain,
Message: msg,
}
hash, err := backend.HashTypedData(typed)
require.NoError(t, err)
assert.NotEqual(t, types.Hash{}, hash)
}
func TestBackendGetVerifiedAccount(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, false)
err := testContext.backend.StartNode(testContext.config)
require.NoError(t, err)
defer func() {
require.NoError(t, testContext.backend.StopNode())
}()
t.Run("AccountDoesntExist", func(t *testing.T) {
pkey, err := gethcrypto.GenerateKey()
require.NoError(t, err)
address := gethcrypto.PubkeyToAddress(pkey.PublicKey)
key, err := testContext.backend.getVerifiedWalletAccount(address.String(), testPassword)
require.EqualError(t, err, accsmanagement.ErrAccountDoesNotExist.Error())
require.Nil(t, key)
})
t.Run("PasswordDoesntMatch", func(t *testing.T) {
pkey, err := gethcrypto.GenerateKey()
require.NoError(t, err)
privateKeyHex := types.EncodeHex(gethcrypto.FromECDSA(pkey))
address := gethcrypto.PubkeyToAddress(pkey.PublicKey)
_, err = testContext.backend.AccountsManager().CreateKeypairFromPrivateKeyAndStore(privateKeyHex, testPassword, "private key keypair", &accsmanagementtypes.AccountCreationDetails{
Path: accsmanagementcommon.PathMaster,
}, 0)
require.NoError(t, err)
key, err := testContext.backend.getVerifiedWalletAccount(address.String(), "wrong-password")
require.EqualError(t, err, keystore.ErrIncorrectPasswordProvided.Error())
require.Nil(t, key)
})
t.Run("PartialAccount", func(t *testing.T) { // This is mobile app specific test and can be removed
// Create a derived wallet account without storing the keys
db, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
err = db.CreateSettings(testContext.settings, *testContext.config)
require.NoError(t, err)
walletRootAddress, err := db.GetWalletRootAddress()
require.NoError(t, err)
// Store keystore file for the wallet root address
masterAcc, derivedAccs, err := testContext.backend.AccountsManager().StoreKeystoreFilesForMnemonic(testContext.mnemonic, testPassword,
[]string{accsmanagementcommon.PathWalletRoot})
require.NoError(t, err)
walletRootGeneratedAccount := derivedAccs[accsmanagementcommon.PathWalletRoot]
require.Equal(t, walletRootAddress, walletRootGeneratedAccount.Address())
// check the number of wallet addresses in the db
walletAddresses, err := db.GetWalletAddresses()
require.NoError(t, err)
require.Equal(t, 2, len(walletAddresses)) // should be 1, but because of the tests `Run` before this one it's 2
// Create a new wallet account and store it to db only, without storing the keystore file
derivedWalletAcc1, err := generator.DeriveChildFromAccount(masterAcc, accsmanagementcommon.CustomWalletPath1)
require.NoError(t, err)
err = db.SaveOrUpdateAccounts([]*accsmanagementtypes.Account{
{
Address: derivedWalletAcc1.Address(),
KeyUID: masterAcc.KeyUID(),
Path: accsmanagementcommon.CustomWalletPath1,
},
}, false)
require.NoError(t, err)
// check the number of wallet addresses in the db, to ensure that the account was saved
walletAddresses, err = db.GetWalletAddresses()
require.NoError(t, err)
require.Equal(t, 3, len(walletAddresses)) // should be 2, but because of the tests `Run` before this one it's 3
// try to load the account, it should fail because the account is not in the keystore, just in the db
loadedWalletAcc1, err := testContext.backend.AccountsManager().LoadAccount(derivedWalletAcc1.Address(), testPassword)
require.Error(t, err)
// Check if the error contains the expected message (new structured error format includes context)
require.Contains(t, err.Error(), "keystore file is missing")
require.Nil(t, loadedWalletAcc1)
// try to get verified wallet account, it should generate a keystore for the wallet account from the wallet root address
verifiedWalletAcc1, err := testContext.backend.AccountsManager().GetVerifiedWalletAccount(derivedWalletAcc1.Address(),
testPassword)
require.NoError(t, err)
loadedWalletAcc1, err = testContext.backend.AccountsManager().LoadAccount(derivedWalletAcc1.Address(), testPassword)
require.NoError(t, err)
// derive, verified and loaded wallet account should be the same
require.Equal(t, derivedWalletAcc1.Address(), verifiedWalletAcc1.Address())
require.Equal(t, derivedWalletAcc1.Address(), loadedWalletAcc1.Address())
})
t.Run("Success", func(t *testing.T) {
pkey, err := crypto.GenerateKey()
require.NoError(t, err)
privateKeyHex := types.EncodeHex(crypto.FromECDSA(pkey))
address := crypto.PubkeyToAddress(pkey.PublicKey)
keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&pkey.PublicKey))
keyUID := types.EncodeHex(keyUIDHex[:])
db, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
defer func() {
handleError(t, db.Close())
}()
keypair, err := testContext.backend.AccountsManager().CreateKeypairFromPrivateKeyAndStore(privateKeyHex, testPassword,
"private key keypair", &accsmanagementtypes.AccountCreationDetails{
Path: accsmanagementcommon.PathMaster,
}, 0)
require.NoError(t, err)
require.Equal(t, keypair.KeyUID, keyUID)
require.Len(t, keypair.Accounts, 1)
require.Equal(t, keypair.Accounts[0].Address, address)
acc, err := testContext.backend.getVerifiedWalletAccount(address.String(), testPassword)
require.NoError(t, err)
require.Equal(t, address, acc.Address())
})
}
func TestRuntimeLogLevelIsNotWrittenToDatabase(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, false)
json := `{
"NetworkId": 3,
"KeycardPairingDataFile": "` + path.Join(testContext.config.RootDataDir, "keycard/pairings.json") + `",
"NoDiscovery": true,
"TorrentConfig": {
"Port": 9025,
"Enabled": false,
"DataDir": "` + testContext.config.RootDataDir + `/archivedata",
"TorrentDir": "` + testContext.config.RootDataDir + `/torrents"
},
"RuntimeLogLevel": "INFO",
"LogLevel": "DEBUG"
}`
newConf, err := params.NewConfigFromJSON(json)
require.NoError(t, err)
require.Equal(t, "INFO", newConf.RuntimeLogLevel)
require.NoError(t, testContext.backend.OpenAccounts())
require.NotNil(t, testContext.backend.statusNode.MediaServer())
err = testContext.backend.ensureDBsOpened(*testContext.multiAcc, testPassword)
require.NoError(t, err)
request := &requests.CreateAccount{
RootDataDir: testContext.config.RootDataDir,
Password: testPassword,
KdfIterations: 1,
}
_, err = testContext.backend.StartNodeWithChatKeyOrMnemonic(
request,
testContext.mnemonic,
nil,
false,
)
require.NoError(t, err)
require.NoError(t, testContext.backend.Logout())
require.NoError(t, testContext.backend.StopNode())
chatPrivKey := strings.TrimPrefix(testContext.chatPrivateKey, "0x")
require.NoError(t, testContext.backend.StartNodeWithKey(*testContext.multiAcc, testPassword, chatPrivKey, newConf))
defer func() {
assert.NoError(t, testContext.backend.Logout())
assert.NoError(t, testContext.backend.StopNode())
}()
c, err := testContext.backend.GetNodeConfig()
require.NoError(t, err)
require.Equal(t, "", c.RuntimeLogLevel)
require.Equal(t, "DEBUG", c.LogLevel)
}
func TestLoginAccount(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, false)
nameserver := "8.8.8.8"
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: testPassword,
RootDataDir: testContext.config.RootDataDir,
LogFilePath: testContext.config.RootDataDir + "/log",
WakuV2Nameserver: &nameserver,
WakuV2Fleet: "status.staging",
}
c := make(chan interface{}, 10)
signal.SetHandler(func(data []byte) {
if strings.Contains(string(data), signal.EventLoggedIn) {
require.Contains(t, string(data), "status.staging")
c <- struct{}{}
}
})
t.Cleanup(signal.ResetHandler)
waitForLogin := func(chan interface{}) {
select {
case <-c:
break
case <-time.After(5 * time.Second):
t.FailNow()
}
}
acc, err := testContext.backend.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
require.Equal(t, nameserver, testContext.backend.config.WakuV2Config.Nameserver)
require.True(t, acc.HasAcceptedTerms)
waitForLogin(c)
require.NoError(t, testContext.backend.Logout())
require.NoError(t, testContext.backend.StopNode())
testContext.backend.UpdateRootDataDir(testContext.config.RootDataDir)
accounts, err := testContext.backend.GetAccounts()
require.NoError(t, err)
require.Len(t, accounts, 1)
require.NotEmpty(t, accounts[0].KeyUID)
require.Equal(t, acc.KeyUID, accounts[0].KeyUID)
migratedLogDir := testContext.config.RootDataDir + "/logs-after-login"
loginAccountRequest := &requests.Login{
KeyUID: accounts[0].KeyUID,
Password: testPassword,
WakuV2Nameserver: nameserver,
LogFilePath: migratedLogDir,
}
err = testContext.backend.LoginAccount(loginAccountRequest)
require.NoError(t, err)
waitForLogin(c)
require.Equal(t, nameserver, testContext.backend.config.WakuV2Config.Nameserver)
// A non-empty LogFilePath overrides and persists the profile's log directory
require.Equal(t, migratedLogDir, testContext.backend.config.LogDir)
persistedConfig, err := testContext.backend.GetNodeConfig()
require.NoError(t, err)
require.Equal(t, migratedLogDir, persistedConfig.LogDir)
// The max-backups setter updates the DB and the live config in one call
require.NoError(t, testContext.backend.SetProfileLogMaxBackups(7))
require.Equal(t, 7, testContext.backend.config.LogMaxBackups)
persistedConfig, err = testContext.backend.GetNodeConfig()
require.NoError(t, err)
require.Equal(t, 7, persistedConfig.LogMaxBackups)
}
func TestVerifyDatabasePassword(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, false)
request := &requests.CreateAccount{
RootDataDir: testContext.config.RootDataDir,
Password: testPassword,
KdfIterations: 1,
}
_, err := testContext.backend.StartNodeWithChatKeyOrMnemonic(
request,
testContext.mnemonic,
nil,
false,
)
require.NoError(t, err)
require.NoError(t, testContext.backend.Logout())
require.NoError(t, testContext.backend.StopNode())
require.Error(t, testContext.backend.VerifyDatabasePassword(testContext.profileKeypair.KeyUID, "wrong-pass"))
require.NoError(t, testContext.backend.VerifyDatabasePassword(testContext.profileKeypair.KeyUID, testPassword))
}
func TestConvertAccount(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, true)
request := &requests.CreateAccount{
RootDataDir: testContext.config.RootDataDir,
Password: testPassword,
KdfIterations: 1,
}
_, err := testContext.backend.StartNodeWithChatKeyOrMnemonic(
request,
testContext.mnemonic,
nil,
false,
)
require.NoError(t, err)
multiaccounts, err := testContext.backend.GetAccounts()
require.NoError(t, err)
require.NotEmpty(t, multiaccounts[0].ColorHash)
serverMessenger := testContext.backend.Messenger()
require.NotNil(t, serverMessenger)
// Ensure all created accounts are in the keystore and can be loaded
masterAddress := types.HexToAddress(testContext.profileKeypair.DerivedFrom)
ok, err := testContext.backend.AccountsManager().VerifyAccountPassword(masterAddress, testPassword)
require.NoError(t, err)
require.True(t, ok)
for _, acc := range testContext.profileKeypair.Accounts {
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(acc.Address, testPassword)
require.NoError(t, err)
require.True(t, ok)
}
// Ensure we're able to open the DB
err = testContext.backend.ensureAppDBOpened(*testContext.multiAcc, dbCredentials{secret: testPassword, kdfIter: testContext.multiAcc.KDFIterations})
require.NoError(t, err)
// db creation
db, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
// Check that the keypair is not yet marked as migrated to cold-wallet
keypair, err := db.GetKeypairByKeyUID(testContext.profileKeypair.KeyUID)
require.NoError(t, err)
require.False(t, keypair.MigratedToColdWallet())
keycardAccount := *testContext.multiAcc
keycardAccount.KeycardPairing = "pairing"
keycardSettings := settings.Settings{}
// Converting to a keycard account
const keycardPassword = "222222" // represents password for a keycard user
err = testContext.backend.ConvertToKeycardAccount(keycardAccount, keycardSettings, testContext.profileKeypair.KeyUID, testPassword, keycardPassword)
require.NoError(t, err)
require.NotNil(t, testContext.backend.appDB)
activeAccount, err := testContext.backend.GetActiveAccount()
require.NoError(t, err)
require.Equal(t, keycardAccount.KeycardPairing, activeAccount.KeycardPairing)
// Validating results of converting to a keycard account.
// All keystore files for the account which is migrated need to be removed.
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(masterAddress, testPassword)
require.Error(t, err)
require.False(t, ok)
for _, acc := range testContext.profileKeypair.Accounts {
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(acc.Address, testPassword)
require.Error(t, err)
require.False(t, ok)
}
require.NoError(t, testContext.backend.Logout())
require.NoError(t, testContext.backend.StopNode())
chatPrivKey := strings.TrimPrefix(testContext.chatPrivateKey, "0x")
require.NoError(t, testContext.backend.StartNodeWithKey(*testContext.multiAcc, keycardPassword, chatPrivKey, testContext.config))
defer func() {
assert.NoError(t, testContext.backend.Logout())
assert.NoError(t, testContext.backend.StopNode())
}()
// Ensure we're able to open the DB
err = testContext.backend.ensureDBsOpened(keycardAccount, keycardPassword)
require.NoError(t, err)
// db creation after re-encryption
db1, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
// Check that the keypair is now marked as cold-wallet (keycard) migrated
keypair, err = db1.GetKeypairByKeyUID(testContext.profileKeypair.KeyUID)
require.NoError(t, err)
require.True(t, keypair.MigratedToColdWallet())
// Converting to a regular account
err = testContext.backend.ConvertToRegularAccount(testContext.mnemonic, keycardPassword, testPassword)
require.NoError(t, err)
activeAccount, err = testContext.backend.GetActiveAccount()
require.NoError(t, err)
require.Empty(t, activeAccount.KeycardPairing)
// Validating results of converting to a regular account.
// All keystore files for need to be created.
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(masterAddress, testPassword)
require.NoError(t, err)
require.True(t, ok)
for _, acc := range testContext.profileKeypair.Accounts {
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(acc.Address, testPassword)
require.NoError(t, err)
require.True(t, ok)
}
// Ensure we're able to open the DB
err = testContext.backend.ensureDBsOpened(keycardAccount, testPassword)
require.NoError(t, err)
// db creation after re-encryption
db2, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
// Check that the keypair is no longer marked as cold-wallet migrated
keypair, err = db2.GetKeypairByKeyUID(testContext.profileKeypair.KeyUID)
require.NoError(t, err)
require.False(t, keypair.MigratedToColdWallet())
}
func copyFile(srcFolder string, dstFolder string, fileName string, t *testing.T) {
data, err := os.ReadFile(path.Join(srcFolder, fileName))
if err != nil {
t.Fail()
}
err = os.WriteFile(path.Join(dstFolder, fileName), data, 0600)
if err != nil {
t.Fail()
}
}
func copyDir(srcFolder string, dstFolder string, t *testing.T) {
files, err := os.ReadDir(srcFolder)
require.NoError(t, err)
for _, file := range files {
if !file.IsDir() {
copyFile(srcFolder, dstFolder, file.Name(), t)
} else {
childFolder := path.Join(srcFolder, file.Name())
newFolder := path.Join(dstFolder, file.Name())
err = os.MkdirAll(newFolder, os.ModePerm)
require.NoError(t, err)
copyDir(childFolder, newFolder, t)
}
}
}
func loginDesktopUser(t *testing.T, conf *params.NodeConfig, keyUID string) {
// The following passwords and DB used in this test unit are only
// used to determine if login process works correctly after a migration
// Expected account data:
username := "TestUser"
passwd := "0xC888C9CE9E098D5864D3DED6EBCC140A12142263BACE3A23A36F9905F12BD64A" // #nosec G101
b := NewStatusBackend(testutils.MustCreateTestLogger())
b.UpdateRootDataDir(conf.RootDataDir)
require.NoError(t, b.OpenAccounts())
accs, err := b.GetAccounts()
require.NoError(t, err)
require.Len(t, accs, 1)
require.Equal(t, username, accs[0].Name)
require.Equal(t, keyUID, accs[0].KeyUID)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
err := b.StartNodeWithAccount(accs[0], passwd, conf, nil)
require.NoError(t, err)
}()
wg.Wait()
require.NoError(t, b.Logout())
require.NotNil(t, b.statusNode.MediaServer())
require.NoError(t, b.StopNode())
}
func TestLoginAndMigrationsStillWorkWithExistingDesktopUser(t *testing.T) {
keyUID := "0x7c46c8f6f059ab72d524f2a6d356904db30bb0392636172ab3929a6bd2220f84" // #nosec G101
srcFolder := "testdata/test-0.132.0-account/"
tmpdir := t.TempDir()
copyDir(srcFolder, tmpdir, t)
keystoreDir := path.Join(tmpdir, "keystore", keyUID)
err := os.MkdirAll(keystoreDir, 0700)
require.NoError(t, err)
srcKeystoreFolder := "testdata/test-0.132.0-account/keystore/"
copyDir(srcKeystoreFolder, keystoreDir, t)
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
loginDesktopUser(t, conf, keyUID)
loginDesktopUser(t, conf, keyUID) // Login twice to catch weird errors that only appear after logout
}
func TestChangeDatabasePassword(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, false)
err := testContext.backend.StartNode(testContext.config)
require.NoError(t, err)
defer func() {
require.NoError(t, testContext.backend.StopNode())
}()
masterAddress := types.HexToAddress(testContext.profileKeypair.DerivedFrom)
ok, err := testContext.backend.AccountsManager().VerifyAccountPassword(masterAddress, testPassword)
require.NoError(t, err)
require.True(t, ok)
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(testContext.profileKeypair.Accounts[0].Address, testPassword)
require.NoError(t, err)
require.True(t, ok)
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(testContext.profileKeypair.Accounts[1].Address, testPassword)
require.NoError(t, err)
require.True(t, ok)
db, err := testContext.backend.accountsDB()
require.NoError(t, err)
acc, err := db.GetAccountByAddress(testContext.profileKeypair.Accounts[0].Address)
require.NoError(t, err)
require.Equal(t, testContext.profileKeypair.Accounts[0].Address, acc.Address)
acc, err = db.GetAccountByAddress(testContext.profileKeypair.Accounts[1].Address)
require.NoError(t, err)
require.Equal(t, testContext.profileKeypair.Accounts[1].Address, acc.Address)
// Change password
const newPassword = "newPassword"
err = testContext.backend.ChangeDatabasePassword(testContext.profileKeypair.KeyUID, testPassword, newPassword, false)
require.NoError(t, err)
testContext.backend.UpdateRootDataDir(testContext.config.RootDataDir)
// Test that keystore can be decrypted with the new password
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(masterAddress, newPassword)
require.NoError(t, err)
require.True(t, ok)
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(testContext.profileKeypair.Accounts[0].Address, newPassword)
require.NoError(t, err)
require.True(t, ok)
ok, err = testContext.backend.AccountsManager().VerifyAccountPassword(testContext.profileKeypair.Accounts[1].Address, newPassword)
require.NoError(t, err)
require.True(t, ok)
}
func TestCreateWallet(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, true)
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: testPassword,
RootDataDir: testContext.config.RootDataDir,
LogFilePath: testContext.config.RootDataDir + "/log",
}
c := make(chan interface{}, 10)
signal.SetHandler(func(data []byte) {
if strings.Contains(string(data), "node.login") {
c <- struct{}{}
}
})
t.Cleanup(signal.ResetHandler)
account, err := testContext.backend.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
statusNode := testContext.backend.statusNode
require.NotNil(t, statusNode)
walletService := statusNode.WalletService()
require.NotNil(t, walletService)
walletAPI := walletservice.NewAPI(walletService)
paths := []string{"m/44'/60'/0'/0/1"}
db, err := accounts.NewDB(testContext.backend.appDB)
require.NoError(t, err)
masterAddress, err := db.GetMasterAddress()
require.NoError(t, err)
mnemonic, err := db.Mnemonic()
require.NoError(t, err)
require.NotEmpty(t, mnemonic)
derivedAddress, err := walletAPI.GetDerivedAddresses(context.Background(), testPassword, masterAddress.String(), paths)
require.NoError(t, err)
require.Len(t, derivedAddress, 1)
accountsService := statusNode.AccountService()
require.NotNil(t, accountsService)
accountsAPI := accountsService.AccountsAPI()
err = accountsAPI.AddAccount(context.Background(), testPassword, &accsmanagementtypes.Account{
Address: derivedAddress[0].Address,
KeyUID: account.KeyUID,
Type: accsmanagementtypes.AccountTypeGenerated,
PublicKey: derivedAddress[0].PublicKey,
Emoji: "some",
ColorID: "so",
Name: "some name",
Path: derivedAddress[0].Path,
})
require.NoError(t, err)
}
func TestSetFleet(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, true)
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: testPassword,
RootDataDir: testContext.config.RootDataDir,
LogFilePath: testContext.config.RootDataDir + "/log",
}
c := make(chan interface{}, 10)
signal.SetHandler(func(data []byte) {
if strings.Contains(string(data), "node.login") {
c <- struct{}{}
}
})
t.Cleanup(signal.ResetHandler)
newAccount, err := testContext.backend.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
statusNode := testContext.backend.statusNode
require.NotNil(t, statusNode)
savedSettings, err := testContext.backend.GetSettings()
require.NoError(t, err)
require.Empty(t, savedSettings.Fleet)
accountsDB, err := testContext.backend.accountsDB()
require.NoError(t, err)
err = accountsDB.SaveSettingField(settings.Fleet, params.FleetStatusProd)
require.NoError(t, err)
savedSettings, err = testContext.backend.GetSettings()
require.NoError(t, err)
require.NotEmpty(t, savedSettings.Fleet)
require.Equal(t, params.FleetStatusProd, *savedSettings.Fleet)
require.NoError(t, testContext.backend.Logout())
testContext.backend.UpdateRootDataDir(testContext.config.RootDataDir)
loginAccountRequest := &requests.Login{
KeyUID: newAccount.KeyUID,
Password: testPassword,
}
require.NoError(t, testContext.backend.LoginAccount(loginAccountRequest))
select {
case <-c:
break
case <-time.After(5 * time.Second):
t.FailNow()
}
// Check is using the right fleet
require.Equal(t, params.FleetStatusProd, testContext.backend.config.ClusterConfig.Fleet)
require.NoError(t, testContext.backend.Logout())
}
func fakeToken() security.SensitiveString {
return security.NewSensitiveString(gofakeit.LetterN(10))
}
func TestWalletConfigOnLoginAccount(t *testing.T) {
testContext := setupTestContext(t, testPassword, false, false, true)
poktToken := fakeToken()
infuraToken := fakeToken()
alchemyAPIKey := fakeToken()
raribleMainnetAPIKey := fakeToken()
raribleTestnetAPIKey := fakeToken()
coingeckoAPIKey := fakeToken()
coingeckoDemoAPIKey := fakeToken()
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: testPassword,
RootDataDir: testContext.config.RootDataDir,
LogFilePath: testContext.config.RootDataDir + "/log",
}
c := make(chan interface{}, 10)
signal.SetHandler(func(data []byte) {
if strings.Contains(string(data), "node.login") {
c <- struct{}{}
}
})
t.Cleanup(signal.ResetHandler)
newAccount, err := testContext.backend.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
statusNode := testContext.backend.statusNode
require.NotNil(t, statusNode)
require.NoError(t, testContext.backend.Logout())
loginAccountRequest := &requests.Login{
KeyUID: newAccount.KeyUID,
Password: testPassword,
WalletSecretsConfig: requests.WalletSecretsConfig{
PoktToken: poktToken,
InfuraToken: infuraToken,
AlchemyAPIKey: alchemyAPIKey,
RaribleMainnetAPIKey: raribleMainnetAPIKey,
RaribleTestnetAPIKey: raribleTestnetAPIKey,
CoingeckoAPIKey: coingeckoAPIKey,
CoingeckoDemoAPIKey: coingeckoDemoAPIKey,
},
}
testContext.backend.UpdateRootDataDir(testContext.config.RootDataDir)
require.NoError(t, testContext.backend.LoginAccount(loginAccountRequest))
select {
case <-c:
break
case <-time.After(5 * time.Second):
t.FailNow()
}
walletConfig := testContext.backend.config.WalletConfig
require.Equal(t, walletConfig.InfuraAPIKey, infuraToken)
require.Equal(t, walletConfig.AlchemyAPIKey, alchemyAPIKey)
require.Equal(t, walletConfig.RaribleMainnetAPIKey, raribleMainnetAPIKey)
require.Equal(t, walletConfig.RaribleTestnetAPIKey, raribleTestnetAPIKey)
require.Equal(t, walletConfig.CoingeckoAPIKey, coingeckoAPIKey)
require.Equal(t, walletConfig.CoingeckoDemoAPIKey, coingeckoDemoAPIKey)
require.NoError(t, testContext.backend.Logout())
}
func TestTestnetEnabledSettingOnCreateAccount(t *testing.T) {
tmpdir := t.TempDir()
b := NewStatusBackend(testutils.MustCreateTestLogger())
// Creating an account with test networks enabled
createAccountRequest1 := &requests.CreateAccount{
DisplayName: "User-1",
CustomizationColor: "#ffffff",
Password: "password123",
RootDataDir: tmpdir,
LogFilePath: tmpdir + "/log",
TestNetworksEnabled: true,
}
_, err := b.CreateAccountAndLogin(createAccountRequest1)
require.NoError(t, err)
statusNode := b.statusNode
require.NotNil(t, statusNode)
settings, err := b.GetSettings()
require.NoError(t, err)
require.True(t, settings.TestNetworksEnabled)
require.NoError(t, b.Logout())
// Creating an account with test networks disabled
createAccountRequest2 := &requests.CreateAccount{
DisplayName: "User-2",
CustomizationColor: "#ffffff",
Password: "password",
RootDataDir: tmpdir,
LogFilePath: tmpdir + "/log",
}
_, err = b.CreateAccountAndLogin(createAccountRequest2)
require.NoError(t, err)
statusNode = b.statusNode
require.NotNil(t, statusNode)
settings, err = b.GetSettings()
require.NoError(t, err)
require.False(t, settings.TestNetworksEnabled)
require.NoError(t, b.Logout())
}
func TestRestoreAccountAndLogin(t *testing.T) {
tmpdir := t.TempDir()
backend := NewStatusBackend(testutils.MustCreateTestLogger())
// Test case 1: Valid restore account request
restoreRequest := &requests.RestoreAccount{
Mnemonic: "test test test test test test test test test test test test",
CreateAccount: requests.CreateAccount{
DisplayName: "Account1",
DeviceName: "StatusIM",
Password: "password",
CustomizationColor: "0x000000",
RootDataDir: tmpdir,
},
}
account, err := backend.RestoreAccountAndLogin(restoreRequest)
require.NoError(t, err)
require.NotNil(t, account)
require.Equal(t, "Account1", account.Name)
// Test case 2: Invalid restore account request
invalidRequest := &requests.RestoreAccount{}
_, err = backend.RestoreAccountAndLogin(invalidRequest)
require.Error(t, err)
db, err := accounts.NewDB(backend.appDB)
require.NoError(t, err)
mnemonic, err := db.Mnemonic()
require.NoError(t, err)
require.Empty(t, mnemonic)
}
func TestRestoreAccountAndLoginWithoutDisplayName(t *testing.T) {
tmpdir := t.TempDir()
backend := NewStatusBackend(testutils.MustCreateTestLogger())
// Test case: Valid restore account request without DisplayName
restoreRequest := &requests.RestoreAccount{
Mnemonic: "test test test test test test test test test test test test",
CreateAccount: requests.CreateAccount{
DeviceName: "StatusIM",
Password: "password",
CustomizationColor: "0x000000",
RootDataDir: tmpdir,
},
}
account, err := backend.RestoreAccountAndLogin(restoreRequest)
require.NoError(t, err)
require.NotNil(t, account)
require.NotEmpty(t, account.Name)
}
func TestAcceptTerms(t *testing.T) {
tmpdir := t.TempDir()
b := NewStatusBackend(testutils.MustCreateTestLogger())
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
b.UpdateRootDataDir(conf.RootDataDir)
require.NoError(t, b.OpenAccounts())
nameserver := "8.8.8.8"
createAccountRequest := &requests.CreateAccount{
DisplayName: "some-display-name",
CustomizationColor: "#ffffff",
Password: "some-password",
RootDataDir: tmpdir,
LogFilePath: tmpdir + "/log",
WakuV2Nameserver: &nameserver,
WakuV2Fleet: "status.staging",
}
_, err = b.CreateAccountAndLogin(createAccountRequest)
require.NoError(t, err)
err = b.AcceptTerms()
require.NoError(t, err)
}
func TestCreateAccountPathsValidation(t *testing.T) {
tmpdir := t.TempDir()
validation := &requests.CreateAccountValidation{
AllowEmptyDisplayName: false,
}
request := &requests.CreateAccount{
DisplayName: "User-1",
Password: "password123",
CustomizationColor: "#ffffff",
RootDataDir: tmpdir,
}
err := request.Validate(validation)
require.NoError(t, err)
request.RootDataDir = ""
err = request.Validate(validation)
require.ErrorIs(t, err, requests.ErrCreateAccountInvalidRootDataDir)
}
func TestRestoreKeycardAccountAndLogin(t *testing.T) {
tmpdir := t.TempDir()
exampleKeycardEvent := map[string]interface{}{
"error": "",
"instanceUID": "a84599394887b742eed9a99d3834a797",
"applicationInfo": map[string]interface{}{
"initialized": false,
"instanceUID": "",
"version": 0,
"availableSlots": 0,
"keyUID": "",
},
"seedPhraseIndexes": []interface{}{},
"freePairingSlots": 0,
"keyUid": "0x579324c53f347e18961c775a00ec13ed7d59a225b1859d5125ff36b450b8778d",
"pinRetries": 0,
"pukRetries": 0,
"cardMetadata": map[string]interface{}{
"name": "",
"walletAccounts": []interface{}{},
},
"generatedWalletAccount": map[string]interface{}{
"address": "",
"publicKey": "",
"privateKey": "",
},
"generatedWalletAccounts": []interface{}{},
"txSignature": map[string]interface{}{
"r": "",
"s": "",
"v": "",
},
"eip1581Key": map[string]interface{}{
"address": "0xA8d50f0B3bc581298446be8FBfF5c71684Ea6c01",
"publicKey": "0x040d7e6e3761ab3d17c220e484ede2f3fa02998b859d4d0e9d34216c6e41b03dc94996fdea23a9233092cee50a768e7428d5de7bd42e8e32c10d6b0e36b10f0e7a",
"privateKey": "",
},
"encryptionKey": map[string]interface{}{
"address": "0x1ec12f2b323ddDD076A1127cEc8FA0B592c46cD3",
"publicKey": "0x04c4b16f670b51702dc130673bf9c64ffd1f69383cef2127dfa05031b9b1359120f7342134af9a350465126a85e87cb003b7c4f93d2ba2ff98bb73277b119c7a87",
"privateKey": "68c830d5b327382a65e6c302594744ec0d28b01d1ea8124f49714f05c9625ddd"},
"masterKey": map[string]interface{}{
"address": "0xbf9dE86774051537b2192Ce9c8d2496f129bA24b",
"publicKey": "0x040d909a07ecca18bbfa7d53d10a86bd956f54b8b446eabd94940e642ae18421b516ec5b63677c4ce65e0e266b58bdb716d8266b25356154eb61713ecb23824075",
"privateKey": "",
},
"walletKey": map[string]interface{}{
"address": "0xB9E1998e1A8854887CA327D1aF5894B6CB0AC07D",
"publicKey": "0x04c16e7748f34e0ab2c9c13350d7872d928e942934dd8b8abd3fb12b8c742a5ee8cf0919731e800907068afec25f577bde3a9c534795e359ee48097e4e55f4aaca",
"privateKey": "",
},
"walletRootKey": map[string]interface{}{
"address": "0xFf59db9F2f97Db7104A906C390D33C342a1309C8",
"publicKey": "0x04c436532398e19ed14b4eb41545b82014435d60e7db4449a371fd80d0d5cd557f60d81f6c2b35ca5440aa60934c23b70489b0e7963e63ec66b51a7e52db711262",
"privateKey": "",
},
"whisperKey": map[string]interface{}{
"address": "0xBa122B9c0Ef560813b5D2C0961094aC36289f846",
"publicKey": "0x0441468c39b579259676350b9736b01cdadb740f67bfd022fa2b985123b1d66fc3191cfe73205e3d3d84148f0248f9a2978afeeda16d7c3db90bd2579f0de33459",
"privateKey": "5a42b4f15ff1a5da95d116442ce11a31e9020f562224bf60b1d8d3a99d90653d",
},
"masterKeyAddress": "",
}
exampleRequest := map[string]interface{}{
"mnemonic": "",
"createAccountRequest": map[string]interface{}{
"rootDataDir": tmpdir,
"kdfIterations": 256000,
"deviceName": "",
"displayName": "",
"password": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"imagePath": "",
"imageCropRectangle": map[string]interface{}{
"ax": 0, "ay": 0, "bx": 0, "by": 0},
"customizationColor": "primary",
"emoji": "",
"wakuV2Nameserver": nil,
"wakuV2LightClient": false,
"logLevel": "DEBUG",
"logFilePath": "",
"logEnabled": false,
"previewPrivacy": true,
"verifyTransactionURL": nil,
"verifyENSURL": nil,
"verifyENSContractAddress": nil,
"verifyTransactionChainID": nil,
"upstreamConfig": "",
"networkID": nil,
"walletSecretsConfig": map[string]interface{}{
"poktToken": "1234567890",
"infuraToken": "1234567890",
"infuraSecret": "",
"raribleMainnetApiKey": "",
"raribleTestnetApiKey": "",
"alchemyEthereumMainnetToken": "",
"alchemyEthereumSepoliaToken": "",
"alchemyArbitrumMainnetToken": "",
"alchemyArbitrumSepoliaToken": "",
"alchemyOptimismMainnetToken": "",
"alchemyOptimismSepoliaToken": "",
"alchemyBaseMainnetToken": "",
"alchemyBaseSepoliaToken": "",
},
"torrentConfigEnabled": false,
"torrentConfigPort": 0,
"keycardInstanceUID": "a84599394887b742eed9a99d3834a797",
"keycardPairingDataFile": path.Join(tmpdir, DefaultKeycardPairingDataFileRelativePath),
},
}
require.NotNil(t, exampleKeycardEvent)
require.NotNil(t, exampleRequest)
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
backend := NewStatusBackend(testutils.MustCreateTestLogger())
backend.UpdateRootDataDir(conf.RootDataDir)
require.NoError(t, backend.OpenAccounts())
keycardPairingDataFile := exampleRequest["createAccountRequest"].(map[string]interface{})["keycardPairingDataFile"].(string)
kp := wallet.NewKeycardPairings()
kp.SetKeycardPairingsFile(keycardPairingDataFile)
err = kp.SetPairingsJSONFileContent([]byte(`{"a84599394887b742eed9a99d3834a797":{"key":"785d52957b05482477728380d9b4bbb5dc9a8ed978ab4a4098e1a279c855d3c6","index":1}}`))
require.NoError(t, err)
request := &requests.RestoreAccount{
Keycard: &requests.KeycardData{
KeyUID: exampleKeycardEvent["keyUid"].(string),
Address: exampleKeycardEvent["masterKey"].(map[string]interface{})["address"].(string),
WhisperPrivateKey: exampleKeycardEvent["whisperKey"].(map[string]interface{})["privateKey"].(string),
WhisperPublicKey: exampleKeycardEvent["whisperKey"].(map[string]interface{})["publicKey"].(string),
WhisperAddress: exampleKeycardEvent["whisperKey"].(map[string]interface{})["address"].(string),
WalletPublicKey: exampleKeycardEvent["walletKey"].(map[string]interface{})["publicKey"].(string),
WalletAddress: exampleKeycardEvent["walletKey"].(map[string]interface{})["address"].(string),
WalletRootAddress: exampleKeycardEvent["walletRootKey"].(map[string]interface{})["address"].(string),
Eip1581Address: exampleKeycardEvent["eip1581Key"].(map[string]interface{})["address"].(string),
EncryptionPublicKey: exampleKeycardEvent["encryptionKey"].(map[string]interface{})["publicKey"].(string),
},
CreateAccount: requests.CreateAccount{
DisplayName: "User-1",
Password: "password123",
CustomizationColor: "#ffffff",
RootDataDir: tmpdir,
KeycardInstanceUID: exampleKeycardEvent["instanceUID"].(string),
KeycardPairingDataFile: &keycardPairingDataFile,
},
}
acc, err := backend.RestoreKeycardAccountAndLogin(request)
require.NoError(t, err)
require.NotNil(t, acc)
}
func TestDeleteMultiaccount(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, true)
rootDataDir := testContext.backend.rootDataDir
keyStoreDir := filepath.Join(rootDataDir, "keystore")
err := testContext.backend.OpenAccounts()
require.NoError(t, err)
files, err := os.ReadDir(rootDataDir)
require.NoError(t, err)
require.NotEqual(t, 3, len(files))
err = testContext.backend.DeleteMultiaccount(testContext.profileKeypair.KeyUID, keyStoreDir)
require.NoError(t, err)
files, err = os.ReadDir(rootDataDir)
require.NoError(t, err)
require.Equal(t, 3, len(files))
}
func TestBackendConnectionChangesConcurrently(t *testing.T) {
connections := [...]string{connection.Wifi, connection.Cellular, connection.Unknown}
testContext := setupTestContext(t, testPassword, true, true, true)
count := 3
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go func() {
defer wg.Done()
connIdx := rand.Intn(len(connections)) // nolint: gosec
testContext.backend.ConnectionChange(connections[connIdx], false)
}()
}
wg.Wait()
}
func TestBackendConnectionChangesToOffline(t *testing.T) {
testContext := setupTestContext(t, testPassword, true, true, true)
testContext.backend.ConnectionChange(connection.None, false)
assert.True(t, testContext.backend.connectionState.Offline)
testContext.backend.ConnectionChange(connection.Wifi, false)
assert.False(t, testContext.backend.connectionState.Offline)
testContext.backend.ConnectionChange("unknown-state", false)
assert.False(t, testContext.backend.connectionState.Offline)
}