refactor(core): move backup-related operations into BackupHandler
What changed, and why it matters
This commit is a straightforward internal code reorganization (refactor) for how the Trezor device displays wallet backup instructions. It introduces a 'BackupHandler' abstraction so that different backup methods can reuse the same display logic. There is no change to security behavior, no bug fix, and no indication of a vulnerability.
No security action required. Treat as normal refactoring during code review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change moves backup display orchestration into a new DisplayBackup class implementing a BackupHandler protocol. Functions like backup_seed, backup_slip39_custom, _backup_bip39, _backup_slip39_single/basic/advanced now receive a handler parameter and call handler.intro() and handler.backup() instead of directly calling layout helpers. The previous show_backup_intro() and display_mnemonics()/_display_share() flow is consolidated inside DisplayBackup. Logic for generating SLIP39 shares, confirming words, and storage operations is unchanged.
Changed components
core/src/apps/management/backup_device.pycore/src/apps/management/reset_device/__init__.pycore/src/apps/management/reset_device/layout.pyInspect captured patch +115 / −69
diff --git a/core/src/apps/management/backup_device.py b/core/src/apps/management/backup_device.py
index 7862f150..2b5a8a74 100644
--- a/core/src/apps/management/backup_device.py
+++ b/core/src/apps/management/backup_device.py
@@ -53,15 +53,26 @@ async def perform_backup(
backup.deactivate_repeated_backup()
storage_device.set_backed_up()
+ handler = layout.DisplayBackup()
if group_threshold is not None:
# Parameters provided from host side.
assert backup_types.is_slip39_backup_type(backup_type)
extendable = backup_types.is_extendable_backup_type(backup_type)
# Run the backup process directly.
- await backup_slip39_custom(mnemonic_secret, group_threshold, groups, extendable)
+ await backup_slip39_custom(
+ handler=handler,
+ encrypted_master_secret=mnemonic_secret,
+ group_threshold=group_threshold,
+ groups=groups,
+ extendable=extendable,
+ )
else:
# No parameters provided, allow the user to configure them on screen.
- await backup_seed(backup_type, mnemonic_secret)
+ await backup_seed(
+ handler=handler,
+ backup_type=backup_type,
+ mnemonic_secret=mnemonic_secret,
+ )
# If the backup was successful, clear the unfinished flag and show success.
diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py
index d1a544a7..b2b685e1 100644
--- a/core/src/apps/management/reset_device/__init__.py
+++ b/core/src/apps/management/reset_device/__init__.py
@@ -125,7 +125,11 @@ async def reset_device(msg: ResetDevice) -> Success:
# generate and display backup information for the master secret
if perform_backup:
- await backup_seed(backup_type, secret)
+ await backup_seed(
+ handler=layout.DisplayBackup(),
+ backup_type=backup_type,
+ mnemonic_secret=secret,
+ )
# write settings and master secret into storage
if msg.label is not None:
@@ -175,29 +179,34 @@ async def _entropy_check(secret: bytes) -> bool:
msg = await get_public_key(req, keychain=keychain)
-async def _backup_bip39(mnemonic: str) -> None:
+async def _backup_bip39(handler: layout.BackupHandler, mnemonic: str) -> None:
words = mnemonic.split()
- await layout.show_backup_intro(num_of_words=len(words))
- await layout.show_and_confirm_single_share(words)
+ await handler.intro(num_of_words=len(words))
+ await layout.show_and_confirm_single_share(handler, words)
async def _backup_slip39_single(
- encrypted_master_secret: bytes, extendable: bool
+ handler: layout.BackupHandler,
+ encrypted_master_secret: bytes,
+ extendable: bool,
) -> None:
mnemonics = _get_slip39_mnemonics(encrypted_master_secret, 1, ((1, 1),), extendable)
words = mnemonics[0][0].split()
# for a single 1-of-1 group, we use the same layouts as for BIP39
- await layout.show_backup_intro(num_of_words=len(words))
- await layout.show_and_confirm_single_share(words)
+ await handler.intro(num_of_words=len(words))
+ await layout.show_and_confirm_single_share(handler, words)
async def _backup_slip39_basic(
- encrypted_master_secret: bytes, num_of_words: int, extendable: bool
+ handler: layout.BackupHandler,
+ encrypted_master_secret: bytes,
+ num_of_words: int,
+ extendable: bool,
) -> None:
group_threshold = 1
- await layout.show_backup_intro()
+ await handler.intro()
# get number of shares
await layout.slip39_show_checklist(0, advanced=False)
@@ -218,13 +227,16 @@ async def _backup_slip39_basic(
await layout.slip39_show_checklist(
2, advanced=False, count=share_count, threshold=share_threshold
)
- await layout.slip39_basic_show_and_confirm_shares(mnemonics[0])
+ await layout.slip39_basic_show_and_confirm_shares(handler, mnemonics[0])
async def _backup_slip39_advanced(
- encrypted_master_secret: bytes, num_of_words: int, extendable: bool
+ handler: layout.BackupHandler,
+ encrypted_master_secret: bytes,
+ num_of_words: int,
+ extendable: bool,
) -> None:
- await layout.show_backup_intro()
+ await handler.intro()
# get number of groups
await layout.slip39_show_checklist(0, advanced=True)
@@ -249,10 +261,11 @@ async def _backup_slip39_advanced(
)
# show and confirm individual shares
- await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
+ await layout.slip39_advanced_show_and_confirm_shares(handler, mnemonics)
async def backup_slip39_custom(
+ handler: layout.BackupHandler,
encrypted_master_secret: bytes,
group_threshold: int,
groups: Sequence[tuple[int, int]],
@@ -260,7 +273,7 @@ async def backup_slip39_custom(
) -> None:
# show and confirm individual shares
if len(groups) == 1 and groups[0][0] == 1 and groups[0][1] == 1:
- await _backup_slip39_single(encrypted_master_secret, extendable)
+ await _backup_slip39_single(handler, encrypted_master_secret, extendable)
else:
mnemonics = _get_slip39_mnemonics(
encrypted_master_secret, group_threshold, groups, extendable
@@ -274,9 +287,9 @@ async def backup_slip39_custom(
verb=TR.buttons__continue,
)
if len(groups) == 1:
- await layout.slip39_basic_show_and_confirm_shares(mnemonics[0])
+ await layout.slip39_basic_show_and_confirm_shares(handler, mnemonics[0])
else:
- await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
+ await layout.slip39_advanced_show_and_confirm_shares(handler, mnemonics)
def _get_slip39_mnemonics(
@@ -338,17 +351,25 @@ def _compute_secret_from_entropy(
return secret
-async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
+async def backup_seed(
+ handler: layout.BackupHandler,
+ backup_type: BackupType,
+ mnemonic_secret: bytes,
+) -> None:
if backup_types.is_slip39_backup_type(backup_type):
num_of_words = backup_types.get_num_of_words_per_share(
backup_type, len(mnemonic_secret)
)
extendable = backup_types.is_extendable_backup_type(backup_type)
if backup_types.is_slip39_advanced_backup_type(backup_type):
- await _backup_slip39_advanced(mnemonic_secret, num_of_words, extendable)
+ await _backup_slip39_advanced(
+ handler, mnemonic_secret, num_of_words, extendable
+ )
elif backup_type == BAK_T_SLIP39_SINGLE_EXT:
- await _backup_slip39_single(mnemonic_secret, extendable)
+ await _backup_slip39_single(handler, mnemonic_secret, extendable)
else:
- await _backup_slip39_basic(mnemonic_secret, num_of_words, extendable)
+ await _backup_slip39_basic(
+ handler, mnemonic_secret, num_of_words, extendable
+ )
else:
- await _backup_bip39(mnemonic_secret.decode())
+ await _backup_bip39(handler, mnemonic_secret.decode())
diff --git a/core/src/apps/management/reset_device/layout.py b/core/src/apps/management/reset_device/layout.py
index 1e0bdeeb..5eb4371c 100644
--- a/core/src/apps/management/reset_device/layout.py
+++ b/core/src/apps/management/reset_device/layout.py
@@ -1,5 +1,5 @@
from micropython import const
-from typing import Any, Callable, Coroutine, Sequence
+from typing import TYPE_CHECKING, Iterable, Protocol, Sequence
from trezor.ui.layouts.reset import ( # noqa: F401
show_share_words,
@@ -58,6 +58,16 @@ class ShareInfo:
self.group_index = group_index
+if TYPE_CHECKING:
+
+ class BackupHandler(Protocol):
+ async def intro(self, num_of_words: int | None = None) -> None:
+ """Show introductory layout about the backup."""
+
+ async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
+ """Backup all the provided shares."""
+
+
async def _share_words_confirmed(share: ShareInfo) -> bool:
"""Shows initial dialog asking the user to select words, then presents
word selectors. Shows success popup if the user is done, failure if the confirmation
@@ -104,12 +114,6 @@ async def _do_confirm_share_words(
return True
-async def show_backup_intro(num_of_words: int | None = None) -> None:
- from trezor.ui.layouts.reset import show_intro_backup
-
- await show_intro_backup(num_of_words)
-
-
async def show_backup_success() -> None:
from trezor.ui.layouts.reset import show_success_backup
@@ -120,55 +124,65 @@ async def show_backup_success() -> None:
# ===
-async def show_and_confirm_single_share(words: Sequence[str]) -> None:
- handler = await display_mnemonics()
- await handler(ShareInfo(words=words, index=None))
+async def show_and_confirm_single_share(
+ handler: BackupHandler, words: Sequence[str]
+) -> None:
+ return await handler.backup((ShareInfo(words=words, index=None),))
# Complex setups: SLIP39, except 1-of-1
# ===
-async def slip39_basic_show_and_confirm_shares(shares: Sequence[str]) -> None:
- handler = await display_mnemonics()
- for index, share in enumerate(shares):
- info = ShareInfo(words=share.split(" "), index=index, num_of_shares=len(shares))
- await handler(info)
+async def slip39_basic_show_and_confirm_shares(
+ handler: BackupHandler, shares: Sequence[str]
+) -> None:
+ return await handler.backup(
+ ShareInfo(words=share.split(" "), index=index, num_of_shares=len(shares))
+ for index, share in enumerate(shares)
+ )
async def slip39_advanced_show_and_confirm_shares(
+ handler: BackupHandler,
shares: Sequence[Sequence[str]],
) -> None:
- handler = await display_mnemonics()
- for group_index, group in enumerate(shares):
- for share_index, share in enumerate(group):
- info = ShareInfo(
- words=share.split(" "),
- index=share_index,
- num_of_shares=len(group),
- group_index=group_index,
- )
- await handler(info)
-
-
-async def display_mnemonics() -> Callable[[ShareInfo], Coroutine[Any, Any, None]]:
- from trezor.ui.layouts.reset import show_warning_backup
-
- # warn user about mnemonic safety
- await show_warning_backup()
-
- return _display_share
-
-
-async def _display_share(share: ShareInfo) -> None:
- while True:
- # display paginated share on the screen
- await show_share_words(
- share_words=share.words,
- share_index=share.index,
- group_index=share.group_index,
+ return await handler.backup(
+ ShareInfo(
+ words=share.split(" "),
+ index=share_index,
+ num_of_shares=len(group),
+ group_index=group_index,
)
+ for group_index, group in enumerate(shares)
+ for share_index, share in enumerate(group)
+ )
+
+
+class DisplayBackup:
+
+ async def intro(self, num_of_words: int | None = None) -> None:
+ from trezor.ui.layouts.reset import show_intro_backup
+
+ # show backup information (`num_of_words` is unset for multi-share backups)
+ await show_intro_backup(num_of_words=num_of_words)
+
+ async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
+ from trezor.ui.layouts.reset import show_warning_backup
+
+ # warn user about mnemonic safety
+ await show_warning_backup()
+
+ # backup all shares
+ for share in iter_shares:
+ while True:
+ # display paginated share on the screen
+ await show_share_words(
+ share_words=share.words,
+ share_index=share.index,
+ group_index=share.group_index,
+ )
- # make the user confirm words from the share
- if await _share_words_confirmed(share):
- break # this share is confirmed, go to next one
+ # make the user confirm words from the share
+ if await _share_words_confirmed(share):
+ break # this share is confirmed, go to next one
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.