fix(@desktop/keycard): can't create accounts if the main account was restored in using an existing keycard account

`I already use Status` -> `Login with Keycard` flow is enabled.

Fixes: #7867
This commit is contained in:
Sale Djenic 2022-10-27 11:26:34 +02:00 committed by saledjenic
parent c8c51a8621
commit 2c03146309
32 changed files with 818 additions and 154 deletions

View File

@ -266,6 +266,7 @@ proc init*(self: Controller) =
password = args.keyUid
let data = SharedKeycarModuleArgs(uniqueIdentifier: self.authenticateUserFlowRequestedBy,
password: password,
pin: args.pin,
keyUid: args.keyUid,
txR: args.txR,
txS: args.txS,

View File

@ -169,7 +169,7 @@ proc newModule*[T](
result.walletSectionModule = wallet_section_module.newModule(
result, events, tokenService,
transactionService, collectible_service, walletAccountService,
settingsService, savedAddressService, networkService, accountsService
settingsService, savedAddressService, networkService, accountsService, keycardService
)
result.browserSectionModule = browser_section_module.newModule(
result, events, bookmarkService, settingsService, networkService,
@ -950,17 +950,18 @@ method onStatusUrlRequested*[T](self: Module[T], action: StatusUrlAction, commun
# self.setActiveSection(item)
# self.browserSectionModule.openUrl(url)
proc isSharedKeycardModuleFlowRunning[T](self: Module[T]): bool =
return not self.keycardSharedModule.isNil
method getKeycardSharedModule*[T](self: Module[T]): QVariant =
return self.keycardSharedModule.getModuleAsVariant()
if self.isSharedKeycardModuleFlowRunning():
return self.keycardSharedModule.getModuleAsVariant()
proc createSharedKeycardModule[T](self: Module[T]) =
self.keycardSharedModule = keycard_shared_module.newModule[Module[T]](self, UNIQUE_MAIN_MODULE_IDENTIFIER,
self.events, self.keycardService, self.settingsService, self.privacyService, self.accountsService,
self.walletAccountService, self.keychainService)
proc isSharedKeycardModuleFlowRunning[T](self: Module[T]): bool =
return not self.keycardSharedModule.isNil
method onSharedKeycarModuleFlowTerminated*[T](self: Module[T], lastStepInTheCurrentFlow: bool) =
if self.isSharedKeycardModuleFlowRunning():
self.view.emitDestroyKeycardSharedModuleFlow()

View File

@ -8,6 +8,7 @@ import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_modu
import ../../../../core/eventemitter
const UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER* = "WalletSection-AccountsModule"
const UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER* = "WalletSection-AccountsModule-Authentication"
type
Controller* = ref object of RootObj
@ -34,21 +35,37 @@ proc delete*(self: Controller) =
proc init*(self: Controller) =
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER:
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER:
return
self.delegate.onUserAuthenticated(args.password)
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED_AND_WALLET_ADDRESS_GENERATED) do(e: Args):
let args = SharedKeycarModuleUserAuthenticatedAndWalletAddressGeneratedArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER:
return
self.delegate.onUserAuthenticatedAndWalletAddressGenerated(args.address, args.publicKey, args.derivedFrom, args.password)
self.events.on(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED) do(e: Args):
let args = DerivedAddressesArgs(e)
var derivedAddress: DerivedAddressDto
if args.derivedAddresses.len > 0:
derivedAddress = args.derivedAddresses[0]
self.delegate.addressDetailsFetched(derivedAddress, args.error)
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
return self.walletAccountService.getWalletAccounts()
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string,
path: string, derivedFrom: string, skipPasswordVerification: bool): string =
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji, path, derivedFrom, skipPasswordVerification)
proc addAccountsFromPrivateKey*(self: Controller, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
proc addAccountsFromPrivateKey*(self: Controller, privateKey: string, password: string, accountName: string, color: string,
emoji: string, skipPasswordVerification: bool): string =
return self.walletAccountService.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji, skipPasswordVerification)
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string,
emoji: string, path: string, skipPasswordVerification: bool): string =
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path, skipPasswordVerification)
proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addWatchOnlyAccount(address, accountName, color, emoji)
@ -56,6 +73,9 @@ proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string
proc deleteAccount*(self: Controller, address: string) =
self.walletAccountService.deleteAccount(address)
proc fetchDerivedAddressDetails*(self: Controller, address: string) =
self.walletAccountService.fetchDerivedAddressDetails(address)
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int)=
self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
@ -70,6 +90,14 @@ proc validSeedPhrase*(self: Controller, seedPhrase: string): bool =
return err.len == 0
proc authenticateUser*(self: Controller, keyUid = "") =
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER,
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER,
keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] =
return self.walletAccountService.getAllMigratedKeyPairs()
proc addWalletAccount*(self: Controller, name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType,
color, emoji: string): string =
return self.walletAccountService.addWalletAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid,
accountType, color, emoji)

View File

@ -7,24 +7,32 @@ type
iconName: string
generatedModel: Model
derivedfrom: string
keyUid: string
migratedToKeycard: bool
proc initGeneratedWalletItem*(
name: string,
iconName: string,
generatedModel: Model,
derivedfrom: string
derivedfrom: string,
keyUid: string,
migratedToKeycard: bool
): GeneratedWalletItem =
result.name = name
result.iconName = iconName
result.generatedModel = generatedModel
result.derivedfrom = derivedfrom
result.keyUid = keyUid
result.migratedToKeycard = migratedToKeycard
proc `$`*(self: GeneratedWalletItem): string =
result = fmt"""GeneratedWalletItem(
name: {self.name},
iconName: {self.iconName},
generatedModel: {self.generatedModel},
derivedfrom: {self.derivedfrom}
derivedfrom: {self.derivedfrom},
keyUid: {self.keyUid},
migratedToKeycard: {self.migratedToKeycard}
]"""
proc getName*(self: GeneratedWalletItem): string =
@ -38,3 +46,9 @@ proc getGeneratedModel*(self: GeneratedWalletItem): Model =
proc getDerivedfrom*(self: GeneratedWalletItem): string =
return self.derivedfrom
proc getKeyUid*(self: GeneratedWalletItem): string =
return self.keyUid
proc getMigratedToKeycard*(self: GeneratedWalletItem): bool =
return self.migratedToKeycard

View File

@ -7,7 +7,9 @@ type
Name = UserRole + 1,
IconName,
GeneratedModel,
DerivedFrom
DerivedFrom,
KeyUid,
MigratedToKeycard
QtObject:
type
@ -47,6 +49,8 @@ QtObject:
ModelRole.IconName.int: "iconName",
ModelRole.GeneratedModel.int: "generatedModel",
ModelRole.DerivedFrom.int: "derivedfrom",
ModelRole.KeyUid.int: "keyUid",
ModelRole.MigratedToKeycard.int: "migratedToKeycard"
}.toTable
method data(self: GeneratedWalletModel, index: QModelIndex, role: int): QVariant =
@ -68,6 +72,10 @@ QtObject:
result = newQVariant(item.getGeneratedModel())
of ModelRole.DerivedFrom:
result = newQVariant(item.getDerivedFrom())
of ModelRole.KeyUid:
result = newQVariant(item.getKeyUid())
of ModelRole.MigratedToKeycard:
result = newQVariant(item.getMigratedToKeycard())
proc setItems*(self: GeneratedWalletModel, items: seq[GeneratedWalletItem]) =
self.beginResetModel()

View File

