From cb3e43784375cb41bf7ea029a8f3bc6653bfb07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ioan=20Biz=C4=83u?= Date: Fri, 18 Oct 2024 12:25:56 +0200 Subject: [PATCH] feat(core): confirm blob with optional pagination --- core/embed/rust/librust_qstr.h | 1 + core/embed/rust/src/ui/flow/page.rs | 11 +++ .../ui/model_mercury/flow/confirm_action.rs | 4 +- .../embed/rust/src/ui/model_mercury/layout.rs | 18 +++- core/src/apps/ethereum/layout.py | 6 +- core/src/apps/ethereum/sign_tx.py | 8 +- .../src/trezor/ui/layouts/mercury/__init__.py | 99 ++++++++++--------- 7 files changed, 91 insertions(+), 56 deletions(-) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 7ac29af8f4e..16de0ed08f2 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -347,6 +347,7 @@ static void _librust_qstrs(void) { MP_QSTR_notification; MP_QSTR_notification_level; MP_QSTR_page_count; + MP_QSTR_page_limit; MP_QSTR_pages; MP_QSTR_paint; MP_QSTR_passphrase__access_wallet; diff --git a/core/embed/rust/src/ui/flow/page.rs b/core/embed/rust/src/ui/flow/page.rs index 476ea1b72a1..e99f6d1ec5d 100644 --- a/core/embed/rust/src/ui/flow/page.rs +++ b/core/embed/rust/src/ui/flow/page.rs @@ -13,6 +13,7 @@ pub struct SwipePage { axis: Axis, pages: usize, current: usize, + limit: Option, } impl SwipePage { @@ -23,6 +24,7 @@ impl SwipePage { axis: Axis::Vertical, pages: 1, current: 0, + limit: None, } } @@ -33,12 +35,18 @@ impl SwipePage { axis: Axis::Horizontal, pages: 1, current: 0, + limit: None, } } pub fn inner(&self) -> &T { &self.inner } + + pub fn with_limit(mut self, limit: Option) -> Self { + self.limit = limit; + self + } } impl Component for SwipePage { @@ -47,6 +55,9 @@ impl Component for SwipePage { fn place(&mut self, bounds: Rect) -> Rect { self.bounds = self.inner.place(bounds); self.pages = self.inner.page_count(); + if let Some(limit) = self.limit { + self.pages = self.pages.min(limit); + } self.bounds } diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs index 0e2913f3200..b65f2b6d7c1 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs @@ -147,6 +147,7 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result prompt_screen: Option>, hold: bool, info: bool, + page_limit: Option, ) -> Result { new_confirm_action_uni( - SwipeContent::new(SwipePage::vertical(content)), + SwipeContent::new(SwipePage::vertical(content).with_limit(page_limit)), title, subtitle, verb_cancel, diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 9908af97361..67daae94a1e 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -245,6 +245,7 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m Some(title), false, false, + None, ) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -263,6 +264,7 @@ struct ConfirmBlobParams { hold: bool, chunkify: bool, text_mono: bool, + page_limit: Option, } impl ConfirmBlobParams { @@ -288,6 +290,7 @@ impl ConfirmBlobParams { hold, chunkify: false, text_mono: true, + page_limit: None, } } @@ -316,6 +319,11 @@ impl ConfirmBlobParams { self } + fn with_page_limit(mut self, page_limit: Option) -> Self { + self.page_limit = page_limit; + self + } + fn into_flow(self) -> Result { let paragraphs = ConfirmBlob { description: self.description.unwrap_or("".into()), @@ -342,6 +350,7 @@ impl ConfirmBlobParams { self.prompt.then_some(self.title), self.hold, self.info_button, + self.page_limit, ) } } @@ -364,6 +373,7 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, true)?; + let page_limit: Option = kwargs.get(Qstr::MP_QSTR_page_limit)?.try_into_option()?; ConfirmBlobParams::new( title, @@ -375,7 +385,9 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map hold, ) .with_extra(extra) + .with_info_button(true) .with_chunkify(chunkify) + .with_page_limit(page_limit) .into_flow() }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -407,7 +419,7 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut } .into_paragraphs(); - flow::new_confirm_action_simple(paragraphs, title, None, None, None, false, false) + flow::new_confirm_action_simple(paragraphs, title, None, None, None, false, false, None) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } @@ -433,6 +445,7 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m hold.then_some(title), hold, false, + None, ) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -462,6 +475,7 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m Some(TR::homescreen__settings_title.into()), false, false, + None, ) } else { if !check_homescreen_format(jpeg) { @@ -570,6 +584,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma Some(title), true, true, + None, ) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -859,6 +874,7 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Some(TR::coinjoin__title.into()), true, false, + None, ) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } diff --git a/core/src/apps/ethereum/layout.py b/core/src/apps/ethereum/layout.py index 8f88d9f8020..385dc0f46d9 100644 --- a/core/src/apps/ethereum/layout.py +++ b/core/src/apps/ethereum/layout.py @@ -4,6 +4,7 @@ from trezor.enums import ButtonRequestType from trezor.ui.layouts import ( confirm_blob, + confirm_blob_with_optional_pagination, confirm_ethereum_staking_tx, confirm_text, should_show_more, @@ -174,13 +175,13 @@ def require_confirm_address(address_bytes: bytes) -> Awaitable[None]: def require_confirm_other_data(data: bytes, data_total: int) -> Awaitable[None]: - return confirm_blob( + return confirm_blob_with_optional_pagination( "confirm_data", TR.ethereum__title_confirm_data, data, TR.ethereum__data_size_template.format(data_total), br_code=ButtonRequestType.SignTx, - ask_pagination=True, + prompt_screen=False, ) @@ -301,7 +302,6 @@ async def confirm_typed_value( title, data, description, - ask_pagination=True, ) else: await confirm_text( diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py index 7e1dc3d0ae3..aed9270f10d 100644 --- a/core/src/apps/ethereum/sign_tx.py +++ b/core/src/apps/ethereum/sign_tx.py @@ -56,7 +56,7 @@ async def sign_tx( raise DataError("Fee overflow") check_common_fields(msg) - # have a user confirm signing + # have the user confirm signing await paths.validate_path(keychain, msg.address_n) address_bytes = bytes_from_address(msg.to) gas_price = int.from_bytes(msg.gas_price, "big") @@ -130,9 +130,9 @@ async def confirm_tx_data( return # Handle ERC-20, currently only 'transfer' function - token, recipient, value = await handle_erc20_transfer(msg, defs, address_bytes) + token, recipient, value = await _handle_erc20_transfer(msg, defs, address_bytes) - if token is None and data_total_len > 0: + if data_total_len > 0: await require_confirm_other_data(msg.data_initial_chunk, data_total_len) await require_confirm_tx( @@ -189,7 +189,7 @@ async def handle_staking( return False -async def handle_erc20_transfer( +async def _handle_erc20_transfer( msg: MsgInSignTx, definitions: Definitions, address_bytes: bytes, diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index 9e819f44b0b..fa58ade5402 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -750,43 +750,54 @@ async def should_show_more( raise ActionCancelled -async def _confirm_ask_pagination( +async def confirm_blob_with_optional_pagination( br_name: str, title: str, data: bytes | str, - description: str, - br_code: ButtonRequestType, -) -> None: - paginated: ui.Layout | None = None - # TODO: make should_show_more/confirm_more accept bytes directly - if isinstance(data, bytes): - from ubinascii import hexlify - - data = hexlify(data).decode() - while True: - if not await should_show_more( - title, - para=[(ui.NORMAL, description), (ui.MONO, data)], + description: str | None = None, + verb: str | None = None, + verb_cancel: str | None = None, + hold: bool = False, + br_code: ButtonRequestType = BR_CODE_OTHER, + chunkify: bool = False, + prompt_screen: bool = True, +) -> Awaitable[None]: + # show first page first + layout = RustLayout( + trezorui2.confirm_blob( + title=title, + data=data, + description=description, + extra=None, + verb=verb, + verb_cancel=verb_cancel, + hold=hold, + chunkify=chunkify, + prompt_screen=prompt_screen, + page_limit=1, + ) + ) + result = await interact( + layout, + br_name, + br_code, + ) + if result is INFO: + # user requested to view the whole blob + return await confirm_blob( br_name=br_name, + title=title, + data=data, + description=description, + verb=verb, + verb_cancel=verb_cancel, + hold=hold, br_code=br_code, - ): - return - - if paginated is None: - paginated = RustLayout( - trezorui2.confirm_more( - title=title, - button=TR.buttons__close, - items=[(ui.MONO, data)], - ) - ) - else: - paginated.request_complete_repaint() - - result = await interact(paginated, br_name, br_code) - assert result in (CONFIRMED, CANCELLED) - - assert False + chunkify=chunkify, + prompt_screen=prompt_screen, + ) + elif result is not CONFIRMED: + raise ActionCancelled def confirm_blob( @@ -798,36 +809,30 @@ def confirm_blob( verb_cancel: str | None = None, hold: bool = False, br_code: ButtonRequestType = BR_CODE_OTHER, - ask_pagination: bool = False, chunkify: bool = False, prompt_screen: bool = True, ) -> Awaitable[None]: layout = RustLayout( trezorui2.confirm_blob( title=title, - description=description, data=data, + description=description, extra=None, - hold=hold, verb=verb, verb_cancel=verb_cancel, + hold=hold, chunkify=chunkify, prompt_screen=prompt_screen, + page_limit=None, ) ) - - if ask_pagination and layout.page_count() > 1: - assert not hold - return _confirm_ask_pagination(br_name, title, data, description or "", br_code) - - else: - return raise_if_not_confirmed( - interact( - layout, - br_name, - br_code, - ) + return raise_if_not_confirmed( + interact( + layout, + br_name, + br_code, ) + ) def confirm_address(