Skip to content

Commit

Permalink
feature(@desktop/keycard): import or restore a Keycard via a seed phrase
Browse files Browse the repository at this point in the history
Fixes: #7029
  • Loading branch information
saledjenic committed Jan 4, 2023
1 parent 8600ef3 commit b2cb263
Show file tree
Hide file tree
Showing 44 changed files with 588 additions and 34 deletions.
5 changes: 4 additions & 1 deletion src/app/modules/main/profile_section/keycard/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ method runCreateNewKeycardWithNewSeedPhrasePopup*(self: Module) =
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.SetupNewKeycardNewSeedPhrase)

method runImportOrRestoreViaSeedPhrasePopup*(self: Module) =
info "TODO: Import or restore via a seed phrase..."
self.createSharedKeycardModule()
if self.keycardSharedModule.isNil:
return
self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.SetupNewKeycardOldSeedPhrase)

method runImportFromKeycardToAppPopup*(self: Module) =
info "TODO: Import from Keycard to Status Desktop..."
Expand Down
15 changes: 14 additions & 1 deletion src/app/modules/shared_modules/keycard_popup/controller.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import chronicles, strutils, os
import chronicles, strutils, os, sequtils, sugar
import uuids
import io_interface

Expand Down Expand Up @@ -177,6 +177,9 @@ proc setContainsMetadata*(self: Controller, value: bool) =
proc setKeyPairForProcessing*(self: Controller, item: KeyPairItem) =
self.delegate.setKeyPairForProcessing(item)

proc prepareKeyPairForProcessing*(self: Controller, keyUid: string) =
self.delegate.prepareKeyPairForProcessing(keyUid)

proc getKeyPairForProcessing*(self: Controller): KeyPairItem =
return self.delegate.getKeyPairForProcessing()

Expand Down Expand Up @@ -516,6 +519,11 @@ proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAcco
return
return self.walletAccountService.fetchAccounts()

proc isKeyPairAlreadyAdded*(self: Controller, keyUid: string): bool =
let walletAccounts = self.getWalletAccounts()
let accountsForKeyUid = walletAccounts.filter(a => a.keyUid == keyUid)
return accountsForKeyUid.len > 0

proc getCurrencyBalanceForAddress*(self: Controller, address: string): float64 =
if not serviceApplicable(self.walletAccountService):
return
Expand All @@ -532,6 +540,11 @@ proc addMigratedKeyPair*(self: Controller, keyPair: KeyPairDto) =
proc getAddingMigratedKeypairSuccess*(self: Controller): bool =
return self.tmpAddingMigratedKeypairSuccess

proc getMigratedKeyPairByKeyUid*(self: Controller, keyUid: string): seq[KeyPairDto] =
if not serviceApplicable(self.walletAccountService):
return
return self.walletAccountService.getMigratedKeyPairByKeyUid(keyUid)