@ -16,6 +16,10 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method generateNewAccount*(self: AccessInterface, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method addNewWalletAccountGeneratedFromKeycard*(self: AccessInterface, accountType: string, accountName: string,
color: string, emoji: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method addAccountsFromPrivateKey*(self: AccessInterface, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.base.} =
raise newException(ValueError, "No implementation available")
@ -53,4 +57,20 @@ method authenticateUser*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} =
raise newException(ValueError, "No implementation available")
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: AccessInterface, keyUid: string, derivationPath: string) {.base.} =
raise newException(ValueError, "No implementation available")
method createSharedKeycardModule*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method destroySharedKeycarModule*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onUserAuthenticatedAndWalletAddressGenerated*(self: AccessInterface, address: string, publicKey: string,
derivedFrom: string, password: string) {.base.} =
raise newException(ValueError, "No implementation available")
method addressDetailsFetched*(self: AccessInterface, derivedAddress: DerivedAddressDto, error: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -17,6 +17,8 @@ type
emoji: string
derivedfrom: string
relatedAccounts: compact_model.Model
keyUid: string
migratedToKeycard: bool
proc initItem*(
name: string,
@ -31,7 +33,9 @@ proc initItem*(
assets: token_model.Model,
emoji: string,
derivedfrom: string,
relatedAccounts: compact_model.Model
relatedAccounts: compact_model.Model,
keyUid: string,
migratedToKeycard: bool
): Item =
result.name = name
result.address = address
@ -46,6 +50,8 @@ proc initItem*(
result.emoji = emoji
result.derivedfrom = derivedfrom
result.relatedAccounts = relatedAccounts
result.keyUid = keyUid
result.migratedToKeycard = migratedToKeycard
proc `$`*(self: Item): string =
result = fmt"""WalletAccountItem(
@ -62,6 +68,8 @@ proc `$`*(self: Item): string =
emoji: {self.emoji},
derivedfrom: {self.derivedfrom},
relatedAccounts: {self.relatedAccounts}
keyUid: {self.keyUid},
migratedToKeycard: {self.migratedToKeycard}
]"""
proc getName*(self: Item): string =
@ -102,3 +110,9 @@ proc getDerivedFrom*(self: Item): string =
proc getRelatedAccounts*(self: Item): compact_model.Model =
return self.relatedAccounts
proc getKeyUid*(self: Item): string =
return self.keyUid
proc getMigratedToKeycard*(self: Item): bool =
return self.migratedToKeycard

View File

@ -16,7 +16,9 @@ type
Assets,
Emoji,
DerivedFrom,
RelatedAccounts
RelatedAccounts,
KeyUid,
MigratedToKeycard
QtObject:
type
@ -64,7 +66,9 @@ QtObject:
ModelRole.CurrencyBalance.int:"currencyBalance",
ModelRole.Emoji.int: "emoji",
ModelRole.DerivedFrom.int: "derivedfrom",
ModelRole.RelatedAccounts.int: "relatedAccounts"
ModelRole.RelatedAccounts.int: "relatedAccounts",
ModelRole.KeyUid.int: "keyUid",
ModelRole.MigratedToKeycard.int: "migratedToKeycard"
}.toTable
@ -111,6 +115,10 @@ QtObject:
result = newQVariant(item.getDerivedFrom())
of ModelRole.RelatedAccounts:
result = newQVariant(item.getRelatedAccounts())
of ModelRole.KeyUid:
result = newQVariant(item.getKeyUid())
of ModelRole.MigratedToKeycard:
result = newQVariant(item.getMigratedToKeycard())
proc getAccountNameByAddress*(self: Model, address: string): string =
for account in self.items:

View File

@ -1,18 +1,28 @@
import tables, NimQml, sequtils, sugar
import tables, NimQml, sequtils, sugar, chronicles
import ./io_interface, ./view, ./item, ./controller
import ../io_interface as delegate_interface
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/common/account_constants
import ../../../../../app_service/service/keycard/service as keycard_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/accounts/service as accounts_service
import ../../../shared_models/token_model as token_model
import ../../../shared_models/token_item as token_item
import ../../../shared_modules/keycard_popup/module as keycard_shared_module
import ./compact_item as compact_item
import ./compact_model as compact_model
export io_interface
type WalletAccountDetails = object
address: string
path: string
addressAccountIsDerivedFrom: string
publicKey: string
keyUid: string
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
@ -20,16 +30,25 @@ type
view: View
controller: Controller
moduleLoaded: bool
keycardService: keycard_service.Service
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
keycardSharedModule: keycard_shared_module.AccessInterface
walletAccountWhichIsAboutToBeAdded: WalletAccountDetails
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
keycardService: keycard_service.Service,
walletAccountService: wallet_account_service.Service,
accountsService: accounts_service.Service
): Module =
result = Module()
result.delegate = delegate
result.events = events
result.keycardService = keycardService
result.accountsService = accountsService
result.walletAccountService = walletAccountService
result.view = newView(result)
result.controller = controller.newController(result, events, walletAccountService, accountsService)
result.moduleLoaded = false
@ -37,9 +56,18 @@ proc newModule*(
method delete*(self: Module) =
self.view.delete
self.controller.delete
if not self.keycardSharedModule.isNil:
self.keycardSharedModule.delete
method refreshWalletAccounts*(self: Module) =
let keyPairMigrated = proc(migratedKeyPairs: seq[KeyPairDto], keyUid: string): bool =
for kp in migratedKeyPairs:
if kp.keyUid == keyUid:
return true
return false
let walletAccounts = self.controller.getWalletAccounts()
let migratedKeyPairs = self.controller.getAllMigratedKeyPairs()
let items = walletAccounts.map(proc (w: WalletAccountDto): item.Item =
@ -102,7 +130,9 @@ method refreshWalletAccounts*(self: Module) =
assets,
w.emoji,
w.derivedfrom,
relatedAccounts
relatedAccounts,
w.keyUid,
keyPairMigrated(migratedKeyPairs, w.keyUid)
))
self.view.setItems(items)
@ -136,6 +166,9 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
self.refreshWalletAccounts()
self.controller.init()
self.view.load()
@ -147,14 +180,20 @@ method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.accountsModuleDidLoad()
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
return self.controller.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string,
path: string, derivedFrom: string): string =
let skipPasswordVerification = singletonInstance.userProfile.getIsKeycardUser()
return self.controller.generateNewAccount(password, accountName, color, emoji, path, derivedFrom, skipPasswordVerification)
method addAccountsFromPrivateKey*(self: Module, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
return self.controller.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
method addAccountsFromPrivateKey*(self: Module, privateKey: string, password: string, accountName: string, color: string,
emoji: string): string =
let skipPasswordVerification = singletonInstance.userProfile.getIsKeycardUser()
return self.controller.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji, skipPasswordVerification)
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string,
emoji: string, path: string): string =
let skipPasswordVerification = singletonInstance.userProfile.getIsKeycardUser()
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path, skipPasswordVerification)
method addWatchOnlyAccount*(self: Module, address: string, accountName: string, color: string, emoji: string): string =
return self.controller.addWatchOnlyAccount(address, accountName, color, emoji)
@ -186,3 +225,76 @@ method onUserAuthenticated*(self: Module, password: string) =
self.view.userAuthenticationSuccess(password)
else:
self.view.userAuthentiactionFail()
method createSharedKeycardModule*(self: Module) =
self.keycardSharedModule = keycard_shared_module.newModule[Module](self, UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER,
self.events, self.keycardService, settingsService = nil, privacyService = nil, self.accountsService,
self.walletAccountService, keychainService = nil)
method destroySharedKeycarModule*(self: Module) =
self.keycardSharedModule.delete
self.keycardSharedModule = nil
proc checkIfWalletAccountIsAlreadyCreated(self: Module, keyUid: string, derivationPath: string): bool =
let walletAccounts = self.controller.getWalletAccounts()
for w in walletAccounts:
if w.keyUid == keyUid and w.path == derivationPath:
return true
return false
proc findFirstAvaliablePathForWallet(self: Module, keyUid: string): string =
let walletAccounts = self.controller.getWalletAccounts()
# starting from 1, "0" is already reserved for the default wallet account
for i in 1 .. 256: # we hope that nobody will have 256 accounts added, so no need to change derivation tree
let path = account_constants.PATH_WALLET_ROOT & "/" & $i
var found = false
for w in walletAccounts:
if w.keyUid == keyUid and w.path == path:
found = true
break
if not found:
return path
error "we couldn't find available wallet account path"
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: Module, keyUid: string, derivationPath: string) =
var finalPath = derivationPath
if self.checkIfWalletAccountIsAlreadyCreated(keyUid, finalPath):
finalPath = self.findFirstAvaliablePathForWallet(keyUid)
if self.keycardSharedModule.isNil:
self.createSharedKeycardModule()
self.walletAccountWhichIsAboutToBeAdded = WalletAccountDetails(keyUid: keyUid, path: finalPath)
self.view.setDerivedAddressesLoading(true)
self.view.setDerivedAddressesError("")
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.AuthenticateAndDeriveAccountAddress, keyUid, finalPath)
method onUserAuthenticatedAndWalletAddressGenerated*(self: Module, address: string, publicKey: string,
derivedFrom: string, password: string) =
if address.len == 0:
self.view.setDerivedAddressesError("wrong-path") # this should be checked on the UI side and we should not allow entering invalid path format
if password.len == 0:
self.view.setDerivedAddressesLoading(false)
return
self.onUserAuthenticated(password)
self.walletAccountWhichIsAboutToBeAdded.address = address
self.walletAccountWhichIsAboutToBeAdded.addressAccountIsDerivedFrom = derivedFrom
self.walletAccountWhichIsAboutToBeAdded.publicKey = publicKey
self.controller.fetchDerivedAddressDetails(address)
method addressDetailsFetched*(self: Module, derivedAddress: DerivedAddressDto, error: string) =
var derivedAddressDto = derivedAddress
if error.len > 0:
derivedAddressDto.address = self.walletAccountWhichIsAboutToBeAdded.address
derivedAddressDto.alreadyCreated = true
self.view.setDerivedAddresses(@[derivedAddressDto], error)
method addNewWalletAccountGeneratedFromKeycard*(self: Module, accountType: string, accountName: string, color: string,
emoji: string): string =
return self.controller.addWalletAccount(accountName,
self.walletAccountWhichIsAboutToBeAdded.address,
self.walletAccountWhichIsAboutToBeAdded.path,
self.walletAccountWhichIsAboutToBeAdded.addressAccountIsDerivedFrom,
self.walletAccountWhichIsAboutToBeAdded.publicKey,
self.walletAccountWhichIsAboutToBeAdded.keyUid,
accountType,
color,
emoji)

