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:
parent
1cdcc0dcc2
commit
49eaabaca5
|
@ -1,6 +1,8 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
|
@ -18,8 +20,8 @@ type StatusBackend interface {
|
||||||
// IsNodeRunning() bool // NOTE: Only used in tests
|
// IsNodeRunning() bool // NOTE: Only used in tests
|
||||||
StartNode(config *params.NodeConfig) error // NOTE: Only used in canary
|
StartNode(config *params.NodeConfig) error // NOTE: Only used in canary
|
||||||
StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, conf *params.NodeConfig) error
|
StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, conf *params.NodeConfig) error
|
||||||
StartNodeWithAccount(acc multiaccounts.Account, password string, conf *params.NodeConfig) 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) error
|
StartNodeWithAccountAndInitialConfig(account multiaccounts.Account, password string, settings settings.Settings, conf *params.NodeConfig, subaccs []*accounts.Account, chatKey *ecdsa.PrivateKey) error
|
||||||
StopNode() error
|
StopNode() error
|
||||||
// RestartNode() error // NOTE: Only used in tests
|
// RestartNode() error // NOTE: Only used in tests
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/services/typeddata"
|
"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"
|
walletservice "github.com/status-im/status-go/services/wallet"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
"github.com/status-im/status-go/sqlite"
|
"github.com/status-im/status-go/sqlite"
|
||||||
|
@ -1073,7 +1074,7 @@ func TestConvertAccount(t *testing.T) {
|
||||||
found = keystoreContainsFileForAccount(keyStoreDir, chatAddress)
|
found = keystoreContainsFileForAccount(keyStoreDir, chatAddress)
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
|
|
||||||
defaultSettings, err := defaultSettings(genAccInfo, derivedAccounts)
|
defaultSettings, err := defaultSettings(genAccInfo.KeyUID, genAccInfo.Address, derivedAccounts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
nodeConfig, err := defaultNodeConfig(defaultSettings.InstallationID, &requests.CreateAccount{
|
nodeConfig, err := defaultNodeConfig(defaultSettings.InstallationID, &requests.CreateAccount{
|
||||||
LogLevel: defaultSettings.LogLevel,
|
LogLevel: defaultSettings.LogLevel,
|
||||||
|
@ -1135,7 +1136,7 @@ func TestConvertAccount(t *testing.T) {
|
||||||
err = backend.ensureAppDBOpened(account, password)
|
err = backend.ensureAppDBOpened(account, password)
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
multiaccounts, err := backend.GetAccounts()
|
multiaccounts, err := backend.GetAccounts()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1294,7 +1295,7 @@ func loginDesktopUser(t *testing.T, conf *params.NodeConfig) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
err := b.StartNodeWithAccount(accounts[0], passwd, conf)
|
err := b.StartNodeWithAccount(accounts[0], passwd, conf, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -1682,3 +1683,168 @@ func TestCreateAccountPathsValidation(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tmpdir, request.RootDataDir)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
const pathWalletRoot = "m/44'/60'/0'/0"
|
const pathWalletRoot = "m/44'/60'/0'/0"
|
||||||
const pathEIP1581 = "m/43'/60'/1581'"
|
const pathEIP1581 = "m/43'/60'/1581'"
|
||||||
const pathDefaultChat = pathEIP1581 + "/0'/0"
|
const pathDefaultChat = pathEIP1581 + "/0'/0"
|
||||||
|
const pathEncryption = pathEIP1581 + "/1'/0"
|
||||||
const pathDefaultWallet = pathWalletRoot + "/0"
|
const pathDefaultWallet = pathWalletRoot + "/0"
|
||||||
const defaultMnemonicLength = 12
|
const defaultMnemonicLength = 12
|
||||||
const shardsTestClusterID = 16
|
const shardsTestClusterID = 16
|
||||||
|
@ -38,11 +39,11 @@ const DefaultListenAddr = ":0"
|
||||||
const DefaultMaxMessageDeliveryAttempts = 3
|
const DefaultMaxMessageDeliveryAttempts = 3
|
||||||
const DefaultVerifyTransactionChainID = 1
|
const DefaultVerifyTransactionChainID = 1
|
||||||
|
|
||||||
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
|
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet, pathEncryption}
|
||||||
|
|
||||||
var DefaultFleet = params.FleetShardsTest
|
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
|
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
|
||||||
|
|
||||||
s := &settings.Settings{}
|
s := &settings.Settings{}
|
||||||
|
@ -51,8 +52,8 @@ func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derive
|
||||||
s.LogLevel = &logLevel
|
s.LogLevel = &logLevel
|
||||||
s.ProfilePicturesShowTo = settings.ProfilePicturesShowToEveryone
|
s.ProfilePicturesShowTo = settings.ProfilePicturesShowToEveryone
|
||||||
s.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
|
s.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
|
||||||
s.KeyUID = generatedAccountInfo.KeyUID
|
s.KeyUID = keyUID
|
||||||
s.Address = types.HexToAddress(generatedAccountInfo.Address)
|
s.Address = types.HexToAddress(address)
|
||||||
s.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
|
s.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
|
||||||
s.URLUnfurlingMode = settings.URLUnfurlingAlwaysAsk
|
s.URLUnfurlingMode = settings.URLUnfurlingAlwaysAsk
|
||||||
|
|
||||||
|
@ -222,8 +223,11 @@ func defaultNodeConfig(installationID string, request *requests.CreateAccount, o
|
||||||
nodeConfig.LogDir = request.LogFilePath
|
nodeConfig.LogDir = request.LogFilePath
|
||||||
nodeConfig.LogLevel = DefaultLogLevel
|
nodeConfig.LogLevel = DefaultLogLevel
|
||||||
nodeConfig.DataDir = DefaultDataDir
|
nodeConfig.DataDir = DefaultDataDir
|
||||||
nodeConfig.KeycardPairingDataFile = DefaultKeycardPairingDataFile
|
|
||||||
nodeConfig.ProcessBackedupMessages = false
|
nodeConfig.ProcessBackedupMessages = false
|
||||||
|
nodeConfig.KeycardPairingDataFile = DefaultKeycardPairingDataFile
|
||||||
|
if request.KeycardPairingDataFile != nil {
|
||||||
|
nodeConfig.KeycardPairingDataFile = *request.KeycardPairingDataFile
|
||||||
|
}
|
||||||
|
|
||||||
if request.LogLevel != nil {
|
if request.LogLevel != nil {
|
||||||
nodeConfig.LogLevel = *request.LogLevel
|
nodeConfig.LogLevel = *request.LogLevel
|
||||||
|
|
|
@ -2,9 +2,9 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
@ -15,8 +15,7 @@ import (
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/ens"
|
"github.com/pkg/errors"
|
||||||
"github.com/status-im/status-go/sqlite"
|
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
|
||||||
|
@ -48,10 +47,13 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/server/pairing/statecontrol"
|
"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/ext"
|
||||||
"github.com/status-im/status-go/services/personal"
|
"github.com/status-im/status-go/services/personal"
|
||||||
"github.com/status-im/status-go/services/typeddata"
|
"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/signal"
|
||||||
|
"github.com/status-im/status-go/sqlite"
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
"github.com/status-im/status-go/walletdatabase"
|
"github.com/status-im/status-go/walletdatabase"
|
||||||
)
|
)
|
||||||
|
@ -454,11 +456,8 @@ func (b *GethStatusBackend) setupLogSettings() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartNodeWithKey instead of loading addresses from database this method derives address from key
|
// Deprecated: Use StartNodeWithAccount instead.
|
||||||
// and uses it in application.
|
func (b *GethStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, nodecfg *params.NodeConfig) error {
|
||||||
// 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 {
|
|
||||||
if acc.KDFIterations == 0 {
|
if acc.KDFIterations == 0 {
|
||||||
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(acc.KeyUID)
|
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(acc.KeyUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -468,83 +467,21 @@ func (b *GethStatusBackend) startNodeWithKey(acc multiaccounts.Account, password
|
||||||
acc.KDFIterations = kdfIterations
|
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)
|
chatKey, err := ethcrypto.HexToECDSA(keyHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.startNodeWithAccount(acc, password, nodecfg, chatKey)
|
||||||
err := b.startNodeWithKey(acc, password, keyHex, nodecfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Stop node for clean up
|
// Stop node for clean up
|
||||||
_ = b.StopNode()
|
_ = b.StopNode()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
// get logged in
|
// get logged in
|
||||||
if !b.LocalPairingStateManager.IsPairing() {
|
if b.LocalPairingStateManager.IsPairing() {
|
||||||
return b.LoggedIn(acc.KeyUID, err)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return b.LoggedIn(acc.KeyUID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) OverwriteNodeConfigValues(conf *params.NodeConfig, n *params.NodeConfig) (*params.NodeConfig, error) {
|
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
|
// Stop node for clean up
|
||||||
_ = b.StopNode()
|
_ = b.StopNode()
|
||||||
}
|
}
|
||||||
|
if b.LocalPairingStateManager.IsPairing() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return b.LoggedIn(request.KeyUID, err)
|
return b.LoggedIn(request.KeyUID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,7 +543,20 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
||||||
return err
|
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{
|
acc := multiaccounts.Account{
|
||||||
KeyUID: request.KeyUID,
|
KeyUID: request.KeyUID,
|
||||||
|
@ -614,9 +567,9 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
||||||
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
err := b.ensureDBsOpened(acc, password)
|
err := b.ensureDBsOpened(acc, request.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to open database")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultCfg := ¶ms.NodeConfig{
|
defaultCfg := ¶ms.NodeConfig{
|
||||||
|
@ -626,14 +579,14 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
||||||
|
|
||||||
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig)
|
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig)
|
||||||
|
|
||||||
err = b.UpdateNodeConfigFleet(acc, password, defaultCfg)
|
err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to update node config fleet")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.loadNodeConfig(defaultCfg)
|
err = b.loadNodeConfig(defaultCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to load node config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.RuntimeLogLevel != "" {
|
if request.RuntimeLogLevel != "" {
|
||||||
|
@ -650,34 +603,34 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
||||||
|
|
||||||
err = b.setupLogSettings()
|
err = b.setupLogSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to setup log settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
accountsDB, err := accounts.NewDB(b.appDB)
|
accountsDB, err := accounts.NewDB(b.appDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to create accounts db")
|
||||||
}
|
}
|
||||||
|
|
||||||
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
|
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to update account color hash and color id")
|
||||||
}
|
}
|
||||||
b.account = multiAccount
|
b.account = multiAccount
|
||||||
|
|
||||||
chatAddr, err := accountsDB.GetChatAddress()
|
chatAddr, err := accountsDB.GetChatAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to get chat address")
|
||||||
}
|
}
|
||||||
walletAddr, err := accountsDB.GetWalletAddress()
|
walletAddr, err := accountsDB.GetWalletAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to get wallet address")
|
||||||
}
|
}
|
||||||
watchAddrs, err := accountsDB.GetWalletAddresses()
|
watchAddrs, err := accountsDB.GetWalletAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to get wallet addresses")
|
||||||
}
|
}
|
||||||
login := account.LoginParams{
|
login := account.LoginParams{
|
||||||
Password: password,
|
Password: request.Password,
|
||||||
ChatAddress: chatAddr,
|
ChatAddress: chatAddr,
|
||||||
WatchAddresses: watchAddrs,
|
WatchAddresses: watchAddrs,
|
||||||
MainAccount: walletAddr,
|
MainAccount: walletAddr,
|
||||||
|
@ -686,17 +639,35 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
||||||
err = b.StartNode(b.config)
|
err = b.StartNode(b.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.log.Info("failed to start node")
|
b.log.Info("failed to start node")
|
||||||
return err
|
return errors.Wrap(err, "failed to start node")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.SelectAccount(login)
|
if chatKey := request.ChatPrivateKey(); chatKey == nil {
|
||||||
if err != nil {
|
err = b.SelectAccount(login)
|
||||||
return err
|
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())
|
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.log.Info("failed to update account")
|
b.log.Error("failed to update account")
|
||||||
return err
|
return errors.Wrap(err, "failed to update account")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -737,7 +708,8 @@ func (b *GethStatusBackend) UpdateNodeConfigFleet(acc multiaccounts.Account, pas
|
||||||
return nil
|
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)
|
err := b.ensureDBsOpened(acc, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -793,10 +765,29 @@ func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, pass
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.SelectAccount(login)
|
if chatKey == nil {
|
||||||
if err != nil {
|
// Load account from keystore
|
||||||
return err
|
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())
|
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.log.Info("failed to update account")
|
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 {
|
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 {
|
func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string, nodecfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
|
||||||
err := b.startNodeWithAccount(acc, password, nodecfg)
|
err := b.startNodeWithAccount(acc, password, nodecfg, chatKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Stop node for clean up
|
// Stop node for clean up
|
||||||
_ = b.StopNode()
|
_ = 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
|
// 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
|
// and should be handled gracefully here if it makes sense to run dummy node after
|
||||||
// logout
|
// logout
|
||||||
_ = b.startNodeWithAccount(*account, password, nil)
|
_ = b.startNodeWithAccount(*account, password, nil, nil)
|
||||||
} else {
|
} 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
b.log.Error("start node", err)
|
b.log.Error("start node", err)
|
||||||
return nil, 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) {
|
func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error) {
|
||||||
|
@ -1319,44 +1383,104 @@ func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error)
|
||||||
return info.KeyUID, nil
|
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)
|
info, err := b.generateAccountInfo(mnemonic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, info.KeyUID)
|
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, info.KeyUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
account, info, err := b.generateAccount(*info, customizationColorClock, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
derivedAddresses, err := b.getDerivedAddresses(info.ID)
|
derivedAddresses, err := b.getDerivedAddresses(info.ID)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, errors.Wrap(err, "failed to build account")
|
||||||
}
|
}
|
||||||
|
|
||||||
processBackedupMessages := mnemonic != "" && fetchBackup
|
response.settings, err = b.prepareSettings(request, input)
|
||||||
nodeConfig, err := b.prepareConfig(processBackedupMessages, account.KeyUID, keyStoreDir, request, opts...)
|
|
||||||
if err != nil {
|
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 {
|
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) {
|
func (b *GethStatusBackend) initKeyStoreDirWithAccount(rootDataDir, keyUID string) (string, error) {
|
||||||
|
@ -1391,36 +1515,39 @@ func (b *GethStatusBackend) generateAccountInfo(mnemonic string) (*generator.Gen
|
||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) generateAccount(info generator.GeneratedAccountInfo, customizationColorClock uint64, request *requests.CreateAccount) (*multiaccounts.Account, *generator.GeneratedAccountInfo, error) {
|
func (b *GethStatusBackend) storeAccount(id string, password string, paths []string) error {
|
||||||
err := b.OpenAccounts()
|
|
||||||
if err != nil {
|
|
||||||
b.log.Error("failed open accounts", "err", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accountGenerator := b.accountManager.AccountsGenerator()
|
accountGenerator := b.accountManager.AccountsGenerator()
|
||||||
|
|
||||||
_, err = accountGenerator.StoreAccount(info.ID, request.Password)
|
_, err := accountGenerator.StoreAccount(id, password)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
account := multiaccounts.Account{
|
return nil
|
||||||
KeyUID: info.KeyUID,
|
}
|
||||||
|
|
||||||
|
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,
|
Name: request.DisplayName,
|
||||||
CustomizationColor: multiacccommon.CustomizationColor(request.CustomizationColor),
|
CustomizationColor: multiacccommon.CustomizationColor(request.CustomizationColor),
|
||||||
CustomizationColorClock: customizationColorClock,
|
CustomizationColorClock: input.customizationColorClock,
|
||||||
KDFIterations: request.KdfIterations,
|
KDFIterations: request.KdfIterations,
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.KDFIterations == 0 {
|
if acc.KDFIterations == 0 {
|
||||||
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.ImagePath != "" {
|
if request.ImagePath != "" {
|
||||||
|
@ -1439,16 +1566,16 @@ func (b *GethStatusBackend) generateAccount(info generator.GeneratedAccountInfo,
|
||||||
imageCropRectangle.Ax, imageCropRectangle.Ay, imageCropRectangle.Bx, imageCropRectangle.By)
|
imageCropRectangle.Ax, imageCropRectangle.Ay, imageCropRectangle.Bx, imageCropRectangle.By)
|
||||||
|
|
||||||
if err != nil {
|
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) {
|
func (b *GethStatusBackend) prepareSettings(request *requests.CreateAccount, input *prepareAccountInput) (*settings.Settings, error) {
|
||||||
settings, err := defaultSettings(info, derivedAddresses)
|
settings, err := defaultSettings(input.keyUID, input.address, input.derivedAddresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1459,9 +1586,8 @@ func (b *GethStatusBackend) prepareSettings(info generator.GeneratedAccountInfo,
|
||||||
settings.CurrentNetwork = request.CurrentNetwork
|
settings.CurrentNetwork = request.CurrentNetwork
|
||||||
settings.TestNetworksEnabled = request.TestNetworksEnabled
|
settings.TestNetworksEnabled = request.TestNetworksEnabled
|
||||||
|
|
||||||
// If restoring an account, we don't set the mnemonic
|
if !input.restoringAccount {
|
||||||
if mnemonic == "" {
|
settings.Mnemonic = &input.mnemonic
|
||||||
settings.Mnemonic = &info.Mnemonic
|
|
||||||
settings.OmitTransfersHistoryScan = true
|
settings.OmitTransfersHistoryScan = true
|
||||||
// TODO(rasom): uncomment it as soon as address will be properly
|
// TODO(rasom): uncomment it as soon as address will be properly
|
||||||
// marked as shown on mobile client
|
// marked as shown on mobile client
|
||||||
|
@ -1471,41 +1597,38 @@ func (b *GethStatusBackend) prepareSettings(info generator.GeneratedAccountInfo,
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) prepareConfig(processBackedupMessages bool, installationID string, userKeyStoreDir string, request *requests.CreateAccount, opts ...params.Option) (*params.NodeConfig, error) {
|
func (b *GethStatusBackend) prepareConfig(request *requests.CreateAccount, input *prepareAccountInput, installationID string) (*params.NodeConfig, error) {
|
||||||
nodeConfig, err := defaultNodeConfig(installationID, request, opts...)
|
nodeConfig, err := defaultNodeConfig(installationID, request, input.opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nodeConfig.ProcessBackedupMessages = processBackedupMessages
|
nodeConfig.ProcessBackedupMessages = input.fetchBackup
|
||||||
|
|
||||||
// when we set nodeConfig.KeyStoreDir, value of nodeConfig.KeyStoreDir should not contain the rootDataDir
|
// when we set nodeConfig.KeyStoreDir, value of nodeConfig.KeyStoreDir should not contain the rootDataDir
|
||||||
// loadNodeConfig will add rootDataDir to nodeConfig.KeyStoreDir
|
// loadNodeConfig will add rootDataDir to nodeConfig.KeyStoreDir
|
||||||
nodeConfig.KeyStoreDir = userKeyStoreDir
|
nodeConfig.KeyStoreDir = input.keyStoreDir
|
||||||
|
|
||||||
return nodeConfig, nil
|
return nodeConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) prepareSubAccounts(mnemonic, keyUID string, derivedAddresses map[string]generator.AccountInfo, request *requests.CreateAccount) ([]*accounts.Account, error) {
|
func (b *GethStatusBackend) prepareSubAccounts(request *requests.CreateAccount, input *prepareAccountInput) ([]*accounts.Account, error) {
|
||||||
walletDerivedAccount := derivedAddresses[pathDefaultWallet]
|
walletDerivedAccount := input.derivedAddresses[pathDefaultWallet]
|
||||||
walletAccount := &accounts.Account{
|
walletAccount := &accounts.Account{
|
||||||
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
|
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
|
||||||
KeyUID: keyUID,
|
KeyUID: input.keyUID,
|
||||||
Address: types.HexToAddress(walletDerivedAccount.Address),
|
Address: types.HexToAddress(walletDerivedAccount.Address),
|
||||||
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
|
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
|
||||||
Emoji: request.Emoji,
|
Emoji: request.Emoji,
|
||||||
Wallet: true,
|
Wallet: true,
|
||||||
Path: pathDefaultWallet,
|
Path: pathDefaultWallet,
|
||||||
Name: walletAccountDefaultName,
|
Name: walletAccountDefaultName,
|
||||||
|
AddressWasNotShown: !input.restoringAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if mnemonic == "" {
|
chatDerivedAccount := input.derivedAddresses[pathDefaultChat]
|
||||||
walletAccount.AddressWasNotShown = true
|
|
||||||
}
|
|
||||||
|
|
||||||
chatDerivedAccount := derivedAddresses[pathDefaultChat]
|
|
||||||
chatAccount := &accounts.Account{
|
chatAccount := &accounts.Account{
|
||||||
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
|
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
|
||||||
KeyUID: keyUID,
|
KeyUID: input.keyUID,
|
||||||
Address: types.HexToAddress(chatDerivedAccount.Address),
|
Address: types.HexToAddress(chatDerivedAccount.Address),
|
||||||
Name: request.DisplayName,
|
Name: request.DisplayName,
|
||||||
Chat: true,
|
Chat: true,
|
||||||
|
@ -1515,14 +1638,40 @@ func (b *GethStatusBackend) prepareSubAccounts(mnemonic, keyUID string, derivedA
|
||||||
return []*accounts.Account{walletAccount, chatAccount}, nil
|
return []*accounts.Account{walletAccount, chatAccount}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) getDerivedAddresses(id string) (map[string]generator.AccountInfo, error) {
|
func (b *GethStatusBackend) prepareForKeycard(request *requests.CreateAccount, input *prepareAccountInput, response *accountBundle) (*accountBundle, error) {
|
||||||
accountGenerator := b.accountManager.AccountsGenerator()
|
if request.KeycardInstanceUID == "" {
|
||||||
derivedAddresses, err := accountGenerator.DeriveAddresses(id, paths)
|
return response, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// CreateAccountAndLogin creates a new account and logs in with it.
|
||||||
|
@ -1535,18 +1684,26 @@ func (b *GethStatusBackend) CreateAccountAndLogin(request *requests.CreateAccoun
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
b.log.Error("start node", err)
|
b.log.Error("start node", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return account, nil
|
return response.account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GethStatusBackend) ConvertToRegularAccount(mnemonic string, currPassword string, newPassword string) error {
|
func (b *GethStatusBackend) ConvertToRegularAccount(mnemonic string, currPassword string, newPassword string) error {
|
||||||
|
@ -1708,7 +1865,15 @@ func enrichMultiAccountByPublicKey(account *multiaccounts.Account, publicKey typ
|
||||||
return nil
|
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)
|
err := enrichMultiAccountBySubAccounts(&account, subaccs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1731,15 +1896,15 @@ func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(account multiaccounts
|
||||||
// StartNodeWithAccountAndInitialConfig is used after account and config was generated.
|
// 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
|
// 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.
|
// status-go side this flow can be simplified.
|
||||||
|
// TODO: Consider passing accountBundle here directly
|
||||||
func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
|
func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
|
||||||
account multiaccounts.Account,
|
account multiaccounts.Account,
|
||||||
password string,
|
password string,
|
||||||
settings settings.Settings,
|
settings settings.Settings,
|
||||||
nodecfg *params.NodeConfig,
|
nodecfg *params.NodeConfig,
|
||||||
subaccs []*accounts.Account,
|
subaccs []*accounts.Account,
|
||||||
|
chatKey *ecdsa.PrivateKey,
|
||||||
) error {
|
) error {
|
||||||
b.log.Info("node config", "config", nodecfg)
|
|
||||||
|
|
||||||
err := enrichMultiAccountBySubAccounts(&account, subaccs)
|
err := enrichMultiAccountBySubAccounts(&account, subaccs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1756,7 +1921,7 @@ func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// TODO: change in `saveAccountsAndSettings` function param `subaccs []*accounts.Account` parameter to `profileKeypair *accounts.Keypair` parameter
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
"github.com/status-im/status-go/protocol/tt"
|
"github.com/status-im/status-go/protocol/tt"
|
||||||
"github.com/status-im/status-go/services/utils"
|
"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/status-im/status-go/wakuv2"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -29,6 +31,7 @@ import (
|
||||||
|
|
||||||
type MessengerRawMessageResendTest struct {
|
type MessengerRawMessageResendTest struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
logger *zap.Logger
|
||||||
aliceBackend *GethStatusBackend
|
aliceBackend *GethStatusBackend
|
||||||
bobBackend *GethStatusBackend
|
bobBackend *GethStatusBackend
|
||||||
aliceMessenger *protocol.Messenger
|
aliceMessenger *protocol.Messenger
|
||||||
|
@ -43,9 +46,14 @@ func TestMessengerRawMessageResendTestSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerRawMessageResendTest) SetupTest() {
|
func (s *MessengerRawMessageResendTest) SetupTest() {
|
||||||
logger, err := zap.NewDevelopment()
|
tutils.Init()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
s.logger, err = zap.NewDevelopment()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
signal.SetMobileSignalHandler(nil)
|
||||||
|
|
||||||
exchangeNodeConfig := &wakuv2.Config{
|
exchangeNodeConfig := &wakuv2.Config{
|
||||||
Port: 0,
|
Port: 0,
|
||||||
EnableDiscV5: true,
|
EnableDiscV5: true,
|
||||||
|
@ -54,7 +62,7 @@ func (s *MessengerRawMessageResendTest) SetupTest() {
|
||||||
UseShardAsDefaultTopic: true,
|
UseShardAsDefaultTopic: true,
|
||||||
DefaultShardPubsubTopic: shard.DefaultShardPubsubTopic(),
|
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(err)
|
||||||
s.Require().NoError(s.exchangeBootNode.Start())
|
s.Require().NoError(s.exchangeBootNode.Start())
|
||||||
|
|
||||||
|
|
|
@ -449,7 +449,7 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
|
||||||
|
|
||||||
fmt.Println(nodeConfig)
|
fmt.Println(nodeConfig)
|
||||||
accounts := []*accounts.Account{walletAccount, chatAccount}
|
accounts := []*accounts.Account{walletAccount, chatAccount}
|
||||||
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
|
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("start node", err)
|
logger.Error("start node", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -498,7 +498,7 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
|
||||||
|
|
||||||
fmt.Println(nodeConfig)
|
fmt.Println(nodeConfig)
|
||||||
accounts := []*accounts.Account{walletAccount, chatAccount}
|
accounts := []*accounts.Account{walletAccount, chatAccount}
|
||||||
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
|
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("start node", err)
|
logger.Error("start node", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -420,9 +420,9 @@ func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
|
||||||
fmt.Println(nodeConfig)
|
fmt.Println(nodeConfig)
|
||||||
accounts := []*accounts.Account{walletAccount, chatAccount}
|
accounts := []*accounts.Account{walletAccount, chatAccount}
|
||||||
if !exist {
|
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) {
|
func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ type Key struct {
|
||||||
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
|
// ExtendedKey is the extended key of the PrivateKey itself, and it's used
|
||||||
// to derive child keys.
|
// to derive child keys.
|
||||||
ExtendedKey *extkeys.ExtendedKey
|
ExtendedKey *extkeys.ExtendedKey
|
||||||
// SubAccountIndex is DEPRECATED
|
// Deprecated: SubAccountIndex
|
||||||
// It was use in Status to keep track of the number of sub-account created
|
// It was use in Status to keep track of the number of sub-account created
|
||||||
// before having multi-account support.
|
// before having multi-account support.
|
||||||
SubAccountIndex uint32
|
SubAccountIndex uint32
|
||||||
|
|
|
@ -237,7 +237,7 @@ func login(accountData, password, configJSON string) error {
|
||||||
return statusBackend.LoggedIn(account.KeyUID, err)
|
return statusBackend.LoggedIn(account.KeyUID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = statusBackend.StartNodeWithAccount(account, password, &conf)
|
err = statusBackend.StartNodeWithAccount(account, password, &conf, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to start a node", "key-uid", account.KeyUID, "error", err)
|
log.Error("failed to start a node", "key-uid", account.KeyUID, "error", err)
|
||||||
return err
|
return err
|
||||||
|
@ -339,7 +339,13 @@ func RestoreAccountAndLogin(requestJSON string) string {
|
||||||
|
|
||||||
api.RunAsync(func() error {
|
api.RunAsync(func() error {
|
||||||
log.Debug("starting a node and restoring account")
|
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 {
|
if err != nil {
|
||||||
log.Error("failed to restore account", "error", err)
|
log.Error("failed to restore account", "error", err)
|
||||||
return err
|
return err
|
||||||
|
@ -347,6 +353,7 @@ func RestoreAccountAndLogin(requestJSON string) string {
|
||||||
log.Debug("started a node, and restored account")
|
log.Debug("started a node, and restored account")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return makeJSONResponse(nil)
|
return makeJSONResponse(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +389,7 @@ func SaveAccountAndLogin(accountData, password, settingsJSON, configJSON, subacc
|
||||||
|
|
||||||
api.RunAsync(func() error {
|
api.RunAsync(func() error {
|
||||||
log.Debug("starting a node, and saving account with configuration", "key-uid", account.KeyUID)
|
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 {
|
if err != nil {
|
||||||
log.Error("failed to start node and save account", "key-uid", account.KeyUID, "error", err)
|
log.Error("failed to start node and save account", "key-uid", account.KeyUID, "error", err)
|
||||||
return err
|
return err
|
||||||
|
@ -419,7 +426,8 @@ func InitKeystore(keydir string) string {
|
||||||
return makeJSONResponse(err)
|
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 {
|
func SaveAccountAndLoginWithKeycard(accountData, password, settingsJSON, configJSON, subaccountData string, keyHex string) string {
|
||||||
var account multiaccounts.Account
|
var account multiaccounts.Account
|
||||||
err := json.Unmarshal([]byte(accountData), &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.
|
// 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.
|
// 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 {
|
func LoginWithKeycard(accountData, password, keyHex string, configJSON string) string {
|
||||||
var account multiaccounts.Account
|
var account multiaccounts.Account
|
||||||
err := json.Unmarshal([]byte(accountData), &account)
|
err := json.Unmarshal([]byte(accountData), &account)
|
||||||
|
|
|
@ -328,6 +328,9 @@ type NodeConfig struct {
|
||||||
KeyStoreDir string `validate:"required"`
|
KeyStoreDir string `validate:"required"`
|
||||||
|
|
||||||
// KeycardPairingDataFile is the file where we keep keycard pairings data.
|
// 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.
|
// note: this field won't be saved into db, it's local to the device.
|
||||||
KeycardPairingDataFile string
|
KeycardPairingDataFile string
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,9 @@ type CreateAccount struct {
|
||||||
TelemetryServerURL string `json:"telemetryServerURL"`
|
TelemetryServerURL string `json:"telemetryServerURL"`
|
||||||
|
|
||||||
APIConfig *APIConfig `json:"apiConfig"`
|
APIConfig *APIConfig `json:"apiConfig"`
|
||||||
|
|
||||||
|
KeycardInstanceUID string `json:"keycardInstanceUID"`
|
||||||
|
KeycardPairingDataFile *string `json:"keycardPairingDataFile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WalletSecretsConfig struct {
|
type WalletSecretsConfig struct {
|
||||||
|
|
|
@ -1,18 +1,38 @@
|
||||||
package requests
|
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 {
|
type Login struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
KeyUID string `json:"keyUid"`
|
KeyUID string `json:"keyUid"`
|
||||||
|
|
||||||
KdfIterations int `json:"kdfIterations"`
|
KdfIterations int `json:"kdfIterations"` // FIXME: KdfIterations should be loaded from multiaccounts db.
|
||||||
RuntimeLogLevel string `json:"runtimeLogLevel"`
|
RuntimeLogLevel string `json:"runtimeLogLevel"`
|
||||||
WakuV2Nameserver string `json:"wakuV2Nameserver"`
|
WakuV2Nameserver string `json:"wakuV2Nameserver"`
|
||||||
BandwidthStatsEnabled bool `json:"bandwidthStatsEnabled"`
|
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
|
WalletSecretsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,5 +40,24 @@ func (c *Login) Validate() error {
|
||||||
if c.KeyUID == "" {
|
if c.KeyUID == "" {
|
||||||
return ErrLoginInvalidKeyUID
|
return ErrLoginInvalidKeyUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.KeycardWhisperPrivateKey != "" {
|
||||||
|
_, err := parsePrivateKey(c.KeycardWhisperPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return ErrLoginInvalidKeycardWhisperPrivateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -4,20 +4,46 @@ import (
|
||||||
"errors"
|
"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 {
|
type RestoreAccount struct {
|
||||||
Mnemonic string `json:"mnemonic"`
|
Mnemonic string `json:"mnemonic"`
|
||||||
FetchBackup bool `json:"fetchBackup"`
|
|
||||||
|
// 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
|
CreateAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RestoreAccount) Validate() error {
|
func (c *RestoreAccount) Validate() error {
|
||||||
if len(c.Mnemonic) == 0 {
|
if len(c.Mnemonic) == 0 && c.Keycard == nil {
|
||||||
return ErrRestoreAccountInvalidMnemonic
|
return ErrRestoreAccountInvalidMnemonic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.Mnemonic) > 0 && c.Keycard != nil {
|
||||||
|
return ErrRestoreAccountMnemonicAndKeycard
|
||||||
|
}
|
||||||
|
|
||||||
return c.CreateAccount.Validate(&CreateAccountValidation{
|
return c.CreateAccount.Validate(&CreateAccountValidation{
|
||||||
AllowEmptyDisplayName: true,
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package pairing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
"github.com/status-im/status-go/api"
|
"github.com/status-im/status-go/api"
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
"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
|
// because client don't know keyUID before received data, we need help client to update keystore dir
|
||||||
keystoreDir := filepath.Join(nodeConfig.KeyStoreDir, account.KeyUID)
|
keystoreDir := filepath.Join(nodeConfig.KeyStoreDir, account.KeyUID)
|
||||||
nodeConfig.KeyStoreDir = keystoreDir
|
nodeConfig.KeyStoreDir = keystoreDir
|
||||||
if accountPayload.exist {
|
|
||||||
if len(accountPayload.chatKey) == 0 {
|
var chatKey *ecdsa.PrivateKey
|
||||||
err = s.backend.StartNodeWithAccount(*account, accountPayload.password, nodeConfig)
|
if accountPayload.chatKey != "" {
|
||||||
} else {
|
chatKeyHex := strings.Trim(accountPayload.chatKey, "0x")
|
||||||
err = s.backend.StartNodeWithKey(*account, accountPayload.password, accountPayload.chatKey, nodeConfig)
|
chatKey, err = ethcrypto.HexToECDSA(chatKeyHex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountPayload.exist {
|
||||||
|
err = s.backend.StartNodeWithAccount(*account, accountPayload.password, nodeConfig, chatKey)
|
||||||
} else {
|
} else {
|
||||||
accountManager := s.backend.AccountManager()
|
accountManager := s.backend.AccountManager()
|
||||||
err = accountManager.InitKeystore(filepath.Join(nodeConfig.RootDataDir, keystoreDir))
|
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.InstallationID = nodeConfig.ShhextConfig.InstallationID
|
||||||
rmp.setting.CurrentNetwork = settingCurrentNetwork
|
rmp.setting.CurrentNetwork = settingCurrentNetwork
|
||||||
|
|
||||||
if len(accountPayload.chatKey) == 0 {
|
err = s.backend.StartNodeWithAccountAndInitialConfig(*account, accountPayload.password, *rmp.setting, nodeConfig, rmp.profileKeypair.Accounts, chatKey)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -140,7 +140,7 @@ func (s *SyncDeviceSuite) prepareBackendWithAccount(mnemonic, tmpdir string) *ap
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts := []*accounts.Account{walletAccount, chatAccount}
|
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)
|
require.NoError(s.T(), err)
|
||||||
multiaccounts, err := backend.GetAccounts()
|
multiaccounts, err := backend.GetAccounts()
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -10,6 +11,11 @@ type KeycardPairings struct {
|
||||||
pairingsFile string
|
pairingsFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeycardPairing struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewKeycardPairings() *KeycardPairings {
|
func NewKeycardPairings() *KeycardPairings {
|
||||||
return &KeycardPairings{}
|
return &KeycardPairings{}
|
||||||
}
|
}
|
||||||
|
@ -43,3 +49,22 @@ func (kp *KeycardPairings) SetPairingsJSONFileContent(content []byte) error {
|
||||||
|
|
||||||
return ioutil.WriteFile(kp.pairingsFile, content, 0600)
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue