From e247b6194979c34f312e17feb14d5089d50560c7 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Thu, 14 Dec 2023 17:37:27 +0100 Subject: [PATCH] feat(core/sdbackup): offer 2nd backup after dry-run - WIP: some pystyle checks fails --- core/src/apps/common/sdcard.py | 2 +- .../management/recovery_device/homescreen.py | 131 +++++++++++------- .../apps/management/recovery_device/layout.py | 16 +++ .../apps/management/reset_device/__init__.py | 8 +- .../apps/management/reset_device/layout.py | 2 +- core/src/apps/management/sd_backup.py | 9 +- core/src/storage/sd_seed_backup.py | 4 +- core/src/trezor/sdcard.py | 1 + core/src/trezor/ui/layouts/tt/__init__.py | 4 +- 9 files changed, 113 insertions(+), 64 deletions(-) diff --git a/core/src/apps/common/sdcard.py b/core/src/apps/common/sdcard.py index d2de9d39d3..dfd7fb54ee 100644 --- a/core/src/apps/common/sdcard.py +++ b/core/src/apps/common/sdcard.py @@ -1,5 +1,5 @@ -from trezor.sdcard import SD_CARD_HOT_SWAPPABLE from trezor import io, wire +from trezor.sdcard import SD_CARD_HOT_SWAPPABLE from trezor.ui.layouts import confirm_action, show_error_and_raise diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index 80d7b272b5..b0203d6e11 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -42,55 +42,7 @@ async def recovery_process() -> Success: raise wire.ActionCancelled -async def _recover_mnemonic_or_share( - is_first_step: bool, - word_count: int | None, - dry_run: bool, - backup_type: BackupType | None, -) -> tuple[str | None, int | None]: - from trezor import utils - - while True: - backup_medium = BackupMedium.Words - if utils.USE_SD_CARD: - from apps.management.sd_backup import choose_recovery_medium - - backup_medium = await choose_recovery_medium(word_count in (20, 33), dry_run) - - if backup_medium == BackupMedium.Words: - if is_first_step: - # If we are starting recovery, ask for word count first... - # For TT, just continuing straight to word count keyboard - if utils.INTERNAL_MODEL == "T2B1": - await layout.homescreen_dialog( - "Continue", "Select the number of words in your backup." - ) - # ask for the number of words - word_count = await layout.request_word_count(dry_run) - await _request_share_first_screen(word_count) - words = await layout.request_mnemonic(word_count, backup_type) - return words, word_count - else: - # try to recover from SD card - from apps.management.sd_backup import sdcard_recover_seed - - try: - mnemonic, _ = await sdcard_recover_seed() # TODO backup type needed? - if mnemonic == None: - # TODO warn and repeat - pass - return mnemonic, len(mnemonic.split()) - except ActionCancelled: - # there might have been a backup - # TODO show guidance: Pick different card/choose words - pass - except Exception: - # generic exception, let the user choose again - pass - - async def _continue_recovery_process() -> Success: - from trezor import utils from trezor.errors import MnemonicError # gather the current recovery state from storage @@ -109,8 +61,9 @@ async def _continue_recovery_process() -> Success: await _request_share_first_screen(word_count) secret = None + backup_medium = None while secret is None: - words, word_count = await _recover_mnemonic_or_share( + words, word_count, backup_medium = await _recover_mnemonic_or_share( is_first_step, word_count, dry_run, backup_type ) @@ -129,15 +82,37 @@ async def _continue_recovery_process() -> Success: await layout.show_invalid_mnemonic(word_count) assert backup_type is not None + assert backup_medium is not None if dry_run: - result = await _finish_recovery_dry_run(secret, backup_type) + result = await _finish_recovery_dry_run(secret, backup_type, backup_medium) else: result = await _finish_recovery(secret, backup_type) return result -async def _finish_recovery_dry_run(secret: bytes, backup_type: BackupType) -> Success: +async def _offer_backup_on_another_medium( + secret: bytes, previous_medium: BackupMedium +) -> None: + try: + await layout._offer_backup_on_another_medium(previous_medium) + if previous_medium == BackupMedium.Words: + from trezor.enums import BackupType + + from apps.management.sd_backup import sdcard_backup_seed + + await sdcard_backup_seed(secret, BackupType.Bip39) + else: + from apps.management.reset_device.layout import show_and_confirm_mnemonic + + await show_and_confirm_mnemonic(secret.decode()) + except wire.ActionCancelled: + return + + +async def _finish_recovery_dry_run( + secret: bytes, backup_type: BackupType, backup_medium: BackupMedium +) -> Success: from trezor import utils from trezor.crypto.hashlib import sha256 @@ -168,6 +143,9 @@ async def _finish_recovery_dry_run(secret: bytes, backup_type: BackupType) -> Su await layout.show_dry_run_result(result, is_slip39) if result: + if utils.USE_SD_CARD and not backup_types.is_slip39_backup_type(backup_type): + # For BIP 39, we offer to create a redundant backup + await _offer_backup_on_another_medium(secret, backup_medium) return Success(message="The seed is valid and matches the one in the device") else: raise wire.ProcessError("The seed does not match the one in the device") @@ -183,7 +161,7 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success: storage_device.store_mnemonic_secret( secret, backup_type, needs_backup=False, no_backup=False ) - if backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced): + if backup_types.is_slip39_backup_type(backup_type): identifier = storage_recovery.get_slip39_identifier() exponent = storage_recovery.get_slip39_iteration_exponent() if identifier is None or exponent is None: @@ -198,6 +176,55 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success: return Success(message="Device recovered") +async def _recover_mnemonic_or_share( + is_first_step: bool, + word_count: int | None, + dry_run: bool, + backup_type: BackupType | None, +) -> tuple[str | None, int | None, BackupMedium | None]: + from trezor import utils + + while True: + backup_medium = BackupMedium.Words + if utils.USE_SD_CARD: + from apps.management.sd_backup import choose_recovery_medium + + backup_medium = await choose_recovery_medium( + backup_types.is_slip39_backup_type(backup_type), dry_run + ) + + if backup_medium == BackupMedium.Words: + if is_first_step: + # If we are starting recovery, ask for word count first... + # For TT, just continuing straight to word count keyboard + if utils.INTERNAL_MODEL == "T2B1": + await layout.homescreen_dialog( + "Continue", "Select the number of words in your backup." + ) + # ask for the number of words + word_count = await layout.request_word_count(dry_run) + await _request_share_first_screen(word_count) + words = await layout.request_mnemonic(word_count, backup_type) + return words, word_count, BackupMedium.Words + else: + # try to recover from SD card + from apps.management.sd_backup import sdcard_recover_seed + + try: + mnemonic, _ = await sdcard_recover_seed() # TODO backup type needed? + if mnemonic == None: + # TODO warn and repeat + pass + return mnemonic, len(mnemonic.split()), BackupMedium.SDCard + except wire.ActionCancelled: + # there might have been a backup + # TODO show guidance: Pick different card/choose words + pass + except Exception: + # generic exception, let the user choose again + pass + + async def _process_words(words: str) -> tuple[bytes | None, BackupType]: word_count = len(words.split(" ")) is_slip39 = backup_types.is_slip39_word_count(word_count) diff --git a/core/src/apps/management/recovery_device/layout.py b/core/src/apps/management/recovery_device/layout.py index 0bdf859c41..3bc030c9cf 100644 --- a/core/src/apps/management/recovery_device/layout.py +++ b/core/src/apps/management/recovery_device/layout.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from storage.sd_seed_backup import BackupMedium from trezor.enums import ButtonRequestType from trezor.ui.layouts import confirm_action from trezor.ui.layouts.recovery import ( # noqa: F401 @@ -118,6 +119,21 @@ async def show_invalid_mnemonic(word_count: int) -> None: ) +async def _offer_backup_on_another_medium(previous_medium: BackupMedium) -> None: + if previous_medium == BackupMedium.Words: + await confirm_action( + "backup_redundant_offer_sd_card", + "Do a 2nd backup", + action="Procced with a backup on SD card.", + ) + else: + await confirm_action( + "backup_redundant_offer_words", + "Do a 2nd backup", + action="Procced with a written backup.", + ) + + async def homescreen_dialog( button_label: str, text: str, diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py index 3669925852..f97a493cf5 100644 --- a/core/src/apps/management/reset_device/__init__.py +++ b/core/src/apps/management/reset_device/__init__.py @@ -4,7 +4,7 @@ import storage import storage.device as storage_device from trezor.crypto import slip39 from trezor.enums import BackupType -from trezor.wire import ProcessError, ActionCancelled +from trezor.wire import ActionCancelled, ProcessError from . import layout @@ -118,8 +118,8 @@ async def _backup_mnemonic_or_share( group_index: int | None = None, groups_total: int | None = None, ): - from trezor import utils from storage.sd_seed_backup import BackupMedium + from trezor import utils while True: # let the user choose between Words/SDcard backup @@ -139,14 +139,14 @@ async def _backup_mnemonic_or_share( group_index=group_index, groups_total=groups_total, ) - break + return else: # try to store seed on SD card from apps.management.sd_backup import sdcard_backup_seed try: await sdcard_backup_seed(mnemonic, backup_type) - break + return except ActionCancelled: # there might have been a backup # TODO show guidance: Pick different card/choose words diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py index 5b83c2f87c..38f4d47b04 100644 --- a/core/src/apps/management/reset_device/layout.py +++ b/core/src/apps/management/reset_device/layout.py @@ -160,7 +160,7 @@ async def show_and_confirm_mnemonic( share_index: int | None = None, shares_total: int | None = None, group_index: int | None = None, - groups_total: int | None = None, # TODO might be unused + groups_total: int | None = None, # TODO might be unused ) -> None: words = mnemonic.split() while True: diff --git a/core/src/apps/management/sd_backup.py b/core/src/apps/management/sd_backup.py index 76d6c3c809..5b9af746e8 100644 --- a/core/src/apps/management/sd_backup.py +++ b/core/src/apps/management/sd_backup.py @@ -10,6 +10,7 @@ async def choose_recovery_medium(is_slip39: bool, dry_run: bool) -> BackupMedium return await choose_recovery_medium(is_slip39, dry_run) + async def choose_backup_medium( share_index: int | None, group_index: int | None, recovery: bool = False ) -> BackupMedium: @@ -18,11 +19,12 @@ async def choose_backup_medium( return await choose_backup_medium(share_index, group_index) -async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType): - from storage.sd_seed_backup import store_seed_on_sdcard, is_backup_present - from apps.common.sdcard import ensure_sdcard +async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType) -> None: + from storage.sd_seed_backup import is_backup_present, store_seed_on_sdcard from trezor.ui.layouts import confirm_action, show_success + from apps.common.sdcard import ensure_sdcard + await ensure_sdcard(ensure_filesystem=False) if is_backup_present(): await confirm_action( @@ -45,6 +47,7 @@ async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType): async def sdcard_recover_seed() -> tuple[str | None, BackupType | None]: from storage.sd_seed_backup import recover_seed_from_sdcard + from apps.common.sdcard import ensure_sdcard await ensure_sdcard(ensure_filesystem=False) diff --git a/core/src/storage/sd_seed_backup.py b/core/src/storage/sd_seed_backup.py index ab5c4d1078..bebac022b1 100644 --- a/core/src/storage/sd_seed_backup.py +++ b/core/src/storage/sd_seed_backup.py @@ -21,7 +21,7 @@ if utils.USE_SD_CARD: SDBACKUP_BLOCK_OFFSET = 130 # TODO arbitrary for now SDBACKUP_N_WRITINGS = 100 # TODO decide between offset/writings SDBACKUP_MAGIC = b"TRZM" - SDBACKUP_VERSION = b"0" + SDBACKUP_VERSION = 0 class BackupMedium(IntEnum): @@ -131,7 +131,7 @@ HASH_LEN = const(32) def _encode_backup_block(mnemonic: bytes, backup_type: BackupType) -> bytes: ret = utils.empty_bytearray(SDCARD_BLOCK_SIZE_B) ret.extend(SDBACKUP_MAGIC) - ret.extend(SDBACKUP_VERSION) + ret.extend(SDBACKUP_VERSION.to_bytes(VERSION_LEN, "big")) ret.extend(backup_type.to_bytes(BACKUPTYPE_LEN, "big")) seed_len = len(mnemonic) ret.extend(seed_len.to_bytes(SEEDLEN_LEN, "big")) diff --git a/core/src/trezor/sdcard.py b/core/src/trezor/sdcard.py index 18f78daa94..be6c2047e3 100644 --- a/core/src/trezor/sdcard.py +++ b/core/src/trezor/sdcard.py @@ -27,6 +27,7 @@ if TYPE_CHECKING: SD_CARD_HOT_SWAPPABLE = False + def is_trz_card() -> bool: return True diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index de06112dea..9ab36b0b30 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -612,7 +612,9 @@ async def choose_backup_medium( return BackupMedium.Words -async def choose_recovery_medium(is_slip39: bool = False, dry_run: bool = False) -> BackupMedium: +async def choose_recovery_medium( + is_slip39: bool = False, dry_run: bool = False +) -> BackupMedium: from storage.sd_seed_backup import BackupMedium action = ""