View File

@ -146,7 +146,7 @@ QtObject:
proc getDerivedAddressesError(self: View): string {.slot.} =
return self.derivedAddressesError
proc setDerivedAddressesError(self: View, error: string) {.slot.} =
proc setDerivedAddressesError*(self: View, error: string) {.slot.} =
self.derivedAddressesError = error
self.derivedAddressErrorChanged()
@ -175,7 +175,8 @@ QtObject:
var generatedAccs: Model = newModel()
generatedAccs.setItems(items.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0))
generatedAccounts.add(initGeneratedWalletItem("Default", "status", generatedAccs, item.getDerivedFrom()))
generatedAccounts.add(initGeneratedWalletItem("Default", "status", generatedAccs, item.getDerivedFrom(),
item.getKeyUid(), item.getMigratedToKeycard()))
generated.add(item)
# Account generated from profile seed phrase
@ -191,7 +192,8 @@ QtObject:
var generatedAccs1: Model = newModel()
var filterItems: seq[Item] = items.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0)
generatedAccs1.setItems(filterItems)
generatedAccounts.add(initGeneratedWalletItem("Seed " & $importedSeedIndex , "seed-phrase", generatedAccs1, item.getDerivedFrom()))
generatedAccounts.add(initGeneratedWalletItem("Seed " & $importedSeedIndex , "seed-phrase", generatedAccs1, item.getDerivedFrom(),
item.getKeyUid(), item.getMigratedToKeycard()))
imported.add(item)
importedSeedIndex += 1
@ -207,6 +209,10 @@ QtObject:
proc generateNewAccount*(self: View, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.slot.} =
return self.delegate.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
proc addNewWalletAccountGeneratedFromKeycard*(self: View, accountType: string, accountName: string, color: string,
emoji: string): string {.slot.} =
return self.delegate.addNewWalletAccountGeneratedFromKeycard(accountType, accountName, color, emoji)
proc addAccountsFromPrivateKey*(self: View, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
return self.delegate.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
@ -282,4 +288,13 @@ QtObject:
proc userAuthentiactionFail*(self: View) {.signal.}
proc authenticateUser*(self: View) {.slot.} =
self.delegate.authenticateUser()
self.delegate.authenticateUser()
proc createSharedKeycardModule*(self: View) {.slot.} =
self.delegate.createSharedKeycardModule()
proc destroySharedKeycarModule*(self: View) {.slot.} =
self.delegate.destroySharedKeycarModule()
proc authenticateUserAndDeriveAddressOnKeycardForPath*(self: View, keyUid: string, derivationPath: string) {.slot.} =
self.delegate.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath)

View File

