What changed, and why it matters
This commit changes how Trezor hardware wallets display the sign/verify message flow on two newer user-interface designs (Eckhart and Delizia). It mostly reorganizes on-screen menus so that extra details (account, derivation path, message size) are grouped under a single 'More info' item, and it makes the cancel/mismatch warning screen consistent with other flows. There is no direct evidence in the commit that this fixes a security vulnerability; it reads as a UI consistency and usability cleanup.
No security action required. Treat as a normal UI/UX refactor. If reviewing for release, verify that the updated test fixtures pass and that the new menu navigation does not accidentally hide security-relevant information from users.
Security signals we found
UI flow consistency change only
No changes to cryptographic signing, verification, or authorization logic
No changes to message parsing, address derivation, or trust assumptions
Test fixtures updated to match new menu navigation
Evidence from the diff
The patch refactors confirm_signverify() in the Delizia and Eckhart layouts. It replaces scattered info items and inline mismatch handling with a shared Menu containing a ‘More info’ leaf and a ‘Cancel’ leaf that invokes show_mismatch(). Eckhart’s Rust show_mismatch() implementation is updated to use a standard TextScreen with a warning hint and a cancel-styled action bar. Test input flows are adjusted to match the new menu positions. The change is cosmetic/structural rather than cryptographic or authorization-related.
Changed components
core/embed/rust/src/ui/layout_eckhart/ui_firmware.rscore/src/trezor/ui/layouts/delizia/__init__.pycore/src/trezor/ui/layouts/eckhart/__init__.pytests/input_flows.pytests/ui_tests/fixtures.jsonInspect captured patch +1768 / −1777
### core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
@@ -1276,21 +1276,19 @@ impl FirmwareUI for UIEckhart {
}
fn show_mismatch(title: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
- let description: TString = TR::addr_mismatch__contact_support_at.into();
- let url: TString = TR::addr_mismatch__support_url.into();
- let button: TString = TR::buttons__quit.into();
-
- let text_style = theme::TEXT_REGULAR;
- let mut ops = OpTextLayout::new(text_style);
- ops.add_text_with_font(description, text_style.text_font)
- .add_newline()
- .add_text_with_font(url, theme::TEXT_MONO_MEDIUM.text_font);
+ let paragraphs = Paragraph::new(&theme::TEXT_REGULAR, TR::addr_mismatch__mismatch)
+ .into_paragraphs()
+ .with_placement(LinearPlacement::vertical());
- let screen = TextScreen::new(FormattedText::new(ops))
+ let screen = TextScreen::new(paragraphs)
.with_header(Header::new(title))
+ .with_hint(Hint::new_instruction(
+ TR::address__cancel_contact_support,
+ Some(theme::ICON_WARNING),
+ ))
.with_action_bar(ActionBar::new_double(
- Button::with_icon(theme::ICON_CROSS),
- Button::with_text(button),
+ Button::with_icon(theme::ICON_CHEVRON_LEFT),
+ Button::with_text(TR::buttons__cancel.into()).styled(theme::button_cancel()),
));
let layout = RootComponent::new(screen);
### core/src/trezor/ui/layouts/delizia/__init__.py
@@ -2226,26 +2226,30 @@ async def confirm_signverify(
external_menu=True,
)
- items: list[MenuLeaf] = [
- cancel_leaf(
- TR.buttons__cancel,
- confirm=lambda: trezorui_api.show_mismatch(
- title=TR.addr_mismatch__mismatch
- ),
- )
- ]
+ info_items: list[StrPropertyType] = []
if account is not None:
- items.append(create_info_menu_leaf(TR.words__account, account))
+ info_items.append((TR.words__account, account, False))
if path is not None:
- items.append(create_info_menu_leaf(TR.address_details__derivation_path, path))
- items.append(
- create_info_menu_leaf(
+ info_items.append((TR.address_details__derivation_path, path, False))
+ info_items.append(
+ (
TR.sign_message__message_size,
TR.sign_message__bytes_template.format(len(message)),
+ False,
)
)
- menu = Menu(items)
+ menu = Menu(
+ [
+ create_info_menu_leaf(TR.buttons__more_info, info_items),
+ cancel_leaf(
+ TR.buttons__cancel,
+ confirm=lambda: trezorui_api.show_mismatch(
+ title=TR.addr_mismatch__mismatch
+ ),
+ ),
+ ]
+ )
with address_ctx as address_layout:
await confirm_with_menu(address_layout, menu, br_name, br_code=BR_CODE_OTHER)
### core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -2337,65 +2337,53 @@ async def confirm_signverify(
account: str | None = None,
chunkify: bool = False,
) -> None:
+ from trezor.ui.layouts.menu import (
+ Menu,
+ cancel_leaf,
+ confirm_with_menu,
+ )
+
if verify:
address_title = TR.sign_message__verify_address
br_name = "verify_message"
else:
address_title = TR.sign_message__confirm_address
br_name = "sign_message"
- address_ctx = trezorui_api.confirm_value(
- title=address_title,
- value=address,
- description=None,
- is_data=True,
- verb=TR.buttons__continue,
- info=True,
- chunkify=chunkify,
- cancel=True,
- )
-
- items: list[StrPropertyType] = []
+ info_items: list[StrPropertyType] = []
if account is not None:
- items.append((TR.words__account, account, True))
+ info_items.append((TR.words__account, account, False))
if path is not None:
- items.append((TR.address_details__derivation_path, path, True))
- items.append(
+ info_items.append((TR.address_details__derivation_path, path, True))
+ info_items.append(
(
TR.sign_message__message_size,
TR.sign_message__bytes_template.format(len(message)),
True,
)
)
+ menu = Menu(
+ [
+ create_info_menu_leaf(TR.buttons__more_info, info_items),
+ cancel_leaf(
+ TR.buttons__cancel,
+ confirm=lambda: trezorui_api.show_mismatch(title=address_title),
+ ),
+ ]
+ )
- info_ctx = trezorui_api.show_info_with_cancel(
- title=TR.words__title_information,
- items=items,
- horizontal=True,
+ address_ctx = trezorui_api.confirm_value(
+ title=address_title,
+ value=address,
+ description=None,
+ is_data=True,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ external_menu=True,
)
- with address_ctx as address_layout, info_ctx as info_layout:
- while True:
- try:
- await with_info(
- address_layout, info_layout, br_name, br_code=BR_CODE_OTHER
- )
- except ActionCancelled:
- with trezorui_api.show_mismatch(
- title=TR.addr_mismatch__mismatch
- ) as layout:
- result = await interact(
- layout,
- None,
- raise_on_cancel=None,
- )
- assert result in (CONFIRMED, CANCELLED)
- # Right button aborts action, left goes back to showing address.
- if result is CONFIRMED:
- raise
- continue
- else:
- break
+ with address_ctx as address_layout:
+ await confirm_with_menu(address_layout, menu, br_name, BR_CODE_OTHER)
with trezorui_api.confirm_value(
title=TR.sign_message__confirm_message,
@@ -2405,10 +2393,10 @@ async def confirm_signverify(
prompt_screen=True,
hold=not verify,
info=False,
- cancel=True,
+ external_menu=True,
) as message_layout:
if message_layout.page_count() <= LONG_MSG_PAGE_THRESHOLD:
- await interact(message_layout, br_name, BR_CODE_OTHER)
+ await confirm_with_menu(message_layout, menu, br_name, BR_CODE_OTHER)
else:
await confirm_blob(
br_name,
### tests/input_flows.py
@@ -506,21 +506,22 @@ def input_flow_delizia(self) -> BRGeneratorType:
self.client.ui.visit_menu_items()
# cancel signature
self.debug.click(self.debug.screen_buttons.menu())
- self.debug.button_actions.navigate_to_menu_item(0)
+ self.debug.button_actions.navigate_to_menu_item(1)
# address mismatch? yes!
self.debug.swipe_up()
yield
def input_flow_eckhart(self) -> BRGeneratorType:
yield
- # go to info menu
- self.debug.click(self.debug.screen_buttons.menu())
- # close menu
+ # show address/message info (visits "More info", skips "Cancel")
+ self.client.ui.visit_menu_items()
+ # cancel signature
self.debug.click(self.debug.screen_buttons.menu())
- # cancel flow
- self.debug.press_no()
- # confirm cancel
- self.debug.press_yes()
+ self.debug.synchronize_at("VerticalMenu")
+ self.debug.button_actions.navigate_to_menu_item(1)
+ # address mismatch? - "Quit" button aborts the flow
+ self.debug.synchronize_at("TextScreen")
+ self.debug.click(self.debug.screen_buttons.ok())
yield
### tests/ui_tests/fixtures.json
[binary or diff unavailable]Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.