proc getAllMigratedKeyPairs*(self: Controller): seq[KeyPairDto] =
if not serviceApplicable(self.walletAccountService):
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ method executePreBackStateCommand*(self: CreatePinState, controller: Controller)
method executeCancelCommand*(self: CreatePinState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.ChangeKeycardPin:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

method getNextSecondaryState*(self: CreatePinState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.ChangeKeycardPin:
if controller.getPin().len == PINLengthForStatusApp:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ method resolveKeycardNextState*(self: CreatingAccountNewSeedPhraseState, keycard
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
return createState(StateType.CreatingAccountNewSeedPhraseSuccess, self.flowType, nil)
return createState(StateType.CreatingAccountNewSeedPhraseSuccess, self.flowType, nil)
return createState(StateType.CreatingAccountNewSeedPhraseFailure, self.flowType, nil)
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
type
CreatingAccountOldSeedPhraseState* = ref object of State
paths: seq[string]
addresses: seq[string]

proc newCreatingAccountOldSeedPhraseState*(flowType: FlowType, backState: State): CreatingAccountOldSeedPhraseState =
result = CreatingAccountOldSeedPhraseState()
result.setup(flowType, StateType.CreatingAccountOldSeedPhrase, backState)

proc delete*(self: CreatingAccountOldSeedPhraseState) =
self.State.delete

proc resolvePaths(self: CreatingAccountOldSeedPhraseState, controller: Controller) =
let kpForPRocessing = controller.getKeyPairForProcessing()
var i = 0
for account in kpForPRocessing.getAccountsModel().getItems():
account.setPath(PATH_WALLET_ROOT & "/" & $i)
self.paths.add(account.getPath())
i.inc

proc findIndexForPath(self: CreatingAccountOldSeedPhraseState, path: string): int =
var ind = -1
for p in self.paths:
ind.inc
if p == path:
return ind
return ind

proc resolveAddresses(self: CreatingAccountOldSeedPhraseState, controller: Controller, keycardEvent: KeycardEvent): bool =
if keycardEvent.generatedWalletAccounts.len != self.paths.len:
return false
let kpForPRocessing = controller.getKeyPairForProcessing()
for account in kpForPRocessing.getAccountsModel().getItems():
let index = self.findIndexForPath(account.getPath())
if index == -1:
## should never be here
return false
kpForPRocessing.setDerivedFrom(keycardEvent.masterKeyAddress)
account.setAddress(keycardEvent.generatedWalletAccounts[index].address)
account.setPubKey(keycardEvent.generatedWalletAccounts[index].publicKey)
self.addresses.add(keycardEvent.generatedWalletAccounts[index].address)
return true

proc addAccountsToWallet(self: CreatingAccountOldSeedPhraseState, controller: Controller): bool =
let kpForPRocessing = controller.getKeyPairForProcessing()
for account in kpForPRocessing.getAccountsModel().getItems():
if not controller.addWalletAccount(name = account.getName(),
address = account.getAddress(),
path = account.getPath(),
addressAccountIsDerivedFrom = kpForPRocessing.getDerivedFrom(),
publicKey = account.getPubKey(),
keyUid = kpForPRocessing.getKeyUid(),
accountType = if account.getPath() == PATH_DEFAULT_WALLET: SEED else: GENERATED,
color = account.getColor(),
emoji = account.getEmoji()):
return false
return true

proc doMigration(self: CreatingAccountOldSeedPhraseState, controller: Controller) =
let kpForPRocessing = controller.getKeyPairForProcessing()
var kpDto = KeyPairDto(keycardUid: controller.getKeycardUid(),
keycardName: kpForPRocessing.getName(),
keycardLocked: false,
accountsAddresses: self.addresses,
keyUid: kpForPRocessing.getKeyUid())
controller.addMigratedKeyPair(kpDto)

proc runStoreMetadataFlow(self: CreatingAccountOldSeedPhraseState, controller: Controller) =
let kpForPRocessing = controller.getKeyPairForProcessing()
controller.runStoreMetadataFlow(kpForPRocessing.getName(), controller.getPin(), self.paths)

method executePrePrimaryStateCommand*(self: CreatingAccountOldSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
self.resolvePaths(controller)
controller.runDeriveAccountFlow(bip44Paths = self.paths, controller.getPin())

method executePreSecondaryStateCommand*(self: CreatingAccountOldSeedPhraseState, controller: Controller) =
## Secondary action is called after each async action during migration process, in this case after `addMigratedKeyPair`.
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if controller.getAddingMigratedKeypairSuccess():
self.runStoreMetadataFlow(controller)

method getNextSecondaryState*(self: CreatingAccountOldSeedPhraseState, controller: Controller): State =
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if not controller.getAddingMigratedKeypairSuccess():
return createState(StateType.CreatingAccountOldSeedPhraseFailure, self.flowType, nil)

method resolveKeycardNextState*(self: CreatingAccountOldSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
let state = ensureReaderAndCardPresenceAndResolveNextState(self, keycardFlowType, keycardEvent, controller)
if not state.isNil:
return state
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if controller.getCurrentKeycardServiceFlow() == KCSFlowType.ExportPublic:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
if not self.resolveAddresses(controller, keycardEvent):
return createState(StateType.CreatingAccountOldSeedPhraseFailure, self.flowType, nil)
if not self.addAccountsToWallet(controller):
return createState(StateType.CreatingAccountOldSeedPhraseFailure, self.flowType, nil)
self.doMigration(controller)
return nil # returning nil, cause we need to remain in this state
if controller.getCurrentKeycardServiceFlow() == KCSFlowType.StoreMetadata:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
return createState(StateType.CreatingAccountOldSeedPhraseSuccess, self.flowType, nil)
return createState(StateType.CreatingAccountOldSeedPhraseFailure, self.flowType, nil)
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ proc delete*(self: EnterKeycardNameState) =
method getNextPrimaryState*(self: EnterKeycardNameState, controller: Controller): State =
if self.flowType == FlowType.RenameKeycard:
return createState(StateType.RenamingKeycard, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycardNewSeedPhrase:
return createState(StateType.ManageKeycardAccounts, self.flowType, self)
if self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
return createState(StateType.ManageKeycardAccounts, self.flowType, self)

method executeCancelCommand*(self: EnterKeycardNameState, controller: Controller) =
if self.flowType == FlowType.RenameKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase:
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ method getNextPrimaryState*(self: EnterPinState, controller: Controller): State
if self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase:
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
return createState(StateType.FactoryResetConfirmation, self.flowType, self)
if self.flowType == FlowType.CreateCopyOfAKeycard:
if isPredefinedKeycardDataFlagSet(controller.getKeycardData(), PredefinedKeycardData.CopyFromAKeycardPartDone):
Expand All @@ -24,6 +25,7 @@ method getNextPrimaryState*(self: EnterPinState, controller: Controller): State
method executePreSecondaryStateCommand*(self: EnterPinState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
Expand All @@ -41,6 +43,7 @@ method executeCancelCommand*(self: EnterPinState, controller: Controller) =
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.Authentication or
self.flowType == FlowType.DisplayKeycardContent or
self.flowType == FlowType.RenameKeycard or
Expand Down Expand Up @@ -118,6 +121,27 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorPIN:
controller.setRemainingAttempts(keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return createState(StateType.WrongPin, self.flowType, nil)
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.DisableSeedPhraseForUnlock, add = true))
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.DisableSeedPhraseForUnlock, add = true))
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.DisableSeedPhraseForUnlock, add = true))
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
return createState(StateType.PinVerified, self.flowType, nil)
if self.flowType == FlowType.Authentication:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
type
EnterSeedPhraseState* = ref object of State
verifiedSeedPhrase: bool
keyPairAlreadyMigrated: bool
keyPairAlreadyAdded: bool

proc newEnterSeedPhraseState*(flowType: FlowType, backState: State): EnterSeedPhraseState =
result = EnterSeedPhraseState()
result.setup(flowType, StateType.EnterSeedPhrase, backState)
result.verifiedSeedPhrase = false
result.keyPairAlreadyMigrated = false
result.keyPairAlreadyAdded = false

proc delete*(self: EnterSeedPhraseState) =
self.State.delete
Expand All @@ -18,6 +22,20 @@ method executePrePrimaryStateCommand*(self: EnterSeedPhraseState, controller: Co
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
else:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WrongSeedPhrase, add = true))
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase())
if self.verifiedSeedPhrase:
## should always be true, since it's not possible to do primary command otherwise (button is disabled on the UI)
let keyUid = controller.getKeyUidForSeedPhrase(controller.getSeedPhrase())
self.keyPairAlreadyMigrated = controller.getMigratedKeyPairByKeyUid(keyUid).len > 0
if self.keyPairAlreadyMigrated:
controller.prepareKeyPairForProcessing(keyUid)
return
self.keyPairAlreadyAdded = controller.isKeyPairAlreadyAdded(keyUid)
if self.keyPairAlreadyAdded:
controller.prepareKeyPairForProcessing(keyUid)
return
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
if self.flowType == FlowType.CreateCopyOfAKeycard:
self.verifiedSeedPhrase = controller.validSeedPhrase(controller.getSeedPhrase()) and
controller.getKeyUidForSeedPhrase(controller.getSeedPhrase()) == controller.getKeyPairForProcessing().getKeyUid()
Expand All @@ -41,9 +59,18 @@ method getNextPrimaryState*(self: EnterSeedPhraseState, controller: Controller):
if self.flowType == FlowType.CreateCopyOfAKeycard:
if not self.verifiedSeedPhrase:
return createState(StateType.WrongSeedPhrase, self.flowType, self.getBackState)
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if not self.verifiedSeedPhrase:
## we should never be here
return createState(StateType.WrongSeedPhrase, self.flowType, self.getBackState)
if self.keyPairAlreadyMigrated or self.keyPairAlreadyAdded:
## Maybe we should differ among these 2 states (keyPairAlreadyMigrated or keyPairAlreadyAdded)
## but we need to check that with designers.
return createState(StateType.SeedPhraseAlreadyInUse, self.flowType, self)