@ -14,6 +14,7 @@ import ./buy_sell_crypto/module as buy_sell_crypto_module
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/keycard/service as keycard_service
import ../../../../app_service/service/token/service as token_service
import ../../../../app_service/service/transaction/service as transaction_service
import ../../../../app_service/service/collectible/service as collectible_service
@ -53,7 +54,8 @@ proc newModule*(
settingsService: settings_service.Service,
savedAddressService: saved_address_service.Service,
networkService: network_service.Service,
accountsService: accounts_service.Service
accountsService: accounts_service.Service,
keycardService: keycard_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -62,7 +64,7 @@ proc newModule*(
result.controller = newController(result, settingsService, walletAccountService, networkService)
result.view = newView(result)
result.accountsModule = accounts_module.newModule(result, events, walletAccountService, accountsService)
result.accountsModule = accounts_module.newModule(result, events, keycardService, walletAccountService, accountsService)
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService)
result.collectiblesModule = collectibles_module.newModule(result, events, collectibleService, walletAccountService)
result.currentAccountModule = current_account_module.newModule(result, events, walletAccountService)

View File

@ -81,6 +81,9 @@ proc newController*(delegate: io_interface.AccessInterface,
proc serviceApplicable[T](service: T): bool =
if not service.isNil:
return true
when (service is keycard_service.Service):
error "KeycardService is mandatory for using shared keycard popup module"
return
var serviceName = ""
when (service is wallet_account_service.Service):
serviceName = "WalletAccountService"
@ -88,7 +91,11 @@ proc serviceApplicable[T](service: T): bool =
serviceName = "PrivacyService"
when (service is settings_service.Service):
serviceName = "SettingsService"
debug "service doesn't meant to be used from the context it's used, check the context shared popup module is used", service=serviceName
when (service is accounts_service.Service):
serviceName = "AccountsService"
when (service is keychain_service.Service):
serviceName = "KeychainService"
debug "service is not set, check the context shared keycard popup module is used", service=serviceName
proc disconnectKeycardReponseSignal(self: Controller) =
self.events.disconnect(self.connectionKeycardResponse)
@ -130,7 +137,7 @@ proc init*(self: Controller) =
if args.uniqueIdentifier != self.uniqueIdentifier:
return
self.connectKeycardReponseSignal()
self.delegate.onUserAuthenticated(args.password)
self.delegate.onUserAuthenticated(args.password, args.pin)
self.connectionIds.add(handlerId)
proc getKeycardData*(self: Controller): string =
@ -259,31 +266,47 @@ proc getSeedPhraseLength*(self: Controller): int =
return self.tmpSeedPhraseLength
proc validSeedPhrase*(self: Controller, seedPhrase: string): bool =
if not serviceApplicable(self.accountsService):
return
let err = self.accountsService.validateMnemonic(seedPhrase)
return err.len == 0
proc getKeyUidForSeedPhrase*(self: Controller, seedPhrase: string): string =
if not serviceApplicable(self.accountsService):
return
let acc = self.accountsService.createAccountFromMnemonic(seedPhrase)
return acc.keyUid
proc seedPhraseRefersToSelectedKeyPair*(self: Controller, seedPhrase: string): bool =
if not serviceApplicable(self.accountsService):
return
let acc = self.accountsService.createAccountFromMnemonic(seedPhrase)
return acc.keyUid == self.tmpSelectedKeyPairDto.keyUid
proc verifyPassword*(self: Controller, password: string): bool =
if not serviceApplicable(self.accountsService):
return
return self.accountsService.verifyPassword(password)
proc convertSelectedKeyPairToKeycardAccount*(self: Controller, password: string): bool =
if not serviceApplicable(self.accountsService):
return
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NOT_NOW)
return self.accountsService.convertToKeycardAccount(self.tmpSelectedKeyPairDto.keyUid, password)
proc getLoggedInAccount*(self: Controller): AccountDto =
if not serviceApplicable(self.accountsService):
return
return self.accountsService.getLoggedInAccount()
proc getCurrentKeycardServiceFlow*(self: Controller): keycard_service.KCSFlowType =
if not serviceApplicable(self.keycardService):
return
return self.keycardService.getCurrentFlow()
proc getLastReceivedKeycardData*(self: Controller): tuple[flowType: string, flowEvent: KeycardEvent] =
if not serviceApplicable(self.keycardService):
return
return self.keycardService.getLastReceivedKeycardData()
proc getMetadataFromKeycard*(self: Controller): CardMetadata =
@ -297,40 +320,71 @@ proc setMetadataFromKeycard*(self: Controller, cardMetadata: CardMetadata, updat
proc setNamePropForKeyPairStoredOnKeycard*(self: Controller, name: string) =
self.delegate.setNamePropForKeyPairStoredOnKeycard(name)
proc notifyAboutGeneratedWalletAccount*(self: Controller, generatedWalletAccount: GeneratedWalletAccount, derivedFrom: string) =
let data = SharedKeycarModuleUserAuthenticatedAndWalletAddressGeneratedArgs(uniqueIdentifier: self.uniqueIdentifier,
address: generatedWalletAccount.address,
publicKey: generatedWalletAccount.publicKey,
derivedFrom: derivedFrom,
password: self.getPassword()
)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED_AND_WALLET_ADDRESS_GENERATED, data)
proc runSharedModuleFlow*(self: Controller, flowToRun: FlowType) =
self.delegate.runFlow(flowToRun)
proc cancelCurrentFlow*(self: Controller) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.cancelCurrentFlow()
# in most cases we're running another flow after canceling the current one,
# this way we're giving to the keycard some time to cancel the current flow
sleep(200)
proc runGetAppInfoFlow*(self: Controller, factoryReset = false) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startGetAppInfoFlow(factoryReset)
proc runGetMetadataFlow*(self: Controller, resolveAddress = false) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startGetMetadataFlow(resolveAddress)
proc runChangePinFlow*(self: Controller) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startChangePinFlow()
proc runChangePukFlow*(self: Controller) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startChangePukFlow()
proc runChangePairingFlow*(self: Controller) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startChangePairingFlow()
proc runStoreMetadataFlow*(self: Controller, cardName: string, pin: string, walletPaths: seq[string]) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startStoreMetadataFlow(cardName, pin, walletPaths)
proc runDeriveAccountFlow*(self: Controller, bip44Path = "", pin = "") =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startExportPublicFlow(bip44Path, exportMasterAddr=true, exportPrivateAddr=false, pin)
proc runLoadAccountFlow*(self: Controller, seedPhraseLength = 0, seedPhrase = "", puk = "", factoryReset = false) =
if not serviceApplicable(self.keycardService):
return
self.cancelCurrentFlow()
self.keycardService.startLoadAccountFlow(seedPhraseLength, seedPhrase, puk, factoryReset)
@ -338,6 +392,8 @@ proc runSignFlow*(self: Controller, keyUid = "", bip44Path = "", txHash = "") =
## For signing a transaction we need to provide a key uid of a keypair that an account we want to sign a transaction
## for belongs to. If we're just doing an authentication for a logged in user, then default key uid is always the key
## uid of the logged in user.
if not serviceApplicable(self.keycardService):
return
self.tmpKeyUidWhichIsBeingAuthenticating = keyUid
if self.tmpKeyUidWhichIsBeingAuthenticating.len == 0:
self.tmpKeyUidWhichIsBeingAuthenticating = singletonInstance.userProfile.getKeyUid()
@ -345,6 +401,8 @@ proc runSignFlow*(self: Controller, keyUid = "", bip44Path = "", txHash = "") =
self.keycardService.startSignFlow(bip44Path, txHash)
proc resumeCurrentFlowLater*(self: Controller) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.resumeCurrentFlowLater()
proc readyToDisplayPopup*(self: Controller) =
@ -357,16 +415,18 @@ proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) =
var data = SharedKeycarModuleFlowTerminatedArgs(uniqueIdentifier: self.uniqueIdentifier,
lastStepInTheCurrentFlow: lastStepInTheCurrentFlow)
if lastStepInTheCurrentFlow:
data.password = self.tmpPassword
data.password = self.getPassword()
data.pin = self.getPin()
data.keyUid = flowEvent.keyUid
data.txR = flowEvent.txSignature.r
data.txS = flowEvent.txSignature.s
data.txV = flowEvent.txSignature.v
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED, data)
proc authenticateUser*(self: Controller) =
proc authenticateUser*(self: Controller, keyUid = "") =
self.disconnectKeycardReponseSignal()
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: self.uniqueIdentifier)
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: self.uniqueIdentifier,
keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
@ -429,24 +489,38 @@ proc getSigningPhrase*(self: Controller): string =
return self.settingsService.getSigningPhrase()
proc enterKeycardPin*(self: Controller, pin: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.enterPin(pin)
proc enterKeycardPuk*(self: Controller, puk: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.enterPuk(puk)
proc storePinToKeycard*(self: Controller, pin: string, puk: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.storePin(pin, puk)
proc storePukToKeycard*(self: Controller, puk: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.storePuk(puk)
proc storePairingCodeToKeycard*(self: Controller, pairingCode: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.storePairingCode(pairingCode)
proc storeSeedPhraseToKeycard*(self: Controller, seedPhraseLength: int, seedPhrase: string) =
if not serviceApplicable(self.keycardService):
return
self.keycardService.storeSeedPhrase(seedPhraseLength, seedPhrase)
proc generateRandomPUK*(self: Controller): string =
if not serviceApplicable(self.keycardService):
return
return self.keycardService.generateRandomPUK()
proc isMnemonicBackedUp*(self: Controller): bool =
@ -470,11 +544,15 @@ proc getMnemonicWordAtIndex*(self: Controller, index: int): string =
return self.privacyService.getMnemonicWordAtIndex(index)
proc tryToObtainDataFromKeychain*(self: Controller) =
if not serviceApplicable(self.keychainService):
return
if(not singletonInstance.userProfile.getUsingBiometricLogin()):
return
let loggedInAccount = self.getLoggedInAccount()
self.keychainService.tryToObtainData(loggedInAccount.name)
proc tryToStoreDataToKeychain*(self: Controller, password: string) =
if not serviceApplicable(self.keychainService):
return
let loggedInAccount = self.getLoggedInAccount()
self.keychainService.storeData(loggedInAccount.name, password)

View File

@ -3,6 +3,8 @@ import ../../../../app/core/eventemitter
from ../../../../app_service/service/keycard/service import KeycardEvent, CardMetadata, KeyDetails
import models/key_pair_item
const SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED_AND_WALLET_ADDRESS_GENERATED* = "sharedKeycarModuleUserAuthenticatedAndWalletAddressGenerated"
const SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP* = "sharedKeycarModuleDisplayPopup"
const SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED* = "sharedKeycarModuleFlowTerminated"
const SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER* = "sharedKeycarModuleAuthenticateUser"
@ -35,6 +37,7 @@ type
type
SharedKeycarModuleArgs* = ref object of SharedKeycarModuleBaseArgs
password*: string
pin*: string # this is used in case we need to run another keycard flow which requires pin, after we successfully authenticated logged in user
keyUid*: string
txR*: string
txS*: string
@ -61,6 +64,15 @@ type FlowType* {.pure.} = enum
ChangeKeycardPin = "ChangeKeycardPin"
ChangeKeycardPuk = "ChangeKeycardPuk"
ChangePairingCode = "ChangePairingCode"
AuthenticateAndDeriveAccountAddress = "AuthenticateAndDeriveAccountAddress"
type
SharedKeycarModuleUserAuthenticatedAndWalletAddressGeneratedArgs* = ref object of Args
uniqueIdentifier*: string
address*: string
publicKey*: string
derivedFrom*: string
password*: string
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -146,7 +158,7 @@ method migratingProfileKeyPair*(self: AccessInterface): bool {.base.} =
method getSigningPhrase*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} =
method onUserAuthenticated*(self: AccessInterface, password: string, pin: string) {.base.} =
raise newException(ValueError, "No implementation available")
method keychainObtainedDataFailure*(self: AccessInterface, errorDescription: string, errorType: string) {.base.} =

View File

@ -19,6 +19,12 @@ export io_interface
logScope:
topics = "keycard-popup-module"
type DerivingAccountDetails = object
keyUid: string
path: string
deriveAddressAfterAuthentication: bool
addressRequested: bool
type
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
delegate: T
@ -28,6 +34,7 @@ type
initialized: bool
tmpLocalState: State # used when flow is run, until response arrives to determine next state appropriatelly
authenticationPopupIsAlreadyRunning: bool
derivingAccountDetails: DerivingAccountDetails
proc newModule*[T](delegate: T,
uniqueIdentifier: string,
@ -47,6 +54,8 @@ proc newModule*[T](delegate: T,
privacyService, accountsService, walletAccountService, keychainService)
result.initialized = false
result.authenticationPopupIsAlreadyRunning = false
result.derivingAccountDetails.deriveAddressAfterAuthentication = false
result.derivingAccountDetails.addressRequested = false
## Forward declaration
proc updateKeyPairItemIfDataAreKnown[T](self: Module[T], address: string, item: var KeyPairItem): bool
@ -207,6 +216,13 @@ method onTertiaryActionClicked*[T](self: Module[T]) =
debug "sm_tertiary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) =
if self.derivingAccountDetails.deriveAddressAfterAuthentication and
self.derivingAccountDetails.addressRequested:
# clearing...
self.derivingAccountDetails = DerivingAccountDetails()
# notify about generated address
self.controller.notifyAboutGeneratedWalletAccount(keycardEvent.generatedWalletAccount, keycardEvent.masterKeyAddress)
return
## Check local state first, in case postponed flow is run
if not self.tmpLocalState.isNil:
let nextState = self.tmpLocalState.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller)
@ -239,14 +255,6 @@ proc buildKeyPairsList[T](self: Module[T], excludeAlreadyMigratedPairs: bool): s
if kp.keyUid == keyUid:
return true
let findItemByDerivedFromAddress = proc(items: seq[KeyPairItem], address: string): KeyPairItem =
if address.len == 0:
return nil
for i in 0 ..< items.len:
if(items[i].derivedFrom == address):
return items[i]
return nil
let countOfKeyPairsForType = proc(items: seq[KeyPairItem], keyPairType: KeyPairType): int =
result = 0
for i in 0 ..< items.len:
@ -258,50 +266,52 @@ proc buildKeyPairsList[T](self: Module[T], excludeAlreadyMigratedPairs: bool): s
for a in accounts:
if a.isChat or a.walletType == WalletTypeWatch or (excludeAlreadyMigratedPairs and keyPairMigrated(a.keyUid)):
continue
var item = findItemByDerivedFromAddress(items, a.derivedfrom)
if a.walletType == WalletTypeDefaultStatusAccount or a.walletType == WalletTypeGenerated:
if item.isNil:
item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = singletonInstance.userProfile.getName(),
image = singletonInstance.userProfile.getIcon(),
icon = "",
pairType = KeyPairType.Profile,
derivedFrom = a.derivedfrom)
items.insert(item, 0) # Status Account must be at first place
var icon = ""
if a.walletType == WalletTypeDefaultStatusAccount:
icon = "wallet"
items[0].addAccount(a.name, a.path, a.address, a.emoji, a.color, icon, balance = 0.0)
if a.walletType == WalletTypeDefaultStatusAccount:
var item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = singletonInstance.userProfile.getName(),
image = singletonInstance.userProfile.getIcon(),
icon = "wallet",
pairType = KeyPairType.Profile,
derivedFrom = a.derivedfrom)
for ga in accounts:
if cmpIgnoreCase(ga.derivedfrom, a.derivedfrom) != 0:
continue
var icon = ""
if a.walletType == WalletTypeDefaultStatusAccount:
icon = "wallet"
item.addAccount(ga.name, ga.path, ga.address, ga.emoji, ga.color, icon, balance = 0.0)
items.insert(item, 0) # Status Account must be at first place
continue
if a.walletType == WalletTypeSeed:
let diffImports = countOfKeyPairsForType(items, KeyPairType.SeedImport)
if item.isNil:
item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = "Seed Phrase " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_seed_phrase",
pairType = KeyPairType.SeedImport,
derivedFrom = a.derivedfrom)
items.add(item)
item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon = "", balance = 0.0)
var item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = "Seed Phrase " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_seed_phrase",
pairType = KeyPairType.SeedImport,
derivedFrom = a.derivedfrom)
for ga in accounts:
if cmpIgnoreCase(ga.derivedfrom, a.derivedfrom) != 0:
continue
item.addAccount(ga.name, ga.path, ga.address, ga.emoji, ga.color, icon = "", balance = 0.0)
items.add(item)
continue
if a.walletType == WalletTypeKey:
let diffImports = countOfKeyPairsForType(items, KeyPairType.PrivateKeyImport)
if item.isNil:
item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = "Key " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_private_key",
pairType = KeyPairType.PrivateKeyImport,
derivedFrom = a.derivedfrom)
items.add(item)
var item = initKeyPairItem(pubKey = a.publicKey,
keyUid = a.keyUid,
locked = false,
name = "Key " & $(diffImports + 1), # string created here should be transalted, but so far it's like it is
image = "",
icon = "key_pair_private_key",
pairType = KeyPairType.PrivateKeyImport,
derivedFrom = a.derivedfrom)
item.addAccount(a.name, a.path, a.address, a.emoji, a.color, icon = "", balance = 0.0)
items.add(item)
continue
if items.len == 0:
debug "sm_there is no any key pair for the logged in user that is not already migrated to a keycard"
@ -409,6 +419,15 @@ method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Path
self.tmpLocalState = newReadingKeycardState(flowToRun, nil)
self.controller.runChangePairingFlow()
return
if flowToRun == FlowType.AuthenticateAndDeriveAccountAddress:
self.derivingAccountDetails = DerivingAccountDetails(
keyUid: keyUid,
path: bip44Path,
deriveAddressAfterAuthentication: true,
addressRequested: false
)
self.controller.authenticateUser(keyUid)
return
method setSelectedKeyPair*[T](self: Module[T], item: KeyPairItem) =
var paths: seq[string]
@ -463,7 +482,12 @@ method setKeyPairStoredOnKeycard*[T](self: Module[T], cardMetadata: CardMetadata
method setNamePropForKeyPairStoredOnKeycard*[T](self: Module[T], name: string) =
self.view.setNamePropForKeyPairStoredOnKeycard(name)
method onUserAuthenticated*[T](self: Module[T], password: string) =
method onUserAuthenticated*[T](self: Module[T], password: string, pin: string) =
if self.derivingAccountDetails.deriveAddressAfterAuthentication:
self.derivingAccountDetails.addressRequested = true
self.controller.setPassword(password)
self.controller.runDeriveAccountFlow(self.derivingAccountDetails.path, pin)
return
let currStateObj = self.view.currentStateObj()
if not currStateObj.isNil and currStateObj.flowType() == FlowType.SetupNewKeycard:
self.controller.setPassword(password)

View File

@ -333,6 +333,7 @@ proc setupKeycardAccount*(self: Controller, storeToKeychain: bool) =
self.storeKeycardAccountAndLogin(storeToKeychain)
else:
self.delegate.moveToLoadingAppState()
self.delegate.storeKeyPairForNewKeycardUser()
self.accountsService.setupAccountKeycard(self.tmpKeycardEvent)
if storeToKeychain:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_STORE)

View File

@ -35,7 +35,8 @@ const RequestParamNewPIN* = "new-pin"
const RequestParamPUK* = "puk"
const RequestParamNewPUK* = "new-puk"
const RequestParamMasterKey* = "master-key"
const RequestParamWalleRootKey* = "wallet-root-key"
const RequestParamMasterKeyAddress* = "master-key-address"
const RequestParamWalletRootKey* = "wallet-root-key"
const RequestParamWalletKey* = "wallet-key"
const RequestParamEIP1581Key* = "eip1581-key"
const RequestParamWhisperKey* = "whisper-key"
@ -49,6 +50,8 @@ const RequestParamBIP44Path* = "bip44-path"
const RequestParamTXSignature* = "tx-signature"
const RequestParamOverwrite* = "overwrite"
const RequestParamResolveAddr* = "resolve-addresses"
const RequestParamExportMasterAddress* = "export-master-address"
const RequestParamExportPrivate* = "export-private"
const RequestParamCardMeta* = "card-metadata"
const RequestParamCardName* = "card-name"
const RequestParamWalletPaths* = "wallet-paths"
@ -98,7 +101,9 @@ const ResponseParamEIP1581Key* = RequestParamEIP1581Key
const ResponseParamEncKey* = RequestParamEncKey
const ResponseParamMasterKey* = RequestParamMasterKey
const ResponseParamWalletKey* = RequestParamWalletKey
const ResponseParamWalleRootKey* = RequestParamWalleRootKey
const ResponseParamWalletRootKey* = RequestParamWalletRootKey
const ResponseParamWhisperKey* = RequestParamWhisperKey
const ResponseParamMnemonicIdxs* = RequestParamMnemonicIdxs
const ResponseParamTXSignature* = RequestParamTXSignature
const ResponseParamTXSignature* = RequestParamTXSignature
const ResponseParamExportedKey* = RequestParamExportedKey
const ResponseParamMasterKeyAddress* = RequestParamMasterKeyAddress

View File

@ -1,3 +1,5 @@
import strutils
type
KeyDetails* = object
address*: string
@ -15,6 +17,11 @@ type
path*: string
address*: string
GeneratedWalletAccount* = object
address*: string
publicKey*: string
privateKey*: string
CardMetadata* = object
name*: string
walletAccounts*: seq[WalletAccount]
@ -34,6 +41,7 @@ type
pinRetries*: int
pukRetries*: int
cardMetadata*: CardMetadata
generatedWalletAccount*: GeneratedWalletAccount
txSignature*: TransactionSignature
eip1581Key*: KeyDetails
encryptionKey*: KeyDetails
@ -41,6 +49,7 @@ type
walletKey*: KeyDetails
walletRootKey*: KeyDetails
whisperKey*: KeyDetails
masterKeyAddress*: string
proc toKeyDetails(jsonObj: JsonNode): KeyDetails =
discard jsonObj.getProp(ResponseParamAddress, result.address)
@ -59,6 +68,13 @@ proc toWalletAccount(jsonObj: JsonNode): WalletAccount =
discard jsonObj.getProp(ResponseParamPath, result.path)
discard jsonObj.getProp(ResponseParamAddress, result.address)
proc toGeneratedWalletAccount(jsonObj: JsonNode): GeneratedWalletAccount =
discard jsonObj.getProp(ResponseParamAddress, result.address)
if jsonObj.getProp(ResponseParamPublicKey, result.publicKey) and not result.publicKey.startsWith("0x"):
result.publicKey = "0x" & result.publicKey
if jsonObj.getProp(ResponseParamPrivateKey, result.privateKey) and not result.privateKey.startsWith("0x"):
result.privateKey = "0x" & result.privateKey
proc toCardMetadata(jsonObj: JsonNode): CardMetadata =
discard jsonObj.getProp(ResponseParamName, result.name)
var accountsArr: JsonNode
@ -77,7 +93,8 @@ proc toKeycardEvent(jsonObj: JsonNode): KeycardEvent =
discard jsonObj.getProp(ResponseParamFreeSlots, result.freePairingSlots)
discard jsonObj.getProp(ResponseParamPINRetries, result.pinRetries)
discard jsonObj.getProp(ResponseParamPUKRetries, result.pukRetries)
if jsonObj.getProp(ResponseParamKeyUID, result.keyUid):
discard jsonObj.getProp(ResponseParamMasterKeyAddress, result.masterKeyAddress)
if jsonObj.getProp(ResponseParamKeyUID, result.keyUid) and not result.keyUid.startsWith("0x"):
result.keyUid = "0x" & result.keyUid
var obj: JsonNode
@ -96,7 +113,7 @@ proc toKeycardEvent(jsonObj: JsonNode): KeycardEvent =
if(jsonObj.getProp(ResponseParamWalletKey, obj)):
result.walletKey = toKeyDetails(obj)
if(jsonObj.getProp(ResponseParamWalleRootKey, obj)):
if(jsonObj.getProp(ResponseParamWalletRootKey, obj)):
result.walletRootKey = toKeyDetails(obj)
if(jsonObj.getProp(ResponseParamWhisperKey, obj)):
@ -109,6 +126,9 @@ proc toKeycardEvent(jsonObj: JsonNode): KeycardEvent =
if(jsonObj.getProp(ResponseParamCardMeta, obj)):
result.cardMetadata = toCardMetadata(obj)
if(jsonObj.getProp(ResponseParamExportedKey, obj)):
result.generatedWalletAccount = toGeneratedWalletAccount(obj)
if(jsonObj.getProp(ResponseParamTXSignature, obj)):
result.txSignature = toTransactionSignature(obj)

