feat(@desktop/keycard): sign flow added to the shared keycard module

This commit is contained in:
Sale Djenic 2023-10-27 19:14:39 +02:00 committed by saledjenic
parent 52d760d1b3
commit 9c9bcef6d2
25 changed files with 488 additions and 191 deletions

View File

@ -2,24 +2,24 @@ import chronicles, tables, strutils, os, sequtils, sugar
import uuids
import io_interface
import ../../../global/app_sections_config as conf
import ../../../global/app_signals
import ../../../global/global_singleton
import ../../../core/signals/types
import ../../../core/eventemitter
import ../../startup/io_interface as startup_io
import ../../../../app_service/common/utils
import ../../../../app_service/common/account_constants
import ../../../../app_service/service/keycard/service as keycard_service
import ../../../../app_service/service/settings/service as settings_service
import ../../../../app_service/service/network/service as network_service
import ../../../../app_service/service/privacy/service as privacy_service
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../app_service/service/keychain/service as keychain_service
import ../../../../app_service/service/currency/dto
import app/global/app_sections_config as conf
import app/global/app_signals
import app/global/global_singleton
import app/core/signals/types
import app/core/eventemitter
import app/modules/startup/io_interface as startup_io
import app_service/common/utils
import app_service/common/account_constants
import app_service/service/keycard/service as keycard_service
import app_service/service/settings/service as settings_service
import app_service/service/network/service as network_service
import app_service/service/privacy/service as privacy_service
import app_service/service/accounts/service as accounts_service
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/keychain/service as keychain_service
import app_service/service/currency/dto
import ../../shared_models/[keypair_item]
import app/modules/shared_models/[keypair_item]
logScope:
topics = "keycard-popup-controller"
@ -56,6 +56,7 @@ type
tmpSeedPhrase: string
tmpSeedPhraseLength: int
tmpKeyUidWhichIsBeingAuthenticating: string
tmpKeyUidWhichIsBeingSigning: string
tmpKeyUidWhichIsBeingSyncing: string
tmpUsePinFromBiometrics: bool
tmpOfferToStoreUpdatedPinToKeychain: bool
@ -69,6 +70,7 @@ type
tmpKeycardSyncingInProgress: bool
tmpFlowData: SharedKeycarModuleFlowTerminatedArgs
tmpRequestedPathsAlongWithAuthentication: seq[string]
tmpPathUsedForSigning: string
tmpUnlockUsingSeedPhrase: bool # true - sp, false - puk
proc newController*(delegate: io_interface.AccessInterface,
@ -306,6 +308,9 @@ proc getPairingCode*(self: Controller): string =
proc getKeyUidWhichIsBeingAuthenticating*(self: Controller): string =
return self.tmpKeyUidWhichIsBeingAuthenticating
proc getKeyUidWhichIsBeingSigning*(self: Controller): string =
return self.tmpKeyUidWhichIsBeingSigning
proc getKeyUidWhichIsBeingSyncing*(self: Controller): string =
return self.tmpKeyUidWhichIsBeingSyncing
@ -531,9 +536,8 @@ proc runDeriveAccountFlow*(self: Controller, bip44Paths: seq[string], pin: strin
self.keycardService.startExportPublicFlow(bip44Paths, exportMasterAddr=true, exportPrivateAddr=false, pin)
proc runAuthenticationFlow*(self: Controller, keyUid = "", bip44Paths: seq[string] = @[]) =
## 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.
## In case of `Authentication` flow, if keyUid is provided, that keypair will be authenticated,
## otherwise the logged in profile will be authenticated.
if not serviceApplicable(self.keycardService):
return
self.tmpKeyUidWhichIsBeingAuthenticating = keyUid
@ -546,6 +550,19 @@ proc runAuthenticationFlow*(self: Controller, keyUid = "", bip44Paths: seq[strin
self.tmpRequestedPathsAlongWithAuthentication = self.tmpRequestedPathsAlongWithAuthentication.deduplicate()
self.keycardService.startExportPublicFlow(self.tmpRequestedPathsAlongWithAuthentication, exportMasterAddr = false, exportPrivateAddr = true)
proc runSignFlow*(self: Controller, keyUid, bip44Path, txHash: string) =
if not serviceApplicable(self.keycardService):
return
self.tmpKeyUidWhichIsBeingSigning = keyUid
if self.tmpKeyUidWhichIsBeingSigning.len == 0:
self.tmpKeyUidWhichIsBeingSigning = singletonInstance.userProfile.getKeyUid()
self.cancelCurrentFlow()
if bip44Path.len == 0:
error "path must be set"
return
self.tmpPathUsedForSigning = bip44Path
self.keycardService.startSignFlow(bip44Path, txHash)
proc runLoadAccountFlow*(self: Controller, seedPhraseLength = 0, seedPhrase = "", pin = "", puk = "", factoryReset = false) =
if not serviceApplicable(self.keycardService):
return
@ -575,6 +592,7 @@ proc readyToDisplayPopup*(self: Controller) =
proc cleanTmpData(self: Controller) =
# we should not reset here `tmpKeycardSyncingInProgress` property
self.tmpKeyUidWhichIsBeingAuthenticating = ""
self.tmpKeyUidWhichIsBeingSigning = ""
self.tmpKeyUidWhichIsBeingSyncing = ""
self.tmpAddingMigratedKeypairSuccess = false
self.tmpConvertingProfileSuccess = false
@ -614,14 +632,19 @@ proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool, nex
continueWithKeyUid: nextKeyUid,
returnToFlow: returnToFlow)
if lastStepInTheCurrentFlow:
if flowEvent.instanceUID.len > 0:
self.tmpFlowData.keyUid = flowEvent.keyUid
self.tmpFlowData.keycardUid = flowEvent.instanceUID
self.tmpFlowData.pin = self.getPin()
self.tmpFlowData.path = self.tmpPathUsedForSigning
self.tmpFlowData.r = flowEvent.txSignature.r
self.tmpFlowData.s = flowEvent.txSignature.s
self.tmpFlowData.v = flowEvent.txSignature.v
var exportedEncryptionPubKey: string
if flowEvent.generatedWalletAccounts.len > 0:
exportedEncryptionPubKey = flowEvent.generatedWalletAccounts[0].publicKey # encryption key is at position 0
if exportedEncryptionPubKey.len > 0:
self.tmpFlowData.password = exportedEncryptionPubKey
self.tmpFlowData.pin = self.getPin()
self.tmpFlowData.keyUid = flowEvent.keyUid
self.tmpFlowData.keycardUid = flowEvent.instanceUID
for i in 0..< self.tmpRequestedPathsAlongWithAuthentication.len:
var path = self.tmpRequestedPathsAlongWithAuthentication[i]
self.tmpFlowData.additinalPathsDetails[path] = KeyDetails(

View File

@ -9,19 +9,23 @@ proc delete*(self: BiometricsPinFailedState) =
self.State.delete
method executePrePrimaryStateCommand*(self: BiometricsPinFailedState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.tryToObtainDataFromKeychain()
method executePreSecondaryStateCommand*(self: BiometricsPinFailedState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setUsePinFromBiometrics(true)
method getNextSecondaryState*(self: BiometricsPinFailedState, controller: Controller): State =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
return createState(StateType.EnterPin, self.flowType, nil)
method executeCancelCommand*(self: BiometricsPinFailedState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: BiometricsPinFailedState, keycardFlowType: string, keycardEvent: KeycardEvent,

View File

@ -9,20 +9,24 @@ proc delete*(self: BiometricsPinInvalidState) =
self.State.delete
method executePrePrimaryStateCommand*(self: BiometricsPinInvalidState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.tryToObtainDataFromKeychain()
method executePreSecondaryStateCommand*(self: BiometricsPinInvalidState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setUsePinFromBiometrics(true)
controller.setOfferToStoreUpdatedPinToKeychain(true)
method getNextSecondaryState*(self: BiometricsPinInvalidState, controller: Controller): State =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
return createState(StateType.EnterPin, self.flowType, nil)
method executeCancelCommand*(self: BiometricsPinInvalidState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: BiometricsPinInvalidState, keycardFlowType: string, keycardEvent: KeycardEvent,

View File

@ -9,19 +9,23 @@ proc delete*(self: BiometricsReadyToSignState) =
self.State.delete
method executePrePrimaryStateCommand*(self: BiometricsReadyToSignState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.tryToObtainDataFromKeychain()
method executePreSecondaryStateCommand*(self: BiometricsReadyToSignState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setUsePinFromBiometrics(true)
method getNextSecondaryState*(self: BiometricsReadyToSignState, controller: Controller): State =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
return createState(StateType.EnterPin, self.flowType, nil)
method executeCancelCommand*(self: BiometricsReadyToSignState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: BiometricsReadyToSignState, keycardFlowType: string, keycardEvent: KeycardEvent,
@ -29,7 +33,8 @@ method resolveKeycardNextState*(self: BiometricsReadyToSignState, keycardFlowTyp
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:

View File

@ -19,7 +19,8 @@ method getNextPrimaryState*(self: EnterPinState, controller: Controller): State
if self.flowType == FlowType.CreateCopyOfAKeycard:
if isPredefinedKeycardDataFlagSet(controller.getKeycardData(), PredefinedKeycardData.CopyFromAKeycardPartDone):
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
@ -38,7 +39,8 @@ method executePreSecondaryStateCommand*(self: EnterPinState, controller: Control
self.flowType == FlowType.MigrateFromAppToKeycard:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setUsePinFromBiometrics(false)
controller.tryToObtainDataFromKeychain()
@ -49,6 +51,7 @@ method executeCancelCommand*(self: EnterPinState, controller: Controller) =
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
@ -184,7 +187,8 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke
)) # name and other params will be set by the user during the flow
controller.setKeyPairForProcessing(item)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:

View File

@ -20,6 +20,7 @@ method executeCancelCommand*(self: InsertKeycardState, controller: Controller) =
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -12,6 +12,7 @@ method executePrePrimaryStateCommand*(self: KeycardEmptyState, controller: Contr
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
@ -35,6 +36,7 @@ method executeCancelCommand*(self: KeycardEmptyState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -27,6 +27,7 @@ method executeCancelCommand*(self: KeycardInsertedState, controller: Controller)
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -21,6 +21,7 @@ method getNextPrimaryState*(self: MaxPairingSlotsReachedState, controller: Contr
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
@ -38,6 +39,7 @@ method executeCancelCommand*(self: MaxPairingSlotsReachedState, controller: Cont
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -33,12 +33,14 @@ method getNextPrimaryState*(self: MaxPinRetriesReachedState, controller: Control
controller.runSharedModuleFlow(FlowType.UnlockKeycard)
return
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.DisableSeedPhraseForUnlock, add = true))
controller.runSharedModuleFlow(FlowType.UnlockKeycard)
method executeCancelCommand*(self: MaxPinRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
self.flowType == FlowType.ChangeKeycardPuk or

View File

@ -21,6 +21,7 @@ method getNextPrimaryState*(self: MaxPukRetriesReachedState, controller: Control
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
@ -38,6 +39,7 @@ method executeCancelCommand*(self: MaxPukRetriesReachedState, controller: Contro
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -15,6 +15,7 @@ method executeCancelCommand*(self: NotKeycardState, controller: Controller) =
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -20,6 +20,7 @@ method executeCancelCommand*(self: PluginReaderState, controller: Controller) =
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or

View File

@ -7,6 +7,7 @@ proc ensureReaderAndCardPresence*(state: State, keycardFlowType: string, keycard
## Handling factory reset or authentication or unlock keycard flow
if state.flowType == FlowType.FactoryReset or
state.flowType == FlowType.Authentication or
state.flowType == FlowType.Sign or
state.flowType == FlowType.UnlockKeycard or
state.flowType == FlowType.DisplayKeycardContent or
state.flowType == FlowType.RenameKeycard or
@ -292,6 +293,45 @@ proc ensureReaderAndCardPresenceAndResolveNextState*(state: State, keycardFlowTy
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
return nil
## Handling sign flow
if state.flowType == FlowType.Sign:
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0:
if keycardEvent.error == ErrorNotAKeycard:
return createState(StateType.NotKeycard, state.flowType, nil)
if keycardEvent.error == ErrorNoKeys:
return createState(StateType.KeycardEmpty, state.flowType, nil)
if keycardEvent.error == ErrorFreePairingSlots:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.UseGeneralMessageForLockedState, add = true))
return createState(StateType.MaxPairingSlotsReached, state.flowType, nil)
if keycardEvent.error == ErrorPUKRetries:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.UseGeneralMessageForLockedState, add = true))
return createState(StateType.MaxPukRetriesReached, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN:
if keycardEvent.keyUid == controller.getKeyUidWhichIsBeingSigning():
if singletonInstance.userProfile.getUsingBiometricLogin():
if keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:
controller.setRemainingAttempts(keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
if not controller.usePinFromBiometrics():
return createState(StateType.WrongKeychainPin, state.flowType, nil)
return createState(StateType.WrongPin, state.flowType, nil)
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.UseGeneralMessageForLockedState, add = true))
return createState(StateType.MaxPinRetriesReached, state.flowType, nil)
return createState(StateType.BiometricsReadyToSign, state.flowType, nil)
return createState(StateType.EnterPin, state.flowType, nil)
return createState(StateType.WrongKeycard, state.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.UseGeneralMessageForLockedState, add = true))
return createState(StateType.MaxPinRetriesReached, state.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
if keycardEvent.error.len == 0:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
return nil
## Handling unlock keycard flow
if state.flowType == FlowType.UnlockKeycard:
if controller.getCurrentKeycardServiceFlow() == KCSFlowType.GetMetadata:

View File

@ -28,6 +28,7 @@ method executePrePrimaryStateCommand*(self: WrongKeycardState, controller: Contr
method executeCancelCommand*(self: WrongKeycardState, controller: Controller) =
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or

View File

@ -9,13 +9,15 @@ proc delete*(self: WrongKeychainPinState) =
self.State.delete
method getNextPrimaryState*(self: WrongKeychainPinState, controller: Controller): State =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
return nil
method executeCancelCommand*(self: WrongKeychainPinState, controller: Controller) =
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
method resolveKeycardNextState*(self: WrongKeychainPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
@ -23,7 +25,8 @@ method resolveKeycardNextState*(self: WrongKeychainPinState, keycardFlowType: st
let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:

View File

@ -17,7 +17,8 @@ method getNextPrimaryState*(self: WrongPinState, controller: Controller): State
if self.flowType == FlowType.CreateCopyOfAKeycard:
if isPredefinedKeycardDataFlagSet(controller.getKeycardData(), PredefinedKeycardData.CopyFromAKeycardPartDone):
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
if self.flowType == FlowType.ImportFromKeycard or
@ -43,7 +44,8 @@ method executePreSecondaryStateCommand*(self: WrongPinState, controller: Control
self.flowType == FlowType.MigrateFromAppToKeycard:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
controller.setUsePinFromBiometrics(false)
controller.tryToObtainDataFromKeychain()
@ -54,6 +56,7 @@ method executeCancelCommand*(self: WrongPinState, controller: Controller) =
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.ImportFromKeycard or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.ChangeKeycardPin or
@ -168,7 +171,8 @@ method resolveKeycardNextState*(self: WrongPinState, keycardFlowType: string, ke
)) # name and other params will be set by the user during the flow
controller.setKeyPairForProcessing(item)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.Authentication:
if self.flowType == FlowType.Authentication or
self.flowType == FlowType.Sign:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:

View File

@ -21,6 +21,7 @@ type FlowType* {.pure.} = enum
CreateCopyOfAKeycard = "CreateCopyOfAKeycard"
MigrateFromKeycardToApp = "MigrateFromKeycardToApp"
MigrateFromAppToKeycard = "MigrateFromAppToKeycard"
Sign = "Sign"
# For the following flows we don't run card syncing.
const FlowsWeShouldNotTryAKeycardSyncFor* = @[
@ -35,6 +36,7 @@ const FlowsWeShouldNotTryAKeycardSyncFor* = @[
FlowType.CreateCopyOfAKeycard,
FlowType.MigrateFromKeycardToApp,
FlowType.MigrateFromAppToKeycard,
FlowType.Sign,
]
const SIGNAL_SHARED_KEYCARD_MODULE_DISPLAY_POPUP* = "sharedKeycarModuleDisplayPopup"

View File

@ -33,6 +33,7 @@ type
initialized: bool
tmpLocalState: State # used when flow is run, until response arrives to determine next state appropriatelly
authenticationPopupIsAlreadyRunning: bool
runningFlow: FlowType # in general used to mark the global shared flow that is being running (`Authentication` or `Sign`)
proc newModule*[T](delegate: T,
uniqueIdentifier: string,
@ -53,6 +54,7 @@ proc newModule*[T](delegate: T,
networkService, privacyService, accountsService, walletAccountService, keychainService)
result.initialized = false
result.authenticationPopupIsAlreadyRunning = false
result.runningFlow = FlowType.General
method delete*[T](self: Module[T]) =
self.view.delete
@ -209,7 +211,8 @@ proc preStateActivities[T](self: Module[T], currFlowType: FlowType, nextStateTyp
let (_, flowEvent) = self.controller.getLastReceivedKeycardData()
self.controller.setCurrentKeycardStateToLocked(flowEvent.keyUid, flowEvent.instanceUID)
if currFlowType == FlowType.Authentication:
if currFlowType == FlowType.Authentication or
currFlowType == FlowType.Sign:
self.view.setLockedPropForKeyPairForProcessing(nextStateType == StateType.MaxPinRetriesReached)
if currFlowType == FlowType.UnlockKeycard:
@ -467,9 +470,8 @@ method prepareKeyPairForProcessing*[T](self: Module[T], keyUid: string, keycardU
self.view.setKeyPairForProcessing(item)
method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Paths: seq[string] = @[], txHash = "", forceFlow = false, returnToFlow = FlowType.General) =
## In case of `Authentication` if we're 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.
## In case of `Authentication` or `Sign` flow, if keyUid is provided, that keypair will be authenticated,
## otherwise the logged in profile will be authenticated.
if flowToRun == FlowType.General:
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
error "sm_cannot run an general flow"
@ -501,14 +503,14 @@ method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Paths
self.controller.runLoadAccountFlow()
return
if flowToRun == FlowType.Authentication:
self.controller.connectKeychainSignals()
self.runningFlow = flowToRun
if keyUid.len == 0 or keyUid == singletonInstance.userProfile.getKeyUid():
if singletonInstance.userProfile.getIsKeycardUser():
self.prepareKeyPairItemForAuthentication(singletonInstance.userProfile.getKeyUid())
self.tmpLocalState = newReadingKeycardState(flowToRun, nil)
self.controller.runAuthenticationFlow(singletonInstance.userProfile.getKeyUid(), bip44Paths)
return
if singletonInstance.userProfile.getUsingBiometricLogin():
self.controller.connectKeychainSignals()
self.controller.tryToObtainDataFromKeychain()
return
self.view.setCurrentState(newEnterPasswordState(flowToRun, nil))
@ -520,6 +522,36 @@ method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Paths
self.tmpLocalState = newReadingKeycardState(flowToRun, nil)
self.controller.runAuthenticationFlow(keyUid, bip44Paths)
return
if flowToRun == FlowType.Sign:
if bip44Paths.len == 0 or bip44Paths[0].len == 0:
error "sm_cannot path must be set when signing"
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
return
if txHash.len == 0:
error "sm_cannot txHash must be set when signing"
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
return
var finalKeyUid = keyUid
if keyUid.len == 0:
finalKeyUid = singletonInstance.userProfile.getKeyUid()
let keypair = self.controller.getKeypairByKeyUid(finalKeyUid)
if keypair.isNil:
error "sm_cannot resolve a keypair for signing"
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
return
if not keypair.migratedToKeycard():
error "sm_cannot sign flow is spcified only for keycard migrated keypairs"
self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
return
self.runningFlow = flowToRun
self.prepareKeyPairItemForAuthentication(keyUid)
self.tmpLocalState = newReadingKeycardState(flowToRun, nil)
self.controller.runSignFlow(keyUid, bip44Paths[0], txHash)
if finalKeyUid == singletonInstance.userProfile.getKeyUid() and
singletonInstance.userProfile.getUsingBiometricLogin():
self.controller.connectKeychainSignals()
self.controller.tryToObtainDataFromKeychain()
return
if flowToRun == FlowType.UnlockKeycard:
## since we can run unlock keycard flow from an already running flow, in order to avoid changing displayed keypair
## (locked keypair) we have to set keycard uid of a keycard used in the flow we're jumping from to `UnlockKeycard` flow.
@ -684,7 +716,7 @@ method keychainObtainedDataFailure*[T](self: Module[T], errorDescription: string
self.controller.readyToDisplayPopup()
return
if not currStateObj.isNil:
self.view.setCurrentState(newBiometricsPinFailedState(FlowType.Authentication, nil))
self.view.setCurrentState(newBiometricsPinFailedState(self.runningFlow, nil))
method keychainObtainedDataSuccess*[T](self: Module[T], data: string) =
let currStateObj = self.view.currentStateObj()
@ -706,4 +738,4 @@ method keychainObtainedDataSuccess*[T](self: Module[T], data: string) =
self.controller.setPin(data)
self.controller.enterKeycardPin(data)
else:
self.view.setCurrentState(newBiometricsPinInvalidState(FlowType.Authentication, nil))
self.view.setCurrentState(newBiometricsPinInvalidState(self.runningFlow, nil))

View File

@ -32,7 +32,12 @@ StatusStackModal {
required property bool requirementsCheckPending
signal joined(string airdropAddress, var sharedAddresses)
property var keypairSigningModel
signal prepareForSigning(string airdropAddress, var sharedAddresses)
signal joinCommunity()
signal signSharedAddressesForAllNonKeycardKeypairs()
signal signSharedAddressesForKeypair(string keyUid)
signal cancelMembershipRequest()
signal sharedAddressesUpdated(var sharedAddresses)
@ -43,21 +48,24 @@ StatusStackModal {
rightButtons: [d.shareButton, finishButton]
finishButton: StatusButton {
text: root.isInvitationPending ? qsTr("Cancel Membership Request")
: (root.accessType === Constants.communityChatOnRequestAccess
? qsTr("Share your addresses to join")
: qsTr("Join %1").arg(root.name) )
text: root.isInvitationPending ?
qsTr("Cancel Membership Request")
: root.accessType === Constants.communityChatOnRequestAccess?
qsTr("Prove ownership")
: qsTr("Join %1").arg(root.name)
type: root.isInvitationPending ? StatusBaseButton.Type.Danger
: StatusBaseButton.Type.Normal
icon.name: root.accessType === Constants.communityChatOnRequestAccess && !root.isInvitationPending ? Constants.authenticationIconByType[root.loginType] : ""
onClicked: {
if (root.isInvitationPending) {
root.cancelMembershipRequest()
} else {
root.joined(d.selectedAirdropAddress, d.selectedSharedAddresses)
root.close()
return
}
root.close()
root.prepareForSigning(d.selectedAirdropAddress, d.selectedSharedAddresses)
root.replace(sharedAddressesSigningPanelComponent)
}
}
@ -139,6 +147,27 @@ StatusStackModal {
}
}
Component {
id: sharedAddressesSigningPanelComponent
SharedAddressesSigningPanel {
keypairSigningModel: root.keypairSigningModel
onSignSharedAddressesForAllNonKeycardKeypairs: {
root.signSharedAddressesForAllNonKeycardKeypairs()
}
onSignSharedAddressesForKeypair: {
root.signSharedAddressesForKeypair(keyUid)
}
onJoinCommunity: {
root.joinCommunity()
root.close()
}
}
}
stackItems: [
StatusScrollView {
id: scrollView

View File

@ -32,6 +32,8 @@ StatusModal {
return qsTr("Factory reset a Keycard")
case Constants.keycardSharedFlow.authentication:
return qsTr("Authenticate")
case Constants.keycardSharedFlow.sign:
return qsTr("Signing")
case Constants.keycardSharedFlow.unlockKeycard:
return qsTr("Unlock Keycard")
case Constants.keycardSharedFlow.displayKeycardContent:

View File

@ -220,6 +220,27 @@ QtObject {
}
break
case Constants.keycardSharedFlow.sign:
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.readingKeycard:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.keycardInserted:
case Constants.keycardSharedState.wrongPin:
case Constants.keycardSharedState.wrongKeychainPin:
case Constants.keycardSharedState.biometricsReadyToSign:
case Constants.keycardSharedState.maxPinRetriesReached:
case Constants.keycardSharedState.maxPukRetriesReached:
case Constants.keycardSharedState.maxPairingSlotsReached:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.wrongKeycard:
case Constants.keycardSharedState.biometricsPinFailed:
case Constants.keycardSharedState.biometricsPinInvalid:
case Constants.keycardSharedState.enterPin:
return true
}
break
case Constants.keycardSharedFlow.unlockKeycard:
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
@ -429,6 +450,32 @@ QtObject {
}
break
case Constants.keycardSharedFlow.sign:
if (userProfile.usingBiometricLogin) {
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.enterPin:
case Constants.keycardSharedState.wrongPin:
return qsTr("Use biometrics")
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.keycardInserted:
case Constants.keycardSharedState.readingKeycard:
case Constants.keycardSharedState.biometricsReadyToSign:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.biometricsPinFailed:
case Constants.keycardSharedState.wrongKeycard:
case Constants.keycardSharedState.keycardEmpty:
return qsTr("Use PIN")
case Constants.keycardSharedState.biometricsPinInvalid:
return qsTr("Update PIN")
}
}
break
case Constants.keycardSharedFlow.unlockKeycard:
switch (root.sharedKeycardModule.currentState.stateType) {
@ -514,6 +561,19 @@ QtObject {
}
break
case Constants.keycardSharedFlow.sign:
if (userProfile.usingBiometricLogin) {
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.wrongKeycard:
case Constants.keycardSharedState.keycardEmpty:
return false
}
}
break
case Constants.keycardSharedFlow.unlockKeycard:
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.unlockKeycardOptions:
@ -805,6 +865,38 @@ QtObject {
}
break
case Constants.keycardSharedFlow.sign:
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.readingKeycard:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.keycardInserted:
case Constants.keycardSharedState.wrongPin:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.biometricsReadyToSign:
case Constants.keycardSharedState.wrongKeycard:
case Constants.keycardSharedState.enterPin:
return qsTr("Sign")
case Constants.keycardSharedState.keycardEmpty:
return qsTr("Done")
case Constants.keycardSharedState.wrongKeychainPin:
return qsTr("Update PIN & authenticate")
case Constants.keycardSharedState.biometricsPinFailed:
case Constants.keycardSharedState.biometricsPinInvalid:
return qsTr("Try biometrics again")
case Constants.keycardSharedState.maxPinRetriesReached:
case Constants.keycardSharedState.maxPukRetriesReached:
case Constants.keycardSharedState.maxPairingSlotsReached:
return qsTr("Unlock Keycard")
}
break
case Constants.keycardSharedFlow.unlockKeycard:
switch (root.sharedKeycardModule.currentState.stateType) {
@ -1178,6 +1270,20 @@ QtObject {
}
break
case Constants.keycardSharedFlow.sign:
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.wrongPin:
case Constants.keycardSharedState.wrongKeychainPin:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.wrongKeycard:
case Constants.keycardSharedState.enterPin:
return root.primaryButtonEnabled
}
break
case Constants.keycardSharedFlow.unlockKeycard:
switch (root.sharedKeycardModule.currentState.stateType) {
@ -1270,6 +1376,24 @@ QtObject {
}
}
break
case Constants.keycardSharedFlow.sign:
if (userProfile.usingBiometricLogin) {
switch (root.sharedKeycardModule.currentState.stateType) {
case Constants.keycardSharedState.pluginReader:
case Constants.keycardSharedState.insertKeycard:
case Constants.keycardSharedState.keycardInserted:
case Constants.keycardSharedState.readingKeycard:
case Constants.keycardSharedState.biometricsPinFailed:
case Constants.keycardSharedState.biometricsPinInvalid:
case Constants.keycardSharedState.biometricsReadyToSign:
case Constants.keycardSharedState.notKeycard:
case Constants.keycardSharedState.wrongKeycard:
return "touch-id"
}
}
break
}
return ""

View File

@ -48,6 +48,8 @@ Item {
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.importingFromKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.unlockingKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.copyingKeycard
readonly property bool authenticationOrSigning: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign
}
Timer {
@ -236,7 +238,7 @@ Item {
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard))
return true
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication &&
if (d.authenticationOrSigning &&
!!root.sharedKeycardModule.keyPairForProcessing &&
root.sharedKeycardModule.keyPairForProcessing.name !== "") {
if(root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
@ -429,7 +431,7 @@ Item {
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard))
return keyPairForProcessingComponent
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) {
if (d.authenticationOrSigning) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardInserted ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.insertKeycard ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.readingKeycard ||
@ -758,19 +760,19 @@ Item {
}
PropertyChanges {
target: image
pattern: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
pattern: d.authenticationOrSigning?
"" : Constants.keycardAnimations.strongError.pattern
source: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
source: d.authenticationOrSigning?
Style.png("keycard/plain-error") : ""
startImgIndexForTheFirstLoop: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
startImgIndexForTheFirstLoop: d.authenticationOrSigning?
0 : Constants.keycardAnimations.strongError.startImgIndexForTheFirstLoop
startImgIndexForOtherLoops: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
startImgIndexForOtherLoops: d.authenticationOrSigning?
0 : Constants.keycardAnimations.strongError.startImgIndexForOtherLoops
endImgIndex: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
endImgIndex: d.authenticationOrSigning?
0 : Constants.keycardAnimations.strongError.endImgIndex
duration: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
duration: d.authenticationOrSigning?
0 : Constants.keycardAnimations.strongError.duration
loops: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
loops: d.authenticationOrSigning?
-1 : Constants.keycardAnimations.strongError.loops
}
PropertyChanges {
@ -831,7 +833,7 @@ Item {
PropertyChanges {
target: message
text: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
if (d.authenticationOrSigning ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.renameKeycard ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.changeKeycardPin ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.changeKeycardPuk ||

View File

@ -40,6 +40,7 @@ Item {
Component.onCompleted: {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.factoryReset) {
timer.start()
}
@ -84,7 +85,8 @@ Item {
anchors.bottomMargin: Style.current.halfPadding
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
spacing: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication?
spacing: root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign?
Style.current.halfPadding : Style.current.padding
KeycardImage {
@ -127,7 +129,8 @@ Item {
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) {
root.sharedKeycardModule.setPin(pinInput)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication)
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign)
return
root.sharedKeycardModule.currentState.doSecondaryAction()
}
@ -174,7 +177,8 @@ Item {
return true
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) {
@ -193,7 +197,8 @@ Item {
return keyPairForProcessingComponent
}
}
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication) {
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.authentication ||
root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.sign) {
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.enterPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongPin ||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeychainPin) {

View File

@ -125,6 +125,7 @@ QtObject {
readonly property string createCopyOfAKeycard: "CreateCopyOfAKeycard"
readonly property string migrateFromKeycardToApp: "MigrateFromKeycardToApp"
readonly property string migrateFromAppToKeycard: "MigrateFromAppToKeycard"
readonly property string sign: "Sign"
}
readonly property QtObject keycardSharedState: QtObject {