feat: adapt create/restore/login endpoints for keycard usage (#5311)

* chore_: remove duplicated `StartNodeWithKey`

* feat(KeycardPairing)_: added GetPairings method

* chore_: simplify startNode... methods

* chore_: added encryption path to be derived

* fix_: error handling in StartNodeWithKey

* feat_: added keycard properties to CreateAccount

* feat_: moved KeycardWhisperPrivateKey to LoginAccount

* fix_: LoginAccount during local pairing

* feat_: added chat key handling to loginAccount

* chore_: struct response from generateOrImportAccount

* fix_: do not store keycard account to keystore

* feat_: added Mnemonic parameter to LoginAccount

* chore_: wrap loginAccount errors

* feat_: RestoreKeycardAccountAndLogin endpoint

* chore_: merge RestoreKeycardAccountRequest into RestoreAccountRequest

* fix_: TestRestoreKeycardAccountAndLogin

* fix_: MessengerRawMessageResendTest

* chore_: cleanup

* chore_: cleanup according to pr comments

* chore_: better doc for Login.Mnemonic

* chore_: add/fix comments

* fix_: lint
This commit is contained in:
Igor Sirotin 2024-06-26 13:14:27 +02:00 committed by GitHub
parent 1cdcc0dcc2
commit 49eaabaca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 674 additions and 218 deletions

View File

@ -1,6 +1,8 @@
package api
import (
"crypto/ecdsa"
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/status-im/status-go/eth-node/types"
@ -18,8 +20,8 @@ type StatusBackend interface {
// IsNodeRunning() bool // NOTE: Only used in tests
StartNode(config *params.NodeConfig) error // NOTE: Only used in canary
StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, conf *params.NodeConfig) error
StartNodeWithAccount(acc multiaccounts.Account, password string, conf *params.NodeConfig) error
StartNodeWithAccountAndInitialConfig(account multiaccounts.Account, password string, settings settings.Settings, conf *params.NodeConfig, subaccs []*accounts.Account) error
StartNodeWithAccount(acc multiaccounts.Account, password string, conf *params.NodeConfig, chatKey *ecdsa.PrivateKey) error
StartNodeWithAccountAndInitialConfig(account multiaccounts.Account, password string, settings settings.Settings, conf *params.NodeConfig, subaccs []*accounts.Account, chatKey *ecdsa.PrivateKey) error
StopNode() error
// RestartNode() error // NOTE: Only used in tests

View File

@ -34,6 +34,7 @@ import (
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/services/wallet"
walletservice "github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/sqlite"
@ -1073,7 +1074,7 @@ func TestConvertAccount(t *testing.T) {
found = keystoreContainsFileForAccount(keyStoreDir, chatAddress)
require.True(t, found)
defaultSettings, err := defaultSettings(genAccInfo, derivedAccounts)
defaultSettings, err := defaultSettings(genAccInfo.KeyUID, genAccInfo.Address, derivedAccounts)
require.NoError(t, err)
nodeConfig, err := defaultNodeConfig(defaultSettings.InstallationID, &requests.CreateAccount{
LogLevel: defaultSettings.LogLevel,
@ -1135,7 +1136,7 @@ func TestConvertAccount(t *testing.T) {
err = backend.ensureAppDBOpened(account, password)
require.NoError(t, err)
err = backend.StartNodeWithAccountAndInitialConfig(account, password, *defaultSettings, nodeConfig, profileKeypair.Accounts)
err = backend.StartNodeWithAccountAndInitialConfig(account, password, *defaultSettings, nodeConfig, profileKeypair.Accounts, nil)
require.NoError(t, err)
multiaccounts, err := backend.GetAccounts()
require.NoError(t, err)
@ -1294,7 +1295,7 @@ func loginDesktopUser(t *testing.T, conf *params.NodeConfig) {
wg.Add(1)
go func() {
defer wg.Done()
err := b.StartNodeWithAccount(accounts[0], passwd, conf)
err := b.StartNodeWithAccount(accounts[0], passwd, conf, nil)
require.NoError(t, err)
}()
@ -1682,3 +1683,168 @@ func TestCreateAccountPathsValidation(t *testing.T) {
require.NoError(t, err)
require.Equal(t, tmpdir, request.RootDataDir)
}
func TestRestoreKeycardAccountAndLogin(t *testing.T) {
utils.Init()
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": "",
"fetchBackup": true,
"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": "",
"openseaApiKey": "",
"raribleMainnetApiKey": "",
"raribleTestnetApiKey": "",
"alchemyEthereumMainnetToken": "",
"alchemyEthereumGoerliToken": "",
"alchemyEthereumSepoliaToken": "",
"alchemyArbitrumMainnetToken": "",
"alchemyArbitrumGoerliToken": "",
"alchemyArbitrumSepoliaToken": "",
"alchemyOptimismMainnetToken": "",
"alchemyOptimismGoerliToken": "",
"alchemyOptimismSepoliaToken": "",
},
"torrentConfigEnabled": false,
"torrentConfigPort": 0,
"keycardInstanceUID": "a84599394887b742eed9a99d3834a797",
"keycardPairingDataFile": path.Join(tmpdir, DefaultKeycardPairingDataFile),
},
}
require.NotNil(t, exampleKeycardEvent)
require.NotNil(t, exampleRequest)
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
backend := NewGethStatusBackend()
require.NoError(t, backend.AccountManager().InitKeystore(conf.KeyStoreDir))
backend.UpdateRootDataDir(conf.DataDir)
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)
}

View File

@ -21,6 +21,7 @@ import (
const pathWalletRoot = "m/44'/60'/0'/0"
const pathEIP1581 = "m/43'/60'/1581'"
const pathDefaultChat = pathEIP1581 + "/0'/0"
const pathEncryption = pathEIP1581 + "/1'/0"
const pathDefaultWallet = pathWalletRoot + "/0"
const defaultMnemonicLength = 12
const shardsTestClusterID = 16
@ -38,11 +39,11 @@ const DefaultListenAddr = ":0"
const DefaultMaxMessageDeliveryAttempts = 3
const DefaultVerifyTransactionChainID = 1
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet, pathEncryption}
var DefaultFleet = params.FleetShardsTest
func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo) (*settings.Settings, error) {
func defaultSettings(keyUID string, address string, derivedAddresses map[string]generator.AccountInfo) (*settings.Settings, error) {
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
s := &settings.Settings{}
@ -51,8 +52,8 @@ func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derive
s.LogLevel = &logLevel
s.ProfilePicturesShowTo = settings.ProfilePicturesShowToEveryone
s.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
s.KeyUID = generatedAccountInfo.KeyUID
s.Address = types.HexToAddress(generatedAccountInfo.Address)
s.KeyUID = keyUID
s.Address = types.HexToAddress(address)
s.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
s.URLUnfurlingMode = settings.URLUnfurlingAlwaysAsk
@ -222,8 +223,11 @@ func defaultNodeConfig(installationID string, request *requests.CreateAccount, o
nodeConfig.LogDir = request.LogFilePath
nodeConfig.LogLevel = DefaultLogLevel
nodeConfig.DataDir = DefaultDataDir
nodeConfig.KeycardPairingDataFile = DefaultKeycardPairingDataFile
nodeConfig.ProcessBackedupMessages = false
nodeConfig.KeycardPairingDataFile = DefaultKeycardPairingDataFile
if request.KeycardPairingDataFile != nil {
nodeConfig.KeycardPairingDataFile = *request.KeycardPairingDataFile
}
if request.LogLevel != nil {
nodeConfig.LogLevel = *request.LogLevel

View File

@ -2,9 +2,9 @@ package api
import (
"context"
"crypto/ecdsa"
"database/sql"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
@ -15,8 +15,7 @@ import (
"go.uber.org/zap"
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/sqlite"
"github.com/pkg/errors"
"github.com/imdario/mergo"
@ -48,10 +47,13 @@ import (
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server/pairing/statecontrol"
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/ext"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/sqlite"
"github.com/status-im/status-go/transactions"
"github.com/status-im/status-go/walletdatabase"
)
@ -454,11 +456,8 @@ func (b *GethStatusBackend) setupLogSettings() error {
return nil
}
// StartNodeWithKey instead of loading addresses from database this method derives address from key
// and uses it in application.
// TODO: we should use a proper struct with optional values instead of duplicating the regular functions
// with small variants for keycard, this created too many bugs
func (b *GethStatusBackend) startNodeWithKey(acc multiaccounts.Account, password string, keyHex string, inputNodeCfg *params.NodeConfig) error {
// Deprecated: Use StartNodeWithAccount instead.
func (b *GethStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, nodecfg *params.NodeConfig) error {
if acc.KDFIterations == 0 {
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(acc.KeyUID)
if err != nil {
@ -468,83 +467,21 @@ func (b *GethStatusBackend) startNodeWithKey(acc multiaccounts.Account, password
acc.KDFIterations = kdfIterations
}
err := b.ensureDBsOpened(acc, password)
if err != nil {
return err
}
err = b.loadNodeConfig(inputNodeCfg)
if err != nil {
return err
}
err = b.setupLogSettings()
if err != nil {
return err
}
accountsDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
if acc.ColorHash == nil {
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
if err != nil {
return err
}
acc = *multiAccount
}
b.account = &acc
walletAddr, err := accountsDB.GetWalletAddress()
if err != nil {
return err
}
watchAddrs, err := accountsDB.GetAddresses()
if err != nil {
return err
}
chatKey, err := ethcrypto.HexToECDSA(keyHex)
if err != nil {
return err
}
err = b.StartNode(b.config)
if err != nil {
return err
}
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
return err
}
_, err = b.accountManager.SelectedChatAccount()
if err != nil {
return err
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountsIntoServices()
if err != nil {
return err
}
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
if err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, nodecfg *params.NodeConfig) error {
err := b.startNodeWithKey(acc, password, keyHex, nodecfg)
err = b.startNodeWithAccount(acc, password, nodecfg, chatKey)
if err != nil {
// Stop node for clean up
_ = b.StopNode()
return err
}
// get logged in
if !b.LocalPairingStateManager.IsPairing() {
return b.LoggedIn(acc.KeyUID, err)
if b.LocalPairingStateManager.IsPairing() {
return nil
}
return nil
return b.LoggedIn(acc.KeyUID, err)
}
func (b *GethStatusBackend) OverwriteNodeConfigValues(conf *params.NodeConfig, n *params.NodeConfig) (*params.NodeConfig, error) {
@ -595,6 +532,9 @@ func (b *GethStatusBackend) LoginAccount(request *requests.Login) error {
// Stop node for clean up
_ = b.StopNode()
}
if b.LocalPairingStateManager.IsPairing() {
return nil
}
return b.LoggedIn(request.KeyUID, err)
}
@ -603,7 +543,20 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
return err
}
password := request.Password
if request.Mnemonic != "" {
info, err := b.generateAccountInfo(request.Mnemonic)
if err != nil {
return errors.Wrap(err, "failed to generate account info")
}
derivedAddresses, err := b.getDerivedAddresses(info.ID)
if err != nil {
return errors.Wrap(err, "failed to get derived addresses")
}
request.Password = derivedAddresses[pathEncryption].PublicKey
request.KeycardWhisperPrivateKey = derivedAddresses[pathDefaultChat].PrivateKey
}
acc := multiaccounts.Account{
KeyUID: request.KeyUID,
@ -614,9 +567,9 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}
err := b.ensureDBsOpened(acc, password)
err := b.ensureDBsOpened(acc, request.Password)
if err != nil {
return err
return errors.Wrap(err, "failed to open database")
}
defaultCfg := &params.NodeConfig{
@ -626,14 +579,14 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig)
err = b.UpdateNodeConfigFleet(acc, password, defaultCfg)
err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg)
if err != nil {
return err
return errors.Wrap(err, "failed to update node config fleet")
}
err = b.loadNodeConfig(defaultCfg)
if err != nil {
return err
return errors.Wrap(err, "failed to load node config")
}
if request.RuntimeLogLevel != "" {
@ -650,34 +603,34 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
err = b.setupLogSettings()
if err != nil {
return err
return errors.Wrap(err, "failed to setup log settings")
}
accountsDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
return errors.Wrap(err, "failed to create accounts db")
}
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
if err != nil {
return err
return errors.Wrap(err, "failed to update account color hash and color id")
}
b.account = multiAccount
chatAddr, err := accountsDB.GetChatAddress()
if err != nil {
return err
return errors.Wrap(err, "failed to get chat address")
}
walletAddr, err := accountsDB.GetWalletAddress()
if err != nil {
return err
return errors.Wrap(err, "failed to get wallet address")
}
watchAddrs, err := accountsDB.GetWalletAddresses()
if err != nil {
return err
return errors.Wrap(err, "failed to get wallet addresses")
}
login := account.LoginParams{
Password: password,
Password: request.Password,
ChatAddress: chatAddr,
WatchAddresses: watchAddrs,
MainAccount: walletAddr,
@ -686,17 +639,35 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
err = b.StartNode(b.config)
if err != nil {
b.log.Info("failed to start node")
return err
return errors.Wrap(err, "failed to start node")
}
err = b.SelectAccount(login)
if err != nil {
return err
if chatKey := request.ChatPrivateKey(); chatKey == nil {
err = b.SelectAccount(login)
if err != nil {
return errors.Wrap(err, "failed to select account")
}
} else {
// In case of keycard, we don't have a keystore, instead we have private key loaded from the keycard
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
return errors.Wrap(err, "failed to set chat account")
}
_, err = b.accountManager.SelectedChatAccount()
if err != nil {
return errors.Wrap(err, "failed to get selected chat account")
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountsIntoServices()
if err != nil {
return errors.Wrap(err, "failed to inject accounts into services")
}
}
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
if err != nil {
b.log.Info("failed to update account")
return err
b.log.Error("failed to update account")
return errors.Wrap(err, "failed to update account")
}
return nil
@ -737,7 +708,8 @@ func (b *GethStatusBackend) UpdateNodeConfigFleet(acc multiaccounts.Account, pas
return nil
}
func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, password string, inputNodeCfg *params.NodeConfig) error {
// Deprecated: Use loginAccount instead
func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, password string, inputNodeCfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
err := b.ensureDBsOpened(acc, password)
if err != nil {
return err
@ -793,10 +765,29 @@ func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, pass
return err
}
err = b.SelectAccount(login)
if err != nil {
return err
if chatKey == nil {
// Load account from keystore
err = b.SelectAccount(login)
if err != nil {
return err
}
} else {
// In case of keycard, we don't have keystore, but we directly have the private key
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
return err
}
_, err = b.accountManager.SelectedChatAccount()
if err != nil {
return err
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountsIntoServices()
if err != nil {
return err
}
}
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
if err != nil {
b.log.Info("failed to update account")
@ -861,11 +852,11 @@ func (b *GethStatusBackend) MigrateKeyStoreDir(acc multiaccounts.Account, passwo
}
func (b *GethStatusBackend) Login(keyUID, password string) error {
return b.startNodeWithAccount(multiaccounts.Account{KeyUID: keyUID}, password, nil)
return b.startNodeWithAccount(multiaccounts.Account{KeyUID: keyUID}, password, nil, nil)
}
func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string, nodecfg *params.NodeConfig) error {
err := b.startNodeWithAccount(acc, password, nodecfg)
func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string, nodecfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
err := b.startNodeWithAccount(acc, password, nodecfg, chatKey)
if err != nil {
// Stop node for clean up
_ = b.StopNode()
@ -993,9 +984,9 @@ func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password strin
// because UI calls Logout and Quit afterwards. It should not be UI-dependent
// and should be handled gracefully here if it makes sense to run dummy node after
// logout
_ = b.startNodeWithAccount(*account, password, nil)
_ = b.startNodeWithAccount(*account, password, nil, nil)
} else {
_ = b.startNodeWithAccount(*account, newPassword, nil)
_ = b.startNodeWithAccount(*account, newPassword, nil, nil)
}
}
}
@ -1294,18 +1285,91 @@ func (b *GethStatusBackend) RestoreAccountAndLogin(request *requests.RestoreAcco
return nil, err
}
account, settings, nodeConfig, subAccounts, err := b.generateOrImportAccount(request.Mnemonic, 0, request.FetchBackup, &request.CreateAccount)
response, err := b.generateOrImportAccount(request.Mnemonic, 0, request.FetchBackup, &request.CreateAccount)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(*account, request.Password, *settings, nodeConfig, subAccounts)
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey,
)
if err != nil {
b.log.Error("start node", err)
return nil, err
}
return account, nil
return response.account, nil
}
func (b *GethStatusBackend) RestoreKeycardAccountAndLogin(request *requests.RestoreAccount) (*multiaccounts.Account, error) {
if err := request.Validate(); err != nil {
return nil, err
}
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, request.Keycard.KeyUID)
if err != nil {
return nil, err
}
derivedAddresses := map[string]generator.AccountInfo{
pathDefaultChat: {
Address: request.Keycard.WhisperAddress,
PublicKey: request.Keycard.WhisperPublicKey,
PrivateKey: request.Keycard.WhisperPrivateKey,
},
pathWalletRoot: {
Address: request.Keycard.WalletRootAddress,
},
pathDefaultWallet: {
Address: request.Keycard.WalletAddress,
PublicKey: request.Keycard.WalletPublicKey,
},
pathEIP1581: {
Address: request.Keycard.Eip1581Address,
},
pathEncryption: {
PublicKey: request.Keycard.EncryptionPublicKey,
},
}
input := &prepareAccountInput{
customizationColorClock: 0,
accountID: "", // empty for keycard
keyUID: request.Keycard.KeyUID,
address: request.Keycard.Address,
mnemonic: "",
restoringAccount: true,
derivedAddresses: derivedAddresses,
fetchBackup: request.FetchBackup, // WARNING: Ensure this value is correct
keyStoreDir: keyStoreDir,
}
response, err := b.prepareNodeAccount(&request.CreateAccount, input)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey, //request.WhisperPrivateKey,
)
if err != nil {
b.log.Error("start node", err)
return nil, errors.Wrap(err, "failed to start node")
}
return response.account, nil
}
func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error) {
@ -1319,44 +1383,104 @@ func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error)
return info.KeyUID, nil
}
func (b *GethStatusBackend) generateOrImportAccount(mnemonic string, customizationColorClock uint64, fetchBackup bool, request *requests.CreateAccount, opts ...params.Option) (*multiaccounts.Account, *settings.Settings, *params.NodeConfig, []*accounts.Account, error) {
type prepareAccountInput struct {
customizationColorClock uint64
accountID string
keyUID string
address string
mnemonic string
restoringAccount bool
derivedAddresses map[string]generator.AccountInfo
fetchBackup bool
keyStoreDir string
opts []params.Option
}
type accountBundle struct {
account *multiaccounts.Account
settings *settings.Settings
nodeConfig *params.NodeConfig
subAccounts []*accounts.Account
chatPrivateKey *ecdsa.PrivateKey
}
func (b *GethStatusBackend) generateOrImportAccount(mnemonic string, customizationColorClock uint64, fetchBackup bool, request *requests.CreateAccount, opts ...params.Option) (*accountBundle, error) {
info, err := b.generateAccountInfo(mnemonic)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, info.KeyUID)
if err != nil {
return nil, nil, nil, nil, err
}
account, info, err := b.generateAccount(*info, customizationColorClock, request)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
derivedAddresses, err := b.getDerivedAddresses(info.ID)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
settings, err := b.prepareSettings(*info, derivedAddresses, request, mnemonic)
input := &prepareAccountInput{
customizationColorClock: customizationColorClock,
accountID: info.ID,
keyUID: info.KeyUID,
address: info.Address,
mnemonic: info.Mnemonic,
restoringAccount: mnemonic != "",
derivedAddresses: derivedAddresses,
fetchBackup: fetchBackup,
keyStoreDir: keyStoreDir,
opts: opts,
}
return b.prepareNodeAccount(request, input)
}
func (b *GethStatusBackend) prepareNodeAccount(request *requests.CreateAccount, input *prepareAccountInput) (*accountBundle, error) {
var err error
response := &accountBundle{}
if request.KeycardInstanceUID != "" {
request.Password = input.derivedAddresses[pathEncryption].PublicKey
}
// NOTE: I intentionally left this condition separately and not an `else` branch. Technically it's an `else`,
// but the statements inside are not the opposite statement of the first statement. It's just kinda like this:
// - replace password when we're using keycard
// - store account when we're not using keycard
if request.KeycardInstanceUID == "" {
err = b.storeAccount(input.accountID, request.Password, paths)
if err != nil {
return nil, err
}
}
response.account, err = b.buildAccount(request, input)
if err != nil {
return nil, nil, nil, nil, err
return nil, errors.Wrap(err, "failed to build account")
}
processBackedupMessages := mnemonic != "" && fetchBackup
nodeConfig, err := b.prepareConfig(processBackedupMessages, account.KeyUID, keyStoreDir, request, opts...)
response.settings, err = b.prepareSettings(request, input)
if err != nil {
return nil, nil, nil, nil, err
return nil, errors.Wrap(err, "failed to prepare settings")
}
subAccounts, err := b.prepareSubAccounts(mnemonic, account.KeyUID, derivedAddresses, request)
response.nodeConfig, err = b.prepareConfig(request, input, response.settings.InstallationID)
if err != nil {
return nil, nil, nil, nil, err
return nil, errors.Wrap(err, "failed to prepare node config")
}
return account, settings, nodeConfig, subAccounts, nil
response.subAccounts, err = b.prepareSubAccounts(request, input)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare sub accounts")
}
response, err = b.prepareForKeycard(request, input, response)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare for keycard")
}
return response, nil
}
func (b *GethStatusBackend) initKeyStoreDirWithAccount(rootDataDir, keyUID string) (string, error) {
@ -1391,36 +1515,39 @@ func (b *GethStatusBackend) generateAccountInfo(mnemonic string) (*generator.Gen
return &info, nil
}
func (b *GethStatusBackend) generateAccount(info generator.GeneratedAccountInfo, customizationColorClock uint64, request *requests.CreateAccount) (*multiaccounts.Account, *generator.GeneratedAccountInfo, error) {
err := b.OpenAccounts()
if err != nil {
b.log.Error("failed open accounts", "err", err)
return nil, nil, err
}
func (b *GethStatusBackend) storeAccount(id string, password string, paths []string) error {
accountGenerator := b.accountManager.AccountsGenerator()
_, err = accountGenerator.StoreAccount(info.ID, request.Password)
_, err := accountGenerator.StoreAccount(id, password)
if err != nil {
return nil, nil, err
return err
}
_, err = accountGenerator.StoreDerivedAccounts(info.ID, request.Password, paths)
_, err = accountGenerator.StoreDerivedAccounts(id, password, paths)
if err != nil {
return nil, nil, err
return err
}
account := multiaccounts.Account{
KeyUID: info.KeyUID,
return nil
}
func (b *GethStatusBackend) buildAccount(request *requests.CreateAccount, input *prepareAccountInput) (*multiaccounts.Account, error) {
err := b.OpenAccounts()
if err != nil {
return nil, err
}
acc := &multiaccounts.Account{
KeyUID: input.keyUID,
Name: request.DisplayName,
CustomizationColor: multiacccommon.CustomizationColor(request.CustomizationColor),
CustomizationColorClock: customizationColorClock,
CustomizationColorClock: input.customizationColorClock,
KDFIterations: request.KdfIterations,
Timestamp: time.Now().Unix(),
}
if account.KDFIterations == 0 {
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
if acc.KDFIterations == 0 {
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}
if request.ImagePath != "" {
@ -1439,16 +1566,16 @@ func (b *GethStatusBackend) generateAccount(info generator.GeneratedAccountInfo,
imageCropRectangle.Ax, imageCropRectangle.Ay, imageCropRectangle.Bx, imageCropRectangle.By)
if err != nil {
return nil, nil, err
return nil, err
}
account.Images = iis
acc.Images = iis
}
return &account, &info, nil
return acc, nil
}
func (b *GethStatusBackend) prepareSettings(info generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, request *requests.CreateAccount, mnemonic string) (*settings.Settings, error) {
settings, err := defaultSettings(info, derivedAddresses)
func (b *GethStatusBackend) prepareSettings(request *requests.CreateAccount, input *prepareAccountInput) (*settings.Settings, error) {
settings, err := defaultSettings(input.keyUID, input.address, input.derivedAddresses)
if err != nil {
return nil, err
}
@ -1459,9 +1586,8 @@ func (b *GethStatusBackend) prepareSettings(info generator.GeneratedAccountInfo,
settings.CurrentNetwork = request.CurrentNetwork
settings.TestNetworksEnabled = request.TestNetworksEnabled
// If restoring an account, we don't set the mnemonic
if mnemonic == "" {
settings.Mnemonic = &info.Mnemonic
if !input.restoringAccount {
settings.Mnemonic = &input.mnemonic
settings.OmitTransfersHistoryScan = true
// TODO(rasom): uncomment it as soon as address will be properly
// marked as shown on mobile client
@ -1471,41 +1597,38 @@ func (b *GethStatusBackend) prepareSettings(info generator.GeneratedAccountInfo,
return settings, nil
}
func (b *GethStatusBackend) prepareConfig(processBackedupMessages bool, installationID string, userKeyStoreDir string, request *requests.CreateAccount, opts ...params.Option) (*params.NodeConfig, error) {
nodeConfig, err := defaultNodeConfig(installationID, request, opts...)
func (b *GethStatusBackend) prepareConfig(request *requests.CreateAccount, input *prepareAccountInput, installationID string) (*params.NodeConfig, error) {
nodeConfig, err := defaultNodeConfig(installationID, request, input.opts...)
if err != nil {
return nil, err
}
nodeConfig.ProcessBackedupMessages = processBackedupMessages
nodeConfig.ProcessBackedupMessages = input.fetchBackup
// when we set nodeConfig.KeyStoreDir, value of nodeConfig.KeyStoreDir should not contain the rootDataDir
// loadNodeConfig will add rootDataDir to nodeConfig.KeyStoreDir
nodeConfig.KeyStoreDir = userKeyStoreDir
nodeConfig.KeyStoreDir = input.keyStoreDir
return nodeConfig, nil
}
func (b *GethStatusBackend) prepareSubAccounts(mnemonic, keyUID string, derivedAddresses map[string]generator.AccountInfo, request *requests.CreateAccount) ([]*accounts.Account, error) {
walletDerivedAccount := derivedAddresses[pathDefaultWallet]
func (b *GethStatusBackend) prepareSubAccounts(request *requests.CreateAccount, input *prepareAccountInput) ([]*accounts.Account, error) {
walletDerivedAccount := input.derivedAddresses[pathDefaultWallet]
walletAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
KeyUID: keyUID,
Address: types.HexToAddress(walletDerivedAccount.Address),
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
Emoji: request.Emoji,
Wallet: true,
Path: pathDefaultWallet,
Name: walletAccountDefaultName,
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
KeyUID: input.keyUID,
Address: types.HexToAddress(walletDerivedAccount.Address),
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
Emoji: request.Emoji,
Wallet: true,
Path: pathDefaultWallet,
Name: walletAccountDefaultName,
AddressWasNotShown: !input.restoringAccount,
}
if mnemonic == "" {
walletAccount.AddressWasNotShown = true
}
chatDerivedAccount := derivedAddresses[pathDefaultChat]
chatDerivedAccount := input.derivedAddresses[pathDefaultChat]
chatAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
KeyUID: keyUID,
KeyUID: input.keyUID,
Address: types.HexToAddress(chatDerivedAccount.Address),
Name: request.DisplayName,
Chat: true,
@ -1515,14 +1638,40 @@ func (b *GethStatusBackend) prepareSubAccounts(mnemonic, keyUID string, derivedA
return []*accounts.Account{walletAccount, chatAccount}, nil
}
func (b *GethStatusBackend) getDerivedAddresses(id string) (map[string]generator.AccountInfo, error) {
accountGenerator := b.accountManager.AccountsGenerator()
derivedAddresses, err := accountGenerator.DeriveAddresses(id, paths)
if err != nil {
return nil, err
func (b *GethStatusBackend) prepareForKeycard(request *requests.CreateAccount, input *prepareAccountInput, response *accountBundle) (*accountBundle, error) {
if request.KeycardInstanceUID == "" {
return response, nil
}
return derivedAddresses, nil
kp := wallet.NewKeycardPairings()
kp.SetKeycardPairingsFile(response.nodeConfig.KeycardPairingDataFile)
pairings, err := kp.GetPairings()
if err != nil {
return nil, errors.Wrap(err, "failed to get keycard pairings")
}
keycard, ok := pairings[request.KeycardInstanceUID]
if !ok {
return nil, errors.New("keycard not found in pairings file")
}
response.settings.KeycardInstanceUID = request.KeycardInstanceUID
response.settings.KeycardPairedOn = time.Now().Unix()
response.settings.KeycardPairing = keycard.Key
response.account.KeycardPairing = keycard.Key
privateKeyHex := strings.TrimPrefix(input.derivedAddresses[pathDefaultChat].PrivateKey, "0x")
response.chatPrivateKey, err = crypto.HexToECDSA(privateKeyHex)
if err != nil {
return nil, errors.Wrap(err, "failed to parse chat private key hex")
}
return response, nil
}
func (b *GethStatusBackend) getDerivedAddresses(id string) (map[string]generator.AccountInfo, error) {
accountGenerator := b.accountManager.AccountsGenerator()
return accountGenerator.DeriveAddresses(id, paths)
}
// CreateAccountAndLogin creates a new account and logs in with it.
@ -1535,18 +1684,26 @@ func (b *GethStatusBackend) CreateAccountAndLogin(request *requests.CreateAccoun
return nil, err
}
account, settings, nodeConfig, subAccounts, err := b.generateOrImportAccount("", 1, false, request, opts...)
response, err := b.generateOrImportAccount("", 1, false, request, opts...)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(*account, request.Password, *settings, nodeConfig, subAccounts)
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey,
)
if err != nil {
b.log.Error("start node", err)
return nil, err
}
return account, nil
return response.account, nil
}
func (b *GethStatusBackend) ConvertToRegularAccount(mnemonic string, currPassword string, newPassword string) error {
@ -1708,7 +1865,15 @@ func enrichMultiAccountByPublicKey(account *multiaccounts.Account, publicKey typ
return nil
}
func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(account multiaccounts.Account, password string, settings settings.Settings, nodecfg *params.NodeConfig, subaccs []*accounts.Account, keyHex string) error {
// Deprecated: Use CreateAccountAndLogin instead
func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(
account multiaccounts.Account,
password string,
settings settings.Settings,
nodecfg *params.NodeConfig,
subaccs []*accounts.Account,
keyHex string,
) error {
err := enrichMultiAccountBySubAccounts(&account, subaccs)
if err != nil {
return err
@ -1731,15 +1896,15 @@ func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(account multiaccounts
// StartNodeWithAccountAndInitialConfig is used after account and config was generated.
// In current setup account name and config is generated on the client side. Once/if it will be generated on
// status-go side this flow can be simplified.
// TODO: Consider passing accountBundle here directly
func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
account multiaccounts.Account,
password string,
settings settings.Settings,
nodecfg *params.NodeConfig,
subaccs []*accounts.Account,
chatKey *ecdsa.PrivateKey,
) error {
b.log.Info("node config", "config", nodecfg)
err := enrichMultiAccountBySubAccounts(&account, subaccs)
if err != nil {
return err
@ -1756,7 +1921,7 @@ func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
if err != nil {
return err
}
return b.StartNodeWithAccount(account, password, nodecfg)
return b.StartNodeWithAccount(account, password, nodecfg, chatKey)
}
// TODO: change in `saveAccountsAndSettings` function param `subaccs []*accounts.Account` parameter to `profileKeypair *accounts.Keypair` parameter

View File

@ -22,6 +22,8 @@ import (
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/services/utils"
"github.com/status-im/status-go/signal"
tutils "github.com/status-im/status-go/t/utils"
"github.com/status-im/status-go/wakuv2"
"github.com/stretchr/testify/suite"
@ -29,6 +31,7 @@ import (
type MessengerRawMessageResendTest struct {
suite.Suite
logger *zap.Logger
aliceBackend *GethStatusBackend
bobBackend *GethStatusBackend
aliceMessenger *protocol.Messenger
@ -43,9 +46,14 @@ func TestMessengerRawMessageResendTestSuite(t *testing.T) {
}
func (s *MessengerRawMessageResendTest) SetupTest() {
logger, err := zap.NewDevelopment()
tutils.Init()
var err error
s.logger, err = zap.NewDevelopment()
s.Require().NoError(err)
signal.SetMobileSignalHandler(nil)
exchangeNodeConfig := &wakuv2.Config{
Port: 0,
EnableDiscV5: true,
@ -54,7 +62,7 @@ func (s *MessengerRawMessageResendTest) SetupTest() {
UseShardAsDefaultTopic: true,
DefaultShardPubsubTopic: shard.DefaultShardPubsubTopic(),
}
s.exchangeBootNode, err = wakuv2.New(nil, "", exchangeNodeConfig, logger.Named("pxServerNode"), nil, nil, nil, nil)
s.exchangeBootNode, err = wakuv2.New(nil, "", exchangeNodeConfig, s.logger.Named("pxServerNode"), nil, nil, nil, nil)
s.Require().NoError(err)
s.Require().NoError(s.exchangeBootNode.Start())

View File

@ -449,7 +449,7 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
fmt.Println(nodeConfig)
accounts := []*accounts.Account{walletAccount, chatAccount}
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
if err != nil {
logger.Error("start node", err)
return err

View File

@ -498,7 +498,7 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
fmt.Println(nodeConfig)
accounts := []*accounts.Account{walletAccount, chatAccount}
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
if err != nil {
logger.Error("start node", err)
return err

View File

@ -420,9 +420,9 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
fmt.Println(nodeConfig)
accounts := []*accounts.Account{walletAccount, chatAccount}
if !exist {
return backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
return backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
}
return backend.StartNodeWithAccount(account, "", nodeConfig)
return backend.StartNodeWithAccount(account, "", nodeConfig, nil)
}
func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration) {

View File

@ -18,7 +18,7 @@ type Key struct {
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
// to derive child keys.
ExtendedKey *extkeys.ExtendedKey
// SubAccountIndex is DEPRECATED
// Deprecated: SubAccountIndex
// It was use in Status to keep track of the number of sub-account created
// before having multi-account support.
SubAccountIndex uint32

View File

@ -237,7 +237,7 @@ func login(accountData, password, configJSON string) error {
return statusBackend.LoggedIn(account.KeyUID, err)
}
err = statusBackend.StartNodeWithAccount(account, password, &conf)
err = statusBackend.StartNodeWithAccount(account, password, &conf, nil)
if err != nil {
log.Error("failed to start a node", "key-uid", account.KeyUID, "error", err)
return err
@ -339,7 +339,13 @@ func RestoreAccountAndLogin(requestJSON string) string {
api.RunAsync(func() error {
log.Debug("starting a node and restoring account")
_, err := statusBackend.RestoreAccountAndLogin(&request)
if request.Keycard != nil {
_, err = statusBackend.RestoreKeycardAccountAndLogin(&request)
} else {
_, err = statusBackend.RestoreAccountAndLogin(&request)
}
if err != nil {
log.Error("failed to restore account", "error", err)
return err
@ -347,6 +353,7 @@ func RestoreAccountAndLogin(requestJSON string) string {
log.Debug("started a node, and restored account")
return nil
})
return makeJSONResponse(nil)
}
@ -382,7 +389,7 @@ func SaveAccountAndLogin(accountData, password, settingsJSON, configJSON, subacc
api.RunAsync(func() error {
log.Debug("starting a node, and saving account with configuration", "key-uid", account.KeyUID)
err := statusBackend.StartNodeWithAccountAndInitialConfig(account, password, settings, &conf, subaccs)
err := statusBackend.StartNodeWithAccountAndInitialConfig(account, password, settings, &conf, subaccs, nil)
if err != nil {
log.Error("failed to start node and save account", "key-uid", account.KeyUID, "error", err)
return err
@ -419,7 +426,8 @@ func InitKeystore(keydir string) string {
return makeJSONResponse(err)
}
// SaveAccountAndLoginWithKeycard saves account in status-go database..
// SaveAccountAndLoginWithKeycard saves account in status-go database.
// Deprecated: Use CreateAndAccountAndLogin with required keycard properties.
func SaveAccountAndLoginWithKeycard(accountData, password, settingsJSON, configJSON, subaccountData string, keyHex string) string {
var account multiaccounts.Account
err := json.Unmarshal([]byte(accountData), &account)
@ -457,6 +465,7 @@ func SaveAccountAndLoginWithKeycard(accountData, password, settingsJSON, configJ
// LoginWithKeycard initializes an account with a chat key and encryption key used for PFS.
// It purges all the previous identities from Whisper, and injects the key as shh identity.
// Deprecated: Use LoginAccount instead.
func LoginWithKeycard(accountData, password, keyHex string, configJSON string) string {
var account multiaccounts.Account
err := json.Unmarshal([]byte(accountData), &account)

View File

@ -328,6 +328,9 @@ type NodeConfig struct {
KeyStoreDir string `validate:"required"`
// KeycardPairingDataFile is the file where we keep keycard pairings data.
// It's specified by clients (and not in status-go) when creating a new account,
// because this file is initialized by status-keycard-go and we need to use it before initializing the node.
// I guess proper way would be to ask status-go for the file path, or just duplicate the file path in both backend and client.
// note: this field won't be saved into db, it's local to the device.
KeycardPairingDataFile string

View File

@ -74,6 +74,9 @@ type CreateAccount struct {
TelemetryServerURL string `json:"telemetryServerURL"`
APIConfig *APIConfig `json:"apiConfig"`
KeycardInstanceUID string `json:"keycardInstanceUID"`
KeycardPairingDataFile *string `json:"keycardPairingDataFile"`
}
type WalletSecretsConfig struct {

View File

@ -1,18 +1,38 @@
package requests
import "errors"
import (
"crypto/ecdsa"
"errors"
"strings"
var ErrLoginInvalidKeyUID = errors.New("login: invalid key-uid")
"github.com/status-im/status-go/eth-node/crypto"
)
var (
ErrLoginInvalidKeyUID = errors.New("login: invalid key-uid")
ErrLoginInvalidKeycardWhisperPrivateKey = errors.New("login: invalid keycard whisper private key")
)
type Login struct {
Password string `json:"password"`
KeyUID string `json:"keyUid"`
KdfIterations int `json:"kdfIterations"`
KdfIterations int `json:"kdfIterations"` // FIXME: KdfIterations should be loaded from multiaccounts db.
RuntimeLogLevel string `json:"runtimeLogLevel"`
WakuV2Nameserver string `json:"wakuV2Nameserver"`
BandwidthStatsEnabled bool `json:"bandwidthStatsEnabled"`
KeycardWhisperPrivateKey string `json:"keycardWhisperPrivateKey"`
// Mnemonic allows to log in to an account when password is lost.
// This is needed for the "Lost keycard -> Start using without keycard" flow, when a keycard account database
// exists locally, but now the keycard is lost. In this case client is responsible for calling
// `convertToRegularAccount` after a successful login. This could be improved in the future.
// When non-empty, mnemonic is used to generate required keypairs and:
// - Password is ignored and replaced with encryption public key
// - KeycardWhisperPrivateKey is ignored and replaced with chat private key
Mnemonic string `json:"mnemonic"`
WalletSecretsConfig
}
@ -20,5 +40,24 @@ func (c *Login) Validate() error {
if c.KeyUID == "" {
return ErrLoginInvalidKeyUID
}
if c.KeycardWhisperPrivateKey != "" {
_, err := parsePrivateKey(c.KeycardWhisperPrivateKey)
if err != nil {
return ErrLoginInvalidKeycardWhisperPrivateKey
}
}
return nil
}
func (c *Login) ChatPrivateKey() *ecdsa.PrivateKey {
// Skip error check, as it's already validated in Validate
privateKey, _ := parsePrivateKey(c.KeycardWhisperPrivateKey)
return privateKey
}
func parsePrivateKey(privateKeyHex string) (*ecdsa.PrivateKey, error) {
privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x")
return crypto.HexToECDSA(privateKeyHex)
}

View File

@ -4,20 +4,46 @@ import (
"errors"
)
var ErrRestoreAccountInvalidMnemonic = errors.New("restore-account: invalid mnemonic")
var (
ErrRestoreAccountInvalidMnemonic = errors.New("restore-account: no mnemonic or keycard is set")
ErrRestoreAccountMnemonicAndKeycard = errors.New("restore-account: both mnemonic and keycard info are set")
)
type RestoreAccount struct {
Mnemonic string `json:"mnemonic"`
FetchBackup bool `json:"fetchBackup"`
Mnemonic string `json:"mnemonic"`
// Keycard info can be set instead of Mnemonic.
// This is to log in using a keycard with existing account.
Keycard *KeycardData `json:"keycard"`
FetchBackup bool `json:"fetchBackup"`
CreateAccount
}
func (c *RestoreAccount) Validate() error {
if len(c.Mnemonic) == 0 {
if len(c.Mnemonic) == 0 && c.Keycard == nil {
return ErrRestoreAccountInvalidMnemonic
}
if len(c.Mnemonic) > 0 && c.Keycard != nil {
return ErrRestoreAccountMnemonicAndKeycard
}
return c.CreateAccount.Validate(&CreateAccountValidation{
AllowEmptyDisplayName: true,
})
}
type KeycardData struct {
KeyUID string `json:"keyUID"`
Address string `json:"address"`
WhisperPrivateKey string `json:"whisperPrivateKey"`
WhisperPublicKey string `json:"whisperPublicKey"`
WhisperAddress string `json:"whisperAddress"`
WalletPublicKey string `json:"walletPublicKey"`
WalletAddress string `json:"walletAddress"`
WalletRootAddress string `json:"walletRootAddress"`
Eip1581Address string `json:"eip1581Address"`
EncryptionPublicKey string `json:"encryptionPublicKey"`
}

View File

@ -2,8 +2,12 @@ package pairing
import (
"context"
"crypto/ecdsa"
"fmt"
"path/filepath"
"strings"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/multiaccounts/accounts"
@ -93,12 +97,18 @@ func (s *SyncRawMessageHandler) HandleRawMessage(accountPayload *AccountPayload,
// because client don't know keyUID before received data, we need help client to update keystore dir
keystoreDir := filepath.Join(nodeConfig.KeyStoreDir, account.KeyUID)
nodeConfig.KeyStoreDir = keystoreDir
if accountPayload.exist {
if len(accountPayload.chatKey) == 0 {
err = s.backend.StartNodeWithAccount(*account, accountPayload.password, nodeConfig)
} else {
err = s.backend.StartNodeWithKey(*account, accountPayload.password, accountPayload.chatKey, nodeConfig)
var chatKey *ecdsa.PrivateKey
if accountPayload.chatKey != "" {
chatKeyHex := strings.Trim(accountPayload.chatKey, "0x")
chatKey, err = ethcrypto.HexToECDSA(chatKeyHex)
if err != nil {
return err
}
}
if accountPayload.exist {
err = s.backend.StartNodeWithAccount(*account, accountPayload.password, nodeConfig, chatKey)
} else {
accountManager := s.backend.AccountManager()
err = accountManager.InitKeystore(filepath.Join(nodeConfig.RootDataDir, keystoreDir))
@ -109,11 +119,7 @@ func (s *SyncRawMessageHandler) HandleRawMessage(accountPayload *AccountPayload,
rmp.setting.InstallationID = nodeConfig.ShhextConfig.InstallationID
rmp.setting.CurrentNetwork = settingCurrentNetwork
if len(accountPayload.chatKey) == 0 {
err = s.backend.StartNodeWithAccountAndInitialConfig(*account, accountPayload.password, *rmp.setting, nodeConfig, rmp.profileKeypair.Accounts)
} else {
err = s.backend.SaveAccountAndStartNodeWithKey(*account, accountPayload.password, *rmp.setting, nodeConfig, rmp.profileKeypair.Accounts, accountPayload.chatKey)
}
err = s.backend.StartNodeWithAccountAndInitialConfig(*account, accountPayload.password, *rmp.setting, nodeConfig, rmp.profileKeypair.Accounts, chatKey)
}
if err != nil {
return err

View File

@ -140,7 +140,7 @@ func (s *SyncDeviceSuite) prepareBackendWithAccount(mnemonic, tmpdir string) *ap
}
accounts := []*accounts.Account{walletAccount, chatAccount}
err = backend.StartNodeWithAccountAndInitialConfig(account, s.password, *settings, nodeConfig, accounts)
err = backend.StartNodeWithAccountAndInitialConfig(account, s.password, *settings, nodeConfig, accounts, nil)
require.NoError(s.T(), err)
multiaccounts, err := backend.GetAccounts()
require.NoError(s.T(), err)

View File

@ -1,6 +1,7 @@
package wallet
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
@ -10,6 +11,11 @@ type KeycardPairings struct {
pairingsFile string
}
type KeycardPairing struct {
Key string `json:"key"`
Index int `json:"index"`
}
func NewKeycardPairings() *KeycardPairings {
return &KeycardPairings{}
}
@ -43,3 +49,22 @@ func (kp *KeycardPairings) SetPairingsJSONFileContent(content []byte) error {
return ioutil.WriteFile(kp.pairingsFile, content, 0600)
}
func (kp *KeycardPairings) GetPairings() (map[string]KeycardPairing, error) {
content, err := kp.GetPairingsJSONFileContent()
if err != nil {
return nil, err
}
if len(content) == 0 {
return nil, os.ErrNotExist
}
pairings := make(map[string]KeycardPairing)
err = json.Unmarshal(content, &pairings)
if err != nil {
return nil, err
}
return pairings, nil
}