mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-30 02:18:16 +00:00
feat(core/sdbackup): offer 2nd backup after dry-run
- WIP: some pystyle checks fails
This commit is contained in:
parent
6dc9c1dbeb
commit
e247b61949
@ -1,5 +1,5 @@
|
|||||||
from trezor.sdcard import SD_CARD_HOT_SWAPPABLE
|
|
||||||
from trezor import io, wire
|
from trezor import io, wire
|
||||||
|
from trezor.sdcard import SD_CARD_HOT_SWAPPABLE
|
||||||
from trezor.ui.layouts import confirm_action, show_error_and_raise
|
from trezor.ui.layouts import confirm_action, show_error_and_raise
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,55 +42,7 @@ async def recovery_process() -> Success:
|
|||||||
raise wire.ActionCancelled
|
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:
|
async def _continue_recovery_process() -> Success:
|
||||||
from trezor import utils
|
|
||||||
from trezor.errors import MnemonicError
|
from trezor.errors import MnemonicError
|
||||||
|
|
||||||
# gather the current recovery state from storage
|
# gather the current recovery state from storage
|
||||||
@ -109,8 +61,9 @@ async def _continue_recovery_process() -> Success:
|
|||||||
await _request_share_first_screen(word_count)
|
await _request_share_first_screen(word_count)
|
||||||
|
|
||||||
secret = None
|
secret = None
|
||||||
|
backup_medium = None
|
||||||
while secret is 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
|
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)
|
await layout.show_invalid_mnemonic(word_count)
|
||||||
|
|
||||||
assert backup_type is not None
|
assert backup_type is not None
|
||||||
|
assert backup_medium is not None
|
||||||
if dry_run:
|
if dry_run:
|
||||||
result = await _finish_recovery_dry_run(secret, backup_type)
|
result = await _finish_recovery_dry_run(secret, backup_type, backup_medium)
|
||||||
else:
|
else:
|
||||||
result = await _finish_recovery(secret, backup_type)
|
result = await _finish_recovery(secret, backup_type)
|
||||||
|
|
||||||
return result
|
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 import utils
|
||||||
from trezor.crypto.hashlib import sha256
|
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)
|
await layout.show_dry_run_result(result, is_slip39)
|
||||||
|
|
||||||
if result:
|
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")
|
return Success(message="The seed is valid and matches the one in the device")
|
||||||
else:
|
else:
|
||||||
raise wire.ProcessError("The seed does not match the one in the device")
|
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(
|
storage_device.store_mnemonic_secret(
|
||||||
secret, backup_type, needs_backup=False, no_backup=False
|
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()
|
identifier = storage_recovery.get_slip39_identifier()
|
||||||
exponent = storage_recovery.get_slip39_iteration_exponent()
|
exponent = storage_recovery.get_slip39_iteration_exponent()
|
||||||
if identifier is None or exponent is None:
|
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")
|
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]:
|
async def _process_words(words: str) -> tuple[bytes | None, BackupType]:
|
||||||
word_count = len(words.split(" "))
|
word_count = len(words.split(" "))
|
||||||
is_slip39 = backup_types.is_slip39_word_count(word_count)
|
is_slip39 = backup_types.is_slip39_word_count(word_count)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from storage.sd_seed_backup import BackupMedium
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.ui.layouts import confirm_action
|
from trezor.ui.layouts import confirm_action
|
||||||
from trezor.ui.layouts.recovery import ( # noqa: F401
|
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(
|
async def homescreen_dialog(
|
||||||
button_label: str,
|
button_label: str,
|
||||||
text: str,
|
text: str,
|
||||||
|
@ -4,7 +4,7 @@ import storage
|
|||||||
import storage.device as storage_device
|
import storage.device as storage_device
|
||||||
from trezor.crypto import slip39
|
from trezor.crypto import slip39
|
||||||
from trezor.enums import BackupType
|
from trezor.enums import BackupType
|
||||||
from trezor.wire import ProcessError, ActionCancelled
|
from trezor.wire import ActionCancelled, ProcessError
|
||||||
|
|
||||||
from . import layout
|
from . import layout
|
||||||
|
|
||||||
@ -118,8 +118,8 @@ async def _backup_mnemonic_or_share(
|
|||||||
group_index: int | None = None,
|
group_index: int | None = None,
|
||||||
groups_total: int | None = None,
|
groups_total: int | None = None,
|
||||||
):
|
):
|
||||||
from trezor import utils
|
|
||||||
from storage.sd_seed_backup import BackupMedium
|
from storage.sd_seed_backup import BackupMedium
|
||||||
|
from trezor import utils
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# let the user choose between Words/SDcard backup
|
# let the user choose between Words/SDcard backup
|
||||||
@ -139,14 +139,14 @@ async def _backup_mnemonic_or_share(
|
|||||||
group_index=group_index,
|
group_index=group_index,
|
||||||
groups_total=groups_total,
|
groups_total=groups_total,
|
||||||
)
|
)
|
||||||
break
|
return
|
||||||
else:
|
else:
|
||||||
# try to store seed on SD card
|
# try to store seed on SD card
|
||||||
from apps.management.sd_backup import sdcard_backup_seed
|
from apps.management.sd_backup import sdcard_backup_seed
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await sdcard_backup_seed(mnemonic, backup_type)
|
await sdcard_backup_seed(mnemonic, backup_type)
|
||||||
break
|
return
|
||||||
except ActionCancelled:
|
except ActionCancelled:
|
||||||
# there might have been a backup
|
# there might have been a backup
|
||||||
# TODO show guidance: Pick different card/choose words
|
# TODO show guidance: Pick different card/choose words
|
||||||
|
@ -160,7 +160,7 @@ async def show_and_confirm_mnemonic(
|
|||||||
share_index: int | None = None,
|
share_index: int | None = None,
|
||||||
shares_total: int | None = None,
|
shares_total: int | None = None,
|
||||||
group_index: 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:
|
) -> None:
|
||||||
words = mnemonic.split()
|
words = mnemonic.split()
|
||||||
while True:
|
while True:
|
||||||
|
@ -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)
|
return await choose_recovery_medium(is_slip39, dry_run)
|
||||||
|
|
||||||
|
|
||||||
async def choose_backup_medium(
|
async def choose_backup_medium(
|
||||||
share_index: int | None, group_index: int | None, recovery: bool = False
|
share_index: int | None, group_index: int | None, recovery: bool = False
|
||||||
) -> BackupMedium:
|
) -> BackupMedium:
|
||||||
@ -18,11 +19,12 @@ async def choose_backup_medium(
|
|||||||
return await choose_backup_medium(share_index, group_index)
|
return await choose_backup_medium(share_index, group_index)
|
||||||
|
|
||||||
|
|
||||||
async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType):
|
async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType) -> None:
|
||||||
from storage.sd_seed_backup import store_seed_on_sdcard, is_backup_present
|
from storage.sd_seed_backup import is_backup_present, store_seed_on_sdcard
|
||||||
from apps.common.sdcard import ensure_sdcard
|
|
||||||
from trezor.ui.layouts import confirm_action, show_success
|
from trezor.ui.layouts import confirm_action, show_success
|
||||||
|
|
||||||
|
from apps.common.sdcard import ensure_sdcard
|
||||||
|
|
||||||
await ensure_sdcard(ensure_filesystem=False)
|
await ensure_sdcard(ensure_filesystem=False)
|
||||||
if is_backup_present():
|
if is_backup_present():
|
||||||
await confirm_action(
|
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]:
|
async def sdcard_recover_seed() -> tuple[str | None, BackupType | None]:
|
||||||
from storage.sd_seed_backup import recover_seed_from_sdcard
|
from storage.sd_seed_backup import recover_seed_from_sdcard
|
||||||
|
|
||||||
from apps.common.sdcard import ensure_sdcard
|
from apps.common.sdcard import ensure_sdcard
|
||||||
|
|
||||||
await ensure_sdcard(ensure_filesystem=False)
|
await ensure_sdcard(ensure_filesystem=False)
|
||||||
|
@ -21,7 +21,7 @@ if utils.USE_SD_CARD:
|
|||||||
SDBACKUP_BLOCK_OFFSET = 130 # TODO arbitrary for now
|
SDBACKUP_BLOCK_OFFSET = 130 # TODO arbitrary for now
|
||||||
SDBACKUP_N_WRITINGS = 100 # TODO decide between offset/writings
|
SDBACKUP_N_WRITINGS = 100 # TODO decide between offset/writings
|
||||||
SDBACKUP_MAGIC = b"TRZM"
|
SDBACKUP_MAGIC = b"TRZM"
|
||||||
SDBACKUP_VERSION = b"0"
|
SDBACKUP_VERSION = 0
|
||||||
|
|
||||||
|
|
||||||
class BackupMedium(IntEnum):
|
class BackupMedium(IntEnum):
|
||||||
@ -131,7 +131,7 @@ HASH_LEN = const(32)
|
|||||||
def _encode_backup_block(mnemonic: bytes, backup_type: BackupType) -> bytes:
|
def _encode_backup_block(mnemonic: bytes, backup_type: BackupType) -> bytes:
|
||||||
ret = utils.empty_bytearray(SDCARD_BLOCK_SIZE_B)
|
ret = utils.empty_bytearray(SDCARD_BLOCK_SIZE_B)
|
||||||
ret.extend(SDBACKUP_MAGIC)
|
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"))
|
ret.extend(backup_type.to_bytes(BACKUPTYPE_LEN, "big"))
|
||||||
seed_len = len(mnemonic)
|
seed_len = len(mnemonic)
|
||||||
ret.extend(seed_len.to_bytes(SEEDLEN_LEN, "big"))
|
ret.extend(seed_len.to_bytes(SEEDLEN_LEN, "big"))
|
||||||
|
@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
SD_CARD_HOT_SWAPPABLE = False
|
SD_CARD_HOT_SWAPPABLE = False
|
||||||
|
|
||||||
|
|
||||||
def is_trz_card() -> bool:
|
def is_trz_card() -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -612,7 +612,9 @@ async def choose_backup_medium(
|
|||||||
return BackupMedium.Words
|
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
|
from storage.sd_seed_backup import BackupMedium
|
||||||
|
|
||||||
action = ""
|
action = ""
|
||||||
|
Loading…
Reference in New Issue
Block a user