diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index b8ffed99f4..9cd8494155 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -648,6 +648,7 @@ static void _librust_qstrs(void) { MP_QSTR_show_homescreen; MP_QSTR_show_info; MP_QSTR_show_info_with_cancel; + MP_QSTR_show_instructions; MP_QSTR_show_lockscreen; MP_QSTR_show_mismatch; MP_QSTR_show_passphrase; diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index e6f65e8a9e..594d7e21be 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -1400,11 +1400,11 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?; - let show_info: bool = kwargs.get(Qstr::MP_QSTR_show_info)?.try_into()?; + let show_instructions: bool = kwargs.get(Qstr::MP_QSTR_show_instructions)?.try_into()?; let mut paragraphs = ParagraphVecShort::new(); paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, description)); - if show_info { + if show_instructions { paragraphs .add(Paragraph::new( &theme::TEXT_NORMAL, @@ -1979,7 +1979,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// button: str, /// recovery_type: RecoveryType, /// info_button: bool, # unused on TR - /// show_info: bool, + /// show_instructions: bool, /// ) -> LayoutObj[UiResult]: /// """Device recovery homescreen.""" Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(), diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 9f131813b9..be45d3644d 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -2064,6 +2064,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// button: str, /// recovery_type: RecoveryType, /// info_button: bool = False, + /// show_instructions: bool = False, # unused on TT /// ) -> LayoutObj[UiResult]: /// """Device recovery homescreen.""" Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(), diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 33675ae16b..a3f6db2a06 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -956,7 +956,7 @@ def confirm_recovery( button: str, recovery_type: RecoveryType, info_button: bool, # unused on TR - show_info: bool, + show_instructions: bool, ) -> LayoutObj[UiResult]: """Device recovery homescreen.""" @@ -1519,6 +1519,7 @@ def confirm_recovery( button: str, recovery_type: RecoveryType, info_button: bool = False, + show_instructions: bool = False, # unused on TT ) -> LayoutObj[UiResult]: """Device recovery homescreen.""" diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index f78bd38e7f..68ca529759 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -275,20 +275,18 @@ async def _request_share_first_screen( button_label, text, TR.recovery__word_count_template.format(word_count), - show_info=True, + show_instructions=True, ) else: # BIP-39 await layout.homescreen_dialog( TR.buttons__continue, TR.recovery__enter_backup, TR.recovery__word_count_template.format(word_count), - show_info=True, + show_instructions=True, ) async def _request_share_next_screen() -> None: - from trezor import strings - remaining = storage_recovery.fetch_slip39_remaining_shares() group_count = storage_recovery.get_slip39_group_count() if not remaining: @@ -296,25 +294,12 @@ async def _request_share_next_screen() -> None: raise RuntimeError if group_count > 1: - await layout.homescreen_dialog( - TR.buttons__enter, - TR.recovery__more_shares_needed, - remaining_shares_info=_get_remaining_groups_and_shares(), + await layout.enter_share( + remaining_shares_info=_get_remaining_groups_and_shares() ) else: - still_needed_shares = remaining[0] - already_entered_shares = len(storage_recovery_shares.fetch_group(0)) - overall_needed = still_needed_shares + already_entered_shares - # TODO: consider kwargs in format here - entered = TR.recovery__x_of_y_entered_template.format( - already_entered_shares, overall_needed - ) - needed = strings.format_plural( - TR.recovery__x_more_shares_needed_template_plural, - still_needed_shares, - TR.plurals__x_shares_needed, - ) - await layout.homescreen_dialog(TR.buttons__enter_share, entered, needed) + entered = len(storage_recovery_shares.fetch_group(0)) + await layout.enter_share(entered_remaining=(entered, remaining[0])) def _get_remaining_groups_and_shares() -> "RemainingSharesInfo": diff --git a/core/src/apps/management/recovery_device/layout.py b/core/src/apps/management/recovery_device/layout.py index 07115c83d2..8d00a9ead1 100644 --- a/core/src/apps/management/recovery_device/layout.py +++ b/core/src/apps/management/recovery_device/layout.py @@ -11,6 +11,8 @@ from trezor.ui.layouts.recovery import ( # noqa: F401 from apps.common import backup_types if TYPE_CHECKING: + from typing import Awaitable, Callable + from trezor.enums import BackupType # RemainingSharesInfo represents the data structure for remaining shares in SLIP-39 recovery: @@ -123,11 +125,52 @@ async def show_invalid_mnemonic(word_count: int) -> None: ) +def enter_share( + word_count: int | None = None, + entered_remaining: tuple[int, int] | None = None, + remaining_shares_info: RemainingSharesInfo | None = None, +) -> Awaitable[None]: + from trezor import strings + + show_instructions = False + + if word_count is not None: + # First-time entry. Show instructions and word count. + text = TR.recovery__enter_any_share + subtext = TR.recovery__word_count_template.format(word_count) + show_instructions = True + + elif entered_remaining is not None: + # Basic Shamir. There is only one group, we report entered/remaining count. + entered, remaining = entered_remaining + total = entered + remaining + text = TR.recovery__x_of_y_entered_template.format(entered, total) + subtext = strings.format_plural( + TR.recovery__x_more_shares_needed_template_plural, + remaining, + TR.plurals__x_shares_needed, + ) + + else: + # SuperShamir. We cannot easily show entered/remaining across groups, + # the caller provided an info_func that has the details. + text = TR.recovery__more_shares_needed + subtext = None + + return homescreen_dialog( + TR.buttons__enter_share, + text, + subtext, + show_instructions, + remaining_shares_info, + ) + + async def homescreen_dialog( button_label: str, text: str, subtext: str | None = None, - show_info: bool = False, + show_instructions: bool = False, remaining_shares_info: "RemainingSharesInfo | None" = None, ) -> None: import storage.recovery as storage_recovery @@ -141,7 +184,7 @@ async def homescreen_dialog( text, subtext, recovery_type, - show_info, + show_instructions, remaining_shares_info, ): raise RecoveryAborted diff --git a/core/src/trezor/ui/layouts/mercury/recovery.py b/core/src/trezor/ui/layouts/mercury/recovery.py index f7e4e628a1..095ee0dac0 100644 --- a/core/src/trezor/ui/layouts/mercury/recovery.py +++ b/core/src/trezor/ui/layouts/mercury/recovery.py @@ -102,12 +102,12 @@ async def continue_recovery( text: str, subtext: str | None, recovery_type: RecoveryType, - show_info: bool = False, + show_instructions: bool = False, remaining_shares_info: "RemainingSharesInfo | None" = None, ) -> bool: result = await interact( trezorui2.flow_continue_recovery( - first_screen=show_info, + first_screen=show_instructions, recovery_type=recovery_type, text=text, subtext=subtext, diff --git a/core/src/trezor/ui/layouts/tr/recovery.py b/core/src/trezor/ui/layouts/tr/recovery.py index 776df1a34a..b1c3fa02eb 100644 --- a/core/src/trezor/ui/layouts/tr/recovery.py +++ b/core/src/trezor/ui/layouts/tr/recovery.py @@ -106,7 +106,7 @@ async def continue_recovery( text: str, subtext: str | None, recovery_type: RecoveryType, - show_info: bool = False, + show_instructions: bool = False, remaining_shares_info: "RemainingSharesInfo | None" = None, # unused on TR ) -> bool: # TODO: implement info_func? @@ -117,7 +117,7 @@ async def continue_recovery( # Never showing info for dry-run, user already saw it and it is disturbing if recovery_type in (RecoveryType.DryRun, RecoveryType.UnlockRepeatedBackup): - show_info = False + show_instructions = False if subtext: text += f"\n\n{subtext}" @@ -128,7 +128,7 @@ async def continue_recovery( button=button_label, recovery_type=recovery_type, info_button=False, - show_info=show_info, # type: ignore [No parameter named "show_info"] + show_instructions=show_instructions, ) while True: result = await interact( diff --git a/core/src/trezor/ui/layouts/tt/recovery.py b/core/src/trezor/ui/layouts/tt/recovery.py index 79c4ffdab6..ca7e163bad 100644 --- a/core/src/trezor/ui/layouts/tt/recovery.py +++ b/core/src/trezor/ui/layouts/tt/recovery.py @@ -133,7 +133,7 @@ async def continue_recovery( text: str, subtext: str | None, recovery_type: RecoveryType, - show_info: bool = False, + show_instructions: bool = False, remaining_shares_info: "RemainingSharesInfo | None" = None, ) -> bool: from trezor.enums import RecoveryType