refactor(core): unify `ButtonRequest` handlers
What changed, and why it matters
This is a small internal code cleanup in the Trezor firmware's user-interface layer. It moves responsibility for sending on-screen confirmation prompts (ButtonRequest messages) into a single place inside the Layout class, and removes an older helper function. There is no indication this change fixes a security bug or introduces a vulnerability; it is a refactoring to simplify the code.
No security action required. Treat as routine refactoring. If reviewing, verify that ButtonRequest ordering and THP synchronization behavior remain unchanged in downstream tests.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors ButtonRequest handling in core/src/trezor/ui/init.py and core/src/trezor/ui/layouts/common.py. Previously, interact() in common.py explicitly sent the first ButtonRequest via _button_request() before awaiting the layout result. After the change, interact() stores the initial ButtonRequest parameters in layout.button_request_box, and Layout.button_request_task() handles sending it. The Layout class is also updated to always use loop.mailbox() (previously guarded by utils.USE_THP) and to keep the ButtonRequest task alive until get_result() awaits and closes it. The diff is +8/-25 lines and removes the _button_request() helper and its imports.
Changed components
core/src/trezor/ui/__init__.pycore/src/trezor/ui/layouts/common.pyInspect captured patch +8 / −25
diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py
index 32c08051a..a49b4b19d 100644
--- a/core/src/trezor/ui/__init__.py
+++ b/core/src/trezor/ui/__init__.py
@@ -227,7 +227,8 @@ class Layout(Generic[T]):
not_closed = set()
for task in self.tasks:
if not _close_all and task is self.button_request_task:
- # keep `ButtonRequest` handler alive to avoid THP desync
+ # Keep `ButtonRequest` handler alive.
+ # It will be awaited and closed in `get_result()`.
not_closed.add(task)
continue
if task != loop.this_task:
@@ -264,14 +265,14 @@ class Layout(Generic[T]):
is_done = None
try:
if self.context is not None and self.result_box.is_empty():
- if utils.USE_THP:
- is_done = loop.mailbox() # (see below)
+ is_done = loop.mailbox() # (see below)
self.button_request_task = self._handle_button_requests(is_done)
self._start_task(self.button_request_task)
result = await self.result_box
+ assert CURRENT_LAYOUT is None # the screen is blank now
- if utils.USE_THP and is_done is not None:
+ if is_done is not None:
# Make sure ButtonRequest is ACKed, before the result is returned.
# Otherwise, THP channel may become desynced (due to two consecutive writes).
if __debug__:
diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py
index 9b1e146d6..ee30acc86 100644
--- a/core/src/trezor/ui/layouts/common.py
+++ b/core/src/trezor/ui/layouts/common.py
@@ -3,11 +3,7 @@ from typing import TYPE_CHECKING
import trezorui_api
from trezor import ui, workflow
from trezor.enums import ButtonRequestType
-from trezor.messages import ButtonAck, ButtonRequest
-from trezor.wire import ActionCancelled, context
-
-if __debug__:
- from trezor import log
+from trezor.wire import ActionCancelled
if TYPE_CHECKING:
from typing import Any, Awaitable, Callable, Coroutine, TypeVar
@@ -21,20 +17,6 @@ if TYPE_CHECKING:
T = TypeVar("T")
-async def _button_request(
- br_name: str,
- code: ButtonRequestType = ButtonRequestType.Other,
- pages: int = 0,
-) -> None:
- if __debug__:
- log.info(__name__, "ButtonRequest sent: %s", br_name)
- await context.maybe_call(
- ButtonRequest(code=code, pages=pages or None, name=br_name), ButtonAck
- )
- if __debug__:
- log.info(__name__, "ButtonRequest acked: %s", br_name)
-
-
async def interact(
layout_obj: ui.LayoutObj[T],
br_name: str | None,
@@ -46,9 +28,9 @@ async def interact(
# start the layout
layout = ui.Layout(layout_obj)
layout.start()
- # send the button request
if br_name is not None:
- await _button_request(br_name, br_code, layout_obj.page_count())
+ # store the first button request to be sent
+ layout.button_request_box.put((br_code, br_name))
# wait for the layout result
result = await layout.get_result()
# raise an exception if the user cancelled the action
Why this scored 12/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.