From bc502287fc51d617362fabb60ff379f0e21e31d3 Mon Sep 17 00:00:00 2001 From: grdddj Date: Thu, 29 Jun 2023 17:26:51 +0200 Subject: [PATCH] feat(core): new design of recovery dialogs [no changelog] --- core/embed/rust/librust_qstr.h | 1 + core/embed/rust/src/ui/model_tr/layout.rs | 104 ++-- core/embed/rust/src/ui/model_tt/layout.rs | 6 +- core/mocks/generated/trezorui2.pyi | 135 +---- .../management/recovery_device/__init__.py | 4 +- .../management/recovery_device/homescreen.py | 30 +- .../apps/management/recovery_device/layout.py | 26 +- .../management/recovery_device/recover.py | 14 +- core/src/storage/recovery.py | 3 +- core/src/trezor/ui/layouts/tr/__init__.py | 65 +- core/src/trezor/ui/layouts/tr/recovery.py | 5 +- core/src/trezor/ui/layouts/tr/reset.py | 6 +- core/src/trezor/ui/layouts/tt_v2/__init__.py | 14 - core/src/trezor/ui/layouts/tt_v2/recovery.py | 1 + tests/click_tests/recovery.py | 54 +- tests/click_tests/test_autolock.py | 6 +- tests/common.py | 11 +- .../reset_recovery/test_recovery_bip39_t2.py | 2 - .../test_recovery_slip39_basic.py | 2 - tests/input_flows.py | 555 +++++------------- .../test_shamir_persistence.py | 4 +- tests/upgrade_tests/test_firmware_upgrades.py | 15 +- 22 files changed, 387 insertions(+), 676 deletions(-) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 3e0f987975..7fcddbeb56 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -146,6 +146,7 @@ static void _librust_qstrs(void) { MP_QSTR_value; MP_QSTR_verb; MP_QSTR_verb_cancel; + MP_QSTR_warning; MP_QSTR_words; MP_QSTR_wrong_pin; MP_QSTR_xpubs; diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index c8227e5aa4..363d2f3852 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -24,8 +24,7 @@ use crate::{ }, ComponentExt, FormattedText, LineBreaking, Timeout, }, - display, - geometry::{self, Alignment}, + display, geometry, layout::{ obj::{ComponentMsgObj, LayoutObj}, result::{CANCELLED, CONFIRMED, INFO}, @@ -805,33 +804,6 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - - let get_page = move |page_index| { - assert!(page_index == 0); - - let btn_layout = ButtonLayout::none_armed_none(button.clone()); - let btn_actions = ButtonActions::none_confirm_none(); - let ops = OpTextLayout::::new(theme::TEXT_NORMAL) - .alignment(Alignment::Center) - .text_bold(title.clone()) - .newline() - .text_normal(description.clone()); - let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center); - Page::new(btn_layout, btn_actions, formatted) - }; - let pages = FlowPages::new(get_page, 1); - - let obj = LayoutObj::new(Flow::new(pages))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?; @@ -914,7 +886,7 @@ extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs: }; let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text); - let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center); + let formatted = FormattedText::new(ops).vertically_centered(); Page::new(btn_layout, btn_actions, formatted) }; @@ -990,6 +962,35 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let warning: StrBuffer = kwargs.get(Qstr::MP_QSTR_warning)?.try_into()?; + let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + + let get_page = move |page_index| { + assert!(page_index == 0); + + let btn_layout = ButtonLayout::none_armed_none(button.clone()); + let btn_actions = ButtonActions::none_confirm_none(); + let mut ops = OpTextLayout::::new(theme::TEXT_NORMAL); + ops = ops.alignment(geometry::Alignment::Center); + if !warning.is_empty() { + ops = ops.text_bold(warning.clone()).newline().newline(); + } + if !description.is_empty() { + ops = ops.text_normal(description.clone()); + } + let formatted = FormattedText::new(ops).vertically_centered(); + Page::new(btn_layout, btn_actions, formatted) + }; + let pages = FlowPages::new(get_page, 1); + let obj = LayoutObj::new(Flow::new(pages))?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; @@ -1267,16 +1268,32 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?; + let show_info: bool = kwargs.get(Qstr::MP_QSTR_show_info)?.try_into()?; - let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]); + let mut paragraphs = ParagraphVecShort::new(); + paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, description)); + if show_info { + let first = "You'll only have to select the first 2-3 letters of each word."; + let second = + "Position of the cursor will change between entries for enhanced security."; + paragraphs + .add(Paragraph::new(&theme::TEXT_NORMAL, first.into())) + .add(Paragraph::new(&theme::TEXT_NORMAL, second.into())); + } let title = if dry_run { - "SEED CHECK" + "BACKUP CHECK" } else { - "WALLET RECOVERY" + "RECOVER WALLET" }; - content_in_button_page(title.into(), paragraphs, button, Some("".into()), false) + content_in_button_page( + title.into(), + paragraphs.into_paragraphs(), + button, + Some("".into()), + false, + ) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } @@ -1561,15 +1578,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Show user how to interact with the device.""" Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(), - /// def show_error( - /// *, - /// title: str, - /// description: str, - /// button: str, - /// ) -> object: - /// """Show a popup with text centered both vertically and horizontally. With just a middle button.""" - Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(), - /// def confirm_modify_fee( /// *, /// title: str, # ignored @@ -1603,6 +1611,15 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Show multiple texts, each on its own page.""" Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(), + /// def show_warning( + /// *, + /// button: str, + /// warning: str, + /// description: str, + /// ) -> object: + /// """Warning modal with middle button and centered text.""" + Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(), + /// def show_info( /// *, /// title: str, @@ -1717,6 +1734,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// button: str, /// dry_run: bool, /// info_button: bool, # unused on TR + /// show_info: bool, /// ) -> object: /// """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 009401d6e2..f9985baadd 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1399,7 +1399,7 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut .with_spacing(theme::RECOVERY_SPACING); let notification = if dry_run { - "SEED CHECK" + "BACKUP CHECK" } else { "RECOVERY MODE" }; @@ -1427,9 +1427,9 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu let block = move |_args: &[Obj], kwargs: &Map| { let dry_run: bool = kwargs.get(Qstr::MP_QSTR_dry_run)?.try_into()?; let title = if dry_run { - "SEED CHECK" + "BACKUP CHECK" } else { - "WALLET RECOVERY" + "RECOVER WALLET" }; let paragraphs = Paragraphs::new( diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index ae5c4d4fc4..8df393bd3d 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -1,19 +1,17 @@ from typing import * + CONFIRMED: object CANCELLED: object INFO: object - # rust/src/ui/model_tr/layout.rs def disable_animation(disable: bool) -> None: """Disable animations, debug builds only.""" - # rust/src/ui/model_tr/layout.rs def toif_info(data: bytes) -> tuple[int, int, bool]: """Get TOIF image dimensions and format (width: int, height: int, is_grayscale: bool).""" - # rust/src/ui/model_tr/layout.rs def confirm_action( *, @@ -28,7 +26,6 @@ def confirm_action( ) -> object: """Confirm action.""" - # rust/src/ui/model_tr/layout.rs def confirm_blob( *, @@ -42,7 +39,6 @@ def confirm_blob( ) -> object: """Confirm byte sequence data.""" - # rust/src/ui/model_tr/layout.rs def confirm_address( *, @@ -53,7 +49,6 @@ def confirm_address( ) -> object: """Confirm address.""" - # rust/src/ui/model_tr/layout.rs def confirm_properties( *, @@ -65,7 +60,6 @@ def confirm_properties( the value is to be rendered as binary with monospace font, False otherwise. This only concerns the text style, you need to decode the value to UTF-8 in python.""" - # rust/src/ui/model_tr/layout.rs def confirm_reset_device( *, @@ -74,12 +68,10 @@ def confirm_reset_device( ) -> object: """Confirm TOS before device setup.""" - # rust/src/ui/model_tr/layout.rs def confirm_backup() -> object: """Strongly recommend user to do backup.""" - # rust/src/ui/model_tr/layout.rs def show_address_details( *, @@ -91,7 +83,6 @@ def show_address_details( ) -> object: """Show address details - QR code, account, path, cosigner xpubs.""" - # rust/src/ui/model_tr/layout.rs def confirm_value( *, @@ -103,7 +94,6 @@ def confirm_value( ) -> object: """Confirm value.""" - # rust/src/ui/model_tr/layout.rs def confirm_joint_total( *, @@ -112,7 +102,6 @@ def confirm_joint_total( ) -> object: """Confirm total if there are external inputs.""" - # rust/src/ui/model_tr/layout.rs def confirm_modify_output( *, @@ -123,7 +112,6 @@ def confirm_modify_output( ) -> object: """Decrease or increase amount for given address.""" - # rust/src/ui/model_tr/layout.rs def confirm_output( *, @@ -135,7 +123,6 @@ def confirm_output( ) -> object: """Confirm output.""" - # rust/src/ui/model_tr/layout.rs def confirm_total( *, @@ -148,12 +135,10 @@ def confirm_total( ) -> object: """Confirm summary of a transaction.""" - # rust/src/ui/model_tr/layout.rs def tutorial() -> object: """Show user how to interact with the device.""" - # rust/src/ui/model_tr/layout.rs def show_error( *, @@ -163,7 +148,6 @@ def show_error( ) -> object: """Show a popup with text centered both vertically and horizontally. With just a middle button.""" - # rust/src/ui/model_tr/layout.rs def confirm_modify_fee( *, @@ -175,7 +159,6 @@ def confirm_modify_fee( ) -> object: """Decrease or increase transaction fee.""" - # rust/src/ui/model_tr/layout.rs def confirm_fido( *, @@ -188,7 +171,6 @@ def confirm_fido( Returns page index in case of confirmation and CANCELLED otherwise. """ - # rust/src/ui/model_tr/layout.rs def multiple_pages_texts( *, @@ -198,6 +180,13 @@ def multiple_pages_texts( ) -> object: """Show multiple texts, each on its own page.""" +def show_warning( + *, + button: str, + warning: str, + description: str, +) -> object: + """Warning modal with middle button and centered text.""" # rust/src/ui/model_tr/layout.rs def show_info( @@ -208,17 +197,14 @@ def show_info( ) -> object: """Info modal.""" - # rust/src/ui/model_tr/layout.rs def show_passphrase() -> object: """Show passphrase on host dialog.""" - # rust/src/ui/model_tr/layout.rs def show_mismatch() -> object: """Warning modal, receiving address mismatch.""" - # rust/src/ui/model_tr/layout.rs def confirm_with_info( *, @@ -230,7 +216,6 @@ def confirm_with_info( """Confirm given items but with third button. Always single page without scrolling.""" - # rust/src/ui/model_tr/layout.rs def confirm_coinjoin( *, @@ -239,7 +224,6 @@ def confirm_coinjoin( ) -> object: """Confirm coinjoin authorization.""" - # rust/src/ui/model_tr/layout.rs def request_pin( *, @@ -250,7 +234,6 @@ def request_pin( ) -> str | object: """Request pin on device.""" - # rust/src/ui/model_tr/layout.rs def request_passphrase( *, @@ -259,7 +242,6 @@ def request_passphrase( ) -> str | object: """Get passphrase.""" - # rust/src/ui/model_tr/layout.rs def request_bip39( *, @@ -267,14 +249,12 @@ def request_bip39( ) -> str: """Get recovery word for BIP39.""" - # rust/src/ui/model_tr/layout.rs def request_slip39( *, prompt: str, ) -> str: - """SLIP39 word input keyboard.""" - + """SLIP39 word input keyboard.""" # rust/src/ui/model_tr/layout.rs def select_word( @@ -283,9 +263,8 @@ def select_word( description: str, words: Iterable[str], ) -> int: - """Select mnemonic word from three possibilities - seed check after backup. The - iterable must be of exact size. Returns index in range `0..3`.""" - + """Select mnemonic word from three possibilities - seed check after backup. The + iterable must be of exact size. Returns index in range `0..3`.""" # rust/src/ui/model_tr/layout.rs def show_share_words( @@ -294,7 +273,6 @@ def show_share_words( ) -> object: """Shows a backup seed.""" - # rust/src/ui/model_tr/layout.rs def request_number( *, @@ -304,8 +282,7 @@ def request_number( max_count: int, description: Callable[[int], str] | None = None, # unused on TR ) -> object: - """Number input with + and - buttons, description, and info button.""" - + """Number input with + and - buttons, description, and info button.""" # rust/src/ui/model_tr/layout.rs def show_checklist( @@ -315,9 +292,8 @@ def show_checklist( active: int, button: str, ) -> object: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - + """Checklist of backup steps. Active index is highlighted, previous items have check + mark next to them.""" # rust/src/ui/model_tr/layout.rs def confirm_recovery( @@ -327,25 +303,23 @@ def confirm_recovery( button: str, dry_run: bool, info_button: bool, # unused on TR + show_info: bool, ) -> object: - """Device recovery homescreen.""" - + """Device recovery homescreen.""" # rust/src/ui/model_tr/layout.rs def select_word_count( *, dry_run: bool, # unused on TR ) -> int | str: # TR returns str - """Select mnemonic word count from (12, 18, 20, 24, 33).""" - + """Select mnemonic word count from (12, 18, 20, 24, 33).""" # rust/src/ui/model_tr/layout.rs def show_group_share_success( *, lines: Iterable[str], ) -> int: - """Shown after successfully finishing a group.""" - + """Shown after successfully finishing a group.""" # rust/src/ui/model_tr/layout.rs def show_progress( @@ -354,10 +328,9 @@ def show_progress( indeterminate: bool = False, description: str = "", ) -> object: - """Show progress loader. Please note that the number of lines reserved on screen for - description is determined at construction time. If you want multiline descriptions - make sure the initial description has at least that amount of lines.""" - + """Show progress loader. Please note that the number of lines reserved on screen for + description is determined at construction time. If you want multiline descriptions + make sure the initial description has at least that amount of lines.""" # rust/src/ui/model_tr/layout.rs def show_progress_coinjoin( @@ -367,9 +340,8 @@ def show_progress_coinjoin( time_ms: int = 0, skip_first_paint: bool = False, ) -> object: - """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - time_ms timeout is passed.""" - + """Show progress loader for coinjoin. Returns CANCELLED after a specified time when + time_ms timeout is passed.""" # rust/src/ui/model_tr/layout.rs def show_homescreen( @@ -382,7 +354,6 @@ def show_homescreen( ) -> CANCELLED: """Idle homescreen.""" - # rust/src/ui/model_tr/layout.rs def show_lockscreen( *, @@ -392,30 +363,26 @@ def show_lockscreen( ) -> CANCELLED: """Homescreen for locked device.""" - # rust/src/ui/model_tr/layout.rs def draw_welcome_screen() -> None: """Show logo icon with the model name at the bottom and return.""" + CONFIRMED: object CANCELLED: object INFO: object - # rust/src/ui/model_tt/layout.rs def disable_animation(disable: bool) -> None: """Disable animations, debug builds only.""" - # rust/src/ui/model_tt/layout.rs def jpeg_info(data: bytes) -> tuple[int, int, int]: """Get JPEG image dimensions (width: int, height: int, mcu_height: int).""" - # rust/src/ui/model_tt/layout.rs def jpeg_test(data: bytes) -> bool: """Test JPEG image.""" - # rust/src/ui/model_tt/layout.rs def confirm_action( *, @@ -430,7 +397,6 @@ def confirm_action( ) -> object: """Confirm action.""" - # rust/src/ui/model_tt/layout.rs def confirm_emphasized( *, @@ -441,7 +407,6 @@ def confirm_emphasized( """Confirm formatted text that has been pre-split in python. For tuples the first component is a bool indicating whether this part is emphasized.""" - # rust/src/ui/model_tt/layout.rs def confirm_homescreen( *, @@ -450,7 +415,6 @@ def confirm_homescreen( ) -> object: """Confirm homescreen.""" - # rust/src/ui/model_tt/layout.rs def confirm_blob( *, @@ -464,7 +428,6 @@ def confirm_blob( ) -> object: """Confirm byte sequence data.""" - # rust/src/ui/model_tt/layout.rs def confirm_address( *, @@ -476,7 +439,6 @@ def confirm_address( """Confirm address. Similar to `confirm_blob` but has corner info button and allows left swipe which does the same thing as the button.""" - # rust/src/ui/model_tt/layout.rs def confirm_properties( *, @@ -487,7 +449,6 @@ def confirm_properties( """Confirm list of key-value pairs. The third component in the tuple should be True if the value is to be rendered as binary with monospace font, False otherwise.""" - # rust/src/ui/model_tt/layout.rs def confirm_reset_device( *, @@ -496,7 +457,6 @@ def confirm_reset_device( ) -> object: """Confirm TOS before device setup.""" - # rust/src/ui/model_tt/layout.rs def show_address_details( *, @@ -508,7 +468,6 @@ def show_address_details( ) -> object: """Show address details - QR code, account, path, cosigner xpubs.""" - # rust/src/ui/model_tt/layout.rs def show_spending_details( *, @@ -519,7 +478,6 @@ def show_spending_details( ) -> object: """Show metadata when for outgoing transaction.""" - # rust/src/ui/model_tt/layout.rs def confirm_value( *, @@ -534,7 +492,6 @@ def confirm_value( ) -> object: """Confirm value. Merge of confirm_total and confirm_output.""" - # rust/src/ui/model_tt/layout.rs def confirm_total( *, @@ -544,7 +501,6 @@ def confirm_total( ) -> object: """Transaction summary. Always hold to confirm.""" - # rust/src/ui/model_tt/layout.rs def confirm_modify_output( *, @@ -555,7 +511,6 @@ def confirm_modify_output( ) -> object: """Decrease or increase amount for given address.""" - # rust/src/ui/model_tt/layout.rs def confirm_modify_fee( *, @@ -567,7 +522,6 @@ def confirm_modify_fee( ) -> object: """Decrease or increase transaction fee.""" - # rust/src/ui/model_tt/layout.rs def confirm_fido( *, @@ -580,7 +534,6 @@ def confirm_fido( Returns page index in case of confirmation and CANCELLED otherwise. """ - # rust/src/ui/model_tt/layout.rs def show_error( *, @@ -592,7 +545,6 @@ def show_error( ) -> object: """Error modal. No buttons shown when `button` is empty string.""" - # rust/src/ui/model_tt/layout.rs def show_warning( *, @@ -604,7 +556,6 @@ def show_warning( ) -> object: """Warning modal. No buttons shown when `button` is empty string.""" - # rust/src/ui/model_tt/layout.rs def show_success( *, @@ -616,7 +567,6 @@ def show_success( ) -> object: """Success modal. No buttons shown when `button` is empty string.""" - # rust/src/ui/model_tt/layout.rs def show_info( *, @@ -628,12 +578,10 @@ def show_info( ) -> object: """Info modal. No buttons shown when `button` is empty string.""" - # rust/src/ui/model_tt/layout.rs def show_mismatch() -> object: """Warning modal, receiving address mismatch.""" - # rust/src/ui/model_tt/layout.rs def show_simple( *, @@ -643,7 +591,6 @@ def show_simple( ) -> object: """Simple dialog with text and one button.""" - # rust/src/ui/model_tt/layout.rs def confirm_with_info( *, @@ -655,7 +602,6 @@ def confirm_with_info( """Confirm given items but with third button. Always single page without scrolling.""" - # rust/src/ui/model_tt/layout.rs def confirm_more( *, @@ -666,7 +612,6 @@ def confirm_more( """Confirm long content with the possibility to go back from any page. Meant to be used with confirm_with_info.""" - # rust/src/ui/model_tt/layout.rs def confirm_coinjoin( *, @@ -675,7 +620,6 @@ def confirm_coinjoin( ) -> object: """Confirm coinjoin authorization.""" - # rust/src/ui/model_tt/layout.rs def request_pin( *, @@ -686,7 +630,6 @@ def request_pin( ) -> str | object: """Request pin on device.""" - # rust/src/ui/model_tt/layout.rs def request_passphrase( *, @@ -695,7 +638,6 @@ def request_passphrase( ) -> str | object: """Passphrase input keyboard.""" - # rust/src/ui/model_tt/layout.rs def request_bip39( *, @@ -703,7 +645,6 @@ def request_bip39( ) -> str: """BIP39 word input keyboard.""" - # rust/src/ui/model_tt/layout.rs def request_slip39( *, @@ -711,7 +652,6 @@ def request_slip39( ) -> str: """SLIP39 word input keyboard.""" - # rust/src/ui/model_tt/layout.rs def select_word( *, @@ -720,8 +660,7 @@ def select_word( words: Iterable[str], ) -> int: """Select mnemonic word from three possibilities - seed check after backup. The - iterable must be of exact size. Returns index in range `0..3`.""" - + iterable must be of exact size. Returns index in range `0..3`.""" # rust/src/ui/model_tt/layout.rs def show_share_words( @@ -731,7 +670,6 @@ def show_share_words( ) -> object: """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" - # rust/src/ui/model_tt/layout.rs def request_number( *, @@ -743,7 +681,6 @@ def request_number( ) -> object: """Number input with + and - buttons, description, and info button.""" - # rust/src/ui/model_tt/layout.rs def show_checklist( *, @@ -753,8 +690,7 @@ def show_checklist( button: str, ) -> object: """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - + mark next to them.""" # rust/src/ui/model_tt/layout.rs def confirm_recovery( @@ -767,7 +703,6 @@ def confirm_recovery( ) -> object: """Device recovery homescreen.""" - # rust/src/ui/model_tt/layout.rs def select_word_count( *, @@ -775,15 +710,10 @@ def select_word_count( ) -> int | str: # TT returns int """Select mnemonic word count from (12, 18, 20, 24, 33).""" - # rust/src/ui/model_tt/layout.rs -def show_group_share_success( - *, - lines: Iterable[str] -) -> int: +def show_group_share_success(*, lines: Iterable[str]) -> int: """Shown after successfully finishing a group.""" - # rust/src/ui/model_tt/layout.rs def show_remaining_shares( *, @@ -791,7 +721,6 @@ def show_remaining_shares( ) -> int: """Shows SLIP39 state after info button is pressed on `confirm_recovery`.""" - # rust/src/ui/model_tt/layout.rs def show_progress( *, @@ -800,9 +729,8 @@ def show_progress( description: str = "", ) -> object: """Show progress loader. Please note that the number of lines reserved on screen for - description is determined at construction time. If you want multiline descriptions - make sure the initial description has at least that amount of lines.""" - + description is determined at construction time. If you want multiline descriptions + make sure the initial description has at least that amount of lines.""" # rust/src/ui/model_tt/layout.rs def show_progress_coinjoin( @@ -813,8 +741,7 @@ def show_progress_coinjoin( skip_first_paint: bool = False, ) -> object: """Show progress loader for coinjoin. Returns CANCELLED after a specified time when - time_ms timeout is passed.""" - + time_ms timeout is passed.""" # rust/src/ui/model_tt/layout.rs def show_homescreen( @@ -827,7 +754,6 @@ def show_homescreen( ) -> CANCELLED: """Idle homescreen.""" - # rust/src/ui/model_tt/layout.rs def show_lockscreen( *, @@ -837,7 +763,6 @@ def show_lockscreen( ) -> CANCELLED: """Homescreen for locked device.""" - # rust/src/ui/model_tt/layout.rs def draw_welcome_screen() -> None: """Show logo icon with the model name at the bottom and return.""" diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index fae39e5b7f..40d81b7fdc 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -56,11 +56,11 @@ async def recovery_device(msg: RecoveryDevice) -> Success: # -------------------------------------------------------- # _continue_dialog if not dry_run: - await confirm_reset_device("Wallet recovery", recovery=True) + await confirm_reset_device("Recover wallet", recovery=True) else: await confirm_action( "confirm_seedcheck", - "Seed check", + "Backup check", description="Do you really want to check the recovery seed?", br_code=ButtonRequestType.ProtectCall, ) diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index 6db92e7f7a..55de7d6927 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING import storage.device as storage_device import storage.recovery as storage_recovery +import storage.recovery_shares as storage_recovery_shares from trezor import wire from trezor.messages import Success @@ -62,7 +63,9 @@ async def _continue_recovery_process() -> Success: if is_first_step: # If we are starting recovery, ask for word count first... # _request_word_count - await layout.homescreen_dialog("Select", "Select number of words") + 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. @@ -150,7 +153,7 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success: storage_recovery.end_progress() - await show_success("success_recovery", "You have finished recovering your wallet.") + await show_success("success_recovery", "Wallet recovered successfully.") return Success(message="Device recovered") @@ -181,11 +184,17 @@ async def _request_share_first_screen(word_count: int) -> None: await _request_share_next_screen() else: await layout.homescreen_dialog( - "Enter share", "Enter any share", f"({word_count} words)" + "Enter share", + "Enter any share", + f"({word_count} words)", + show_info=True, ) else: # BIP-39 await layout.homescreen_dialog( - "Enter seed", "Enter recovery seed", f"({word_count} words)" + "Continue", + "Enter your backup.", + f"({word_count} words)", + show_info=True, ) @@ -205,8 +214,16 @@ async def _request_share_next_screen() -> None: info_func=_show_remaining_groups_and_shares, ) else: - text = strings.format_plural("{count} more {plural}", remaining[0], "share") - await layout.homescreen_dialog("Enter share", text, "needed to enter") + still_needed_shares = remaining[0] + already_entered_shares = len(storage_recovery_shares.fetch_group(0)) + overall_needed = still_needed_shares + already_entered_shares + entered = ( + f"{already_entered_shares} of {overall_needed} shares entered successfully." + ) + needed = strings.format_plural( + "{count} more {plural} needed.", still_needed_shares, "share" + ) + await layout.homescreen_dialog("Enter share", entered, needed) async def _show_remaining_groups_and_shares() -> None: @@ -214,7 +231,6 @@ async def _show_remaining_groups_and_shares() -> None: Show info dialog for Slip39 Advanced - what shares are to be entered. """ from trezor.crypto import slip39 - import storage.recovery_shares as storage_recovery_shares shares_remaining = storage_recovery.fetch_slip39_remaining_shares() # should be stored at this point diff --git a/core/src/apps/management/recovery_device/layout.py b/core/src/apps/management/recovery_device/layout.py index 1bb7ff1f44..3e530707f5 100644 --- a/core/src/apps/management/recovery_device/layout.py +++ b/core/src/apps/management/recovery_device/layout.py @@ -20,8 +20,8 @@ async def _confirm_abort(dry_run: bool = False) -> None: if dry_run: await confirm_action( "abort_recovery", - "Abort seed check", - description="Do you really want to abort the seed check?", + "Abort backup check", + description="Do you really want to abort the backup check?", br_code=ButtonRequestType.ProtectCall, ) else: @@ -41,9 +41,6 @@ async def request_mnemonic( from . import word_validity from trezor.ui.layouts.common import button_request from trezor.ui.layouts.recovery import request_word - from trezor.ui.layouts import mnemonic_word_entering - - await mnemonic_word_entering() await button_request("mnemonic", code=ButtonRequestType.MnemonicInput) @@ -60,7 +57,8 @@ async def request_mnemonic( # show_share_already_added await show_recovery_warning( "warning_known_share", - "Share already entered, please enter a different share.", + "Share already entered", + "Please enter a different share.", ) return None except word_validity.IdentifierMismatch: @@ -74,7 +72,8 @@ async def request_mnemonic( # show_group_threshold_reached await show_recovery_warning( "warning_group_threshold", - "Threshold of this group has been reached. Input share from different group.", + "Group threshold reached.", + "Enter share from a different group.", ) return None @@ -97,19 +96,21 @@ async def show_dry_run_result(result: bool, is_slip39: bool) -> None: text = "The entered recovery shares are valid but do not match what is currently in the device." else: text = "The entered recovery seed is valid but does not match the one in the device." - await show_recovery_warning("warning_dry_recovery", text, button="Continue") + await show_recovery_warning("warning_dry_recovery", "", text, button="Continue") async def show_invalid_mnemonic(word_count: int) -> None: if backup_types.is_slip39_word_count(word_count): await show_recovery_warning( "warning_invalid_share", - "You have entered an invalid recovery share.", + "Invalid recovery share entered.", + "Please try again", ) else: await show_recovery_warning( "warning_invalid_seed", - "You have entered an invalid recovery seed.", + "Invalid recovery seed entered.", + "Please try again", ) @@ -118,6 +119,7 @@ async def homescreen_dialog( text: str, subtext: str | None = None, info_func: Callable | None = None, + show_info: bool = False, ) -> None: from .recover import RecoveryAborted import storage.recovery as storage_recovery @@ -126,7 +128,9 @@ async def homescreen_dialog( while True: dry_run = storage_recovery.is_dry_run() - if await continue_recovery(button_label, text, subtext, info_func, dry_run): + if await continue_recovery( + button_label, text, subtext, info_func, dry_run, show_info + ): # go forward in the recovery process break # user has chosen to abort, confirm the choice diff --git a/core/src/apps/management/recovery_device/recover.py b/core/src/apps/management/recovery_device/recover.py index 0f3461bd24..7e43cb1ad4 100644 --- a/core/src/apps/management/recovery_device/recover.py +++ b/core/src/apps/management/recovery_device/recover.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING import storage.recovery as storage_recovery -import storage.recovery_shares +import storage.recovery_shares as storage_recovery_shares from trezor.crypto import slip39 if TYPE_CHECKING: @@ -42,7 +42,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]: storage_recovery.set_slip39_iteration_exponent(share.iteration_exponent) storage_recovery.set_slip39_identifier(share.identifier) storage_recovery.set_slip39_remaining_shares(share.threshold - 1, group_index) - storage.recovery_shares.set(share.index, group_index, words) + storage_recovery_shares.set(share.index, group_index, words) # if share threshold and group threshold are 1 # we can calculate the secret right away @@ -58,7 +58,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]: raise RuntimeError("Slip39: Share identifiers do not match") if share.iteration_exponent != storage_recovery.get_slip39_iteration_exponent(): raise RuntimeError("Slip39: Share exponents do not match") - if storage.recovery_shares.get(share.index, group_index): + if storage_recovery_shares.get(share.index, group_index): raise RuntimeError("Slip39: This mnemonic was already entered") if share.group_count != storage_recovery.get_slip39_group_count(): raise RuntimeError("Slip39: Group count does not match") @@ -68,7 +68,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]: ) storage_recovery.set_slip39_remaining_shares(remaining_for_share - 1, group_index) remaining[group_index] = remaining_for_share - 1 - storage.recovery_shares.set(share.index, group_index, words) + storage_recovery_shares.set(share.index, group_index, words) if remaining.count(0) < share.group_threshold: # we need more shares @@ -79,11 +79,11 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]: for i, r in enumerate(remaining): # if we have multiple groups pass only the ones with threshold reached if r == 0: - group = storage.recovery_shares.fetch_group(i) + group = storage_recovery_shares.fetch_group(i) mnemonics.extend(group) else: # in case of slip39 basic we only need the first and only group - mnemonics = storage.recovery_shares.fetch_group(0) + mnemonics = storage_recovery_shares.fetch_group(0) _, _, secret = slip39.recover_ems(mnemonics) return secret, share @@ -111,7 +111,7 @@ def fetch_previous_mnemonics() -> list[list[str]] | None: if not storage_recovery.get_slip39_group_count(): return None for i in range(storage_recovery.get_slip39_group_count()): - mnemonics.append(storage.recovery_shares.fetch_group(i)) + mnemonics.append(storage_recovery_shares.fetch_group(i)) if not any(p for p in mnemonics): return None return mnemonics diff --git a/core/src/storage/recovery.py b/core/src/storage/recovery.py index 082c171b34..5e7324aa26 100644 --- a/core/src/storage/recovery.py +++ b/core/src/storage/recovery.py @@ -10,13 +10,13 @@ _NAMESPACE = common.APP_RECOVERY _IN_PROGRESS = const(0x00) # bool _DRY_RUN = const(0x01) # bool _SLIP39_IDENTIFIER = const(0x03) # bytes -_SLIP39_THRESHOLD = const(0x04) # int _REMAINING = const(0x05) # int _SLIP39_ITERATION_EXPONENT = const(0x06) # int _SLIP39_GROUP_COUNT = const(0x07) # int # Deprecated Keys: # _WORD_COUNT = const(0x02) # int +# _SLIP39_THRESHOLD = const(0x04) # int # fmt: on # Default values: @@ -130,7 +130,6 @@ def end_progress() -> None: _IN_PROGRESS, _DRY_RUN, _SLIP39_IDENTIFIER, - _SLIP39_THRESHOLD, _REMAINING, _SLIP39_ITERATION_EXPONENT, _SLIP39_GROUP_COUNT, diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index befcf700ed..450c58dc1f 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -399,6 +399,16 @@ async def confirm_reset_device( ) ) + if recovery: + await confirm_action( + ctx, + "recover_device", + title, + description="It is safe to eject your Trezor anytime and continue later.", + verb="CONTINUE", + br_code=ButtonRequestType.ProtectCall, + ) + async def confirm_backup() -> bool: br_type = "backup_device" @@ -597,21 +607,23 @@ async def show_error_and_raise( raise exc -def show_warning( +async def show_warning( br_type: str, content: str, subheader: str | None = None, - button: str = "Try again", + button: str = "CONTINUE", br_code: ButtonRequestType = ButtonRequestType.Warning, -) -> Awaitable[None]: - return _show_modal( +) -> None: + await interact( + RustLayout( + trezorui2.show_warning( # type: ignore [Argument missing for parameter "title"] + button=button.upper(), + warning=content, # type: ignore [No parameter named "warning"] + description=subheader or "", + ) + ), br_type, - "", - subheader or "WARNING", - content, - button_confirm=button, - button_cancel=None, - br_code=br_code, + br_code, ) @@ -1160,26 +1172,6 @@ async def confirm_reenter_pin( ) -async def show_error( - br_type: str, - title: str, - description: str, - button: str, - br_code: ButtonRequestType = BR_TYPE_OTHER, -) -> None: - await interact( - RustLayout( - trezorui2.show_error( - title=title, - description=description, - button=button, - ) - ), - br_type, - br_code, - ) - - async def confirm_multiple_pages_texts( br_type: str, title: str, @@ -1207,7 +1199,7 @@ async def pin_mismatch_popup( ) -> None: description = "wipe codes" if is_wipe_code else "PINs" br_code = "wipe_code_mismatch" if is_wipe_code else "pin_mismatch" - return await show_error( + return await show_warning( br_code, f"Entered {description} do not match!", "Please check again.", @@ -1259,14 +1251,3 @@ async def confirm_set_new_pin( "CONTINUE", br_code, ) - - -async def mnemonic_word_entering() -> None: - await confirm_action( - "request_word", - "WORD ENTERING", - description="You'll only have to select the first 2-3 letters.", - verb="CONTINUE", - verb_cancel=None, - br_code=ButtonRequestType.MnemonicInput, - ) diff --git a/core/src/trezor/ui/layouts/tr/recovery.py b/core/src/trezor/ui/layouts/tr/recovery.py index 5e3269defb..f28e90462e 100644 --- a/core/src/trezor/ui/layouts/tr/recovery.py +++ b/core/src/trezor/ui/layouts/tr/recovery.py @@ -4,12 +4,11 @@ from trezor.enums import ButtonRequestType import trezorui2 -from ..common import button_request, interact +from ..common import interact from . import RustLayout, raise_if_not_confirmed, show_warning async def request_word_count(dry_run: bool) -> int: - await button_request("word_count", code=ButtonRequestType.MnemonicWordCount) count = await interact( RustLayout(trezorui2.select_word_count(dry_run=dry_run)), "word_count", @@ -66,6 +65,7 @@ async def continue_recovery( subtext: str | None, info_func: Callable | None, dry_run: bool, + show_info: bool = False, ) -> bool: # TODO: implement info_func? # There is very limited space on the screen @@ -82,6 +82,7 @@ async def continue_recovery( button=button_label.upper(), info_button=False, dry_run=dry_run, + show_info=show_info, # type: ignore [No parameter named "show_info"] ) ) result = await interact( diff --git a/core/src/trezor/ui/layouts/tr/reset.py b/core/src/trezor/ui/layouts/tr/reset.py index fc2150f5bd..7f8fa14cd5 100644 --- a/core/src/trezor/ui/layouts/tr/reset.py +++ b/core/src/trezor/ui/layouts/tr/reset.py @@ -6,7 +6,7 @@ from trezor.wire import ActionCancelled import trezorui2 from ..common import interact -from . import RustLayout, confirm_action, show_error +from . import RustLayout, confirm_action, show_warning CONFIRMED = trezorui2.CONFIRMED # global_import_cache @@ -247,7 +247,7 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int: async def show_warning_backup(slip39: bool) -> None: - await show_error( + await show_warning( "backup_warning", "REMEMBER", "Never make a digital copy of your backup or upload it online!", @@ -275,7 +275,7 @@ async def show_reset_warning( button: str = "TRY AGAIN", br_code: ButtonRequestType = ButtonRequestType.Warning, ) -> None: - await show_error( + await show_warning( ctx, br_type, button.upper(), diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 1454f26523..ae517c6cbd 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -1238,17 +1238,3 @@ async def confirm_set_new_pin( br_code, ) ) - - # await confirm_action( - # ctx, - # br_type, - # title, - # description=description, - # verb="TURN ON", - # br_code=br_code, - # ) - - -async def mnemonic_word_entering() -> None: - """Not supported for TT.""" - pass diff --git a/core/src/trezor/ui/layouts/tt_v2/recovery.py b/core/src/trezor/ui/layouts/tt_v2/recovery.py index d2f21823ba..ae4b2ce766 100644 --- a/core/src/trezor/ui/layouts/tt_v2/recovery.py +++ b/core/src/trezor/ui/layouts/tt_v2/recovery.py @@ -102,6 +102,7 @@ async def continue_recovery( subtext: str | None, info_func: Callable | None, dry_run: bool, + show_info: bool = False, # unused on TT ) -> bool: from ..common import button_request diff --git a/tests/click_tests/recovery.py b/tests/click_tests/recovery.py index b2c948bd3f..b3a315f8b8 100644 --- a/tests/click_tests/recovery.py +++ b/tests/click_tests/recovery.py @@ -42,12 +42,16 @@ def confirm_recovery(debug: "DebugLink") -> None: if debug.model == "T": if not debug.legacy_ui and not debug.legacy_debug: layout = debug.wait_layout() - assert layout.title().startswith("WALLET RECOVERY") + assert layout.title().startswith( + ("WALLET RECOVERY", "RECOVER WALLET", "BACKUP CHECK") + ) debug.click(buttons.OK, wait=True) elif debug.model == "R": layout = debug.wait_layout() - assert layout.title() == "WALLET RECOVERY" + assert layout.title() == "RECOVER WALLET" debug.press_right(wait=True) + layout = debug.press_right(wait=True) + assert "safe to eject" in layout.text_content() debug.press_right() @@ -66,8 +70,11 @@ def select_number_of_words( elif debug.legacy_debug: assert "SelectWordCount" in layout.json_str else: - # Two title options - assert layout.title() in ("SEED CHECK", "WALLET RECOVERY") + assert layout.title() in ( + "WALLET RECOVERY", + "BACKUP CHECK", + "RECOVER WALLET", + ) # click the number word_option_offset = 6 @@ -96,10 +103,12 @@ def select_number_of_words( if num_of_words in (20, 33): assert "Enter any share" in layout.text_content() else: - assert "Enter recovery seed" in layout.text_content() + assert "Enter your backup" in layout.text_content() -def enter_share(debug: "DebugLink", share: str) -> "LayoutContent": +def enter_share( + debug: "DebugLink", share: str, is_first: bool = True +) -> "LayoutContent": if debug.model == "T": layout = debug.click(buttons.OK, wait=True) @@ -115,10 +124,12 @@ def enter_share(debug: "DebugLink", share: str) -> "LayoutContent": return layout elif debug.model == "R": + assert "RECOVER WALLET" in debug.wait_layout().title() layout = debug.press_right(wait=True) - assert layout.title() == "WORD ENTERING" - - layout = debug.press_right(wait=True) + if is_first: + # Word entering info + debug.press_right() + layout = debug.press_right(wait=True) assert "Slip39Entry" in layout.all_components() for word in share.split(" "): @@ -133,39 +144,32 @@ def enter_shares(debug: "DebugLink", shares: list[str]) -> None: layout = debug.read_layout() expected_text = "Enter any share" remaining = len(shares) - for share in shares: + for index, share in enumerate(shares): assert expected_text in layout.text_content() - layout = enter_share(debug, share) + layout = enter_share(debug, share, is_first=index == 0) remaining -= 1 expected_text = f"{remaining} more share" - assert "You have finished recovering your wallet" in layout.text_content() + assert "Wallet recovered successfully" in layout.text_content() def enter_seed(debug: "DebugLink", seed_words: list[str]) -> None: + assert "Enter" in debug.read_layout().text_content() if debug.model == "T": - assert "Enter" in debug.read_layout().text_content() - layout = debug.click(buttons.OK, wait=True) assert layout.main_component() == "MnemonicKeyboard" - - for word in seed_words: - layout = enter_word(debug, word, is_slip39=False) - - assert "You have finished recovering your wallet" in layout.text_content() elif debug.model == "R": - assert "Enter" in debug.read_layout().text_content() - layout = debug.press_right(wait=True) - assert layout.title() == "WORD ENTERING" + assert "RECOVER WALLET" in layout.title() + debug.press_right() layout = debug.press_right(wait=True) assert "Bip39Entry" in layout.all_components() - for word in seed_words: - layout = enter_word(debug, word, is_slip39=False) + for word in seed_words: + layout = enter_word(debug, word, is_slip39=False) - assert "You have finished recovering your wallet" in layout.text_content() + assert "Wallet recovered successfully" in layout.text_content() # type: ignore def finalize(debug: "DebugLink") -> None: diff --git a/tests/click_tests/test_autolock.py b/tests/click_tests/test_autolock.py index 6e1fa07257..a6f5d2a75b 100644 --- a/tests/click_tests/test_autolock.py +++ b/tests/click_tests/test_autolock.py @@ -301,7 +301,8 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"): assert layout.main_component() == "MnemonicKeyboard" elif debug.model == "R": layout = debug.press_right(wait=True) - assert "WORD ENTERING" in layout.title() + assert "RECOVER WALLET" in layout.title() + debug.press_right() layout = debug.press_right(wait=True) assert "Slip39Entry" in layout.all_components() @@ -337,7 +338,8 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"): assert layout.main_component() == "MnemonicKeyboard" elif debug.model == "R": layout = debug.press_right(wait=True) - assert "WORD ENTERING" in layout.title() + assert "RECOVER WALLET" in layout.title() + debug.press_right() layout = debug.press_right(wait=True) assert "Slip39Entry" in layout.all_components() diff --git a/tests/common.py b/tests/common.py index a2dae59cf3..56832be53c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -227,16 +227,15 @@ def recovery_enter_shares_tr( debug.input(str(word_count)) # Homescreen - proceed to share entry yield + assert "Enter any share" in debug.wait_layout().text_content() + debug.press_right() + debug.press_right() debug.press_yes() # Enter shares for share in shares: br = yield - assert br.code == ButtonRequestType.RecoveryHomepage - - # Word entering - yield - debug.press_yes() + assert br.code == ButtonRequestType.MnemonicInput # Enter mnemonic words for word in share.split(" "): @@ -368,7 +367,7 @@ def read_and_confirm_mnemonic_tr( return " ".join(mnemonic) -def click_info_button(debug: "DebugLink"): +def click_info_button_tt(debug: "DebugLink"): """Click Shamir backup info button and return back.""" debug.press_info() yield # Info screen with text diff --git a/tests/device_tests/reset_recovery/test_recovery_bip39_t2.py b/tests/device_tests/reset_recovery/test_recovery_bip39_t2.py index 6e90e0f855..582389318f 100644 --- a/tests/device_tests/reset_recovery/test_recovery_bip39_t2.py +++ b/tests/device_tests/reset_recovery/test_recovery_bip39_t2.py @@ -30,7 +30,6 @@ def test_tt_pin_passphrase(client: Client): with client: IF = InputFlowBip39RecoveryPIN(client, MNEMONIC12.split(" ")) client.set_input_flow(IF.get()) - client.watch_layout() device.recover( client, pin_protection=True, @@ -51,7 +50,6 @@ def test_tt_nopin_nopassphrase(client: Client): with client: IF = InputFlowBip39RecoveryNoPIN(client, MNEMONIC12.split(" ")) client.set_input_flow(IF.get()) - client.watch_layout() device.recover( client, pin_protection=False, diff --git a/tests/device_tests/reset_recovery/test_recovery_slip39_basic.py b/tests/device_tests/reset_recovery/test_recovery_slip39_basic.py index e48b3653e3..0bbf6fb2bc 100644 --- a/tests/device_tests/reset_recovery/test_recovery_slip39_basic.py +++ b/tests/device_tests/reset_recovery/test_recovery_slip39_basic.py @@ -117,8 +117,6 @@ def test_noabort(client: Client): @pytest.mark.setup_client(uninitialized=True) def test_ask_word_number(client: Client): - if client.features.model == "R": - pytest.skip("Flow is not working correctly for TR") with client: IF = InputFlowSlip39BasicRecoveryRetryFirst(client) client.set_input_flow(IF.get()) diff --git a/tests/input_flows.py b/tests/input_flows.py index 8f5cd95aaf..c5ada32475 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -23,7 +23,7 @@ from trezorlib.debuglink import ( from . import buttons from .common import ( check_pin_backoff_time, - click_info_button, + click_info_button_tt, click_through, read_and_confirm_mnemonic, recovery_enter_shares, @@ -396,14 +396,14 @@ class InputFlowPaymentRequestDetails(InputFlowBase): self.debug.press_info() yield # confirm first output - assert self.outputs[0].address[:16] in self.layout().text_content() + assert self.outputs[0].address[:16] in self.layout().text_content() # type: ignore self.debug.press_yes() yield # confirm first output self.debug.wait_layout() self.debug.press_yes() yield # confirm second output - assert self.outputs[1].address[:16] in self.layout().text_content() + assert self.outputs[1].address[:16] in self.layout().text_content() # type: ignore self.debug.press_yes() yield # confirm second output self.debug.wait_layout() @@ -503,12 +503,12 @@ class InputFlowSignTxInformation(InputFlowBase): def input_flow_tt(self) -> GeneratorType: content = yield from sign_tx_go_to_info(self.client) self.assert_content(content) - self.client.debug.press_yes() + self.debug.press_yes() def input_flow_tr(self) -> GeneratorType: content = yield from sign_tx_go_to_info_tr(self.client) self.assert_content(content.lower()) - self.client.debug.press_yes() + self.debug.press_yes() class InputFlowSignTxInformationMixed(InputFlowBase): @@ -524,12 +524,12 @@ class InputFlowSignTxInformationMixed(InputFlowBase): def input_flow_tt(self) -> GeneratorType: content = yield from sign_tx_go_to_info(self.client) self.assert_content(content) - self.client.debug.press_yes() + self.debug.press_yes() def input_flow_tr(self) -> GeneratorType: content = yield from sign_tx_go_to_info_tr(self.client) self.assert_content(content.lower()) - self.client.debug.press_yes() + self.debug.press_yes() class InputFlowSignTxInformationCancel(InputFlowBase): @@ -538,11 +538,11 @@ class InputFlowSignTxInformationCancel(InputFlowBase): def input_flow_tt(self) -> GeneratorType: yield from sign_tx_go_to_info(self.client) - self.client.debug.press_no() + self.debug.press_no() def input_flow_tr(self) -> GeneratorType: yield from sign_tx_go_to_info_tr(self.client) - self.client.debug.press_left() + self.debug.press_left() class InputFlowSignTxInformationReplacement(InputFlowBase): @@ -551,15 +551,15 @@ class InputFlowSignTxInformationReplacement(InputFlowBase): def input_flow_tt(self) -> GeneratorType: yield # confirm txid - self.client.debug.press_yes() + self.debug.press_yes() yield # confirm address - self.client.debug.press_yes() + self.debug.press_yes() # go back to address - self.client.debug.press_no() + self.debug.press_no() # confirm address - self.client.debug.press_yes() + self.debug.press_yes() yield # confirm amount - self.client.debug.press_yes() + self.debug.press_yes() yield # transaction summary, press info self.client.debug.click(buttons.CORNER_BUTTON, wait=True) @@ -568,16 +568,16 @@ class InputFlowSignTxInformationReplacement(InputFlowBase): def input_flow_tr(self) -> GeneratorType: yield # confirm txid - self.client.debug.press_right() - self.client.debug.press_right() + self.debug.press_right() + self.debug.press_right() yield # confirm address - self.client.debug.press_right() - self.client.debug.press_right() - self.client.debug.press_right() + self.debug.press_right() + self.debug.press_right() + self.debug.press_right() yield # confirm amount - self.client.debug.press_right() - self.client.debug.press_right() - self.client.debug.press_right() + self.debug.press_right() + self.debug.press_right() + self.debug.press_right() def lock_time_input_flow_tt( @@ -1015,13 +1015,13 @@ class InputFlowSlip39BasicBackup(InputFlowBase): yield # 1. Checklist self.debug.press_yes() if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # 2. Number of shares (5) self.debug.press_yes() yield # 3. Checklist self.debug.press_yes() if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # 4. Threshold (3) self.debug.press_yes() yield # 5. Checklist @@ -1037,21 +1037,21 @@ class InputFlowSlip39BasicBackup(InputFlowBase): self.debug.press_yes() def input_flow_tr(self) -> GeneratorType: - yield # Checklist + yield # 1. Checklist self.debug.press_yes() - yield # Number of shares info + yield # 1.5 Number of shares info self.debug.press_yes() - yield # Number of shares (5) + yield # 2. Number of shares (5) self.debug.input("5") - yield # Checklist + yield # 3. Checklist self.debug.press_yes() - yield # Threshold info + yield # 3.5 Threshold info self.debug.press_yes() - yield # Threshold (3) + yield # 4. Threshold (3) self.debug.input("3") - yield # Checklist + yield # 5. Checklist self.debug.press_yes() - yield # Confirm show seeds + yield # 6. Confirm show seeds self.debug.press_yes() # Mnemonic phrases @@ -1143,24 +1143,24 @@ class InputFlowSlip39AdvancedBackup(InputFlowBase): yield # 1. Checklist self.debug.press_yes() if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # 2. Set and confirm group count self.debug.press_yes() yield # 3. Checklist self.debug.press_yes() if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # 4. Set and confirm group threshold self.debug.press_yes() yield # 5. Checklist self.debug.press_yes() for _ in range(5): # for each of 5 groups if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # Set & Confirm number of shares self.debug.press_yes() if self.click_info: - yield from click_info_button(self.debug) + yield from click_info_button_tt(self.debug) yield # Set & confirm share threshold value self.debug.press_yes() yield # Confirm show seeds @@ -1266,7 +1266,9 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase): self.debug.press_yes() -def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType: +def enter_recovery_seed_dry_run_tt( + debug: DebugLink, mnemonic: list[str] +) -> GeneratorType: yield assert "check the recovery seed" in debug.wait_layout().text_content() debug.click(buttons.OK) @@ -1284,7 +1286,7 @@ def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> Genera debug.click(buttons.grid34(index % 3, index // 3)) yield - assert "Enter recovery seed" in debug.wait_layout().text_content() + assert "Enter your backup" in debug.wait_layout().text_content() debug.click(buttons.OK) yield @@ -1299,7 +1301,7 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase): self.mnemonic = mnemonic def input_flow_tt(self) -> GeneratorType: - yield from enter_recovery_seed_dry_run(self.debug, self.mnemonic) + yield from enter_recovery_seed_dry_run_tt(self.debug, self.mnemonic) yield self.debug.wait_layout() @@ -1310,33 +1312,9 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase): assert "check the recovery seed" in self.layout().text_content() self.debug.press_yes() - yield - assert "number of words" in self.layout().text_content() - self.debug.press_yes() + yield from enter_recovery_seed_tr(self.debug, self.mnemonic) yield - yield - assert "NUMBER OF WORDS" in self.layout().title() - word_options = (12, 18, 20, 24, 33) - index = word_options.index(len(self.mnemonic)) - for _ in range(index): - self.debug.press_right() - self.debug.input(str(len(self.mnemonic))) - - yield - assert "Enter recovery seed" in self.layout().text_content() - self.debug.press_yes() - - yield - self.debug.press_yes() - yield - for index, word in enumerate(self.mnemonic): - assert "WORD" in self.layout().title() - assert str(index + 1) in self.layout().title() - self.debug.input(word) - - yield - self.debug.press_right() self.debug.press_yes() @@ -1346,11 +1324,11 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase): def input_flow_tt(self) -> GeneratorType: mnemonic = ["stick"] * 12 - yield from enter_recovery_seed_dry_run(self.debug, mnemonic) + yield from enter_recovery_seed_dry_run_tt(self.debug, mnemonic) br = yield assert br.code == messages.ButtonRequestType.Warning - assert "invalid recovery seed" in self.layout().text_content() + assert "Invalid recovery seed" in self.layout().text_content() self.debug.click(buttons.OK) yield # retry screen @@ -1358,7 +1336,7 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase): self.debug.click(buttons.CANCEL) yield - assert "ABORT SEED CHECK" == self.layout().title() + assert "ABORT BACKUP CHECK" == self.layout().title() self.debug.click(buttons.OK) def input_flow_tr(self) -> GeneratorType: @@ -1366,33 +1344,13 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase): assert "check the recovery seed" in self.layout().text_content() self.debug.press_right() - yield - assert "number of words" in self.layout().text_content() - self.debug.press_yes() - - yield - yield - assert "NUMBER OF WORDS" in self.layout().title() - # select 12 words - self.debug.press_middle() - - yield - assert "Enter recovery seed" in self.layout().text_content() - self.debug.press_yes() - - yield - assert "WORD ENTERING" in self.layout().title() - self.debug.press_yes() - - yield - for _ in range(12): - assert "WORD" in self.layout().title() - self.debug.input("stick") + mnemonic = ["stick"] * 12 + yield from enter_recovery_seed_tr(self.debug, mnemonic) br = yield assert br.code == messages.ButtonRequestType.Warning - assert "invalid recovery seed" in self.layout().text_content() - self.debug.press_right() + assert "Invalid recovery seed" in self.layout().text_content() + self.debug.press_middle() yield # retry screen assert "number of words" in self.layout().text_content() @@ -1403,7 +1361,7 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase): self.debug.press_right() -def bip39_recovery_possible_pin( +def bip39_recovery_possible_pin_tt( debug: DebugLink, mnemonic: list[str], pin: Optional[str] ) -> GeneratorType: yield @@ -1429,7 +1387,7 @@ def bip39_recovery_possible_pin( debug.input(str(len(mnemonic))) yield - assert "Enter recovery seed" in debug.wait_layout().text_content() + assert "Enter your backup" in debug.wait_layout().text_content() debug.press_yes() yield @@ -1438,65 +1396,76 @@ def bip39_recovery_possible_pin( debug.input(word) yield - assert ( - "You have finished recovering your wallet." - in debug.wait_layout().text_content() - ) + assert "Wallet recovered successfully" in debug.wait_layout().text_content() debug.press_yes() +def bip39_recovery_possible_pin_tr( + debug: DebugLink, mnemonic: list[str], pin: Optional[str] +) -> GeneratorType: + yield + assert "By continuing you agree" in debug.wait_layout().text_content() + debug.press_right() + assert "trezor.io/tos" in debug.wait_layout().text_content() + debug.press_yes() + + yield + assert "safe to eject" in debug.wait_layout().text_content() + debug.press_yes() + + # PIN when requested + if pin is not None: + yield + debug.input("654") + + yield + assert "re-enter to confirm" in debug.wait_layout().text_content() + debug.press_right() + + yield + debug.input("654") + + yield from enter_recovery_seed_tr(debug, mnemonic) + + yield + assert "Wallet recovered successfully" in debug.wait_layout().text_content() + debug.press_yes() + + +def enter_recovery_seed_tr(debug: DebugLink, mnemonic: list[str]) -> GeneratorType: + yield + assert "number of words" in debug.wait_layout().text_content() + debug.press_yes() + + yield + assert "NUMBER OF WORDS" in debug.wait_layout().title() + debug.input(str(len(mnemonic))) + + yield + assert "Enter your backup" in debug.wait_layout().text_content() + # Paginate to see info + debug.press_right() + debug.press_right() + debug.press_yes() + + yield + for index, word in enumerate(mnemonic): + title = debug.wait_layout().title() + assert "WORD" in title + assert str(index + 1) in title + debug.input(word) + + class InputFlowBip39RecoveryPIN(InputFlowBase): def __init__(self, client: Client, mnemonic: list[str]): super().__init__(client) self.mnemonic = mnemonic def input_flow_tt(self) -> GeneratorType: - yield from bip39_recovery_possible_pin(self.debug, self.mnemonic, pin="654") + yield from bip39_recovery_possible_pin_tt(self.debug, self.mnemonic, pin="654") def input_flow_tr(self) -> GeneratorType: - yield - assert "By continuing you agree" in self.layout().text_content() - self.debug.press_right() - assert "trezor.io/tos" in self.layout().text_content() - self.debug.press_yes() - - yield - self.debug.input("654") - - yield - assert "re-enter PIN" in self.layout().text_content() - self.debug.press_right() - - yield - self.debug.input("654") - - yield - assert "number of words" in self.layout().text_content() - self.debug.press_yes() - - yield - yield - assert "NUMBER OF WORDS" in self.layout().title() - self.debug.input(str(len(self.mnemonic))) - - yield - assert "Enter recovery seed" in self.layout().text_content() - self.debug.press_yes() - - yield - assert "WORD ENTERING" in self.layout().title() - self.debug.press_right() - - yield - for word in self.mnemonic: - assert "WORD" in self.layout().title() - self.debug.input(word) - - yield - assert ( - "You have finished recovering your wallet." in self.layout().text_content() - ) - self.debug.press_yes() + yield from bip39_recovery_possible_pin_tr(self.debug, self.mnemonic, pin="654") class InputFlowBip39RecoveryNoPIN(InputFlowBase): @@ -1505,28 +1474,10 @@ class InputFlowBip39RecoveryNoPIN(InputFlowBase): self.mnemonic = mnemonic def input_flow_tt(self) -> GeneratorType: - yield from bip39_recovery_possible_pin(self.debug, self.mnemonic, pin=None) + yield from bip39_recovery_possible_pin_tt(self.debug, self.mnemonic, pin=None) def input_flow_tr(self) -> GeneratorType: - yield # Confirm recovery - self.debug.press_yes() - yield # Homescreen - self.debug.press_yes() - - yield # Enter word count - self.debug.input(str(len(self.mnemonic))) - - yield # Homescreen - self.debug.press_yes() - yield # Homescreen - self.debug.press_yes() - yield # Enter words - for word in self.mnemonic: - self.debug.input(word) - - yield # confirm success - self.debug.press_yes() - yield + yield from bip39_recovery_possible_pin_tr(self.debug, self.mnemonic, pin=None) class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase): @@ -1541,6 +1492,18 @@ class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase): yield from recovery_enter_shares(self.debug, self.shares, groups=True) +def confirm_recovery(debug: DebugLink) -> GeneratorType: + if debug.model == "T": + yield # Confirm Recovery + debug.press_yes() + elif debug.model == "R": + yield # Confirm Recovery + debug.press_right() + debug.press_yes() + yield # Safe to eject + debug.press_yes() + + class InputFlowSlip39AdvancedRecovery(InputFlowBase): def __init__(self, client: Client, shares: list[str], click_info: bool): super().__init__(client) @@ -1548,9 +1511,7 @@ class InputFlowSlip39AdvancedRecovery(InputFlowBase): self.click_info = click_info def input_flow_common(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() - # Proceed with recovery + yield from confirm_recovery(self.debug) yield from recovery_enter_shares( self.debug, self.shares, groups=True, click_info=self.click_info ) @@ -1561,8 +1522,7 @@ class InputFlowSlip39AdvancedRecoveryAbort(InputFlowBase): super().__init__(client) def input_flow_common(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + yield from confirm_recovery(self.debug) yield # Homescreen - abort process self.debug.press_no() yield # Homescreen - confirm abort @@ -1575,11 +1535,12 @@ class InputFlowSlip39AdvancedRecoveryNoAbort(InputFlowBase): self.shares = shares def input_flow_common(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + yield from confirm_recovery(self.debug) yield # Homescreen - abort process self.debug.press_no() yield # Homescreen - go back to process + if self.debug.model == "R": + self.debug.press_right() self.debug.press_no() yield from recovery_enter_shares(self.debug, self.shares, groups=True) @@ -1590,18 +1551,9 @@ class InputFlowSlip39AdvancedRecoveryTwoSharesWarning(InputFlowBase): self.first_share = first_share self.second_share = second_share - def input_flow_tt(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input(str(len(self.first_share))) - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - for word in self.first_share: - self.debug.input(word) + def input_flow_common(self) -> GeneratorType: + yield from confirm_recovery(self.debug) + yield from slip39_recovery_setup_and_first_share(self.debug, self.first_share) yield # Continue to next share self.debug.press_yes() @@ -1613,38 +1565,6 @@ class InputFlowSlip39AdvancedRecoveryTwoSharesWarning(InputFlowBase): br = yield assert br.code == messages.ButtonRequestType.Warning - - self.client.cancel() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input(str(len(self.first_share))) - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - self.debug.press_yes() - yield # Enter first share - for word in self.first_share: - self.debug.input(word) - - yield # Continue to next share - self.debug.press_yes() - yield # Homescreen - next share - self.debug.press_yes() - yield # Homescreen - next share - self.debug.press_yes() - yield # Enter next share - for word in self.second_share: - self.debug.input(word) - - yield - br = yield - assert br.code == messages.ButtonRequestType.Warning - self.debug.press_right() self.debug.press_yes() yield @@ -1655,6 +1575,11 @@ def slip39_recovery_possible_pin( debug: DebugLink, shares: list[str], pin: Optional[str] ) -> GeneratorType: yield # Confirm Recovery/Dryrun + if debug.model == "R" and "BACKUP CHECK" not in debug.wait_layout().title(): + # dryruns do not have extra dialogs + debug.press_right() + debug.press_yes() + yield debug.press_yes() if pin is not None: @@ -1694,8 +1619,7 @@ class InputFlowSlip39BasicRecoveryAbort(InputFlowBase): super().__init__(client) def input_flow_common(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + yield from confirm_recovery(self.debug) yield # Homescreen - abort process self.debug.press_no() yield # Homescreen - confirm abort @@ -1708,11 +1632,12 @@ class InputFlowSlip39BasicRecoveryNoAbort(InputFlowBase): self.shares = shares def input_flow_common(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + yield from confirm_recovery(self.debug) yield # Homescreen - abort process self.debug.press_no() yield # Homescreen - go back to process + if self.debug.model == "R": + self.debug.press_right() self.debug.press_no() # run recovery flow yield from recovery_enter_shares(self.debug, self.shares) @@ -1726,6 +1651,9 @@ def slip39_recovery_setup_and_first_share( yield # Enter number of words debug.input(str(len(first_share))) yield # Homescreen - proceed to share entry + if debug.model == "R": + debug.press_right(wait=True) + debug.press_right(wait=True) debug.press_yes() yield # Enter first share for word in first_share: @@ -1736,9 +1664,8 @@ class InputFlowSlip39BasicRecoveryRetryFirst(InputFlowBase): def __init__(self, client: Client): super().__init__(client) - def input_flow_tt(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + def input_flow_common(self) -> GeneratorType: + yield from confirm_recovery(self.debug) first_share = ["slush"] * 20 yield from slip39_recovery_setup_and_first_share(self.debug, first_share) @@ -1757,55 +1684,8 @@ class InputFlowSlip39BasicRecoveryRetryFirst(InputFlowBase): yield # Homescreen self.debug.press_no() yield # Confirm abort - self.debug.press_yes() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_right() - self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input("20") - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - self.debug.press_yes() - for _ in range(20): - self.debug.input("slush") - - yield - # assert br.code == messages.ButtonRequestType.Warning - self.debug.press_yes() - - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input("33") - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield - for _ in range(33): - self.debug.input("slush") - - yield - self.debug.press_yes() - - yield - self.debug.press_no() - - yield - self.debug.press_right() - - yield - self.debug.press_right() - - yield - self.debug.press_right() - - yield + if self.debug.model == "R": + self.debug.press_right(wait=True) self.debug.press_yes() @@ -1814,9 +1694,8 @@ class InputFlowSlip39BasicRecoveryRetrySecond(InputFlowBase): super().__init__(client) self.shares = shares - def input_flow_tt(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + def input_flow_common(self) -> GeneratorType: + yield from confirm_recovery(self.debug) # First valid share first_share = self.shares[0].split(" ") @@ -1842,45 +1721,8 @@ class InputFlowSlip39BasicRecoveryRetrySecond(InputFlowBase): yield # More shares needed self.debug.press_no() yield # Confirm abort - self.debug.press_yes() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_right() - self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input("20") - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - self.debug.press_yes() - yield # Enter first share - share = self.shares[0].split(" ") - for word in share: - self.debug.input(word) - - yield # More shares needed - self.debug.press_yes() - - yield # Enter another share - share = share[:3] + ["slush"] * 17 - for word in share: - self.debug.input(word) - - yield # Invalid share - # assert br.code == messages.ButtonRequestType.Warning - self.debug.press_yes() - - yield # Proceed to next share - share = self.shares[1].split(" ") - for word in share: - self.debug.input(word) - - yield # More shares needed - self.debug.press_no() - yield # Confirm abort + if self.debug.model == "R": + self.debug.press_right(wait=True) self.debug.press_yes() @@ -1890,9 +1732,8 @@ class InputFlowSlip39BasicRecoveryWrongNthWord(InputFlowBase): self.share = share self.nth_word = nth_word - def input_flow_tt(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + def input_flow_common(self) -> GeneratorType: + yield from confirm_recovery(self.debug) # First complete share yield from slip39_recovery_setup_and_first_share(self.debug, self.share) @@ -1909,39 +1750,8 @@ class InputFlowSlip39BasicRecoveryWrongNthWord(InputFlowBase): br = yield assert br.code == messages.ButtonRequestType.Warning - - self.client.cancel() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_right() self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input(str(len(self.share))) - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - self.debug.press_yes() - yield # Enter first share - for word in self.share: - self.debug.input(word) - - yield # Continue to next share - self.debug.press_yes() - yield # Enter next share - self.debug.press_yes() - yield # Enter next share - for i, word in enumerate(self.share): - if i < self.nth_word: - self.debug.input(word) - else: - self.debug.input(self.share[-1]) - break - yield - # assert br.code == messages.ButtonRequestType.Warning self.client.cancel() @@ -1952,9 +1762,8 @@ class InputFlowSlip39BasicRecoverySameShare(InputFlowBase): self.first_share = first_share self.second_share = second_share - def input_flow_tt(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_yes() + def input_flow_common(self) -> GeneratorType: + yield from confirm_recovery(self.debug) # First complete share yield from slip39_recovery_setup_and_first_share(self.debug, self.first_share) @@ -1967,44 +1776,9 @@ class InputFlowSlip39BasicRecoverySameShare(InputFlowBase): br = yield assert br.code == messages.ButtonRequestType.Warning - - # To catch the WARNING screen self.debug.press_yes() + yield - - self.client.cancel() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_right() - self.debug.press_yes() - yield # Homescreen - start process - self.debug.press_yes() - yield # Enter number of words - self.debug.input(str(len(self.first_share))) - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Homescreen - proceed to share entry - self.debug.press_yes() - yield # Enter first share - for word in self.first_share: - self.debug.input(word) - - yield # Continue to next share - self.debug.press_yes() - yield # Continue to next share - self.debug.press_yes() - yield # Enter next share - for word in self.second_share: - self.debug.input(word) - - br = yield - br = yield - assert br.code == messages.ButtonRequestType.Warning - self.debug.press_right() - self.debug.press_yes() - yield - self.client.cancel() @@ -2012,19 +1786,14 @@ class InputFlowResetSkipBackup(InputFlowBase): def __init__(self, client: Client): super().__init__(client) - def input_flow_tt(self) -> GeneratorType: + def input_flow_common(self) -> GeneratorType: yield # Confirm Recovery + if self.debug.model == "R": + self.debug.press_right() self.debug.press_yes() yield # Skip Backup self.debug.press_no() yield # Confirm skip backup - self.debug.press_no() - - def input_flow_tr(self) -> GeneratorType: - yield # Confirm Recovery - self.debug.press_right() - self.debug.press_yes() - yield # Skip Backup - self.debug.press_no() - yield # Confirm skip backup + if self.debug.model == "R": + self.debug.press_right() self.debug.press_no() diff --git a/tests/persistence_tests/test_shamir_persistence.py b/tests/persistence_tests/test_shamir_persistence.py index d053f4490f..9290445b79 100644 --- a/tests/persistence_tests/test_shamir_persistence.py +++ b/tests/persistence_tests/test_shamir_persistence.py @@ -42,7 +42,7 @@ def test_abort(core_emulator: Emulator): device_handler.run(device.recover, pin_protection=False) - assert debug.wait_layout().title() == "WALLET RECOVERY" + assert debug.wait_layout().title() == "RECOVER WALLET" layout = debug.click(buttons.OK, wait=True) assert "number of words" in layout.text_content() @@ -174,7 +174,7 @@ def test_recovery_multiple_resets(core_emulator: Emulator): expected_text = "You have entered" debug = _restart(device_handler, core_emulator) - assert "You have finished recovering your wallet" in layout.text_content() + assert "Wallet recovered successfully" in layout.text_content() device_handler = BackgroundDeviceHandler(core_emulator.client) debug = device_handler.debuglink() diff --git a/tests/upgrade_tests/test_firmware_upgrades.py b/tests/upgrade_tests/test_firmware_upgrades.py index ec7d466813..65ff39724c 100644 --- a/tests/upgrade_tests/test_firmware_upgrades.py +++ b/tests/upgrade_tests/test_firmware_upgrades.py @@ -313,7 +313,10 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]): recovery.select_number_of_words(debug, wait=not debug.legacy_debug) layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[0]) if not debug.legacy_ui and not debug.legacy_debug: - assert "2 more shares" in layout.text_content() + assert ( + "1 of 3 shares entered" in layout.text_content() + or "2 more shares" in layout.text_content() + ) device_id = emu.client.features.device_id storage = emu.get_storage() @@ -327,11 +330,17 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]): # second share layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2]) - assert "1 more share" in layout.text_content() + assert ( + "2 of 3 shares entered" in layout.text_content() + or "1 more share" in layout.text_content() + ) # last one layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1]) - assert "You have finished recovering your wallet" in layout.text_content() + assert ( + "Wallet recovered successfully" in layout.text_content() + or "finished recovering" in layout.text_content() + ) # Check the result state = debug.state()