From 76e3ce611eb74821ed68fcef14b1e0a0a739c0b4 Mon Sep 17 00:00:00 2001
From: M1nd3r <petrsedlacek.km@seznam.cz>
Date: Thu, 30 Jan 2025 13:10:13 +0100
Subject: [PATCH] feat(core, python): implement nfc pairing for tests, add
 device test [no changelog]

---
 core/src/apps/thp/pairing.py       | 16 ++++---
 tests/device_tests/thp/test_thp.py | 68 ++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+), 7 deletions(-)

diff --git a/core/src/apps/thp/pairing.py b/core/src/apps/thp/pairing.py
index 367c85f8e2..289070a542 100644
--- a/core/src/apps/thp/pairing.py
+++ b/core/src/apps/thp/pairing.py
@@ -221,10 +221,6 @@ async def _handle_code_entry_is_selected_first_time(ctx: PairingContext) -> None
 @check_state_and_log(ChannelState.TP1)
 async def _handle_nfc_is_selected(ctx: PairingContext) -> None:
     ctx.nfc_secret = random.bytes(16)
-    sha_ctx = sha256(ctx.channel_ctx.get_handshake_hash())
-    sha_ctx.update(ctx.nfc_secret)
-    sha_ctx.update(bytes("PairingMethod_NfcUnidirectional", "utf-8"))
-    ctx.display_data.code_nfc = sha_ctx.digest()[:16]
     await ctx.write_force(ThpPairingPreparationsFinished())
 
 
@@ -327,9 +323,13 @@ async def _handle_nfc_tag(
 ) -> protobuf.MessageType:
     if TYPE_CHECKING:
         assert isinstance(message, ThpNfcTagHost)
+
+    assert ctx.nfc_secret is not None
+    assert ctx.handshake_hash_host is not None
+    assert ctx.nfc_secret_host is not None
+
     sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big"))
     sha_ctx.update(ctx.channel_ctx.get_handshake_hash())
-    assert ctx.nfc_secret is not None
     sha_ctx.update(ctx.nfc_secret)
     expected_tag = sha_ctx.digest()
     if expected_tag != message.tag:
@@ -338,10 +338,12 @@ async def _handle_nfc_tag(
         )  # TODO remove after testing
         raise ThpError("Unexpected NFC Unidirectional Tag")
 
+    if ctx.handshake_hash_host[:16] != ctx.channel_ctx.get_handshake_hash()[:16]:
+        raise ThpError("Handshake hash mismatch")
+
     sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big"))
     sha_ctx.update(ctx.channel_ctx.get_handshake_hash())
-    # TODO add Host's secret from NFC message transferred over NFC
-    # sha_ctx.update(host's secret)
+    sha_ctx.update(ctx.nfc_secret_host)
     trezor_tag = sha_ctx.digest()
     return await _handle_secret_reveal(
         ctx,
diff --git a/tests/device_tests/thp/test_thp.py b/tests/device_tests/thp/test_thp.py
index c436213694..8c0b16433d 100644
--- a/tests/device_tests/thp/test_thp.py
+++ b/tests/device_tests/thp/test_thp.py
@@ -18,6 +18,8 @@ from trezorlib.messages import (
     ThpCodeEntrySecret,
     ThpEndRequest,
     ThpEndResponse,
+    ThpNfcTagHost,
+    ThpNfcTagTrezor,
     ThpPairingMethod,
     ThpPairingPreparationsFinished,
     ThpPairingRequest,
@@ -252,3 +254,69 @@ def test_pairing_code_entry(client: Client) -> None:
     _read_message(ThpEndResponse)
 
     protocol._has_valid_channel = True
+
+
+def test_pairing_nfc(client: Client) -> None:
+    global protocol
+    _prepare_protocol(client)
+
+    # Generate ephemeral keys
+    host_ephemeral_privkey = curve25519.get_private_key(os.urandom(32))
+    host_ephemeral_pubkey = curve25519.get_public_key(host_ephemeral_privkey)
+
+    protocol._do_channel_allocation()
+
+    protocol._do_handshake(host_ephemeral_privkey, host_ephemeral_pubkey)
+
+    _send_message(ThpPairingRequest())
+
+    _read_message(ButtonRequest)
+
+    _send_message(ButtonAck())
+
+    client.debug.press_yes()
+
+    _read_message(ThpPairingRequestApproved)
+
+    _send_message(ThpSelectMethod(selected_pairing_method=ThpPairingMethod.NFC))
+
+    _read_message(ThpPairingPreparationsFinished)
+
+    # NFC screen shown
+    _read_message(ButtonRequest)
+    _send_message(ButtonAck())
+
+    nfc_secret_host = b"\x02\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"  # TODO generate randomly
+
+    # Read `nfc_secret` and `handshake_hash` from Trezor using debuglink
+    pairing_info = client.debug.pairing_info(
+        thp_channel_id=protocol.channel_id.to_bytes(2, "big"),
+        handshake_hash=protocol.handshake_hash,
+        nfc_secret_host=nfc_secret_host,
+    )
+    handshake_hash_trezor = pairing_info.handshake_hash
+    nfc_secret_trezor = pairing_info.nfc_secret_trezor
+
+    assert handshake_hash_trezor[:16] == protocol.handshake_hash[:16]
+
+    # Compute tag for response
+    sha_ctx = hashlib.sha256(ThpPairingMethod.NFC.to_bytes(1, "big"))
+    sha_ctx.update(protocol.handshake_hash)
+    sha_ctx.update(nfc_secret_trezor)
+    tag_host = sha_ctx.digest()
+
+    _send_message(ThpNfcTagHost(tag=tag_host))
+
+    tag_trezor_msg = _read_message(ThpNfcTagTrezor)
+
+    # Check that the `code` was derived from the revealed secret
+    sha_ctx = hashlib.sha256(ThpPairingMethod.NFC.to_bytes(1, "big"))
+    sha_ctx.update(protocol.handshake_hash)
+    sha_ctx.update(nfc_secret_host)
+    computed_tag = sha_ctx.digest()
+    assert tag_trezor_msg.tag == computed_tag
+
+    _send_message(ThpEndRequest())
+    _read_message(ThpEndResponse)
+
+    protocol._has_valid_channel = True