method executeCancelCommand*(self: EnterSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.UnlockKeycard or
self.flowType == FlowType.CreateCopyOfAKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)
Expand All @@ -57,6 +84,11 @@ method resolveKeycardNextState*(self: EnterSeedPhraseState, keycardFlowType: str
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
return createState(StateType.MigratingKeyPair, self.flowType, nil)
if self.flowType == FlowType.SetupNewKeycardOldSeedPhrase:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.CreatePin, self.flowType, nil)
if self.flowType == FlowType.UnlockKeycard:
if controller.getCurrentKeycardServiceFlow() == KCSFlowType.GetMetadata:
controller.setMetadataFromKeycard(keycardEvent.cardMetadata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ method executePrePrimaryStateCommand*(self: FactoryResetConfirmationDisplayMetad
controller.runGetAppInfoFlow(factoryReset = true)
elif self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.HideKeyPair, add = true))
controller.runGetAppInfoFlow(factoryReset = true)
Expand All @@ -21,6 +22,7 @@ method executeCancelCommand*(self: FactoryResetConfirmationDisplayMetadataState,
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ method executePrePrimaryStateCommand*(self: FactoryResetConfirmationState, contr
controller.runGetAppInfoFlow(factoryReset = true)
elif self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:
controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.HideKeyPair, add = true))
controller.runGetAppInfoFlow(factoryReset = true)
Expand All @@ -21,6 +22,7 @@ method executeCancelCommand*(self: FactoryResetConfirmationState, controller: Co
if self.flowType == FlowType.FactoryReset or
self.flowType == FlowType.SetupNewKeycard or
self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or
self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or
self.flowType == FlowType.CreateCopyOfAKeycard:
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false)

Expand Down
Loading

0 comments on commit b2cb263

Please sign in to comment.