feat(core): display recent THP `host_name` instead of BLE MAC address
What changed, and why it matters
This commit is a user-experience improvement for Trezor hardware wallets that connect over Bluetooth. It makes the device menu show a friendly computer name (like "Alice's MacBook") instead of a raw Bluetooth MAC address for recently paired devices. The change also stores that friendly name in device flash memory the first time pairing happens. There is no direct security bug visible in the diff, but it slightly increases the amount of untrusted data (the host name supplied by the connecting computer) that is saved and later displayed on the device screen.
No immediate security action is required. As a defensive review, verify that `paired_cache.store()` has appropriate flash wear-leveling and integrity checks, and confirm that `trim_str` handles Unicode correctly so that a 32-byte limit cannot be abused to produce misleading display strings. Review whether the cached host_name needs sanitization for screen rendering beyond length trimming.
Security signals we found
Untrusted host_name from pairing message is persisted to flash storage
New flash-backed cache keyed by BLE MAC address introduced
Host name length is capped at 32 bytes before storage
Pairing approval UI is shown before cache write, preserving existing user confirmation flow
No new cryptographic, authorization, or memory-safety changes are present
Evidence from the diff
The patch adds a host-name cache keyed by BLE MAC address. During THP (Trezor Host Protocol) pairing, handle_pairing_request now reads ctx.channel_ctx.iface_ctx.connected_addr(), shows the pairing dialog, and writes a ThpPairedCacheEntry(mac_addr, host_name) to flash via paired_cache.store(). The homescreen device menu loads this cache and displays the cached host_name instead of the reversed MAC. A new InterfaceContext.connected_addr() method returns the peer MAC only for the BLE interface. The host name is trimmed to 32 bytes with trim_str before storage. No input validation beyond the existing DataError for missing host_name is added, and the pairing dialog is shown before the cache write.
Changed components
core/src/apps/homescreen/device_menu.pycore/src/apps/thp/pairing.pycore/src/trezor/wire/thp/interface_context.pyTrezor Host Protocol (THP) pairing flowBLE interface contextInspect captured patch +48 / −5
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index c68684b2f..4bf30aa98 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -8,9 +8,11 @@ from trezorui_api import CANCELLED, DeviceMenuResult
BLE_MAX_BONDS = 8
-def _format_mac(ble_addr: bytes) -> str:
- """Internal MAC address representation is using reversed byte order."""
- return ":".join(f"{byte:02X}" for byte in reversed(ble_addr))
+def _get_hostname(ble_addr: bytes, hostname_map: dict[bytes, str]) -> str:
+ if (hostname := hostname_map.get(ble_addr)) is None:
+ # Internal MAC address representation is using reversed byte order.
+ return ":".join(f"{byte:02X}" for byte in reversed(ble_addr))
+ return hostname
def _find_device(connected_addr: bytes | None, bonds: list[bytes]) -> int | None:
@@ -25,6 +27,8 @@ def _find_device(connected_addr: bytes | None, bonds: list[bytes]) -> int | None
async def handle_device_menu() -> None:
from trezor import strings
+ from ..thp import paired_cache
+
is_initialized = storage_device.is_initialized()
led_configurable = is_initialized and utils.USE_RGB_LED
haptic_configurable = is_initialized and utils.USE_HAPTIC
@@ -38,7 +42,12 @@ async def handle_device_menu() -> None:
connected_idx = _find_device(connected_addr, bonds)
if __debug__:
log.debug(__name__, "connected: %s (%s)", connected_addr, connected_idx)
- paired_devices = [_format_mac(bond) for bond in bonds]
+
+ hostname_map = {e.mac_addr: e.host_name for e in paired_cache.load()}
+ if __debug__:
+ log.debug(__name__, "hostname_map: %s", hostname_map)
+
+ paired_devices = [_get_hostname(bond, hostname_map) for bond in bonds]
bluetooth_version = "2.3.1.1"
# ###
diff --git a/core/src/apps/thp/pairing.py b/core/src/apps/thp/pairing.py
index 61f01b608..266e62e97 100644
--- a/core/src/apps/thp/pairing.py
+++ b/core/src/apps/thp/pairing.py
@@ -115,9 +115,13 @@ async def handle_pairing_request(
if not message.host_name:
raise DataError("Missing host_name.")
+ peer_addr = ctx.channel_ctx.iface_ctx.connected_addr()
+ await ui.show_pairing_dialog(message.host_name, message.app_name)
ctx.host_name = message.host_name
ctx.app_name = message.app_name
- await ui.show_pairing_dialog(ctx.host_name, ctx.app_name)
+ if peer_addr is not None:
+ _cache_host_name(peer_addr, ctx.host_name)
+
await ctx.write(ThpPairingRequestApproved())
assert ThpSelectMethod.MESSAGE_WIRE_TYPE is not None
select_method_msg = await ctx.read(
@@ -474,3 +478,19 @@ def _check_method_is_allowed(ctx: PairingContext, method: ThpPairingMethod) -> N
def _check_method_is_selected(ctx: PairingContext, method: ThpPairingMethod) -> None:
if method is not ctx.selected_method:
raise ThpError("Not selected pairing method")
+
+
+def _cache_host_name(mac_addr: bytes, host_name: str) -> None:
+ from trezor.messages import ThpPairedCacheEntry
+ from trezor.strings import trim_str
+
+ from . import paired_cache
+
+ entries = paired_cache.load()
+ if any(mac_addr == e.mac_addr for e in entries):
+ # skip writing to flash if this MAC address is already cached
+ return
+
+ host_name = trim_str(host_name, max_bytes=32)
+ entries.append(ThpPairedCacheEntry(mac_addr=mac_addr, host_name=host_name))
+ paired_cache.store(entries)
diff --git a/core/src/trezor/wire/thp/interface_context.py b/core/src/trezor/wire/thp/interface_context.py
index 78968620e..a8cdf682c 100644
--- a/core/src/trezor/wire/thp/interface_context.py
+++ b/core/src/trezor/wire/thp/interface_context.py
@@ -204,6 +204,20 @@ class InterfaceContext:
header = PacketHeader.get_error_header(cid, length)
return self.write_payload(header, msg_data)
+ def connected_addr(self) -> bytes | None:
+ """
+ Return peer MAC address (if connected).
+
+ Currently supported by BLE (used for caching THP host names).
+ """
+ if utils.USE_BLE:
+ import trezorble as ble
+
+ if self._iface is ble.interface:
+ return ble.connected_addr()
+
+ return None
+
def _get_ctrl_byte(packet: bytes) -> int:
return packet[0]
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.