refactor: move clear signing UI to layouts
What changed, and why it matters
This commit is a code cleanup that moves the on-screen confirmation flow for Ethereum 'clear signing' into the device's UI layout modules. It also adds a final summary screen showing the maximum transaction fee. There is no direct evidence of a security vulnerability being fixed; it appears to be a user-interface refactor. However, adding the fee summary screen is a security-relevant improvement because it gives users more complete information before they approve a transaction.
Treat as a routine refactor with a minor security-hardening aspect (additional fee transparency). No urgent action is indicated, but reviewers should verify that the new `confirm_ethereum_clear_signing()` implementations preserve the original confirmation semantics and that the added summary screen cannot be bypassed. Confirm that the fallback to raw `msg.to` for unknown recipients is intentional and does not introduce spoofing risks (e.g., address formatting or truncation).
Security signals we found
UI flow refactor for transaction confirmation
New maximum fee summary screen added to clear-signing path
Recipient display fallback changed from None to raw msg.to for unknown addresses
No explicit security fix or vulnerability disclosure in commit message
[no changelog] tag present
Evidence from the diff
The change refactors Ethereum clear-signing UI by introducing a new helper require_confirm_clear_signing() in core/src/apps/ethereum/layout.py and a corresponding confirm_ethereum_clear_signing() implementation in each device layout (bolt, caesar, delizia, eckhart). Previously _handle_generic_ui() in clear_signing.py called confirm_action() and confirm_properties() directly; now it delegates to the layout helper. The new flow keeps the provider, intent, and property confirmations and adds a confirm_summary() call that displays maximum_fee. The recipient_str fallback changes from KNOWN_ADDRESSES.get(...) returning None for unknown addresses to defaulting to the raw msg.to address string. The commit is tagged ‘[no changelog]’ and contains no explicit security disclosure.
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__.pyInspect captured patch +128 / −16
diff --git a/core/src/apps/ethereum/clear_signing.py b/core/src/apps/ethereum/clear_signing.py
index 4ac995e9..2d0925ea 100644
--- a/core/src/apps/ethereum/clear_signing.py
+++ b/core/src/apps/ethereum/clear_signing.py
@@ -702,7 +702,9 @@ def _get_summary_handler(
# generic UI for any function that has a `DisplayFormat`
- return _handle_generic_ui(context, msg, definitions, address_bytes, token)
+ return _handle_generic_ui(
+ context, msg, definitions, address_bytes, token, maximum_fee
+ )
async def _handle_approve(
@@ -803,15 +805,12 @@ async def _handle_generic_ui(
definitions: Definitions,
address_bytes: bytes,
token: EthereumTokenInfo,
+ maximum_fee: str,
) -> None:
- from trezor.ui.layouts import (
- confirm_action,
- confirm_properties,
- )
-
from . import tokens
from .clear_signing_definitions import KNOWN_ADDRESSES
from .helpers import bytes_from_address
+ from .layout import require_confirm_clear_signing
_, fields = context.get_parameters_and_fields(
msg.address_n, msg.value, definitions, token
@@ -823,17 +822,18 @@ async def _handle_generic_ui(
properties_to_confirm.append(field)
if actual_token is tokens.UNKNOWN_TOKEN:
assert actual_token_address is not None
- token_address_str = address_from_bytes(actual_token_address, definitions.network)
- token_address_property: StrPropertyType = (TR.ethereum__token_contract, token_address_str, None)
+ token_address_str = address_from_bytes(
+ actual_token_address, definitions.network
+ )
+ token_address_property: StrPropertyType = (
+ TR.ethereum__token_contract,
+ token_address_str,
+ None,
+ )
properties_to_confirm.append(token_address_property)
- # TODO ??
- recipient_str = KNOWN_ADDRESSES.get(bytes_from_address(msg.to))
+ recipient_str = KNOWN_ADDRESSES.get(bytes_from_address(msg.to), msg.to)
- await confirm_action("confirm_contract", "Provider", recipient_str)
- await confirm_action("confirm_contract", "Intent", context.display_format.intent)
- await confirm_properties(
- "confirm_contract",
- "Confirm contract",
- properties_to_confirm,
+ await require_confirm_clear_signing(
+ recipient_str, context.display_format.intent, properties_to_confirm, maximum_fee
)
diff --git a/core/src/apps/ethereum/layout.py b/core/src/apps/ethereum/layout.py
index 33fc60a2..ad001ef9 100644
--- a/core/src/apps/ethereum/layout.py
+++ b/core/src/apps/ethereum/layout.py
@@ -80,6 +80,14 @@ async def require_confirm_approve(
)
+async def require_confirm_clear_signing(
+ recipient_str: str, intent: str, properties: list[StrPropertyType], maximum_fee: str
+) -> None:
+ from trezor.ui.layouts import confirm_ethereum_clear_signing
+
+ await confirm_ethereum_clear_signing(recipient_str, intent, properties, maximum_fee)
+
+
async def require_confirm_tx(
recipient: str | None,
total_amount: str,
diff --git a/core/src/trezor/ui/layouts/bolt/__init__.py b/core/src/trezor/ui/layouts/bolt/__init__.py
index 994c9218..84f7aa14 100644
--- a/core/src/trezor/ui/layouts/bolt/__init__.py
+++ b/core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1243,6 +1243,33 @@ if not utils.BITCOIN_ONLY:
TR.confirm_total__title_fee,
)
+ async def confirm_ethereum_clear_signing(
+ recipient_str: str,
+ intent: str,
+ properties: list[StrPropertyType],
+ maximum_fee: str,
+ ) -> 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)
+ await confirm_properties(
+ "confirm_contract",
+ TR.ethereum__confirm_contract,
+ properties,
+ )
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=with_colon(TR.send__maximum_fee),
+ extra_items=None,
+ extra_title=None,
+ ),
+ br_name="confirm_ethereum_tx",
+ )
+
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
diff --git a/core/src/trezor/ui/layouts/caesar/__init__.py b/core/src/trezor/ui/layouts/caesar/__init__.py
index 2b4a206a..65a090d6 100644
--- a/core/src/trezor/ui/layouts/caesar/__init__.py
+++ b/core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1221,6 +1221,33 @@ if not utils.BITCOIN_ONLY:
br_name="confirm_ethereum_approve",
)
+ async def confirm_ethereum_clear_signing(
+ recipient_str: str,
+ intent: str,
+ properties: list[StrPropertyType],
+ maximum_fee: str,
+ ) -> 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)
+ await confirm_properties(
+ "confirm_contract",
+ TR.ethereum__confirm_contract,
+ properties,
+ )
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=with_colon(TR.send__maximum_fee),
+ extra_items=None,
+ extra_title=None,
+ ),
+ br_name="confirm_ethereum_tx",
+ )
+
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
diff --git a/core/src/trezor/ui/layouts/delizia/__init__.py b/core/src/trezor/ui/layouts/delizia/__init__.py
index b1f9a535..f318e9ca 100644
--- a/core/src/trezor/ui/layouts/delizia/__init__.py
+++ b/core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1215,6 +1215,31 @@ if not utils.BITCOIN_ONLY:
TR.confirm_total__title_fee,
)
+ async def confirm_ethereum_clear_signing(
+ recipient_str: str,
+ intent: str,
+ properties: list[StrPropertyType],
+ maximum_fee: str,
+ ) -> None:
+ await confirm_action("confirm_contract", TR.words__provider, recipient_str)
+ await confirm_action("confirm_contract", TR.words__intent, intent)
+ await confirm_properties(
+ "confirm_contract",
+ TR.ethereum__confirm_contract,
+ properties,
+ )
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=TR.send__maximum_fee,
+ extra_items=None,
+ extra_title=None,
+ ),
+ br_name="confirm_ethereum_tx",
+ )
+
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
diff --git a/core/src/trezor/ui/layouts/eckhart/__init__.py b/core/src/trezor/ui/layouts/eckhart/__init__.py
index 434fd54e..2c74ff2c 100644
--- a/core/src/trezor/ui/layouts/eckhart/__init__.py
+++ b/core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -1260,6 +1260,31 @@ if not utils.BITCOIN_ONLY:
TR.confirm_total__title_fee,
)
+ async def confirm_ethereum_clear_signing(
+ recipient_str: str,
+ intent: str,
+ properties: list[StrPropertyType],
+ maximum_fee: str,
+ ) -> None:
+ await confirm_action("confirm_contract", TR.words__provider, recipient_str)
+ await confirm_action("confirm_contract", TR.words__intent, intent)
+ await confirm_properties(
+ "confirm_contract",
+ TR.ethereum__confirm_contract,
+ properties,
+ )
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=TR.send__maximum_fee,
+ extra_items=None,
+ extra_title=None,
+ ),
+ br_name="confirm_ethereum_tx",
+ )
+
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
Why this scored 18/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.