feat(core+python): support device unlocking during THP handshake
What changed, and why it matters
This commit changes how a Trezor device handles its initial secure connection (THP handshake) when the device is PIN-locked. Previously, the handshake simply failed if the device was locked. Now, the host can send a new 'try to unlock' flag in the very first handshake message. If that flag is set and the device is locked, the device shows the PIN keyboard so the user can unlock it, and then the handshake continues. If the flag is not set, the device still fails the handshake when locked, preserving the old behavior. The change is described by the vendor as backward-incompatible, meaning companion software (Suite) must be updated to match the new message format.
Treat this as a protocol-breaking feature change rather than a vulnerability. Reviewers should verify that: (1) the new flag byte is authenticated/covered by the handshake hash (it is); (2) `unlock_device()` cannot be abused to bypass lockout or brute-force PINs (rate-limiting and attempt counters remain in effect); (3) the workflow spawn does not create a race where a second handshake can pre-empt or skip the PIN screen; and (4) companion software (Trezor Suite) is updated to send the 33-byte init request, since old implementations will be rejected. No immediate security patch is indicated from the diff alone.
Security signals we found
Protocol message format change: handshake init request length increased from 32 to 33 bytes with a new unlock-intent flag.
PIN prompt triggered during handshake before pairing is complete, introducing user-interaction into a previously purely cryptographic step.
Handshake hash now includes the new flag byte, binding the unlock intent to the cryptographic state.
Backward-incompatible protocol change noted by the vendor; old hosts sending a 32-byte init request will be rejected by new firmware.
Device-side unlock flow is delegated to existing `unlock_device()` and workflow manager, rather than custom logic.
Tests added for negative path (locked, no unlock), positive unlock, wrong PIN, and cancel.
Evidence from the diff
The patch extends the THP (Trezor Host Protocol) handshake init request payload from 32 bytes (the host ephemeral Curve25519 public key) to 33 bytes (public key plus one flag byte). The flag byte’s least-significant bit controls whether the device should prompt for PIN entry when it is locked. On the device side, received_message_handler.py now parses payload[:PUBKEY_LENGTH] as the host ephemeral public key and payload[PUBKEY_LENGTH] & 0x01 as try_to_unlock. If locked and try_to_unlock is true, it spawns unlock_device() via the workflow manager; otherwise it raises ThpDeviceLockedError. The crypto code now includes the extra flag byte in the handshake hash (self.h = _hash_of_two(self.h, payload) instead of self.h = _hash_of_two(self.h, b"")), so the protocol binding covers the unlock intent. The Python host library (protocol_v2.py) defaults try_to_unlock=True and passes the flag byte into the Noise message. New device tests cover locked-handshake failure, successful unlock, wrong PIN retry, and cancel.
Changed components
core/src/trezor/wire/thp/received_message_handler.pycore/src/trezor/wire/thp/crypto.pypython/src/trezorlib/transport/thp/protocol_v2.pytests/device_tests/thp/test_handshake.pytests/ui_tests/fixtures.jsonInspect captured patch +181 / −24
diff --git a/core/.changelog.d/5922.added b/core/.changelog.d/5922.added
new file mode 100644
index 00000000..448cba49
--- /dev/null
+++ b/core/.changelog.d/5922.added
@@ -0,0 +1 @@
+[T3W1] Support device unlocking during THP handshake.
diff --git a/core/src/trezor/wire/thp/crypto.py b/core/src/trezor/wire/thp/crypto.py
index aa6245a3..6c5eb8a4 100644
--- a/core/src/trezor/wire/thp/crypto.py
+++ b/core/src/trezor/wire/thp/crypto.py
@@ -96,6 +96,7 @@ class Handshake:
self,
device_properties: AnyBytes,
host_ephemeral_public_key: AnyBytes,
+ payload: AnyBytes,
) -> tuple[bytes, bytes, bytes]:
trezor_static_private_key, trezor_static_public_key = _derive_static_key_pair()
@@ -105,7 +106,7 @@ class Handshake:
)
self.h = _hash_of_two(PROTOCOL_NAME, device_properties)
self.h = _hash_of_two(self.h, host_ephemeral_public_key)
- self.h = _hash_of_two(self.h, b"")
+ self.h = _hash_of_two(self.h, payload)
self.h = _hash_of_two(self.h, trezor_ephemeral_public_key)
point = curve25519.multiply(
self.trezor_ephemeral_private_key, host_ephemeral_public_key
diff --git a/core/src/trezor/wire/thp/received_message_handler.py b/core/src/trezor/wire/thp/received_message_handler.py
index dd37bf21..b1db4106 100644
--- a/core/src/trezor/wire/thp/received_message_handler.py
+++ b/core/src/trezor/wire/thp/received_message_handler.py
@@ -89,19 +89,48 @@ async def _handle_state_handshake(
payload = await ctx.recv_payload(control_byte.is_handshake_init_req)
- if len(payload) != PUBKEY_LENGTH:
- log.error(__name__, "Message received is not a valid handshake init request!")
+ if len(payload) != PUBKEY_LENGTH + 1:
+ if __debug__:
+ log.error(
+ __name__,
+ "Message received is not a valid handshake init request: %d bytes",
+ len(payload),
+ )
return
- if not config.is_unlocked():
+ host_ephemeral_public_key = payload[:PUBKEY_LENGTH]
+ # show the PIN keyboard to allow the user to unlock the device
+ try_to_unlock = payload[PUBKEY_LENGTH] & 0x01 == 1
+
+ async def _check_unlocked() -> None:
+ if config.is_unlocked():
+ return
+
+ if try_to_unlock:
+ from trezor import workflow
+
+ from apps.common.lock_manager import unlock_device
+
+ # Register the unlock prompt with the workflow management system
+ # (in order to avoid immediately respawning the lockscreen task)
+ try:
+ return await workflow.spawn(unlock_device())
+ except Exception as e:
+ if __debug__:
+ log.exception(__name__, e)
+
+ # Fail pairing if still locked
raise ThpDeviceLockedError
+ await _check_unlocked()
+
handshake = Handshake()
trezor_ephemeral_public_key, encrypted_trezor_static_public_key, tag = (
handshake.handle_th1_crypto(
get_encoded_device_properties(ctx.iface),
- host_ephemeral_public_key=payload,
+ host_ephemeral_public_key=host_ephemeral_public_key,
+ payload=payload[PUBKEY_LENGTH:],
)
)
@@ -130,8 +159,7 @@ async def _handle_state_handshake(
# will be `None` on USB interface, to be ignored by `cache_host_info()`
mac_addr: AnyBytes | None = ctx.iface_ctx.connected_addr()
- if not config.is_unlocked():
- raise ThpDeviceLockedError
+ await _check_unlocked()
host_encrypted_static_public_key = payload[: KEY_LENGTH + TAG_LENGTH]
handshake_completion_request_noise_payload = payload[KEY_LENGTH + TAG_LENGTH :]
diff --git a/core/tests/test_trezor.wire.thp.crypto.py b/core/tests/test_trezor.wire.thp.crypto.py
index da05553f..56d046a3 100644
--- a/core/tests/test_trezor.wire.thp.crypto.py
+++ b/core/tests/test_trezor.wire.thp.crypto.py
@@ -116,7 +116,7 @@ class TestTrezorHostProtocolCrypto(unittest.TestCase):
host_ephemeral_private_key = curve25519.generate_secret()
host_ephemeral_public_key = curve25519.publickey(host_ephemeral_private_key)
- handshake.handle_th1_crypto(b"", host_ephemeral_public_key)
+ handshake.handle_th1_crypto(b"", host_ephemeral_public_key, payload=b"\x00")
def test_th2_crypto(self):
handshake = self.handshake
diff --git a/python/.changelog.d/5922.added b/python/.changelog.d/5922.added
new file mode 100644
index 00000000..b19d4285
--- /dev/null
+++ b/python/.changelog.d/5922.added
@@ -0,0 +1 @@
+Support device unlocking during THP handshake.
diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py
index 6d59bedd..0ab6e2ea 100644
--- a/python/src/trezorlib/transport/thp/protocol_v2.py
+++ b/python/src/trezorlib/transport/thp/protocol_v2.py
@@ -225,12 +225,14 @@ class ProtocolV2Channel(Channel):
self._read_ack()
return self._read_handshake_completion_response()
- def _send_handshake_init_request(self) -> None:
- ha_init_req_header = MessageHeader(0, self.channel_id, 36)
- host_ephemeral_pubkey = self._noise.write_message()
+ def _send_handshake_init_request(self, try_to_unlock: bool = True) -> None:
+ payload = self._noise.write_message(bytes([try_to_unlock]))
+ ha_init_req_header = MessageHeader(
+ 0, self.channel_id, len(payload) + CHECKSUM_LENGTH
+ )
thp_io.write_payload_to_wire_and_add_checksum(
- self.transport, ha_init_req_header, host_ephemeral_pubkey
+ self.transport, ha_init_req_header, payload
)
def _read_handshake_init_response(self) -> bytes:
diff --git a/tests/device_tests/thp/test_handshake.py b/tests/device_tests/thp/test_handshake.py
index 8bc814fc..a5abcb10 100644
--- a/tests/device_tests/thp/test_handshake.py
+++ b/tests/device_tests/thp/test_handshake.py
@@ -4,6 +4,7 @@ import pytest
from trezorlib.client import ProtocolV2Channel
from trezorlib.debuglink import TrezorClientDebugLink as Client
+from trezorlib.exceptions import DeviceLocked
from .connect import prepare_protocol_for_handshake
@@ -49,3 +50,97 @@ def test_handshake(client: Client) -> None:
# so far no luck in solving it - it should be also tackled in FW, as it causes unexpected FW error
client.protocol = protocol
client.do_pairing()
+
+
+PIN4 = "1234"
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_no_unlock(client: Client):
+ protocol = prepare_protocol_for_handshake(client)
+
+ randomness_static = os.urandom(32)
+
+ protocol._do_channel_allocation()
+ protocol._init_noise(
+ randomness_static=randomness_static,
+ )
+ # the handshake should fail since the device is locked
+ protocol._send_handshake_init_request(try_to_unlock=False)
+ protocol._read_ack()
+ with pytest.raises(DeviceLocked):
+ protocol._read_handshake_init_response()
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_unlock_pin(client: Client):
+ protocol = prepare_protocol_for_handshake(client)
+ debug = client.debug
+
+ randomness_static = os.urandom(32)
+
+ protocol._do_channel_allocation()
+ protocol._init_noise(
+ randomness_static=randomness_static,
+ )
+ # the device should show the PIN keyboard
+ protocol._send_handshake_init_request(try_to_unlock=True)
+ protocol._read_ack()
+ debug.synchronize_at("PinKeyboard")
+ debug.input(PIN4)
+
+ protocol._read_handshake_init_response()
+ protocol._send_handshake_completion_request()
+ protocol._read_ack()
+ protocol._read_handshake_completion_response()
+ client.do_pairing()
+ client.get_seedless_session().ping("unlocked")
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_unlock_pin_wrong(client: Client):
+ protocol = prepare_protocol_for_handshake(client)
+ debug = client.debug
+
+ randomness_static = os.urandom(32)
+
+ protocol._do_channel_allocation()
+ protocol._init_noise(
+ randomness_static=randomness_static,
+ )
+ # the device should show the PIN keyboard
+ protocol._send_handshake_init_request(try_to_unlock=True)
+ protocol._read_ack()
+ debug.synchronize_at("PinKeyboard")
+ # enter wrong PIN
+ debug.input(PIN4 + "0")
+
+ debug.synchronize_at("PinKeyboard")
+ debug.input(PIN4)
+
+ protocol._read_handshake_init_response()
+ protocol._send_handshake_completion_request()
+ protocol._read_ack()
+ protocol._read_handshake_completion_response()
+ client.do_pairing()
+ client.get_seedless_session().ping("unlocked")
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_unlock_cancel(client: Client):
+ protocol = prepare_protocol_for_handshake(client)
+ debug = client.debug
+
+ randomness_static = os.urandom(32)
+
+ protocol._do_channel_allocation()
+ protocol._init_noise(
+ randomness_static=randomness_static,
+ )
+ # cancelling unlock should fail the handshake
+ protocol._send_handshake_init_request(try_to_unlock=True)
+ protocol._read_ack()
+ debug.synchronize_at("PinKeyboard")
+ debug.click(debug.screen_buttons.pin_passphrase_erase())
+ with pytest.raises(DeviceLocked):
+ protocol._read_handshake_init_response()
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index de55acaf..86957f62 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -30830,6 +30830,10 @@
"T3W1_cs_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_cs_thp-test_handshake.py::test_allocate_channel": "c24521e569c08e3605b164212c876f8ac57c5eef6cca6f2ca53a635a883ebc4b",
"T3W1_cs_thp-test_handshake.py::test_handshake": "96da6ebeefcc4055ede7928327e6f5c14ae5989ada460c685ab471df8232f98b",
+"T3W1_cs_thp-test_handshake.py::test_no_unlock": "05416515d2a63d94fc0ba3b6afdb9b0eb139ed403b7c20b62203c776ffe5d6ee",
+"T3W1_cs_thp-test_handshake.py::test_unlock_cancel": "0554bf51a56d34acd6288102cd384b0a18e8f1f6bb6486523d920f56f2f4b548",
+"T3W1_cs_thp-test_handshake.py::test_unlock_pin": "a5b6594d0d0897e2d26903fbdd619201dcb9f1ab808da16658e280f452cc6a29",
+"T3W1_cs_thp-test_handshake.py::test_unlock_pin_wrong": "b6681d8e69c2d0638b51cd50a5f8faf161f81b475aa3787d3396691441145986",
"T3W1_cs_thp-test_multiple_hosts.py::test_concurrent_handshakes": "c24521e569c08e3605b164212c876f8ac57c5eef6cca6f2ca53a635a883ebc4b",
"T3W1_cs_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "c77726a6e4397ade8cfc3a5316534f3dab93d0d39e729effd90e2905d6f7655f",
"T3W1_cs_thp-test_pairing.py::test_channel_replacement": "5bc85431229c56bab5546a9fbcaa8a5bf150beb051831fbcc1b29cd4c0b47c1d",
@@ -30838,8 +30842,8 @@
"T3W1_cs_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "7b97a23531342775f0318a74d41becacd269e51a543b12656923763269a2f22f",
"T3W1_cs_thp-test_pairing.py::test_pairing_cancel_1": "1483b32f7c93c627e805fdfa620ff95018d1f99c0d141e670077ceb240b1243d",
"T3W1_cs_thp-test_pairing.py::test_pairing_cancel_2": "a5cac060747b4be07468c92002563e15b0f6dbb6b65ea3d741a3321c12a4460d",
-"T3W1_cs_thp-test_pairing.py::test_pairing_code_entry": "f9f8447622a8672a565abfc6d5d7efe16b0ba0c4872aa17898b529fa138a333a",
-"T3W1_cs_thp-test_pairing.py::test_pairing_code_entry_cancel": "f9f8447622a8672a565abfc6d5d7efe16b0ba0c4872aa17898b529fa138a333a",
+"T3W1_cs_thp-test_pairing.py::test_pairing_code_entry": "396ba8cc20daa344c1528b31f468a397633d6f3864f1d1c8c20fab92b01c5a77",
+"T3W1_cs_thp-test_pairing.py::test_pairing_code_entry_cancel": "396ba8cc20daa344c1528b31f468a397633d6f3864f1d1c8c20fab92b01c5a77",
"T3W1_cs_thp-test_pairing.py::test_pairing_nfc": "613bcea2556dddf79d2394f5c5609235f7bde128fd21f6409613043f1a7d4657",
"T3W1_cs_thp-test_pairing.py::test_pairing_qr_code": "c24521e569c08e3605b164212c876f8ac57c5eef6cca6f2ca53a635a883ebc4b",
"T3W1_cs_webauthn-test_msg_webauthn.py::test_add_remove": "41e0ff0345000141e896ac5e1b7efccba213037de18b5eb8c0882be670034b18",
@@ -32321,6 +32325,10 @@
"T3W1_de_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_de_thp-test_handshake.py::test_allocate_channel": "98735f888f827501b25c78c6a737c1983a800c620f709c65b1d95f6c8c9a2b90",
"T3W1_de_thp-test_handshake.py::test_handshake": "058812890e58728a415456044e913cce75c8a7e0c7444cfa30c95a5c4e2622af",
+"T3W1_de_thp-test_handshake.py::test_no_unlock": "9be3988c015d9a5260eb1537ab5cd398f4ddd36e2378157fe88f8ee04f533b92",
+"T3W1_de_thp-test_handshake.py::test_unlock_cancel": "836b253b70886f38e456c65a155825ff2d710579f13036931e2a809599f3d13d",
+"T3W1_de_thp-test_handshake.py::test_unlock_pin": "4316426003da97d4b37fdbc7bac8690864cc0e84b7c79cd71987d69afbde0bd0",
+"T3W1_de_thp-test_handshake.py::test_unlock_pin_wrong": "597278160c6ea781e7135d16226f06e971aa5dd4b0dff4fdce4141072b29aaaa",
"T3W1_de_thp-test_multiple_hosts.py::test_concurrent_handshakes": "98735f888f827501b25c78c6a737c1983a800c620f709c65b1d95f6c8c9a2b90",
"T3W1_de_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "14e0b2567f1bcdced122be1a1e89e4a3498c42194e8ec5306640a496901086ab",
"T3W1_de_thp-test_pairing.py::test_channel_replacement": "20606031e2be8c539f1eb59cf463b538e054043ef3dcd57527833afe494bc171",
@@ -32329,8 +32337,8 @@
"T3W1_de_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "7833feed00c2236903c9eb2e66788c2a28199e3f090ab6c6308be8ad8b7fa14c",
"T3W1_de_thp-test_pairing.py::test_pairing_cancel_1": "2f7a8459b66ad064e3c2954c9e4454c83ee51176d6fb4344897ed7c244306ce4",
"T3W1_de_thp-test_pairing.py::test_pairing_cancel_2": "fe6296e82b0d14758c9c855ba5de4ff222c738f951e26d04fee14a5a48814cdc",
-"T3W1_de_thp-test_pairing.py::test_pairing_code_entry": "a3281752e1457c6768089b9ce69d8a0ac5baf900ed187cdb454cf340628a3b0b",
-"T3W1_de_thp-test_pairing.py::test_pairing_code_entry_cancel": "a3281752e1457c6768089b9ce69d8a0ac5baf900ed187cdb454cf340628a3b0b",
+"T3W1_de_thp-test_pairing.py::test_pairing_code_entry": "06beef53378740ca33197b8009cef60ae65deb1d8ea297f69e117342e899a021",
+"T3W1_de_thp-test_pairing.py::test_pairing_code_entry_cancel": "06beef53378740ca33197b8009cef60ae65deb1d8ea297f69e117342e899a021",
"T3W1_de_thp-test_pairing.py::test_pairing_nfc": "f0ad4b3d776c076dab666cc0e6ef7ac8261d98eba206c5089eb1bcd5f5aa0afc",
"T3W1_de_thp-test_pairing.py::test_pairing_qr_code": "98735f888f827501b25c78c6a737c1983a800c620f709c65b1d95f6c8c9a2b90",
"T3W1_de_webauthn-test_msg_webauthn.py::test_add_remove": "615265e559145574c93bf984fedbfffebea4f9b257d381f6f2c59afc6de60f26",
@@ -33812,6 +33820,10 @@
"T3W1_en_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_en_thp-test_handshake.py::test_allocate_channel": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
"T3W1_en_thp-test_handshake.py::test_handshake": "105afb3d1012e0c793b974dc6ce7d510e6ffa0cd3eec9012abcb63be4c25cacb",
+"T3W1_en_thp-test_handshake.py::test_no_unlock": "a60a24172d0470601707825e0287a91a7388e8aeb00b5e49ec9b2643de35c1ca",
+"T3W1_en_thp-test_handshake.py::test_unlock_cancel": "f99f343d0a4a51f52f5b43a885172f4167239b4cd1fd3e9397e0d62e9d1f9b80",
+"T3W1_en_thp-test_handshake.py::test_unlock_pin": "83a847fad4d8f96ccf701ae37069dabed0277ea08b3a599dc5bff3fd6836200e",
+"T3W1_en_thp-test_handshake.py::test_unlock_pin_wrong": "3c6ba1fc0404112f8e4138dd8fa298f0526ac79e876d7edfd790499ff5e189d5",
"T3W1_en_thp-test_multiple_hosts.py::test_concurrent_handshakes": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
"T3W1_en_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "275f60f064e22b9818ceaff3e21707e45367f7898771e187d261bf9bcb05a303",
"T3W1_en_thp-test_pairing.py::test_channel_replacement": "da671e2676d5d9761b631da809685bc865042ca2dfb49666d88d0cca6cfc96f0",
@@ -33820,8 +33832,8 @@
"T3W1_en_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "d9d06c263ad3d67de0662b7d07c91a9ef4ab7b21c25f8f7c5a9eef233276ff81",
"T3W1_en_thp-test_pairing.py::test_pairing_cancel_1": "cd8b89c41f1fa45320970832327c3626a281f2f9c37f8d043e48878d63f7553a",
"T3W1_en_thp-test_pairing.py::test_pairing_cancel_2": "554f7a5c9236a8ec9f1cebbaa0e56e057c862f1e37b3b796879c54b5f4f60db9",
-"T3W1_en_thp-test_pairing.py::test_pairing_code_entry": "18f7685c618c71492398bbca3b09b6885edc5c865777569df1270d8bd0bdc8ad",
-"T3W1_en_thp-test_pairing.py::test_pairing_code_entry_cancel": "18f7685c618c71492398bbca3b09b6885edc5c865777569df1270d8bd0bdc8ad",
+"T3W1_en_thp-test_pairing.py::test_pairing_code_entry": "3f3701ffe7a47fb2f631320420c2e3687315e7dce1a9966f455cf187e4583bb2",
+"T3W1_en_thp-test_pairing.py::test_pairing_code_entry_cancel": "3f3701ffe7a47fb2f631320420c2e3687315e7dce1a9966f455cf187e4583bb2",
"T3W1_en_thp-test_pairing.py::test_pairing_nfc": "a35f27a25123e935371966b194c0221d93139450f612a63518f5f5a5865d30d1",
"T3W1_en_thp-test_pairing.py::test_pairing_qr_code": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
"T3W1_en_webauthn-test_msg_webauthn.py::test_add_remove": "222e1dc521b9a3ccfb91530fd9246664a62a102f1595b78b7207ec9348036017",
@@ -35303,6 +35315,10 @@
"T3W1_es_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_es_thp-test_handshake.py::test_allocate_channel": "56536ae9cd7c4ff8022def4ec3350031d3614064d961f53a2850f2be425af201",
"T3W1_es_thp-test_handshake.py::test_handshake": "09cd65588f433780cfad0dddaac3fdea531ae73467b4720f766f08be055fc1df",
+"T3W1_es_thp-test_handshake.py::test_no_unlock": "d362078d046200bf6a6421a18a3ad8c3e0eba86f88512fc934c15c850ae4148d",
+"T3W1_es_thp-test_handshake.py::test_unlock_cancel": "7d85e976d806ab78a16a9bb796092bf10983ec2f8bb9bbf8c24174b4e146f25c",
+"T3W1_es_thp-test_handshake.py::test_unlock_pin": "573db6cadbd770829f86174eb8fc9805c035054d5a8438d27479f08903418ad7",
+"T3W1_es_thp-test_handshake.py::test_unlock_pin_wrong": "b90f47fac097157d50f05e39cbd07e0085a0268e8613cfb90c1ed93026a2f0cd",
"T3W1_es_thp-test_multiple_hosts.py::test_concurrent_handshakes": "56536ae9cd7c4ff8022def4ec3350031d3614064d961f53a2850f2be425af201",
"T3W1_es_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "181ada03bf12587816c22805605bfdbb800d8093bf8e09f6559fdf0e1127a92f",
"T3W1_es_thp-test_pairing.py::test_channel_replacement": "cb770bf45b2c752b2fbddde9d64f2bcee0db5f4236928229974aa8698b839ec6",
@@ -35311,8 +35327,8 @@
"T3W1_es_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "f88136b7237c5f8fc5344d82573a6dfa1bd3409eafaf1afe77bbc9dbc2a3cf60",
"T3W1_es_thp-test_pairing.py::test_pairing_cancel_1": "028d63c695abceafd5b94d8918d3fdcc47739fc52d23e335468be473a60dba01",
"T3W1_es_thp-test_pairing.py::test_pairing_cancel_2": "f726e1f2cc1799a63bb83373fb9f34fb72474751e57134da0c1702c582f84fd0",
-"T3W1_es_thp-test_pairing.py::test_pairing_code_entry": "c72f5d8f10b00c56df290e334fd0abb75a67709c42366f201648c8892d3e8de5",
-"T3W1_es_thp-test_pairing.py::test_pairing_code_entry_cancel": "c72f5d8f10b00c56df290e334fd0abb75a67709c42366f201648c8892d3e8de5",
+"T3W1_es_thp-test_pairing.py::test_pairing_code_entry": "0f919b5a417df3d7dded42d7b5564774a81c5a3317cc6bf41cca9eac1ac55fd7",
+"T3W1_es_thp-test_pairing.py::test_pairing_code_entry_cancel": "0f919b5a417df3d7dded42d7b5564774a81c5a3317cc6bf41cca9eac1ac55fd7",
"T3W1_es_thp-test_pairing.py::test_pairing_nfc": "f6ec6f54fd638746b170fbed5f0ac16a1c134e2bbd5711c794e5a37507c5c3c1",
"T3W1_es_thp-test_pairing.py::test_pairing_qr_code": "56536ae9cd7c4ff8022def4ec3350031d3614064d961f53a2850f2be425af201",
"T3W1_es_webauthn-test_msg_webauthn.py::test_add_remove": "85d81bed1998bbca18bbe3069c7fa7a419ea29de0d2b8f9feed5e965c2c6d5cb",
@@ -36794,6 +36810,10 @@
"T3W1_fr_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_fr_thp-test_handshake.py::test_allocate_channel": "e8156cf4eda1d29060f05b4a18d50032b0b344230609b7de7cababfe0a86b20b",
"T3W1_fr_thp-test_handshake.py::test_handshake": "56f65ffc58119860c36bee37bd9b86962caba6d13817ad1794061d6af22475b4",
+"T3W1_fr_thp-test_handshake.py::test_no_unlock": "56132ede5cabeccec0514da02d7c88fb8ebd78d8fb14a2b75ec8c124a7ccbf2d",
+"T3W1_fr_thp-test_handshake.py::test_unlock_cancel": "4eb97d51e6588554a58c2b96410bb99b8680ebf5521272841e7d3244109998cc",
+"T3W1_fr_thp-test_handshake.py::test_unlock_pin": "ae83094c950e6a2c56de71278e4789d237d0838dcdb9644704933926228bed65",
+"T3W1_fr_thp-test_handshake.py::test_unlock_pin_wrong": "63e599fadca5939193ac9bcfcf17f7548df346714cd3c0092bcf551a7f0394df",
"T3W1_fr_thp-test_multiple_hosts.py::test_concurrent_handshakes": "e8156cf4eda1d29060f05b4a18d50032b0b344230609b7de7cababfe0a86b20b",
"T3W1_fr_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "4b1dd45c6b06af4d58e295bc066cce207a123487a50d0ad05aa2b1ea8367b925",
"T3W1_fr_thp-test_pairing.py::test_channel_replacement": "7f20c91c1bb09b118ecedef2a2c9c48ee7771e1c17185f4311e215081fa7b160",
@@ -36802,8 +36822,8 @@
"T3W1_fr_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "b0edf80268145e849cbcd9274133830fc0d567f0aff68b80024bf113b23243a6",
"T3W1_fr_thp-test_pairing.py::test_pairing_cancel_1": "57243dca9a71f2b4b8317817c56645660a2779ef7ef8e91a97def277a4c828cd",
"T3W1_fr_thp-test_pairing.py::test_pairing_cancel_2": "32939a93d45c088d23b4ec1415803e7963cce8879f4623294ea76a3ef3726dfb",
-"T3W1_fr_thp-test_pairing.py::test_pairing_code_entry": "b10a93053f70a40d39c65951ea424ec542d2e237d2712bd62be18164dede18b6",
-"T3W1_fr_thp-test_pairing.py::test_pairing_code_entry_cancel": "b10a93053f70a40d39c65951ea424ec542d2e237d2712bd62be18164dede18b6",
+"T3W1_fr_thp-test_pairing.py::test_pairing_code_entry": "afef8c12a6a6daaa35a505f4a811744bcf8e564f2a40a4e1e77b3e879c3f3032",
+"T3W1_fr_thp-test_pairing.py::test_pairing_code_entry_cancel": "afef8c12a6a6daaa35a505f4a811744bcf8e564f2a40a4e1e77b3e879c3f3032",
"T3W1_fr_thp-test_pairing.py::test_pairing_nfc": "0023ee64940f8bc2bc7ff1e9e94eb6e69ceddaa4e7242b9eb8320f8086fac947",
"T3W1_fr_thp-test_pairing.py::test_pairing_qr_code": "e8156cf4eda1d29060f05b4a18d50032b0b344230609b7de7cababfe0a86b20b",
"T3W1_fr_webauthn-test_msg_webauthn.py::test_add_remove": "5346304ff4c3d817f5804c64a656a3bd9e4c353575bb1fcbab46796003f61f64",
@@ -36817,6 +36837,11 @@
"T3W1_fr_zcash-test_sign_tx.py::test_spend_v5_input": "72afe1376b17720a463bed369d93d2b74aed599cec16ba829b42f3c92fa2cc73",
"T3W1_fr_zcash-test_sign_tx.py::test_unified_address": "17471fcb2c86be3b3185bd7acc731118e2904841762a70a355da3ac7fc353fda",
"T3W1_fr_zcash-test_sign_tx.py::test_version_group_id_missing": "e8156cf4eda1d29060f05b4a18d50032b0b344230609b7de7cababfe0a86b20b",
+"T3W1_it_thp-test_handshake.py::test_allocate_channel": "941f4ba6ceb60b48f39bc83a957c7a6376f5600ed961b37a0e5f61547c3b8938",
+"T3W1_it_thp-test_handshake.py::test_handshake": "9d1a2e76d5e3da751a40e955972f766d2d9ad4c869ef1b95cf865f280909598a",
+"T3W1_it_thp-test_handshake.py::test_no_unlock": "a60a24172d0470601707825e0287a91a7388e8aeb00b5e49ec9b2643de35c1ca",
+"T3W1_it_thp-test_handshake.py::test_unlock_cancel": "33385b846e01e228c3e99b998d9cf81f18934f2de2b99b2800e6929ade9ed9e9",
+"T3W1_it_thp-test_handshake.py::test_unlock_pin": "192dc3aeaab79d72b6697ab25f438557d7e0761dea2dd8fc7583b74657efef9d",
"T3W1_pt_bitcoin-test_authorize_coinjoin.py::test_cancel_authorization": "fad82459cde73f03e7adbf11f954d76f1c944521bd2435eaf48edb1653a79804",
"T3W1_pt_bitcoin-test_authorize_coinjoin.py::test_get_address": "044bec50777d5f7e1ec5c4fd75a2f3ee03a3c3db7346e2f94109d55c1d1d2e0b",
"T3W1_pt_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "ce1f6caa38cc5e5da092820deb03f7c8640826d87df1684d37bbb1d19c1e784a",
@@ -38285,6 +38310,10 @@
"T3W1_pt_thp-test_basic.py::test_v2_unallocated": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
"T3W1_pt_thp-test_handshake.py::test_allocate_channel": "1c74667c078e25e7e0d37c7b2aa35f7c8ab02cd88e74695ecb355c82a293b0cc",
"T3W1_pt_thp-test_handshake.py::test_handshake": "8d19e0bf7cf8062db07ad9701ec6317ef1d609c875b6c5f3acd8c786a9bfea1f",
+"T3W1_pt_thp-test_handshake.py::test_no_unlock": "d362078d046200bf6a6421a18a3ad8c3e0eba86f88512fc934c15c850ae4148d",
+"T3W1_pt_thp-test_handshake.py::test_unlock_cancel": "66d2e74418ab24dfde8728734282f4299865ec532489c0eabd4ea569915f1a37",
+"T3W1_pt_thp-test_handshake.py::test_unlock_pin": "4e5ef32c3c2ebdd84b0b15f8eb48bc3795a9a27d88c364d39f558ab5cee1c58c",
+"T3W1_pt_thp-test_handshake.py::test_unlock_pin_wrong": "a01d3c01bff8dffb8a069c9f34a32f9cacf76dcf73cc3c16151ac5e8ff5b3817",
"T3W1_pt_thp-test_multiple_hosts.py::test_concurrent_handshakes": "1c74667c078e25e7e0d37c7b2aa35f7c8ab02cd88e74695ecb355c82a293b0cc",
"T3W1_pt_thp-test_pairing.py::test_autoconnect_credential_request_cancel": "a76608f0fbf535320a0244c3c9ce6bad547b23be39620675e6cb1a6e7070649e",
"T3W1_pt_thp-test_pairing.py::test_channel_replacement": "125127f791e729af9139d777aadc918168686ef39d77c79795ec23cbdbff6028",
@@ -38293,8 +38322,8 @@
"T3W1_pt_thp-test_pairing.py::test_credential_request_in_encrypted_transport_phase": "19dbf4de54643b55a610c7847cbacd4debc9468a0bed56cfad9fe77ed7a6b912",
"T3W1_pt_thp-test_pairing.py::test_pairing_cancel_1": "6bd57962052e3d4a0a253db32b27e3f7515e6982ef51bfc8a668bf62c0606d6f",
"T3W1_pt_thp-test_pairing.py::test_pairing_cancel_2": "3bbed5036b01edbe9a99a5e5c84db5c4d66c828693a806cc6c08252e2f234451",
-"T3W1_pt_thp-test_pairing.py::test_pairing_code_entry": "578286ff24b406f3ff46cbd080157492e1bb87656d22f890829a374c7b0ea04f",
-"T3W1_pt_thp-test_pairing.py::test_pairing_code_entry_cancel": "578286ff24b406f3ff46cbd080157492e1bb87656d22f890829a374c7b0ea04f",
+"T3W1_pt_thp-test_pairing.py::test_pairing_code_entry": "ed494d99d4080a339d0ea2acf3694d8280ecbced6c4b34f75fc8e9db15d6d25c",
+"T3W1_pt_thp-test_pairing.py::test_pairing_code_entry_cancel": "ed494d99d4080a339d0ea2acf3694d8280ecbced6c4b34f75fc8e9db15d6d25c",
"T3W1_pt_thp-test_pairing.py::test_pairing_nfc": "8ebd562dc235541da50206bd6d71861a1021ca25ada9e1ec28ae1fa0fc4fbd99",
"T3W1_pt_thp-test_pairing.py::test_pairing_qr_code": "1c74667c078e25e7e0d37c7b2aa35f7c8ab02cd88e74695ecb355c82a293b0cc",
"T3W1_pt_webauthn-test_msg_webauthn.py::test_add_remove": "56cb3438720d4a6f161cafc263d23d8ea2ce40df0af04e9d211c9921a493b19e",
Why this scored 37/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.