feat(core): notify host about channel replacement [no changelog]
What changed, and why it matters
This commit adds a new device state flag so the Trezor hardware wallet can tell a connected computer: 'this session replaced an existing channel, so the user does not need to confirm again.' The host-side library is updated to recognize the new state. It is a feature change, not an obvious vulnerability fix, but it touches the authentication/authorization boundary between device and host.
Review the THP specification and the `is_channel_to_replace()` logic to confirm that bypassing user confirmation is only possible when the replacement is cryptographically authorized by the existing paired channel. Treat this as a design review item, not as a confirmed vulnerability.
Security signals we found
Change to pairing/authentication state machine
Introduction of an auto-connect / no-user-confirmation path
Host-side assertion relaxed to accept a new state value
No changelog entry despite security-relevant area
Evidence from the diff
In the Trezor Host Protocol (THP) implementation, the device now returns a third handshake-completion state, _TREZOR_STATE_PAIRED_AUTOCONNECT (0x02), when a new channel is replacing an existing one. The device code skips user confirmation in that case. The Python host library now accepts all three states and treats any non-unpaired state as paired. The change is additive and appears to implement a deliberate UX flow rather than patch a security bug.
Changed components
core/src/trezor/wire/thp/received_message_handler.pypython/src/trezorlib/transport/thp/protocol_v2.pyInspect captured patch +14 / −2
diff --git a/core/src/trezor/wire/thp/received_message_handler.py b/core/src/trezor/wire/thp/received_message_handler.py
index c551281f..dd37bf21 100644
--- a/core/src/trezor/wire/thp/received_message_handler.py
+++ b/core/src/trezor/wire/thp/received_message_handler.py
@@ -189,6 +189,9 @@ async def _handle_state_handshake(
)
trezor_state = _TREZOR_STATE_PAIRED
ctx.credential = credential
+ if ctx.is_channel_to_replace():
+ # When replacing existing channel, user confirmation is not needed
+ trezor_state = _TREZOR_STATE_PAIRED_AUTOCONNECT
else:
ctx.credential = None
except DataError as e:
diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py
index b200a60f..6d59bedd 100644
--- a/python/src/trezorlib/transport/thp/protocol_v2.py
+++ b/python/src/trezorlib/transport/thp/protocol_v2.py
@@ -38,6 +38,15 @@ DEFAULT_SESSION_ID: int = 0
MAX_RETRANSMISSION_COUNT = 50
+TREZOR_STATE_UNPAIRED = b"\x00"
+TREZOR_STATE_PAIRED = b"\x01"
+TREZOR_STATE_PAIRED_AUTOCONNECT = b"\x02"
+TREZOR_STATES = [
+ TREZOR_STATE_UNPAIRED,
+ TREZOR_STATE_PAIRED,
+ TREZOR_STATE_PAIRED_AUTOCONNECT,
+]
+
if t.TYPE_CHECKING:
pass
MT = t.TypeVar("MT", bound=protobuf.MessageType)
@@ -269,9 +278,9 @@ class ProtocolV2Channel(Channel):
if not header.is_handshake_comp_response():
LOG.error("Received message is not a valid handshake completion response")
trezor_state = self._noise.decrypt(bytes(data))
- assert trezor_state == b"\x00" or trezor_state == b"\x01"
+ assert trezor_state in TREZOR_STATES
self._send_ack_bit(bit=1)
- self._is_paired = bool(int.from_bytes(trezor_state, "big"))
+ self._is_paired = trezor_state != TREZOR_STATE_UNPAIRED
def _read_ack(self) -> None:
header, payload = self._read_until_valid_crc_check()
Why this scored 27/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.