"""SIGN_TX end to end, including an independent check of what was signed.""" import pytest from ecdsa import SECP256k1, VerifyingKey from ecdsa.util import sigdecode_string from ragger.error import ExceptionRAPDU from application_client.handshake_command_sender import Errors, HandshakeCommandSender from application_client.handshake_response_unpacker import ( unpack_get_public_key_response, unpack_sign_input_response, ) from application_client.handshake_sighash import ( p2wpkh_script_code, serialize_output, sighash_all, ) from .review_nav import approve_review from application_client.handshake_transaction import ( COVENANT_OPEN, COVENANT_TRANSFER, SIGHASH_ALL, begin_payload, input_payload, open_items, output_payload, transfer_items, ) PATH = "m/44'/5353'/0'/0/0" COIN = 100_000_000 TXID = b"\x11" * 32 DEST = b"\x22" * 20 CHANGE = b"\x33" * 20 def _pubkey(client: HandshakeCommandSender) -> bytes: pubkey, _, _ = unpack_get_public_key_response(client.get_public_key(path=PATH).data) return pubkey def test_sign_tx_accepted(backend, device, navigator, default_screenshot_path, test_name): """Approve a two-output spend and verify the signature commits to exactly the transaction the device was given.""" client = HandshakeCommandSender(backend) pubkey = _pubkey(client) outputs = [ output_payload(60_000_000, addr_hash=DEST), output_payload(39_000_000, addr_hash=CHANGE), ] client.sign_tx_begin(begin_payload(n_in=1, n_out=2)) client.sign_tx_add_input(input_payload(PATH, COIN, txid=TXID)) for out in outputs: client.sign_tx_add_output(out) with client.sign_tx_review(): approve_review(device, navigator, default_screenshot_path, test_name) rapdu = client.sign_tx_input(0) signature, sighash_type, returned_pubkey = unpack_sign_input_response(rapdu.data) assert sighash_type == SIGHASH_ALL assert returned_pubkey == pubkey digest = sighash_all( version=0, inputs=[(TXID, 0, 0xFFFFFFFF)], outputs=[serialize_output(60_000_000, DEST), serialize_output(39_000_000, CHANGE)], locktime=0, index=0, script_code=p2wpkh_script_code(pubkey), value=COIN, ) vk = VerifyingKey.from_string(pubkey, curve=SECP256k1) assert vk.verify_digest(signature, digest, sigdecode=sigdecode_string) def test_sign_tx_refused(backend, scenario_navigator): client = HandshakeCommandSender(backend) client.sign_tx_begin(begin_payload(n_in=1, n_out=1)) client.sign_tx_add_input(input_payload(PATH, COIN, txid=TXID)) client.sign_tx_add_output(output_payload(90_000_000, addr_hash=DEST)) with pytest.raises(ExceptionRAPDU) as e: with client.sign_tx_review(): scenario_navigator.review_reject() assert e.value.status == Errors.SW_DENY # A refusal must leave no session behind for the host to sign against. with pytest.raises(ExceptionRAPDU) as e: client.sign_tx_input(0) assert e.value.status == Errors.SW_WRONG_STATE def test_sign_tx_open_covenant(backend, device, navigator, default_screenshot_path, test_name): """OPEN carries the plaintext name, which the device verifies against the hash the covenant commits to before showing it.""" client = HandshakeCommandSender(backend) client.sign_tx_begin(begin_payload(n_in=1, n_out=1)) client.sign_tx_add_input(input_payload(PATH, COIN, txid=TXID)) client.sign_tx_add_output(output_payload( 0, addr_hash=DEST, covenant_kind=COVENANT_OPEN, covenant_items=open_items(b"alice"))) with client.sign_tx_review(): approve_review(device, navigator, default_screenshot_path, test_name) assert client.sign_tx_input(0).status == 0x9000 def test_sign_tx_transfer_covenant(backend, device, navigator, default_screenshot_path, test_name): """TRANSFER's destination is the only thing distinguishing a transfer to yourself from one to a thief, and it lives in the covenant rather than in the output address.""" client = HandshakeCommandSender(backend) client.sign_tx_begin(begin_payload(n_in=1, n_out=1)) client.sign_tx_add_input(input_payload(PATH, COIN, txid=TXID)) client.sign_tx_add_output(output_payload( COIN // 2, addr_hash=CHANGE, covenant_kind=COVENANT_TRANSFER, covenant_items=transfer_items(b"alice", dest_hash=DEST))) with client.sign_tx_review(): approve_review(device, navigator, default_screenshot_path, test_name) assert client.sign_tx_input(0).status == 0x9000