View File

@ -1,4 +1,4 @@
import NimQml, json, os, chronicles, random
import NimQml, json, os, chronicles, random, strutils
import keycard_go
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
@ -25,6 +25,7 @@ type KCSFlowType* {.pure.} = enum
const EmptyTxHash = "0000000000000000000000000000000000000000000000000000000000000000"
const DefaultBIP44Path = "m/0"
const DefaultEIP1581Path = "m/43'/60'/1581'"
const PINLengthForStatusApp* = 6
const PUKLengthForStatusApp* = 12
@ -237,6 +238,23 @@ QtObject:
self.currentFlow = KCSFlowType.ChangePairing
self.startFlow(payload)
proc startExportPublicFlow*(self: Service, path: string, exportMasterAddr = false, exportPrivateAddr = false, pin = "") =
if exportPrivateAddr and not path.startsWith(DefaultEIP1581Path):
error "in order to export private address path must not be outside of eip1581 tree"
return
var payload = %* {
RequestParamBIP44Path: DefaultBIP44Path,
RequestParamExportMasterAddress: exportMasterAddr,
RequestParamExportPrivate: exportPrivateAddr
}
if path.len > 0:
payload[RequestParamBIP44Path] = %* path
if pin.len > 0:
payload[RequestParamPIN] = %* pin
self.currentFlow = KCSFlowType.ExportPublic
self.startFlow(payload)
proc startStoreMetadataFlow*(self: Service, cardName: string, pin: string, walletPaths: seq[string]) =
var name = cardName
if cardName.len > CardNameLength:

View File

@ -72,6 +72,24 @@ const getDerivedAddressForPrivateKeyTask*: Task = proc(argEncoded: string) {.gcs
}
arg.finish(output)
type
FetchDerivedAddressDetailsTaskArg* = ref object of QObjectTaskArg
address: string
const fetchDerivedAddressDetailsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchDerivedAddressDetailsTaskArg](argEncoded)
var data = %* {
"details": "",
"error": ""
}
try:
let response = status_go_accounts.getDerivedAddressDetails(arg.address)
data["details"] = response.result
except Exception as e:
let err = fmt"Error getting details for an address: {e.msg}"
data["error"] = %* err
arg.finish(data)
#################################################
# Async timer
#################################################

View File

@ -32,6 +32,7 @@ const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated"
const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED* = "walletAccount/derivedAddressDetailsFetched"
const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet"
const SIGNAL_KEYCARD_LOCKED* = "keycardLocked"
@ -232,43 +233,73 @@ QtObject:
self.buildAllTokens()
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string,
path: string, derivedFrom: string, skipPasswordVerification: bool): string =
try:
discard backend.generateAccountWithDerivedPath(
hashPassword(password),
accountName,
color,
emoji,
path,
derivedFrom)
if skipPasswordVerification:
discard backend.generateAccountWithDerivedPathPasswordVerified(
hashPassword(password),
accountName,
color,
emoji,
path,
derivedFrom)
else:
discard backend.generateAccountWithDerivedPath(
hashPassword(password),
accountName,
color,
emoji,
path,
derivedFrom)
except Exception as e:
return fmt"Error generating new account: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: string, accountName: string, color: string,
emoji: string, skipPasswordVerification: bool): string =
try:
discard backend.addAccountWithPrivateKey(
privateKey,
hashPassword(password),
accountName,
color,
emoji)
if skipPasswordVerification:
discard backend.addAccountWithPrivateKeyPasswordVerified(
privateKey,
hashPassword(password),
accountName,
color,
emoji)
else:
discard backend.addAccountWithPrivateKey(
privateKey,
hashPassword(password),
accountName,
color,
emoji)
except Exception as e:
return fmt"Error adding account with private key: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string,
emoji: string, path: string, skipPasswordVerification: bool): string =
try:
discard backend.addAccountWithMnemonicAndPath(
mnemonic,
hashPassword(password),
accountName,
color,
emoji,
path
)
if skipPasswordVerification:
discard backend.addAccountWithMnemonicAndPathPasswordVerified(
mnemonic,
hashPassword(password),
accountName,
color,
emoji,
path
)
else:
discard backend.addAccountWithMnemonicAndPath(
mnemonic,
hashPassword(password),
accountName,
color,
emoji,
path
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
@ -379,6 +410,27 @@ QtObject:
error: error
))
proc fetchDerivedAddressDetails*(self: Service, address: string) =
let arg = FetchDerivedAddressDetailsTaskArg(
address: address,
tptr: cast[ByteAddress](fetchDerivedAddressDetailsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onDerivedAddressDetailsFetched",
)
self.threadpool.start(arg)
proc onDerivedAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} =
var data = DerivedAddressesArgs()
try:
let response = parseJson(jsonString)
let addrDto = response{"details"}.toDerivedAddressDto()
data.derivedAddresses.add(addrDto)
data.error = response["error"].getStr()
except Exception as e:
error "error: ", procName="getDerivedAddressDetails", errName = e.name, errDesription = e.msg
data.error = e.msg
self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED, data)
proc onStartBuildingTokensTimer*(self: Service, response: string) {.slot.} =
if ((now().toTime().toUnix() - self.timerStartTimeInSeconds) < CheckBalanceSlotExecuteIntervalInSeconds):
self.startBuildingTokensTimer(resetTimeToNow = false)
@ -566,4 +618,16 @@ QtObject:
return self.responseHasNoErrors("deleteKeycard", response)
except Exception as e:
error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg
return false
return false
proc addWalletAccount*(self: Service, name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType,
color, emoji: string): string =
try:
let response = status_go_accounts.saveAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid,
accountType, color, emoji, walletDefaultAccount = false, chatDefaultAccount = false)
if not response.error.isNil:
return "(" & $response.error.code & ") " & response.error.message
self.addNewAccountToLocalStore()
except Exception as e:
error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg
return "error: " & e.msg

View File

