feat(ethereum): add receiver and owner address
What changed, and why it matters
This commit changes how Trezor hardware wallets display Ethereum vault transactions. It adds extra on-screen confirmation steps showing the 'receiver' and 'owner' addresses for certain vault operations, and it makes address values easier to read by chunking them. The change appears to be a user-interface improvement that increases transparency rather than a security fix. There is no evidence in the commit of a vulnerability or exploit.
No immediate action required. Treat as a normal feature/UI improvement. If reviewing for security, verify that the new `receiver_bytes` and `owner_bytes` are correctly validated before being passed to layout functions and that chunking does not truncate or misrepresent addresses.
Security signals we found
Additional user confirmation screens for receiver and owner addresses
Address chunking enabled for vault/receiver/owner values
Non-owned address warning footer added for receiver and owner in eckhart layout
No changelog entry, but title describes a feature addition
Evidence from the diff
The patch extends require_confirm_vault_tx in apps/ethereum/layout.py to accept optional receiver_bytes and owner_bytes, converts them to human-readable addresses, and passes them to the UI layout functions. The four device-specific layout implementations (bolt, caesar, delizia, eckhart) now add extra confirm_value screens for these addresses when present, with chunkify enabled and, in some layouts, a ‘not yours’ warning footer. The vault_is_address flag controls whether the vault identifier itself is rendered as chunked address data. No cryptographic, parsing, or authorization logic is changed; this is purely additional user confirmation.
Changed components
core/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 +182 / −7
### core/src/apps/ethereum/layout.py
@@ -29,8 +29,8 @@
EthereumTokenInfo,
PaymentRequest,
)
- from trezor.ui.layouts import StrPropertyType
from trezor.ui.layouts.properties import AboveThreshold
+ from trezorui_api import StrPropertyType
async def require_confirm_approve(
@@ -272,7 +272,11 @@ async def require_confirm_vault_tx(
vault_str: str,
token: EthereumTokenInfo,
func_sig: AnyBytes,
+ vault_is_address: bool = False,
extra_data: AnyBytes | None = None,
+ receiver_bytes: AnyBytes | None = None,
+ owner_bytes: AnyBytes | None = None,
+ chunkify: bool = True,
) -> None:
from .yielding import FUNC_SIG_DEPOSIT, FUNC_SIG_REDEEM, FUNC_SIG_WITHDRAW
@@ -304,11 +308,17 @@ async def require_confirm_vault_tx(
"0x" + extra_data.hex() if extra_data is not None else None
)
+ receiver_address = (
+ address_from_bytes(receiver_bytes, network) if receiver_bytes else None
+ )
+ owner_address = address_from_bytes(owner_bytes, network) if owner_bytes else None
+
await confirm_ethereum_vault_tx(
title=title,
intro_question=intro_question,
verb=verb,
vault_str=vault_str,
+ vault_is_address=vault_is_address,
amount=amount,
amount_label=amount_label,
account=account,
@@ -318,6 +328,9 @@ async def require_confirm_vault_tx(
chain=network.name,
br_name=br_name,
extra_data=extra_data_str,
+ receiver_address=receiver_address,
+ owner_address=owner_address,
+ chunkify=chunkify,
)
### core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1404,6 +1404,10 @@ async def confirm_ethereum_vault_tx(
br_name: str = "ethereum/vault",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
extra_data: str | None = None,
+ receiver_address: str | None = None,
+ owner_address: str | None = None,
+ chunkify: bool = True,
+ vault_is_address: bool = False,
) -> None:
account_properties: list[StrPropertyType] = []
@@ -1433,6 +1437,7 @@ async def confirm_ethereum_vault_tx(
br_name=f"{br_name}/vault",
br_code=br_code,
verb=TR.buttons__continue,
+ chunkify=chunkify and vault_is_address,
)
await confirm_properties(
@@ -1445,6 +1450,30 @@ async def confirm_ethereum_vault_tx(
br_code=br_code,
)
+ if receiver_address is not None:
+ await confirm_value(
+ title=title,
+ value=receiver_address,
+ description=TR.words__recipient,
+ br_name=f"{br_name}/receiver_address",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ cancel=True,
+ )
+
+ if owner_address is not None:
+ await confirm_value(
+ title=title,
+ value=owner_address,
+ description=TR.ethereum__vault_owner_address,
+ br_name=f"{br_name}/owner_address",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ cancel=True,
+ )
+
if extra_data is not None:
await confirm_value(
title=title,
### core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1387,6 +1387,10 @@ async def confirm_ethereum_vault_tx(
br_name: str = "ethereum/vault",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
extra_data: str | None = None,
+ receiver_address: str | None = None,
+ owner_address: str | None = None,
+ chunkify: bool = True,
+ vault_is_address: bool = False,
) -> None:
from ..properties import with_colon
@@ -1417,6 +1421,7 @@ async def confirm_ethereum_vault_tx(
description=verb,
verb=TR.buttons__continue,
cancel=True,
+ chunkify=chunkify and vault_is_address,
br_name=f"{br_name}/vault",
br_code=br_code,
)
@@ -1430,6 +1435,30 @@ async def confirm_ethereum_vault_tx(
],
)
+ if receiver_address is not None:
+ await confirm_value(
+ title=title,
+ value=receiver_address,
+ description=TR.words__recipient,
+ br_name=f"{br_name}/receiver_address",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ cancel=True,
+ )
+
+ if owner_address is not None:
+ await confirm_value(
+ title=title,
+ value=owner_address,
+ description=TR.ethereum__vault_owner_address,
+ br_name=f"{br_name}/owner_address",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ cancel=True,
+ )
+
if extra_data is not None:
await confirm_value(
title=title,
### core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1397,6 +1397,10 @@ async def confirm_ethereum_vault_tx(
br_name: str = "ethereum/vault",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
extra_data: str | None = None,
+ receiver_address: str | None = None,
+ owner_address: str | None = None,
+ chunkify: bool = True,
+ vault_is_address: bool = False,
) -> None:
from trezor.ui.layouts.menu import Menu, cancel_leaf, interact_with_menu
@@ -1431,7 +1435,8 @@ async def _step2() -> trezorui_api.UiResult:
with trezorui_api.confirm_value(
title=title,
value=vault_str,
- is_data=False,
+ is_data=vault_is_address,
+ chunkify=chunkify and vault_is_address,
description=verb,
verb="",
) as layout:
@@ -1460,6 +1465,46 @@ async def _step3() -> trezorui_api.UiResult:
steps = [_step1, _step2, _step3]
+ if receiver_address is not None:
+
+ async def _step3a() -> trezorui_api.UiResult:
+ with trezorui_api.confirm_value(
+ title=title,
+ value=receiver_address,
+ description=TR.words__recipient,
+ is_data=True,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ ) as layout:
+ return await interact_with_menu(
+ layout,
+ Menu(menu_items),
+ f"{br_name}/receiver_address",
+ br_code,
+ )
+
+ steps.append(_step3a)
+
+ if owner_address is not None:
+
+ async def _step3b() -> trezorui_api.UiResult:
+ with trezorui_api.confirm_value(
+ title=title,
+ value=owner_address,
+ description=TR.ethereum__vault_owner_address,
+ is_data=True,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ ) as layout:
+ return await interact_with_menu(
+ layout,
+ Menu(menu_items),
+ f"{br_name}/owner_address",
+ br_code,
+ )
+
+ steps.append(_step3b)
+
if extra_data is not None:
async def _step4() -> trezorui_api.UiResult:
### core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -19,10 +19,11 @@
from typing import NoReturn, TypeVar
from trezor.ui.layouts.menu import MenuLeaf
+ from trezorui_api import PropertyType, StrPropertyType
from apps.stellar.tokens import StellarToken
- from ..common import ExceptionType, PropertyType, StrPropertyType
+ from ..common import ExceptionType
from ..properties import AboveThreshold
from ..slip24 import Refund, Trade
@@ -1396,6 +1397,10 @@ async def confirm_ethereum_vault_tx(
br_name: str = "ethereum/vault",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
extra_data: str | None = None,
+ receiver_address: str | None = None,
+ owner_address: str | None = None,
+ chunkify: bool = True,
+ vault_is_address: bool = False,
) -> None:
from trezor.ui.layouts.menu import Menu, cancel_leaf, interact_with_menu
@@ -1428,12 +1433,18 @@ async def _step1() -> trezorui_api.UiResult:
)
async def _step2() -> trezorui_api.UiResult:
- with trezorui_api.confirm_with_info(
+ vault_layout = trezorui_api.confirm_value(
title=title,
- subtitle=verb,
- items=[(vault_str, True)],
+ value=vault_str,
+ description=verb,
+ is_data=True,
+ hold=False,
verb=TR.buttons__continue,
- ) as layout:
+ chunkify=vault_is_address and chunkify,
+ cancel=False,
+ external_menu=True,
+ )
+ with vault_layout as layout:
return await interact_with_menu(
layout,
Menu(menu_items),
@@ -1450,6 +1461,7 @@ async def _step3() -> trezorui_api.UiResult:
],
hold=False,
verb=TR.buttons__continue,
+ external_menu=True,
) as layout:
return await interact_with_menu(
layout,
@@ -1460,6 +1472,52 @@ async def _step3() -> trezorui_api.UiResult:
steps = [_step1, _step2, _step3]
+ if receiver_address is not None:
+
+ async def _step3a() -> trezorui_api.UiResult:
+ with trezorui_api.confirm_value(
+ title=title,
+ value=receiver_address,
+ description=TR.words__recipient,
+ is_data=True,
+ hold=False,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ footer=(TR.address__warning_not_yours, True),
+ external_menu=True,
+ ) as layout:
+ return await interact_with_menu(
+ layout,
+ Menu(menu_items),
+ f"{br_name}/receiver_address",
+ br_code,
+ )
+
+ steps.append(_step3a)
+
+ if owner_address is not None:
+
+ async def _step3b() -> trezorui_api.UiResult:
+ with trezorui_api.confirm_value(
+ title=title,
+ value=owner_address,
+ description=TR.ethereum__vault_owner_address,
+ is_data=True,
+ hold=False,
+ verb=TR.buttons__continue,
+ chunkify=chunkify,
+ footer=(TR.address__warning_not_yours, True),
+ external_menu=True,
+ ) as layout:
+ return await interact_with_menu(
+ layout,
+ Menu(menu_items),
+ f"{br_name}/owner_address",
+ br_code,
+ )
+
+ steps.append(_step3b)
+
if extra_data is not None:
async def _step4() -> trezorui_api.UiResult:
@@ -1468,6 +1526,7 @@ async def _step4() -> trezorui_api.UiResult:
items=[(TR.ethereum__calldata_suffix, extra_data, True)],
hold=False,
verb=TR.buttons__continue,
+ external_menu=True,
) as layout:
return await interact_with_menu(
layout,Why this scored 19/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.