From a303f9c98a5aa57f457497bd613c941a022707fb Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 18 Aug 2025 15:38:37 +0300 Subject: [PATCH 1/4] refactor(python): don't return SEQ bit for irrelevant THP messages [no changelog] --- python/src/trezorlib/transport/thp/control_byte.py | 8 +++++++- python/src/trezorlib/transport/thp/protocol_v2.py | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/python/src/trezorlib/transport/thp/control_byte.py b/python/src/trezorlib/transport/thp/control_byte.py index 01d9ad07046..59bc0ea8e13 100644 --- a/python/src/trezorlib/transport/thp/control_byte.py +++ b/python/src/trezorlib/transport/thp/control_byte.py @@ -14,6 +14,8 @@ # You should have received a copy of the License along with this library. # If not, see . +from typing import Optional + CODEC_V1 = 0x3F CONTINUATION_PACKET = 0x80 HANDSHAKE_INIT_REQ = 0x00 @@ -51,7 +53,11 @@ def add_ack_bit_to_ctrl_byte(ctrl_byte: int, ack_bit: int) -> int: raise Exception("Unexpected acknowledgement bit") -def get_seq_bit(ctrl_byte: int) -> int: +def get_seq_bit(ctrl_byte: int) -> Optional[int]: + if ctrl_byte & 0xE0: + # not all message types contain SEQ bit + return None + return (ctrl_byte & 0x10) >> 4 diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py index 6573d69715d..c1c44695d43 100644 --- a/python/src/trezorlib/transport/thp/protocol_v2.py +++ b/python/src/trezorlib/transport/thp/protocol_v2.py @@ -350,13 +350,15 @@ def read_and_decrypt( + ")" ) + seq_bit = control_byte.get_seq_bit(header.ctrl_byte) + assert seq_bit is not None LOG.debug( "--> Get sequence bit %d %s %s", - control_byte.get_seq_bit(header.ctrl_byte), + seq_bit, "from control byte", hexlify(header.ctrl_byte.to_bytes(1, "big")).decode(), ) - self._send_ack_bit(bit=control_byte.get_seq_bit(header.ctrl_byte)) + self._send_ack_bit(bit=seq_bit) message = self._noise.decrypt(bytes(raw_payload)) session_id = message[0] From 3406932fd790f3542ef817020b9313e28de62240 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 18 Aug 2025 15:39:11 +0300 Subject: [PATCH 2/4] refactor(python): inline `ProtocolV2Channel.prepare_channel_without_pairing()` [no changelog] --- python/src/trezorlib/transport/thp/protocol_v2.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py index c1c44695d43..c34a3e34b8c 100644 --- a/python/src/trezorlib/transport/thp/protocol_v2.py +++ b/python/src/trezorlib/transport/thp/protocol_v2.py @@ -68,10 +68,11 @@ def __init__( prepare_channel_without_pairing: bool = True, ) -> None: super().__init__(transport, mapping) + self._reset_sync_bits() if prepare_channel_without_pairing: - self.trezor_state = self.prepare_channel_without_pairing( - credential=credential - ) + # allow skipping unrelated response packets (e.g. in case of retransmissions) + self._do_channel_allocation(retries=MAX_RETRANSMISSION_COUNT) + self.trezor_state = self._do_handshake(credential=credential) def get_channel(self) -> ProtocolV2Channel: if not self._has_valid_channel: @@ -127,12 +128,6 @@ def _read_message(self, message_type: type[MT], timeout: float | None = None) -> assert isinstance(msg, message_type) return msg - def prepare_channel_without_pairing(self, credential: bytes | None = None) -> int: - self._reset_sync_bits() - # allow skipping unrelated response packets (e.g. in case of retransmissions) - self._do_channel_allocation(retries=MAX_RETRANSMISSION_COUNT) - return self._do_handshake(credential=credential) - def _reset_sync_bits(self) -> None: self.sync_bit_send = 0 self.sync_bit_receive = 0 From 8b19697a39467b3aedfaa971572201c696028679 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Mon, 18 Aug 2025 13:24:03 +0300 Subject: [PATCH 3/4] feat(python): ignore retransmitted THP payloads [no changelog] --- .../trezorlib/transport/thp/protocol_v2.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py index c34a3e34b8c..c805e249f2a 100644 --- a/python/src/trezorlib/transport/thp/protocol_v2.py +++ b/python/src/trezorlib/transport/thp/protocol_v2.py @@ -371,18 +371,28 @@ def _read_until_valid_crc_check( if timeout is None: timeout = self._DEFAULT_READ_TIMEOUT - is_valid = False - header, payload, chksum = thp_io.read(self.transport, timeout) - while not is_valid: - is_valid = checksum.is_valid(chksum, header.to_bytes_init() + payload) - if not is_valid: + while True: + header, payload, chksum = thp_io.read(self.transport, timeout) + if not checksum.is_valid(chksum, header.to_bytes_init() + payload): LOG.error( "Received a message with an invalid checksum:" + hexlify(header.to_bytes_init() + payload + chksum).decode() ) - header, payload, chksum = thp_io.read(self.transport, timeout) + continue - return header, payload + seq_bit = control_byte.get_seq_bit(header.ctrl_byte) + if seq_bit is not None: + if seq_bit != self.sync_bit_receive: + LOG.warning( + "Received unexpected message: sync bit=%d, expected=%d", + seq_bit, + self.sync_bit_receive, + ) + continue + + self.sync_bit_receive = 1 - self.sync_bit_receive + + return header, payload def _is_valid_channel_allocation_response( self, header: MessageHeader, payload: bytes, original_nonce: bytes From e451adc9cced52b19eb3c53099f541f35fc78ff0 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 20 Aug 2025 21:22:45 +0300 Subject: [PATCH 4/4] chore(core): remove forgotten `log.info()` in `interface_context.py` [no changelog] --- core/src/trezor/wire/thp/interface_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/trezor/wire/thp/interface_context.py b/core/src/trezor/wire/thp/interface_context.py index 963574700f9..f52a95f5681 100644 --- a/core/src/trezor/wire/thp/interface_context.py +++ b/core/src/trezor/wire/thp/interface_context.py @@ -147,7 +147,6 @@ async def _handle_broadcast(self, packet: bytes) -> None: if ctrl_byte != CHANNEL_ALLOCATION_REQ: raise ThpError("Unexpected ctrl_byte in a broadcast channel packet") - log.info(__name__, "got alloc: %s", utils.hexlify_if_bytes(packet)) channel_cache = channel_manager.create_new_channel(self._iface) channel = self._load_channel(channel_cache)