diff --git a/core/.changelog.d/4054.added b/core/.changelog.d/4054.added new file mode 100644 index 0000000000..9abac987df --- /dev/null +++ b/core/.changelog.d/4054.added @@ -0,0 +1 @@ +[T3T1] Added reassuring screen when entering empty passphrase diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 6d6aaa6d61..ad8a9e0a75 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -248,6 +248,7 @@ static void _librust_qstrs(void) { MP_QSTR_flow_get_address; MP_QSTR_flow_prompt_backup; MP_QSTR_flow_request_number; + MP_QSTR_flow_request_passphrase; MP_QSTR_flow_show_share_words; MP_QSTR_flow_warning_hi_prio; MP_QSTR_get_language; @@ -346,6 +347,7 @@ static void _librust_qstrs(void) { MP_QSTR_paint; MP_QSTR_passphrase__access_wallet; MP_QSTR_passphrase__always_on_device; + MP_QSTR_passphrase__continue_with_empty_passphrase; MP_QSTR_passphrase__from_host_not_shown; MP_QSTR_passphrase__hide; MP_QSTR_passphrase__next_screen_will_show_passphrase; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index 0729a15eec..5ac8a98c19 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -1357,6 +1357,7 @@ pub enum TranslatedString { words__title_done = 956, // "Done" reset__slip39_checklist_more_info_threshold = 957, // "The threshold sets the minumum number of shares needed to recover your wallet." reset__slip39_checklist_more_info_threshold_example_template = 958, // "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet." + passphrase__continue_with_empty_passphrase = 959, // "Continue with empty passphrase?" } impl TranslatedString { @@ -2708,6 +2709,7 @@ impl TranslatedString { Self::words__title_done => "Done", Self::reset__slip39_checklist_more_info_threshold => "The threshold sets the minumum number of shares needed to recover your wallet.", Self::reset__slip39_checklist_more_info_threshold_example_template => "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet.", + Self::passphrase__continue_with_empty_passphrase => "Continue with empty passphrase?", } } @@ -4060,6 +4062,7 @@ impl TranslatedString { Qstr::MP_QSTR_words__title_done => Some(Self::words__title_done), Qstr::MP_QSTR_reset__slip39_checklist_more_info_threshold => Some(Self::reset__slip39_checklist_more_info_threshold), Qstr::MP_QSTR_reset__slip39_checklist_more_info_threshold_example_template => Some(Self::reset__slip39_checklist_more_info_threshold_example_template), + Qstr::MP_QSTR_passphrase__continue_with_empty_passphrase => Some(Self::passphrase__continue_with_empty_passphrase), _ => None, } } diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs index 6f3ab77a7b..94aad186bb 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/passphrase.rs @@ -1,5 +1,5 @@ use crate::{ - strutil::TString, + strutil::{ShortString, TString}, translations::TR, ui::{ component::{ @@ -26,7 +26,7 @@ use core::cell::Cell; use num_traits::ToPrimitive; pub enum PassphraseKeyboardMsg { - Confirmed, + Confirmed(ShortString), Cancelled, } diff --git a/core/embed/rust/src/ui/model_mercury/flow/mod.rs b/core/embed/rust/src/ui/model_mercury/flow/mod.rs index a5e3397bb7..962e989726 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/mod.rs @@ -8,6 +8,7 @@ pub mod continue_recovery; pub mod get_address; pub mod prompt_backup; pub mod request_number; +pub mod request_passphrase; pub mod set_brightness; pub mod show_share_words; pub mod show_tutorial; @@ -25,6 +26,7 @@ pub use continue_recovery::new_continue_recovery; pub use get_address::GetAddress; pub use prompt_backup::PromptBackup; pub use request_number::RequestNumber; +pub use request_passphrase::RequestPassphrase; pub use set_brightness::SetBrightness; pub use show_share_words::ShowShareWords; pub use show_tutorial::ShowTutorial; diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 815ba43bce..646eab2e3e 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -4,10 +4,9 @@ use super::{ component::{ AddressDetails, Bip39Input, Button, CancelConfirmMsg, CancelInfoConfirmMsg, CoinJoinProgress, FidoConfirm, FidoMsg, Frame, FrameMsg, Homescreen, HomescreenMsg, - Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, - PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen, - SelectWordCount, SelectWordCountMsg, Slip39Input, StatusScreen, SwipeUpScreen, - SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg, + Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PinKeyboard, + PinKeyboardMsg, Progress, PromptScreen, SelectWordCount, SelectWordCountMsg, Slip39Input, + StatusScreen, SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg, }, flow, theme, }; @@ -124,15 +123,6 @@ impl ComponentMsgObj for PinKeyboard<'_> { } } -impl ComponentMsgObj for PassphraseKeyboard { - fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { - match msg { - PassphraseKeyboardMsg::Confirmed => self.passphrase().try_into(), - PassphraseKeyboardMsg::Cancelled => Ok(CANCELLED.as_obj()), - } - } -} - impl ComponentMsgObj for MnemonicKeyboard where T: MnemonicInput, @@ -954,16 +944,6 @@ extern "C" fn new_request_pin(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_request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let _prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; - let _max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?; - let obj = LayoutObj::new(PassphraseKeyboard::new())?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; @@ -1570,13 +1550,13 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Request pin on device.""" Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(), - /// def request_passphrase( + /// def flow_request_passphrase( /// *, /// prompt: str, /// max_len: int, /// ) -> LayoutObj[str | UiResult]: /// """Passphrase input keyboard.""" - Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(), + Qstr::MP_QSTR_flow_request_passphrase => obj_fn_kw!(0, flow::request_passphrase::new_request_passphrase).as_obj(), /// def request_bip39( /// *, diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index a928e89580..290cc53d10 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -349,7 +349,7 @@ def request_pin( # rust/src/ui/model_mercury/layout.rs -def request_passphrase( +def flow_request_passphrase( *, prompt: str, max_len: int, diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index 511b8c3543..d2a24425bc 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -487,6 +487,7 @@ class TR: nem__unknown_mosaic: str = "Unknown mosaic!" passphrase__access_wallet: str = "Access passphrase wallet?" passphrase__always_on_device: str = "Always enter your passphrase on Trezor?" + passphrase__continue_with_empty_passphrase: str = "Continue with empty passphrase?" passphrase__from_host_not_shown: str = "Passphrase provided by host will be used but will not be displayed due to the device settings." passphrase__hide: str = "Hide passphrase coming from host?" passphrase__next_screen_will_show_passphrase: str = "The next screen shows your passphrase." diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index dd92777d4f..b224329187 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -1374,7 +1374,7 @@ def show_wait_text(message: str) -> None: async def request_passphrase_on_device(max_len: int) -> str: result = await interact( RustLayout( - trezorui2.request_passphrase( + trezorui2.flow_request_passphrase( prompt=TR.passphrase__title_enter, max_len=max_len ) ), @@ -1384,8 +1384,18 @@ async def request_passphrase_on_device(max_len: int) -> str: if result is CANCELLED: raise ActionCancelled("Passphrase entry cancelled") - assert isinstance(result, str) - return result + if __debug__: + if not isinstance(result, tuple): + # TODO: DebugLink problem, better comment or solution? + result = (CONFIRMED, str(result)) + + status, value = result + if status == CONFIRMED: + assert isinstance(value, str) + return value + else: + # flow_request_pin returns either CANCELLED or (CONFIRMED, str) so this branch shouldn't be taken + raise ActionCancelled("Passphrase entry cancelled") async def request_pin_on_device( diff --git a/core/translations/en.json b/core/translations/en.json index b2292633a2..3faf93793f 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -489,6 +489,7 @@ "nem__unknown_mosaic": "Unknown mosaic!", "passphrase__access_wallet": "Access passphrase wallet?", "passphrase__always_on_device": "Always enter your passphrase on Trezor?", + "passphrase__continue_with_empty_passphrase": "Continue with empty passphrase?", "passphrase__from_host_not_shown": "Passphrase provided by host will be used but will not be displayed due to the device settings.", "passphrase__wallet": "Passphrase wallet", "passphrase__hide": "Hide passphrase coming from host?", diff --git a/core/translations/order.json b/core/translations/order.json index 9349b9a858..cf4b8424ad 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -957,5 +957,6 @@ "955": "brightness__change_title", "956": "words__title_done", "957": "reset__slip39_checklist_more_info_threshold", - "958": "reset__slip39_checklist_more_info_threshold_example_template" + "958": "reset__slip39_checklist_more_info_threshold_example_template", + "959": "passphrase__continue_with_empty_passphrase" } diff --git a/tests/buttons.py b/tests/buttons.py index b1a290e399..8de58cfcb9 100644 --- a/tests/buttons.py +++ b/tests/buttons.py @@ -13,6 +13,20 @@ def grid(dim: int, grid_cells: int, cell: int) -> int: return cell * step + ofs +def grid35(x: int, y: int) -> Coords: + return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 5, y) + + +def grid34(x: int, y: int) -> Coords: + return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y) + + +def _grid34_from_index(idx: int) -> Coords: + grid_x = idx % 3 + grid_y = idx // 3 + 1 # first line is empty + return grid34(grid_x, grid_y) + + LEFT = grid(DISPLAY_WIDTH, 3, 0) MID = grid(DISPLAY_WIDTH, 3, 1) RIGHT = grid(DISPLAY_WIDTH, 3, 2) @@ -31,6 +45,9 @@ CORNER_BUTTON = (215, 25) CONFIRM_WORD = (MID, TOP) TOP_ROW = (MID, TOP) +MERCURY_YES = grid34(2, 2) +MERCURY_NO = grid34(0, 2) + def reset_minus(model_internal_name: str) -> Coords: RESET_MINUS_T3T1 = (LEFT, grid(DISPLAY_HEIGHT, 5, 3)) @@ -109,20 +126,6 @@ def pin_passphrase_grid(idx: int) -> Coords: return grid35(grid_x, grid_y) -def grid35(x: int, y: int) -> Coords: - return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 5, y) - - -def grid34(x: int, y: int) -> Coords: - return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y) - - -def _grid34_from_index(idx: int) -> Coords: - grid_x = idx % 3 - grid_y = idx // 3 + 1 # first line is empty - return grid34(grid_x, grid_y) - - def type_word(word: str, is_slip39: bool = False) -> Iterator[Coords]: if is_slip39: yield from _type_word_slip39(word) diff --git a/tests/click_tests/test_passphrase_mercury.py b/tests/click_tests/test_passphrase_mercury.py index 23343d95f7..7f37d334d4 100644 --- a/tests/click_tests/test_passphrase_mercury.py +++ b/tests/click_tests/test_passphrase_mercury.py @@ -168,8 +168,11 @@ def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> def enter_passphrase(debug: "DebugLink") -> None: """Enter a passphrase""" - coords = buttons.grid35(2, 0) # top-right corner + is_empty: bool = len(debug.read_layout().passphrase()) == 0 + coords = buttons.CORNER_BUTTON # top-right corner debug.click(coords, wait=True) + if is_empty: + debug.click(buttons.MERCURY_YES) def delete_char(debug: "DebugLink") -> None: diff --git a/tests/input_flows.py b/tests/input_flows.py index 14545bd7f9..03353899ce 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -1597,7 +1597,7 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase): # Mnemonic phrases self.mnemonics = yield from load_N_shares(self.debug, 5) - br = yield # safety warning + br = yield # success screen assert br.code == B.Success self.debug.press_yes()