mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-05 05:15:27 +00:00
feat(core/sdbackup): shamir backup and recovery
This commit is contained in:
parent
69372af267
commit
6dc9c1dbeb
@ -51,8 +51,8 @@
|
|||||||
// this is a fixed size and should not be changed
|
// this is a fixed size and should not be changed
|
||||||
#define SDCARD_BLOCK_SIZE (512)
|
#define SDCARD_BLOCK_SIZE (512)
|
||||||
// fixed offset for SD seed backup:
|
// fixed offset for SD seed backup:
|
||||||
// Maximal size for FAT16 + overhead + start offset (65525 + 552 + 63)
|
// Maximal size for FAT16 + overhead + start offset
|
||||||
#define SDCARD_BACKUP_BLOCK_START (66140)
|
#define SDCARD_BACKUP_BLOCK_START (65525 + 552 + 63)
|
||||||
|
|
||||||
void sdcard_init(void);
|
void sdcard_init(void);
|
||||||
secbool __wur sdcard_power_on(void);
|
secbool __wur sdcard_power_on(void);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from storage.sd_salt import SD_CARD_HOT_SWAPPABLE
|
from trezor.sdcard import SD_CARD_HOT_SWAPPABLE
|
||||||
from trezor import io, wire
|
from trezor import io, wire
|
||||||
from trezor.ui.layouts import confirm_action, show_error_and_raise
|
from trezor.ui.layouts import confirm_action, show_error_and_raise
|
||||||
|
|
||||||
@ -111,15 +111,17 @@ async def ensure_sdcard(
|
|||||||
fatfs = io.fatfs # local_cache_attribute
|
fatfs = io.fatfs # local_cache_attribute
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
try:
|
if not for_sd_backup:
|
||||||
with sdcard.filesystem(mounted=False):
|
# cards for backup must be formatted to keep the unallocated space after partition
|
||||||
fatfs.mount()
|
try:
|
||||||
except fatfs.NoFilesystem:
|
with sdcard.filesystem(mounted=False):
|
||||||
# card not formatted. proceed out of the except clause
|
fatfs.mount()
|
||||||
pass
|
except fatfs.NoFilesystem:
|
||||||
else:
|
# card not formatted. proceed out of the except clause
|
||||||
# no error when mounting
|
pass
|
||||||
return
|
else:
|
||||||
|
# no error when mounting
|
||||||
|
return
|
||||||
|
|
||||||
await _confirm_format_card()
|
await _confirm_format_card()
|
||||||
|
|
||||||
|
@ -42,25 +42,57 @@ async def recovery_process() -> Success:
|
|||||||
raise wire.ActionCancelled
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def _choose_backup_medium() -> BackupMedium:
|
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
|
from trezor import utils
|
||||||
|
|
||||||
if utils.USE_SD_CARD:
|
while True:
|
||||||
from apps.management.sd_backup import bip39_choose_backup_medium
|
backup_medium = BackupMedium.Words
|
||||||
|
if utils.USE_SD_CARD:
|
||||||
|
from apps.management.sd_backup import choose_recovery_medium
|
||||||
|
|
||||||
# ask the user for backup type (words/SD card)
|
backup_medium = await choose_recovery_medium(word_count in (20, 33), dry_run)
|
||||||
return await bip39_choose_backup_medium(recovery=True)
|
|
||||||
else:
|
if backup_medium == BackupMedium.Words:
|
||||||
return 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 import utils
|
||||||
from trezor.errors import MnemonicError
|
from trezor.errors import MnemonicError
|
||||||
|
|
||||||
# if utils.USE_SD_CARD:
|
|
||||||
from apps.management.sd_backup import sdcard_recover_seed
|
|
||||||
|
|
||||||
# gather the current recovery state from storage
|
# gather the current recovery state from storage
|
||||||
dry_run = storage_recovery.is_dry_run()
|
dry_run = storage_recovery.is_dry_run()
|
||||||
word_count, backup_type = recover.load_slip39_state()
|
word_count, backup_type = recover.load_slip39_state()
|
||||||
@ -77,43 +109,15 @@ async def _continue_recovery_process() -> Success:
|
|||||||
await _request_share_first_screen(word_count)
|
await _request_share_first_screen(word_count)
|
||||||
|
|
||||||
secret = None
|
secret = None
|
||||||
words = None
|
|
||||||
backup_medium = BackupMedium.Words
|
|
||||||
while secret is None:
|
while secret is None:
|
||||||
if is_first_step:
|
words, word_count = await _recover_mnemonic_or_share(
|
||||||
backup_medium = await _choose_backup_medium()
|
is_first_step, word_count, dry_run, backup_type
|
||||||
if utils.USE_SD_CARD and backup_medium == BackupMedium.SDCard:
|
)
|
||||||
# attempt to recover words from sd card
|
|
||||||
words, backup_type = await sdcard_recover_seed()
|
|
||||||
if words is None:
|
|
||||||
continue
|
|
||||||
word_count = len(words.split())
|
|
||||||
if word_count not in (12, 24):
|
|
||||||
await layout.show_recovery_warning(
|
|
||||||
"recovery", "Shamir not yet supported for SD"
|
|
||||||
)
|
|
||||||
raise wire.ProcessError("Attempt to recover Shamir from SD card.")
|
|
||||||
else:
|
|
||||||
# If we are starting recovery, ask for word count first...
|
|
||||||
# _request_word_count
|
|
||||||
# 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)
|
|
||||||
# ...and only then show the starting screen with word count.
|
|
||||||
await _request_share_first_screen(word_count)
|
|
||||||
assert word_count is not None
|
|
||||||
|
|
||||||
if backup_medium == BackupMedium.Words:
|
|
||||||
# ask for mnemonic words one by one
|
|
||||||
words = await layout.request_mnemonic(word_count, backup_type)
|
|
||||||
|
|
||||||
# if they were invalid or some checks failed we continue and request them again
|
# if they were invalid or some checks failed we continue and request them again
|
||||||
if not words:
|
if not words:
|
||||||
continue
|
continue
|
||||||
|
assert word_count is not None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
secret, backup_type = await _process_words(words)
|
secret, backup_type = await _process_words(words)
|
||||||
|
@ -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
|
from trezor.wire import ProcessError, ActionCancelled
|
||||||
|
|
||||||
from . import layout
|
from . import layout
|
||||||
|
|
||||||
@ -110,6 +110,57 @@ async def reset_device(msg: ResetDevice) -> Success:
|
|||||||
return Success(message="Initialized")
|
return Success(message="Initialized")
|
||||||
|
|
||||||
|
|
||||||
|
async def _backup_mnemonic_or_share(
|
||||||
|
mnemonic: bytes,
|
||||||
|
backup_type: BackupType,
|
||||||
|
share_index: int | None = None,
|
||||||
|
shares_total: int | None = None,
|
||||||
|
group_index: int | None = None,
|
||||||
|
groups_total: int | None = None,
|
||||||
|
):
|
||||||
|
from trezor import utils
|
||||||
|
from storage.sd_seed_backup import BackupMedium
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# let the user choose between Words/SDcard backup
|
||||||
|
backup_medium = BackupMedium.Words
|
||||||
|
if utils.USE_SD_CARD:
|
||||||
|
from apps.management.sd_backup import choose_backup_medium
|
||||||
|
|
||||||
|
backup_medium = await choose_backup_medium(share_index, group_index)
|
||||||
|
|
||||||
|
# proceed with backup
|
||||||
|
if backup_medium == BackupMedium.Words:
|
||||||
|
# show words
|
||||||
|
await layout.show_and_confirm_mnemonic(
|
||||||
|
mnemonic.decode(),
|
||||||
|
share_index=share_index,
|
||||||
|
shares_total=shares_total,
|
||||||
|
group_index=group_index,
|
||||||
|
groups_total=groups_total,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
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
|
||||||
|
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 _backup_bip39(mnemonic_secret: bytes):
|
||||||
|
await layout.show_backup_warning()
|
||||||
|
await _backup_mnemonic_or_share(mnemonic_secret, BAK_T_BIP39)
|
||||||
|
|
||||||
|
|
||||||
async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
|
async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
|
||||||
# get number of shares
|
# get number of shares
|
||||||
await layout.slip39_show_checklist(0, BAK_T_SLIP39_BASIC)
|
await layout.slip39_show_checklist(0, BAK_T_SLIP39_BASIC)
|
||||||
@ -133,9 +184,17 @@ async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
|
|||||||
encrypted_master_secret,
|
encrypted_master_secret,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
# show and confirm individual shares
|
# backup individual shares
|
||||||
await layout.slip39_show_checklist(2, BAK_T_SLIP39_BASIC)
|
await layout.slip39_show_checklist(2, BAK_T_SLIP39_BASIC)
|
||||||
await layout.slip39_basic_show_and_confirm_shares(mnemonics)
|
|
||||||
|
await layout.show_backup_warning(True)
|
||||||
|
for share_index, share in enumerate(mnemonics):
|
||||||
|
await _backup_mnemonic_or_share(
|
||||||
|
share.encode(),
|
||||||
|
BAK_T_SLIP39_BASIC,
|
||||||
|
share_index,
|
||||||
|
len(mnemonics),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
|
async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
|
||||||
@ -169,8 +228,18 @@ async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
|
|||||||
encrypted_master_secret,
|
encrypted_master_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
# show and confirm individual shares
|
# backup individual shares
|
||||||
await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
|
await layout.show_backup_warning(True)
|
||||||
|
for group_index, group in enumerate(mnemonics):
|
||||||
|
for share_index, share in enumerate(group):
|
||||||
|
await _backup_mnemonic_or_share(
|
||||||
|
share.encode(),
|
||||||
|
BAK_T_SLIP39_ADVANCED,
|
||||||
|
share_index,
|
||||||
|
len(group),
|
||||||
|
group_index,
|
||||||
|
len(mnemonics),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _validate_reset_device(msg: ResetDevice) -> None:
|
def _validate_reset_device(msg: ResetDevice) -> None:
|
||||||
@ -213,33 +282,10 @@ def _compute_secret_from_entropy(
|
|||||||
return secret
|
return secret
|
||||||
|
|
||||||
|
|
||||||
async def _backup_bip39_sdcard(mnemonic: bytes, backup_type: BackupType) -> None:
|
|
||||||
from apps.management.sd_backup import sdcard_backup_seed
|
|
||||||
|
|
||||||
backup_success: bool = await sdcard_backup_seed(mnemonic, backup_type)
|
|
||||||
if not backup_success:
|
|
||||||
raise ProcessError("SD Card backup could not be verified.")
|
|
||||||
|
|
||||||
|
|
||||||
async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
|
async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
|
||||||
from storage.sd_seed_backup import BackupMedium
|
|
||||||
from trezor import utils
|
|
||||||
|
|
||||||
if backup_type == BAK_T_SLIP39_BASIC:
|
if backup_type == BAK_T_SLIP39_BASIC:
|
||||||
await _backup_slip39_basic(mnemonic_secret)
|
await _backup_slip39_basic(mnemonic_secret)
|
||||||
elif backup_type == BAK_T_SLIP39_ADVANCED:
|
elif backup_type == BAK_T_SLIP39_ADVANCED:
|
||||||
await _backup_slip39_advanced(mnemonic_secret)
|
await _backup_slip39_advanced(mnemonic_secret)
|
||||||
else:
|
else:
|
||||||
if utils.USE_SD_CARD:
|
await _backup_bip39(mnemonic_secret)
|
||||||
from apps.management.sd_backup import bip39_choose_backup_medium
|
|
||||||
|
|
||||||
backup_medium = await bip39_choose_backup_medium()
|
|
||||||
else:
|
|
||||||
backup_medium = BackupMedium.Words
|
|
||||||
|
|
||||||
if backup_medium == BackupMedium.SDCard:
|
|
||||||
await _backup_bip39_sdcard(mnemonic_secret, backup_type)
|
|
||||||
elif backup_medium == BackupMedium.Words:
|
|
||||||
await layout.bip39_show_and_confirm_mnemonic(mnemonic_secret.decode())
|
|
||||||
else:
|
|
||||||
raise ProcessError("Invalid backup medium.")
|
|
||||||
|
@ -155,59 +155,18 @@ async def show_backup_success() -> None:
|
|||||||
await show_success_backup()
|
await show_success_backup()
|
||||||
|
|
||||||
|
|
||||||
# BIP39
|
async def show_and_confirm_mnemonic(
|
||||||
# ===
|
mnemonic: str,
|
||||||
|
share_index: int | None = None,
|
||||||
|
shares_total: int | None = None,
|
||||||
async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None:
|
group_index: int | None = None,
|
||||||
# warn user about mnemonic safety
|
groups_total: int | None = None, # TODO might be unused
|
||||||
await show_backup_warning()
|
) -> None:
|
||||||
|
|
||||||
words = mnemonic.split()
|
words = mnemonic.split()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# display paginated mnemonic on the screen
|
# display paginated mnemonic on the screen
|
||||||
await show_share_words(words)
|
await show_share_words(words, share_index, group_index)
|
||||||
|
|
||||||
# make the user confirm some words from the mnemonic
|
# make the user confirm some words from the mnemonic
|
||||||
if await _share_words_confirmed(None, words):
|
if await _share_words_confirmed(share_index, words, shares_total, group_index):
|
||||||
break # mnemonic is confirmed, go next
|
break # mnemonic is confirmed, go next
|
||||||
|
|
||||||
|
|
||||||
# SLIP39
|
|
||||||
# ===
|
|
||||||
|
|
||||||
|
|
||||||
async def slip39_basic_show_and_confirm_shares(shares: Sequence[str]) -> None:
|
|
||||||
# warn user about mnemonic safety
|
|
||||||
await show_backup_warning(True)
|
|
||||||
|
|
||||||
for index, share in enumerate(shares):
|
|
||||||
share_words = share.split(" ")
|
|
||||||
while True:
|
|
||||||
# display paginated share on the screen
|
|
||||||
await show_share_words(share_words, index)
|
|
||||||
|
|
||||||
# make the user confirm words from the share
|
|
||||||
if await _share_words_confirmed(index, share_words, len(shares)):
|
|
||||||
break # this share is confirmed, go to next one
|
|
||||||
|
|
||||||
|
|
||||||
async def slip39_advanced_show_and_confirm_shares(
|
|
||||||
shares: Sequence[Sequence[str]],
|
|
||||||
) -> None:
|
|
||||||
# warn user about mnemonic safety
|
|
||||||
await show_backup_warning(True)
|
|
||||||
|
|
||||||
for group_index, group in enumerate(shares):
|
|
||||||
for share_index, share in enumerate(group):
|
|
||||||
share_words = share.split(" ")
|
|
||||||
while True:
|
|
||||||
# display paginated share on the screen
|
|
||||||
await show_share_words(share_words, share_index, group_index)
|
|
||||||
|
|
||||||
# make the user confirm words from the share
|
|
||||||
if await _share_words_confirmed(
|
|
||||||
share_index, share_words, len(group), group_index
|
|
||||||
):
|
|
||||||
break # this share is confirmed, go to next one
|
|
||||||
|
@ -5,25 +5,46 @@ if TYPE_CHECKING:
|
|||||||
from trezor.enums import BackupType
|
from trezor.enums import BackupType
|
||||||
|
|
||||||
|
|
||||||
async def bip39_choose_backup_medium(recovery: bool = False) -> BackupMedium:
|
async def choose_recovery_medium(is_slip39: bool, dry_run: bool) -> BackupMedium:
|
||||||
# TODO this will be general, not only for BIP39
|
from trezor.ui.layouts import choose_recovery_medium
|
||||||
|
|
||||||
|
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:
|
||||||
from trezor.ui.layouts import choose_backup_medium
|
from trezor.ui.layouts import choose_backup_medium
|
||||||
|
|
||||||
return await choose_backup_medium(recovery)
|
return await choose_backup_medium(share_index, group_index)
|
||||||
|
|
||||||
|
|
||||||
async def sdcard_backup_seed(mnemonic_secret: bytes, bak_t: BackupType) -> bool:
|
async def sdcard_backup_seed(mnemonic_secret: bytes, backup_type: BackupType):
|
||||||
from storage.sd_seed_backup import store_seed_on_sdcard
|
from storage.sd_seed_backup import store_seed_on_sdcard, is_backup_present
|
||||||
|
|
||||||
from apps.common.sdcard import ensure_sdcard
|
from apps.common.sdcard import ensure_sdcard
|
||||||
|
from trezor.ui.layouts import confirm_action, show_success
|
||||||
|
|
||||||
|
await ensure_sdcard(ensure_filesystem=False)
|
||||||
|
if is_backup_present():
|
||||||
|
await confirm_action(
|
||||||
|
"warning_sdcard_backup_exists",
|
||||||
|
"Backup present",
|
||||||
|
action="There is already a Trezor backup on this card!",
|
||||||
|
description="Replace the backup?",
|
||||||
|
verb="Replace",
|
||||||
|
verb_cancel="Abort",
|
||||||
|
hold=True,
|
||||||
|
hold_danger=True,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
await ensure_sdcard(ensure_filesystem=True, for_sd_backup=True)
|
await ensure_sdcard(ensure_filesystem=True, for_sd_backup=True)
|
||||||
return store_seed_on_sdcard(mnemonic_secret, bak_t)
|
|
||||||
|
store_seed_on_sdcard(mnemonic_secret, backup_type)
|
||||||
|
|
||||||
|
await show_success("success_sdcard_backup", "Backup on SD card successful!")
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -13,7 +13,6 @@ if TYPE_CHECKING:
|
|||||||
if utils.USE_SD_CARD:
|
if utils.USE_SD_CARD:
|
||||||
fatfs = io.fatfs # global_import_cache
|
fatfs = io.fatfs # global_import_cache
|
||||||
|
|
||||||
SD_CARD_HOT_SWAPPABLE = False
|
|
||||||
SD_SALT_LEN_BYTES = const(32)
|
SD_SALT_LEN_BYTES = const(32)
|
||||||
_SD_SALT_AUTH_TAG_LEN_BYTES = const(16)
|
_SD_SALT_AUTH_TAG_LEN_BYTES = const(16)
|
||||||
|
|
||||||
|
@ -30,13 +30,12 @@ class BackupMedium(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
@with_filesystem
|
@with_filesystem
|
||||||
def store_seed_on_sdcard(mnemonic_secret: bytes, backup_type: BackupType) -> bool:
|
def store_seed_on_sdcard(mnemonic_secret: bytes, backup_type: BackupType):
|
||||||
_write_seed_unalloc(mnemonic_secret, backup_type)
|
_write_seed_unalloc(mnemonic_secret, backup_type)
|
||||||
if _verify_backup(mnemonic_secret, backup_type):
|
if _verify_backup(mnemonic_secret, backup_type):
|
||||||
_write_readme()
|
_write_readme()
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
raise ProcessError("SD card verification failed")
|
||||||
|
|
||||||
|
|
||||||
@with_sdcard
|
@with_sdcard
|
||||||
@ -44,6 +43,12 @@ def recover_seed_from_sdcard() -> tuple[bytes | None, BackupType | None]:
|
|||||||
return _read_seed_unalloc()
|
return _read_seed_unalloc()
|
||||||
|
|
||||||
|
|
||||||
|
@with_sdcard
|
||||||
|
def is_backup_present() -> bool:
|
||||||
|
decoded_mnemonic, decoded_backup_type = _read_seed_unalloc()
|
||||||
|
return decoded_mnemonic is not None and decoded_backup_type is not None
|
||||||
|
|
||||||
|
|
||||||
def _verify_backup(mnemonic_secret: bytes, backup_type: BackupType) -> bool:
|
def _verify_backup(mnemonic_secret: bytes, backup_type: BackupType) -> bool:
|
||||||
decoded_mnemonic, decoded_backup_type = _read_seed_unalloc()
|
decoded_mnemonic, decoded_backup_type = _read_seed_unalloc()
|
||||||
if decoded_mnemonic is None or decoded_backup_type is None:
|
if decoded_mnemonic is None or decoded_backup_type is None:
|
||||||
|
@ -25,6 +25,7 @@ if TYPE_CHECKING:
|
|||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
SD_CARD_HOT_SWAPPABLE = False
|
||||||
|
|
||||||
def is_trz_card() -> bool:
|
def is_trz_card() -> bool:
|
||||||
return True
|
return True
|
||||||
|
@ -577,23 +577,58 @@ async def show_success(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def choose_backup_medium(recovery: bool = False) -> BackupMedium:
|
async def choose_backup_medium(
|
||||||
# TODO what br type
|
share_index: int | None = None, group_index: int | None = None
|
||||||
|
) -> BackupMedium:
|
||||||
from storage.sd_seed_backup import BackupMedium
|
from storage.sd_seed_backup import BackupMedium
|
||||||
|
|
||||||
br_type = "br_type"
|
if share_index is None:
|
||||||
if recovery:
|
action = f"Recovery seed"
|
||||||
br_code: ButtonRequestType = ButtonRequestType.RecoveryHomepage
|
description = "You can backup your wallet using either SD card or word list."
|
||||||
description: str = "Do you have written words or SD card for recovery?"
|
elif group_index is None:
|
||||||
|
action = f"Recovery share #{share_index + 1}"
|
||||||
|
description = "You can backup your share using either SD card or word list."
|
||||||
else:
|
else:
|
||||||
br_code: ButtonRequestType = ButtonRequestType.ResetDevice
|
action = f"Group {group_index + 1} - share {share_index + 1}"
|
||||||
description: str = "You will be able to backup on the 2n medium later."
|
description = "You can backup your share using either SD card or word list."
|
||||||
|
br_type = "choose_backup_medium"
|
||||||
|
br_code: ButtonRequestType = ButtonRequestType.ResetDevice
|
||||||
result = await interact(
|
result = await interact(
|
||||||
RustLayout(
|
RustLayout(
|
||||||
trezorui2.confirm_action(
|
trezorui2.confirm_action(
|
||||||
title="Backup medium", # TODO naming convention (backup medium?)
|
title="Backup medium", # TODO naming convention
|
||||||
action="Choose backup medium.",
|
action=action,
|
||||||
|
description=description,
|
||||||
|
verb="SD card",
|
||||||
|
verb_cancel="Words",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is CONFIRMED:
|
||||||
|
return BackupMedium.SDCard
|
||||||
|
else:
|
||||||
|
return BackupMedium.Words
|
||||||
|
|
||||||
|
|
||||||
|
async def choose_recovery_medium(is_slip39: bool = False, dry_run: bool = False) -> BackupMedium:
|
||||||
|
from storage.sd_seed_backup import BackupMedium
|
||||||
|
|
||||||
|
action = ""
|
||||||
|
|
||||||
|
thing = "share" if is_slip39 else "wallet"
|
||||||
|
if dry_run:
|
||||||
|
description = f"You can check your {thing} using either SD card or word list."
|
||||||
|
else:
|
||||||
|
description = f"You can recover your {thing} using either SD card or word list."
|
||||||
|
br_type = "choose_recovery_medium"
|
||||||
|
br_code: ButtonRequestType = ButtonRequestType.ResetDevice
|
||||||
|
result = await interact(
|
||||||
|
RustLayout(
|
||||||
|
trezorui2.confirm_action(
|
||||||
|
title="Wallet recovery",
|
||||||
|
action=action,
|
||||||
description=description,
|
description=description,
|
||||||
verb="SD card",
|
verb="SD card",
|
||||||
verb_cancel="Words",
|
verb_cancel="Words",
|
||||||
|
@ -18,8 +18,7 @@ class TestStorageSdSeedBackup(unittest.TestCase):
|
|||||||
io.fatfs.mkfs(True)
|
io.fatfs.mkfs(True)
|
||||||
io.fatfs.mount()
|
io.fatfs.mount()
|
||||||
|
|
||||||
success = store_seed_on_sdcard(self.mnemonic, BackupType.Bip39)
|
store_seed_on_sdcard(self.mnemonic, BackupType.Bip39)
|
||||||
self.assertTrue(success)
|
|
||||||
|
|
||||||
restored_mnemonic, restored_backup_type = recover_seed_from_sdcard()
|
restored_mnemonic, restored_backup_type = recover_seed_from_sdcard()
|
||||||
self.assertEqual(restored_mnemonic, self.mnemonic)
|
self.assertEqual(restored_mnemonic, self.mnemonic)
|
||||||
@ -28,10 +27,9 @@ class TestStorageSdSeedBackup(unittest.TestCase):
|
|||||||
io.fatfs.unmount()
|
io.fatfs.unmount()
|
||||||
io.sdcard.power_off()
|
io.sdcard.power_off()
|
||||||
|
|
||||||
def test_backup_partlywipe_restore(self):
|
def test_backup_and_partlywipe_then_restore(self):
|
||||||
with sdcard.filesystem(mounted=True):
|
with sdcard.filesystem(mounted=True):
|
||||||
success = store_seed_on_sdcard(self.mnemonic, BackupType.Bip39)
|
store_seed_on_sdcard(self.mnemonic, BackupType.Bip39)
|
||||||
self.assertTrue(success)
|
|
||||||
|
|
||||||
# wipe half of the card, restore must succeed
|
# wipe half of the card, restore must succeed
|
||||||
block_buffer = bytearray(SDCARD_BLOCK_SIZE_B)
|
block_buffer = bytearray(SDCARD_BLOCK_SIZE_B)
|
||||||
|
Loading…
Reference in New Issue
Block a user