feat(clear signing): show signing context on all flows
What changed, and why it matters
This commit changes how Trezor hardware wallets display Ethereum transaction details on screen during 'clear signing' flows. It adds more context—such as the contract address, account name, and derivation path—to the confirmation screens across all supported device models (Bolt, Caesar, Delizia, Eckhart). The change is purely user-interface related and does not alter transaction validation, cryptography, or how funds are moved.
No security action required. Treat as a normal feature/UI review. Verify updated UI test fixtures reflect the new screens and that the additional context is displayed consistently across device models.
Security signals we found
UI-only change with no modification to transaction validation or signing logic
Adds previously hidden context (contract address, account, derivation path) to user confirmation screens
May reduce phishing risk by making the contract address and signing account more visible, but this is a defensive UX improvement rather than a vulnerability fix
No changelog entry, but commit title clearly describes a feature
Evidence from the diff
The patch extends the Ethereum clear-signing UI to surface additional signing context on every confirmation flow. It threads the contract address, account label, and BIP-32 account path through clear_signing.py and layout.py, then updates each device-specific layout implementation to show these items via info menus or summary screens. Button-request names are also unified under ethereum/clear_signing. There is no change to transaction parsing, signature generation, or security checks.
Changed components
core/src/apps/ethereum/clear_signing.pycore/src/apps/ethereum/layout.pycore/src/trezor/ui/layouts/bolt/__init__.pycore/src/trezor/ui/layouts/caesar/__init__.pycore/src/trezor/ui/layouts/delizia/__init__.pycore/src/trezor/ui/layouts/eckhart/__init__.pytests/ui_tests/fixtures.jsonInspect captured patch +12914 / −12727
### core/src/apps/ethereum/clear_signing.py
@@ -1390,6 +1390,7 @@ async def try_confirm(
await _handle_generic_ui(
calldata,
display_format,
+ contract_address,
msg,
defs,
maximum_fee,
@@ -1548,6 +1549,7 @@ async def _handle_transfer(
async def _handle_generic_ui(
calldata: memoryview,
display_format: DisplayFormat,
+ address_bytes: bytes,
msg: MsgInSignTx,
defs: Definitions,
maximum_fee: str,
@@ -1593,10 +1595,15 @@ async def _handle_generic_ui(
else display_format.provider_name
)
+ account, account_path = get_account_and_path(msg.address_n)
+
await require_confirm_clear_signing(
- recipient_str,
- display_format.intent,
- properties_to_confirm,
- maximum_fee,
- None if value_shown_as_field else amount,
+ recipient_str=recipient_str,
+ intent=display_format.intent,
+ properties=properties_to_confirm,
+ maximum_fee=maximum_fee,
+ contract_address=address_from_bytes(address_bytes, defs.network),
+ amount=None if value_shown_as_field else amount,
+ account=account,
+ account_path=account_path,
)
### core/src/apps/ethereum/layout.py
@@ -90,12 +90,22 @@ async def require_confirm_clear_signing(
intent: str,
properties: list[StrPropertyType],
maximum_fee: str,
+ contract_address: str,
amount: str | None = None,
+ account: str | None = None,
+ account_path: str | None = None,
) -> None:
from trezor.ui.layouts import confirm_ethereum_clear_signing
await confirm_ethereum_clear_signing(
- recipient_str, intent, properties, maximum_fee, amount
+ recipient_str,
+ intent,
+ properties,
+ maximum_fee,
+ contract_address,
+ amount,
+ account,
+ account_path,
)
### core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1277,29 +1277,61 @@ async def confirm_ethereum_clear_signing(
intent: str,
properties: list[StrPropertyType],
maximum_fee: str,
+ contract_address: str,
amount: str | None = None,
+ account: str | None = None,
+ account_path: str | None = None,
) -> None:
from ..properties import with_colon
- await confirm_action("confirm_contract", TR.words__provider, recipient_str)
- await confirm_action("confirm_contract", TR.words__intent, intent)
+ br_name = "ethereum/clear_signing"
+
+ info_items: list[StrPropertyType] = []
+ if account_path:
+ assert account is not None
+ info_items.append((TR.words__account, account, None))
+ info_items.append((TR.address_details__derivation_path, account_path, None))
+ info_items.append((TR.ethereum__contract_address, contract_address, None))
+ info_items = with_colon(info_items)
+
+ def _info_ctx() -> trezorui_api.LayoutContext[ui.UiResult]:
+ return trezorui_api.show_info_with_cancel(
+ title=TR.address_details__account_info,
+ items=info_items,
+ )
+
+ await confirm_action(f"{br_name}/provider", TR.words__provider, recipient_str)
+ await confirm_action(f"{br_name}/intent", TR.words__intent, intent)
if properties:
- await confirm_properties(
- "confirm_contract",
- TR.ethereum__confirm_contract,
- properties,
+ props_ctx = trezorui_api.confirm_properties(
+ title=TR.ethereum__confirm_contract,
+ items=with_colon(properties),
+ hold=False,
+ external_menu=bool(info_items),
)
- with trezorui_api.confirm_summary(
+ with props_ctx as props_layout, _info_ctx() as info_layout:
+ await with_info(
+ props_layout,
+ info_layout,
+ br_name,
+ ButtonRequestType.ConfirmOutput,
+ )
+
+ summary_ctx = trezorui_api.confirm_summary(
amount=amount,
amount_label=with_colon(TR.words__amount) if amount is not None else None,
fee=maximum_fee,
fee_label=with_colon(TR.send__maximum_fee),
+ account_items=info_items or None,
extra_items=None,
extra_title=None,
- ) as layout:
- await raise_if_not_confirmed(
- layout,
- br_name="confirm_ethereum_tx",
+ )
+ with summary_ctx as summary_layout, _info_ctx() as info_layout:
+ await with_info(
+ summary_layout,
+ info_layout,
+ f"{br_name}/summary",
+ BR_CODE_OTHER,
)
async def confirm_ethereum_staking_tx(
### core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1253,31 +1253,64 @@ async def confirm_ethereum_clear_signing(
intent: str,
properties: list[StrPropertyType],
maximum_fee: str,
+ contract_address: str,
amount: str | None = None,
+ account: str | None = None,
+ account_path: str | None = None,
) -> None:
+ from trezor.ui.layouts.menu import Menu, confirm_with_menu
+
from ..properties import with_colon
- await confirm_action("confirm_contract", TR.words__provider, recipient_str)
- await confirm_action("confirm_contract", TR.words__intent, intent)
- if properties:
- await confirm_properties(
- "confirm_contract",
- TR.ethereum__confirm_contract,
- properties,
+ br_name = "ethereum/clear_signing"
+
+ account_properties: list[StrPropertyType] = []
+ if account_path:
+ assert account is not None
+ account_properties.append((TR.words__account, account, None))
+ account_properties.append(
+ (TR.address_details__derivation_path, account_path, None)
)
+ def _menu() -> Menu[None]:
+ menu_items: list[MenuLeaf[None]] = []
+ if account_properties:
+ menu_items.append(
+ create_info_menu_leaf(
+ TR.address_details__account_info,
+ with_colon(account_properties),
+ )
+ )
+ menu_items.append(
+ create_info_menu_leaf(TR.ethereum__contract_address, contract_address)
+ )
+ return Menu(menu_items)
+
+ await confirm_action(f"{br_name}/provider", TR.words__provider, recipient_str)
+ await confirm_action(f"{br_name}/intent", TR.words__intent, intent)
+ if properties:
+ with trezorui_api.confirm_properties(
+ title=TR.ethereum__confirm_contract,
+ items=with_colon(properties),
+ hold=False,
+ external_menu=True,
+ ) as layout:
+ await confirm_with_menu(
+ layout, _menu(), br_name, ButtonRequestType.ConfirmOutput
+ )
+
+ # NOTE: `account_items`/`extra_items` cannot be combined with an external
+ # menu on Caesar, so all the context goes into the menu.
with trezorui_api.confirm_summary(
amount=amount,
amount_label=with_colon(TR.words__amount) if amount is not None else None,
fee=maximum_fee,
fee_label=with_colon(TR.send__maximum_fee),
extra_items=None,
extra_title=None,
+ external_menu=True,
) as layout:
- await raise_if_not_confirmed(
- layout,
- br_name="confirm_ethereum_tx",
- )
+ await confirm_with_menu(layout, _menu(), f"{br_name}/summary")
async def confirm_ethereum_staking_tx(
title: str,
### core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1252,27 +1252,85 @@ async def confirm_ethereum_clear_signing(
intent: str,
properties: list[StrPropertyType],
maximum_fee: str,
+ contract_address: str,
amount: str | None = None,
+ account: str | None = None,
+ account_path: str | None = None,
) -> None:
- await confirm_action("confirm_contract", TR.words__provider, recipient_str)
- await confirm_action("confirm_contract", TR.words__intent, intent)
- if properties:
- await confirm_properties(
- "confirm_contract",
- TR.ethereum__confirm_contract,
- properties,
+ from trezor.ui.layouts.menu import Menu, cancel_leaf, confirm_with_menu
+
+ br_name = "ethereum/clear_signing"
+
+ account_properties: list[StrPropertyType] = []
+ if account_path:
+ assert account is not None
+ account_properties.append((TR.words__account, account, None))
+ account_properties.append(
+ (TR.address_details__derivation_path, account_path, None)
+ )
+ contract_properties: list[StrPropertyType] = [
+ (TR.ethereum__contract_address, contract_address, None)
+ ]
+
+ def _menu() -> Menu[None]:
+ menu_items: list[MenuLeaf[None]] = [cancel_leaf(TR.buttons__cancel)]
+ if account_properties:
+ menu_items.append(
+ create_info_menu_leaf(
+ TR.address_details__account_info,
+ account_properties,
+ TR.address_details__account_info,
+ )
+ )
+ menu_items.append(
+ create_info_menu_leaf(
+ TR.ethereum__contract_address,
+ contract_address,
+ TR.ethereum__contract_address,
+ )
)
+ return Menu(menu_items)
+
+ for screen, title, value in (
+ ("provider", TR.words__provider, recipient_str),
+ ("intent", TR.words__intent, intent),
+ ):
+ with trezorui_api.confirm_action(
+ title=title,
+ action=value,
+ description=None,
+ prompt_title=title,
+ external_menu=True,
+ ) as layout:
+ await confirm_with_menu(
+ layout, _menu(), f"{br_name}/{screen}", BR_CODE_OTHER
+ )
+
+ if properties:
+ with trezorui_api.confirm_properties(
+ title=TR.ethereum__confirm_contract,
+ subtitle=None,
+ items=properties,
+ hold=False,
+ external_menu=True,
+ ) as layout:
+ await confirm_with_menu(
+ layout, _menu(), br_name, ButtonRequestType.ConfirmOutput
+ )
+
with trezorui_api.confirm_summary(
amount=amount,
amount_label=TR.words__amount if amount is not None else None,
fee=maximum_fee,
fee_label=TR.send__maximum_fee,
- extra_items=None,
- extra_title=None,
+ account_items=account_properties or None,
+ account_title=TR.address_details__account_info,
+ extra_items=contract_properties,
+ extra_title=TR.ethereum__contract_address,
) as layout:
await raise_if_not_confirmed(
layout,
- br_name="confirm_ethereum_tx",
+ br_name=f"{br_name}/summary",
)
async def confirm_ethereum_staking_tx(
### core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -1305,28 +1305,75 @@ async def confirm_ethereum_clear_signing(
intent: str,
properties: list[StrPropertyType],
maximum_fee: str,
+ contract_address: str,
amount: str | None = None,
+ account: str | None = None,
+ account_path: str | None = None,
) -> None:
- await confirm_action("confirm_contract", TR.words__provider, recipient_str)
- await confirm_action("confirm_contract", TR.words__intent, intent)
- if properties:
- await confirm_properties(
- "confirm_contract",
- TR.ethereum__confirm_contract,
- properties,
+ from trezor.ui.layouts.menu import Menu, cancel_leaf, confirm_with_menu
+
+ br_name = "ethereum/clear_signing"
+
+ def _menu() -> Menu[None]:
+ menu_items: list[MenuLeaf[None]] = []
+ account_properties = _get_account_info_items(account, account_path)
+ if account_properties:
+ menu_items.append(
+ create_info_menu_leaf(
+ TR.address_details__account_info,
+ account_properties,
+ title=TR.address_details__account_info,
+ subtitle=TR.send__send_from,
+ )
+ )
+ menu_items.append(
+ create_info_menu_leaf(
+ TR.ethereum__contract_address,
+ contract_address,
+ title=TR.ethereum__contract_address,
+ )
)
+ menu_items.append(cancel_leaf(TR.buttons__cancel))
+ return Menu(menu_items)
+
+ for screen, title, value in (
+ ("provider", TR.words__provider, recipient_str),
+ ("intent", TR.words__intent, intent),
+ ):
+ with trezorui_api.confirm_action(
+ title=title,
+ action=value,
+ description=None,
+ cancel=False,
+ prompt_title=title,
+ external_menu=True,
+ ) as layout:
+ await confirm_with_menu(
+ layout, _menu(), f"{br_name}/{screen}", BR_CODE_OTHER
+ )
+
+ if properties:
+ with trezorui_api.confirm_properties(
+ title=TR.ethereum__confirm_contract,
+ subtitle=None,
+ items=properties,
+ hold=False,
+ external_menu=True,
+ ) as layout:
+ await confirm_with_menu(
+ layout, _menu(), br_name, ButtonRequestType.ConfirmOutput
+ )
+
with trezorui_api.confirm_summary(
amount=amount,
amount_label=TR.words__amount if amount is not None else None,
fee=maximum_fee,
fee_label=TR.send__maximum_fee,
extra_items=None,
extra_title=None,
+ external_menu=True,
) as layout:
- await raise_if_not_confirmed(
- layout,
- br_name="confirm_ethereum_tx",
- )
+ await confirm_with_menu(layout, _menu(), f"{br_name}/summary")
async def confirm_ethereum_vault_tx(
title: str,
### tests/ui_tests/fixtures.json
[binary or diff unavailable]Why this scored 17/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.