feat(python/thp): support send-side THP ACK piggy-backing
What changed, and why it matters
This commit updates the Trezor Python client library (trezorlib) to support a new efficiency feature in the Trezor Host Protocol (THP) version 2.1. It allows the host computer to 'piggyback' acknowledgment (ACK) flags onto normal outgoing messages rather than sending separate ACK packets. The change is a feature addition, not a fix for a vulnerability, and the code comments explicitly describe backward-compatible behavior for older firmware.
No security action required. Treat as a normal feature update. Reviewers may optionally verify that version-gating logic correctly prevents misinterpretation by older firmware and that the ACK bit does not interfere with the handshake state machine on THP 2.1 devices.
Security signals we found
Protocol feature negotiation based on device-reported version numbers
ACK bit set on all sent messages, including HANDSHAKE_INIT_REQ, which older firmware is documented to ignore
No input parsing changes, no memory-unsafe operations, no cryptographic key handling changes
Rename of helper function from add_ack_bit_to_ctrl_byte to set_ack_bit (cosmetic/API cleanup)
Evidence from the diff
The patch adds detection of THP 2.1 devices via protocol version major/minor fields and sets the ACK bit on sent THP messages when the device supports it. A new with_ack_bit() helper is added to Message, and control_byte.add_ack_bit_to_ctrl_byte is renamed to set_ack_bit. The _send_message() path now calls message.with_ack_bit(not self.sync_bit_receive). The changelog fragment labels this as ‘Support THP ACK piggybacking.’ No security bug, bounds error, cryptographic misuse, or unauthorized access is visible in the diff.
Changed components
python/src/trezorlib/thp/channel.pypython/src/trezorlib/thp/control_byte.pypython/src/trezorlib/thp/message.pypython/.changelog.d/6202.addedInspect captured patch +26 / −4
diff --git a/python/.changelog.d/6202.added b/python/.changelog.d/6202.added
new file mode 100644
index 00000000..77241714
--- /dev/null
+++ b/python/.changelog.d/6202.added
@@ -0,0 +1 @@
+Support THP ACK piggybacking.
diff --git a/python/src/trezorlib/thp/channel.py b/python/src/trezorlib/thp/channel.py
index 8fbace51..2d0317ac 100644
--- a/python/src/trezorlib/thp/channel.py
+++ b/python/src/trezorlib/thp/channel.py
@@ -16,11 +16,13 @@
from __future__ import annotations
+import functools
import io
import logging
import secrets
import time
import typing as t
+from contextlib import contextmanager
from enum import Enum, IntEnum, auto
import typing_extensions as tx
@@ -139,6 +141,14 @@ class Channel:
self._noise: NoiseConnection | None = None
self.state = channel_state
self.trezor_public_keys: TrezorPublicKeys | None = None
+ self._active_workflow: object | None = None
+
+ @functools.cached_property
+ def is_ack_piggybacking_allowed(self) -> bool:
+ """See https://github.com/trezor/trezor-firmware/pull/6202 for details."""
+ major = self.device_properties.protocol_version_major
+ minor = self.device_properties.protocol_version_minor
+ return (major, minor) >= (2, 1)
@property
def noise(self) -> NoiseConnection:
@@ -361,7 +371,11 @@ class Channel:
self.state = ChannelState.CREDENTIAL_PHASE
def _send_message(self, message: Message) -> None:
- msg_with_seq_bit = message.with_seq_bit(self.sync_bit_send)
+ message = message.with_seq_bit(self.sync_bit_send)
+ # older firmware ignores ACK bit on non-ACK THP packets.
+ # newer firmware will use non-zero ACK bit on HANDSHAKE_INIT_REQ as a signal from the host to enable ACK piggybacking.
+ message = message.with_ack_bit(not self.sync_bit_receive)
+
self.sync_bit_send = not self.sync_bit_send
retries_left = self.BUSY_RETRIES
@@ -378,9 +392,9 @@ class Channel:
while True:
try:
- thp_io.write_payload_to_wire(self.transport, msg_with_seq_bit)
+ thp_io.write_payload_to_wire(self.transport, message)
try:
- self._read_ack(msg_with_seq_bit)
+ self._read_ack(message)
except transport.Timeout:
if should_back_off():
continue
diff --git a/python/src/trezorlib/thp/control_byte.py b/python/src/trezorlib/thp/control_byte.py
index 9fdab9e0..b560c4dd 100644
--- a/python/src/trezorlib/thp/control_byte.py
+++ b/python/src/trezorlib/thp/control_byte.py
@@ -93,7 +93,7 @@ def set_seq_bit(ctrl_byte: int, seq_bit: bool) -> int:
return ctrl_byte | (DATA_SEQ_BIT * seq_bit)
-def add_ack_bit_to_ctrl_byte(ctrl_byte: int, ack_bit: int) -> int:
+def set_ack_bit(ctrl_byte: int, ack_bit: int) -> int:
return ctrl_byte | (DATA_ACK_SEQ_BIT * ack_bit)
diff --git a/python/src/trezorlib/thp/message.py b/python/src/trezorlib/thp/message.py
index f838a842..078867eb 100644
--- a/python/src/trezorlib/thp/message.py
+++ b/python/src/trezorlib/thp/message.py
@@ -126,6 +126,13 @@ class Message:
self.data,
)
+ def with_ack_bit(self, ack_bit: bool) -> Self:
+ return self.__class__(
+ control_byte.set_ack_bit(self.ctrl_byte, ack_bit),
+ self.cid,
+ self.data,
+ )
+
@cached_property
def seq_bit(self) -> bool | None:
return control_byte.get_seq_bit(self.ctrl_byte)
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.