@ -24,6 +24,26 @@ proc getAccounts*(): RpcResponse[JsonNode] {.raises: [Exception].} =
proc deleteAccount*(address: string): RpcResponse[JsonNode] {.raises: [Exception].} =
return core.callPrivateRPC("accounts_deleteAccount", %* [address])
proc saveAccount*(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, color, emoji: string,
walletDefaultAccount: bool, chatDefaultAccount: bool):
RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [
[{
"name": name,
"address": address,
"path": path,
"derived-from": addressAccountIsDerivedFrom,
"public-key": publicKey,
"key-uid": keyUid,
"type": accountType,
"color": color,
"emoji": emoji,
"wallet": walletDefaultAccount,
"chat": chatDefaultAccount
}]
]
return core.callPrivateRPC("accounts_saveAccounts", payload)
proc updateAccount*(name, address, publicKey, walletType, color, emoji: string) {.raises: [Exception].} =
discard core.callPrivateRPC("accounts_saveAccounts", %* [
[{
@ -285,6 +305,10 @@ proc getDerivedAddressForPrivateKey*(privateKey: string,): RpcResponse[JsonNode]
let payload = %* [privateKey]
result = core.callPrivateRPC("wallet_getDerivedAddressForPrivateKey", payload)
proc getDerivedAddressDetails*(address: string,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [address]
result = core.callPrivateRPC("wallet_getDerivedAddressDetails", payload)
proc verifyPassword*(password: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [password]
return core.callPrivateRPC("accounts_verifyPassword", payload)

View File

@ -122,6 +122,14 @@ rpc(generateAccountWithDerivedPath, "accounts"):
path: string
derivedFrom: string
rpc(generateAccountWithDerivedPathPasswordVerified, "accounts"):
password: string
name: string
color: string
emoji: string
path: string
derivedFrom: string
rpc(addAccountWithMnemonicAndPath, "accounts"):
mnemonic: string
password: string
@ -130,6 +138,14 @@ rpc(addAccountWithMnemonicAndPath, "accounts"):
emoji: string
path: string
rpc(addAccountWithMnemonicAndPathPasswordVerified, "accounts"):
mnemonic: string
password: string
name: string
color: string
emoji: string
path: string
rpc(addAccountWithPrivateKey, "accounts"):
privateKey: string
password: string
@ -137,6 +153,13 @@ rpc(addAccountWithPrivateKey, "accounts"):
color: string
emoji: string
rpc(addAccountWithPrivateKeyPasswordVerified, "accounts"):
privateKey: string
password: string
name: string
color: string
emoji: string
rpc(addAccountWatch, "accounts"):
address: string
name: string

View File

@ -149,8 +149,7 @@ Item {
//TODO remove when sync code is implemented
opacity: 0.0
}
// TODO: Functionality is not completed. Missing saving of keystore files which blocks wallet account creation. @see #7867
PropertyChanges {
PropertyChanges {
target: keycardLink
text: qsTr("Login with Keycard")
}

View File

@ -6,6 +6,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
@ -14,9 +15,17 @@ import "../stores"
Item {
id: derivedAddresses
property int selectedAccountType: RootStore.defaultSelectedType
property string selectedKeyUid: RootStore.defaultSelectedKeyUid
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedPath: ""
property string pathSubFix: ""
property bool isLoading: RootStore.derivedAddressesLoading
property bool pathError: Utils.isInvalidPath(RootStore.derivedAddressesError)
property string enterPasswordIcon: ""
property alias selectedAddress: selectedDerivedAddress.title
property alias selectedAddressAvailable: selectedDerivedAddress.enabled
function reset() {
RootStore.resetDerivedAddressModel()
@ -42,6 +51,10 @@ Item {
QtObject {
id: _internal
readonly property bool showEnterPinPassButton: !RootStore.loggedInUserAuthenticated &&
derivedAddresses.selectedAccountType !== SelectGeneratedAccount.AddAccountType.ImportSeedPhrase &&
derivedAddresses.selectedAccountType !== SelectGeneratedAccount.AddAccountType.ImportPrivateKey
property int pageSize: 6
property int noOfPages: Math.ceil(RootStore.derivedAddressesList.count/pageSize)
property int lastPageSize: RootStore.derivedAddressesList.count - ((noOfPages -1) * pageSize)
@ -62,6 +75,13 @@ Item {
// dimensions
property int popupWidth: 359
property int maxAddressWidth: 102
function runAction() {
if (derivedAddresses.selectedKeyUidMigratedToKeycard)
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(derivedAddresses.selectedKeyUid, derivedAddresses.selectedPath)
else
RootStore.authenticateUser()
}
}
Connections {
@ -90,6 +110,7 @@ Item {
property int pathSubFix: 0
property bool hasActivity: false
implicitWidth: parent.width
visible: !_internal.showEnterPinPassButton
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
@ -117,6 +138,17 @@ Item {
enabled: RootStore.derivedAddressesList.count > 0
Component.onCompleted: derivedAddresses.pathSubFix = Qt.binding(function() { return pathSubFix})
}
StatusButton {
visible: _internal.showEnterPinPassButton
text: derivedAddresses.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser?
qsTr("Enter PIN") :
qsTr("Enter password")
icon.name: derivedAddresses.enterPasswordIcon
highlighted: focus
onClicked: _internal.runAction()
}
}
StatusPopupMenu {

View File

@ -14,6 +14,9 @@ StatusSelect {
property int addAccountType
property string derivedFromAddress: ""
property string selectedKeyUid: userProfile.keyUid
property bool selectedKeyUidMigratedToKeycard: userProfile.isKeycardUser
enum AddAccountType {
GenerateNew,
@ -24,7 +27,9 @@ StatusSelect {
function resetMe() {
_internal.getGeneratedAccountsModel()
addAccountType = SelectGeneratedAccount.AddAccountType.GenerateNew
selectAccountType.addAccountType = SelectGeneratedAccount.AddAccountType.GenerateNew
selectAccountType.selectedKeyUid = userProfile.keyUid
selectAccountType.selectedKeyUidMigratedToKeycard = userProfile.isKeycardUser
}
Connections {
@ -55,16 +60,17 @@ StatusSelect {
for (var row = 0; row < _internal.delegateModel.model.count; row++) {
if (_internal.delegateModel.items.count > 0) {
var item = _internal.delegateModel.items.get(row).model;
generatedAccountsModel.append({"name": item.name, "iconName": item.iconName, "generatedModel": item.generatedModel, "derivedfrom": item.derivedfrom, "isHeader": false})
generatedAccountsModel.append({"name": item.name, "iconName": item.iconName, "generatedModel": item.generatedModel, "derivedfrom": item.derivedfrom, "isHeader": false,
"keyUid": item.keyUid, "migratedToKeycard": item.migratedToKeycard})
if (row === 0 && _internal.delegateModel.model.count > 1) {
generatedAccountsModel.append({"name": qsTr("Imported"), "iconName": "", "derivedfrom": "", "isHeader": true})
generatedAccountsModel.append({"name": qsTr("Imported"), "iconName": "", "derivedfrom": "", "isHeader": true, "keyUid": "", "migratedToKeycard": false})
}
}
}
generatedAccountsModel.append({"name": qsTr("Add new"), "iconName": "", "derivedfrom": "", "isHeader": true})
generatedAccountsModel.append({"name": _internal.importSeedPhraseString, "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false})
generatedAccountsModel.append({"name": _internal.importPrivateKeyString, "iconName": "password", "derivedfrom": "", "isHeader": false})
generatedAccountsModel.append({"name": _internal.addWatchOnlyAccountString, "iconName": "show", "derivedfrom": "", "isHeader": false})
generatedAccountsModel.append({"name": qsTr("Add new"), "iconName": "", "derivedfrom": "", "isHeader": true, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.importSeedPhraseString, "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.importPrivateKeyString, "iconName": "password", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.addWatchOnlyAccountString, "iconName": "show", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
}
}
}
@ -120,6 +126,9 @@ StatusSelect {
selectedItem.enabled = !model.isHeader
selectAccountType.derivedFromAddress = model.derivedfrom
selectAccountType.selectedKeyUid = model.keyUid
selectAccountType.selectedKeyUidMigratedToKeycard = model.migratedToKeycard
selectMenu.close()
}
Component.onCompleted: {

View File

@ -27,7 +27,8 @@ StatusModal {
property var emojiPopup: null
header.title: qsTr("Generate an account")
closePolicy: Popup.CloseOnEscape
closePolicy: nextButton.loading? Popup.NoAutoClose : Popup.CloseOnEscape
hasCloseButton: !nextButton.loading
signal afterAddAccount()
@ -45,11 +46,21 @@ StatusModal {
onUserAuthenticationSuccess: {
validationError.text = ""
d.password = password
d.getDerivedAddressList()
RootStore.loggedInUserAuthenticated = true
if (d.selectedAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey) {
d.generateNewAccount()
}
else {
if (!d.selectedKeyUidMigratedToKeycard) {
d.getDerivedAddressList()
}
}
}
onUserAuthentiactionFail: {
d.password = ""
RootStore.loggedInUserAuthenticated = false
validationError.text = qsTr("An authentication failed")
nextButton.loading = false
}
}
@ -60,13 +71,24 @@ StatusModal {
readonly property int pageNumber: 1
property string password: ""
property int selectedAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew
property int selectedAccountType: RootStore.defaultSelectedType
property string selectedAccountDerivedFromAddress: ""
property string selectedKeyUid: RootStore.defaultSelectedKeyUid
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedPath: ""
property string selectedAddress: ""
property bool selectedAddressAvailable: true
readonly property bool authenticationNeeded: d.selectedAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly &&
d.password === ""
property string addAccountIcon: ""
property bool isLoading: RootStore.derivedAddressesLoading
onIsLoadingChanged: {
if(!isLoading && nextButton.loading) {
d.generateNewAccount()
}
}
function getDerivedAddressList() {
if(d.selectedAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase
@ -83,7 +105,6 @@ StatusModal {
function generateNewAccount() {
// TODO the loading doesn't work because the function freezes the view. Might need to use threads
nextButton.loading = true
if (!advancedSelection.validate()) {
Global.playErrorSound()
return nextButton.loading = false
@ -93,9 +114,17 @@ StatusModal {
switch(d.selectedAccountType) {
case SelectGeneratedAccount.AddAccountType.GenerateNew:
if (d.selectedKeyUidMigratedToKeycard) {
errMessage = RootStore.addNewWalletAccountGeneratedFromKeycard(Constants.generatedWalletType,
accountNameInput.text,
colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji)
}
else {
errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji, advancedSelection.expandableItem.completePath,
advancedSelection.expandableItem.derivedFromAddress)
}
break
case SelectGeneratedAccount.AddAccountType.ImportSeedPhrase:
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, d.password,
@ -123,9 +152,16 @@ StatusModal {
}
function nextButtonClicked() {
nextButton.loading = true
if (d.authenticationNeeded) {
d.password = ""
RootStore.authenticateUser()
if (d.selectedKeyUidMigratedToKeycard &&
d.selectedAccountType === SelectGeneratedAccount.AddAccountType.GenerateNew) {
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(d.selectedKeyUid, d.selectedPath)
}
else {
RootStore.authenticateUser()
}
}
else {
d.generateNewAccount()
@ -134,6 +170,7 @@ StatusModal {
}
onOpened: {
RootStore.loggedInUserAuthenticated = false
d.addAccountIcon = "password"
if (RootStore.loggedInUserUsesBiometricLogin()) {
d.addAccountIcon = "touch-id"
@ -148,6 +185,7 @@ StatusModal {
}
onClosed: {
RootStore.loggedInUserAuthenticated = false
d.password = ""
validationError.text = ""
accountNameInput.reset()
@ -157,7 +195,7 @@ StatusModal {
contentItem: StatusScrollView {
id: scroll
width: popup.width
width: root.width
topPadding: Style.current.halfPadding
bottomPadding: Style.current.halfPadding
leftPadding: Style.current.padding
@ -214,6 +252,7 @@ StatusModal {
StatusColorSelectorGrid {
id: colorSelectionGrid
anchors.horizontalCenter: parent.horizontalCenter
enabled: accountNameInput.valid
titleText: qsTr("color").toUpperCase()
}
@ -232,13 +271,24 @@ StatusModal {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
enabled: accountNameInput.valid
primaryText: qsTr("Advanced")
type: StatusExpandableItem.Type.Tertiary
expandable: true
expandableComponent: AdvancedAddAccountView {
width: parent.width
onCalculateDerivedPath: d.getDerivedAddressList()
enterPasswordIcon: d.addAccountIcon
onCalculateDerivedPath: {
if (d.selectedKeyUidMigratedToKeycard) {
d.password = ""
validationError.text = ""
RootStore.loggedInUserAuthenticated = false
}
else{
d.getDerivedAddressList()
}
}
onEnterPressed: {
if (nextButton.enabled) {
nextButton.clicked(null)
@ -246,12 +296,14 @@ StatusModal {
}
}
onAddAccountTypeChanged: {
d.selectedAccountType = addAccountType
}
Component.onCompleted: {
d.selectedAccountType = addAccountType
d.selectedAccountType = Qt.binding(() => addAccountType)
d.selectedAccountDerivedFromAddress = Qt.binding(() => derivedFromAddress)
d.selectedKeyUid = Qt.binding(() => selectedKeyUid)
d.selectedKeyUidMigratedToKeycard = Qt.binding(() => selectedKeyUidMigratedToKeycard)
d.selectedPath = Qt.binding(() => path)
d.selectedAddress = Qt.binding(() => selectedAddress)
d.selectedAddressAvailable = Qt.binding(() => selectedAddressAvailable)
advancedSelection.isValid = Qt.binding(() => isValid)
}
}
@ -264,9 +316,6 @@ StatusModal {
id: nextButton
text: {
if (d.authenticationNeeded) {
return qsTr("Authenticate")
}
if (loading) {
return qsTr("Loading...")
}
@ -275,19 +324,18 @@ StatusModal {
enabled: {
if (d.authenticationNeeded) {
return true
if (!accountNameInput.valid) {
return false
}
if (loading) {
return false
}
return accountNameInput.text !== "" && advancedSelection.isValid
return advancedSelection.isValid
}
icon.name: d.authenticationNeeded? d.addAccountIcon : ""
highlighted: focus
Keys.onReturnPressed: d.nextButtonClicked()
onClicked : d.nextButtonClicked()
}
]

View File

@ -5,9 +5,17 @@ import QtQuick 2.13
import utils 1.0
import shared.stores 1.0 as SharedStore
import "../panels"
QtObject {
id: root
readonly property int defaultSelectedType: SelectGeneratedAccount.AddAccountType.GenerateNew
readonly property string defaultSelectedKeyUid: userProfile.keyUid
readonly property bool defaultSelectedKeyUidMigratedToKeycard: userProfile.isKeycardUser
property bool loggedInUserAuthenticated: false
property string backButtonName: ""
property var currentAccount: Constants.isCppApp ? walletSectionAccounts.currentAccount: walletSectionCurrent
property var accounts: walletSectionAccounts.model
@ -114,6 +122,10 @@ QtObject {
return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
}
function addNewWalletAccountGeneratedFromKeycard(accountType, accountName, color, emoji) {
return walletSectionAccounts.addNewWalletAccountGeneratedFromKeycard(accountType, accountName, color, emoji)
}
function addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji) {
return walletSectionAccounts.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
}
@ -234,4 +246,16 @@ QtObject {
function loggedInUserIsKeycardUser() {
return userProfile.isKeycardUser
}
function createSharedKeycardModule() {
walletSectionAccounts.createSharedKeycardModule()
}
function destroySharedKeycarModule() {
walletSectionAccounts.destroySharedKeycarModule()
}
function authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath) {
walletSectionAccounts.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath)
}
}

View File

@ -17,6 +17,11 @@ ColumnLayout {
id: advancedSection
property int addAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew
property string selectedKeyUid: RootStore.defaultSelectedKeyUid
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedAddress: ""
property bool selectedAddressAvailable: true
property string enterPasswordIcon: ""
property string derivedFromAddress: ""
property string mnemonicText: ""
property alias privateKey: importPrivateKeyPanel.text
@ -100,6 +105,8 @@ ColumnLayout {
Component.onCompleted: {
advancedSection.addAccountType = Qt.binding(function() {return addAccountType})
advancedSection.derivedFromAddress = Qt.binding(function() {return derivedFromAddress})
advancedSection.selectedKeyUid = Qt.binding(function() {return selectedKeyUid})
advancedSection.selectedKeyUidMigratedToKeycard = Qt.binding(function() {return selectedKeyUidMigratedToKeycard})
}
}
@ -155,7 +162,18 @@ ColumnLayout {
id: derivedAddressesPanel
Layout.preferredWidth: ((parent.width - (Style.current.bigPadding/2))/2)
Layout.alignment: Qt.AlignTop
Component.onCompleted: advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
selectedAccountType: advancedSection.addAccountType
selectedKeyUid: advancedSection.selectedKeyUid
selectedKeyUidMigratedToKeycard: advancedSection.selectedKeyUidMigratedToKeycard
selectedPath: advancedSection.path
enterPasswordIcon: advancedSection.enterPasswordIcon
Component.onCompleted: {
advancedSection.selectedAddress = Qt.binding(function() { return derivedAddressesPanel.selectedAddress})
advancedSection.selectedAddressAvailable = Qt.binding(function() { return derivedAddressesPanel.selectedAddressAvailable})
advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
}
}
}
}

View File

@ -12,6 +12,7 @@ import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.controls 1.0
import shared.popups.keycard 1.0
import "../controls"
import "../popups"
@ -30,11 +31,33 @@ Rectangle {
color: Style.current.secondaryMenuBackground
AddAccountModal {
Loader {
id: addAccountModal
anchors.centerIn: parent
onAfterAddAccount: root.onAfterAddAccount()
emojiPopup: root.emojiPopup
active: false
asynchronous: true
function open() {
if (!active) {
RootStore.createSharedKeycardModule()
active = true
}
item.open()
}
function close() {
if (item) {
RootStore.destroySharedKeycarModule()
item.close()
}
active = false
}
sourceComponent: AddAccountModal {
anchors.centerIn: parent
onAfterAddAccount: root.onAfterAddAccount()
emojiPopup: root.emojiPopup
onClosed: addAccountModal.close()
}
}
ColumnLayout {

View File

@ -664,6 +664,7 @@ QtObject {
readonly property bool isCppApp: typeof cppApp !== "undefined" ? cppApp : false
readonly property string existingAccountError: "account already exists"
readonly property string wrongDerivationPathError: "error parsing derivation path"
enum TransactionStatus {
Failure = 0,

View File

@ -735,11 +735,11 @@ QtObject {
}
function isInvalidPath(msg) {
return msg.includes("error parsing derivation path")
return msg.includes(Constants.wrongDerivationPathError)
}
function accountAlreadyExistsError(msg) {
return msg.includes("account already exists")
return msg.includes(Constants.existingAccountError)
}
// See also: backend/interpret/cropped_image.nim

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit bb4237f616ea17fb7083b91bf20ceed11ca253aa
Subproject commit c2b17acc074fa2630fd5a5c69aaa8ac8099d5c65