From 4326d1a2fd43d6477ef952003c8a71012d97d977 Mon Sep 17 00:00:00 2001 From: Sarah GLINER Date: Mon, 11 Mar 2024 15:57:32 +0100 Subject: [PATCH] tests: udpdate with ragger client --- tests/instructions.py | 360 +++++++++++++++++ tests/test_dashboard.py | 41 +- tests/test_e2e_miniscript.py | 116 ++++-- tests/test_e2e_multisig.py | 53 ++- tests/test_e2e_tapscripts.py | 120 +++--- tests/test_get_extended_pubkey.py | 202 +++------- tests/test_get_master_fingerprint.py | 4 +- tests/test_get_version.py | 4 +- tests/test_get_wallet_address.py | 72 ++-- tests/test_get_wallet_address_v1.py | 135 +++---- tests/test_protocol.py | 21 +- tests/test_register_wallet.py | 240 ++++++++---- tests/test_register_wallet_v1.py | 85 ++-- tests/test_sign_message.py | 82 ++-- tests/test_sign_psbt.py | 433 ++++++++------------- tests/test_sign_psbt_v1.py | 326 +++++----------- tests/test_sign_psbt_with_sighash_types.py | 262 ++++++++----- tests/test_status_word.py | 2 +- 18 files changed, 1422 insertions(+), 1136 deletions(-) create mode 100644 tests/instructions.py diff --git a/tests/instructions.py b/tests/instructions.py new file mode 100644 index 000000000..b03aa4466 --- /dev/null +++ b/tests/instructions.py @@ -0,0 +1,360 @@ +from ragger.navigator import NavInsID +from ragger.firmware import Firmware + +from ragger_bitcoin.ragger_instructions import Instructions + + +def message_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Path") + instructions.same_request("Sign") + else: + instructions.confirm_message() + return instructions + + +def message_instruction_approve_long(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Path") + instructions.same_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_message() + return instructions + + +def message_instruction_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.reject_message() + + return instructions + + +def pubkey_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + else: + instructions.choice_confirm() + return instructions + + +def pubkey_instruction_reject_early(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.footer_cancel() + return instructions + + +def pubkey_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Reject") + instructions.same_request("Reject") + else: + instructions.choice_reject() + + return instructions + + +def wallet_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + else: + instructions.address_confirm() + return instructions + + +def register_wallet_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_approve_long(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_approve_unusual(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.choice_reject() + + return instructions + + +def sign_psbt_instruction_tap(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + return instructions + + instructions.navigate_end_of_flow() + return instructions + + +def sign_psbt_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_2(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Accept") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_3(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Continue") + instructions.same_request("Accept") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.warning_accept() + instructions.same_request_confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_4(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_5(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Accept") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_6(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Accept") + else: + instructions.confirm_wallet() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_7(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.confirm_wallet() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_8(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Continue") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.confirm_wallet() + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_9(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_external_inputs(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_external_inputs_2(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.same_request("Accept") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_10(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Approve") + instructions.new_request("Accept") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def e2e_register_wallet_instruction(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + for _ in range(23): + instructions.new_request("Approve", save_screenshot=False) + else: + for _ in range(23): + instructions.choice_confirm(save_screenshot=False) + return instructions + + +def e2e_sign_psbt_instruction(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve", save_screenshot=False) + instructions.new_request("Approve", save_screenshot=False) + instructions.new_request("Accept", save_screenshot=False) + else: + instructions.confirm_wallet(save_screenshot=False) + instructions.navigate_end_of_flow(save_screenshot=False) + instructions.navigate_end_of_flow(save_screenshot=False) + instructions.confirm_transaction(save_screenshot=False) + return instructions diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index cbfb9a12e..c5027e8fd 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1,27 +1,24 @@ -import pytest +from ragger.firmware import Firmware +from ragger.navigator import NavInsID, Navigator +from pathlib import Path -from speculos.client import SpeculosClient +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() -def test_dashboard(comm: SpeculosClient, is_speculos: bool, app_version: str, model: str): +def test_dashboard(navigator: Navigator, firmware: Firmware, test_name: str): # Tests that the text shown in the dashboard screens are the expected ones - if not is_speculos: - pytest.skip("Requires speculos") - - if model == "stax": - pytest.skip("No dashboard test for stax") - - comm.press_and_release("right") - comm.wait_for_text_event("Version") - comm.wait_for_text_event(app_version) - - comm.press_and_release("right") - comm.wait_for_text_event("About") - - comm.press_and_release("right") - comm.wait_for_text_event("Quit") - - comm.press_and_release("right") - comm.wait_for_text_event("Bitcoin Testnet") - comm.wait_for_text_event("is ready") + if firmware.device.startswith("nano"): + instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK + ] + else: + instructions = [ + NavInsID.USE_CASE_HOME_INFO, + NavInsID.USE_CASE_SETTINGS_SINGLE_PAGE_EXIT + ] + + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, test_name, instructions, + screen_change_before_first_instruction=False) diff --git a/tests/test_e2e_miniscript.py b/tests/test_e2e_miniscript.py index d5d387421..b925efad9 100644 --- a/tests/test_e2e_miniscript.py +++ b/tests/test_e2e_miniscript.py @@ -6,24 +6,30 @@ from hashlib import sha256 from decimal import Decimal -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.client_base import TransportClient -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys -from speculos.client import SpeculosClient -from test_utils.speculos import automation +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU + +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T from .conftest import AuthServiceProxy -def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): - with automation(comm, "automations/register_wallet_accept.json"): - wallet_id, wallet_hmac = client.register_wallet(wallet_policy) +def run_test_e2e(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, speculos_globals: SpeculosGlobals, + instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str): + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") assert wallet_id == wallet_policy.id @@ -96,8 +102,9 @@ def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac) + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") n_internal_keys = count_internal_keys(speculos_globals.seed, "test", wallet_policy) assert len(hww_sigs) == n_internal_keys * len(psbt.inputs) # should be true as long as all inputs are internal @@ -124,17 +131,21 @@ def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: rpc.sendrawtransaction(rawtx) -def run_test_invalid(client: Client, descriptor_template: str, keys_info: List[str]): +def run_test_invalid(client: RaggerClient, descriptor_template: str, keys_info: List[str]): wallet_policy = WalletPolicy( name="Invalid wallet", descriptor_template=descriptor_template, keys_info=keys_info) - with pytest.raises((IncorrectDataError, NotSupportedError)): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet_policy) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError or DeviceException.exc.get( + e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_e2e_miniscript_one_of_two_1(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_one_of_two_1(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # One of two keys (equally likely) # or(pk(key_1),pk(key_2)) @@ -149,10 +160,12 @@ def test_e2e_miniscript_one_of_two_1(rpc, rpc_test_wallet, client: Client, specu f"{core_xpub_orig}", ]) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_one_of_two_2(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_one_of_two_2(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # One of two keys (one likely, one unlikely) # or(99@pk(key_likely),pk(key_unlikely)) @@ -167,10 +180,12 @@ def test_e2e_miniscript_one_of_two_2(rpc, rpc_test_wallet, client: Client, specu f"{core_xpub_orig}", ]) - run_test_e2e(wallet_policy, [_], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [_], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_2fa(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_2fa(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # A user and a 2FA service need to sign off, but after 90 days the user alone is enough # and(pk(key_user),or(99@pk(key_service),older(12960))) @@ -185,10 +200,12 @@ def test_e2e_miniscript_2fa(rpc, rpc_test_wallet, client: Client, speculos_globa f"{core_xpub_orig}", ]) - run_test_e2e(wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_decaying_3of3(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_decaying_3of3(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # A 3-of-3 that becomes a 2-of-3 after 90 days # thresh(3,pk(key_1),pk(key_2),pk(key_3),older(12960)) @@ -205,11 +222,12 @@ def test_e2e_miniscript_decaying_3of3(rpc, rpc_test_wallet, client: Client, spec f"{core_xpub_orig2}", ]) - run_test_e2e(wallet_policy, [core_wallet_name1, core_wallet_name2], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_bolt3_offered_htlc(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_bolt3_offered_htlc(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # The BOLT #3 offered HTLC policy # or(pk(key_revocation),and(pk(key_remote),or(pk(key_local),hash160(H)))) @@ -227,11 +245,12 @@ def test_e2e_miniscript_bolt3_offered_htlc(rpc, rpc_test_wallet, client: Client, f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", ]) - run_test_e2e(wallet_policy, [core_wallet_name1, core_wallet_name2], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_bolt3_received_htlc(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_bolt3_received_htlc(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # The BOLT #3 received HTLC policy # andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation)) @@ -249,11 +268,12 @@ def test_e2e_miniscript_bolt3_received_htlc(rpc, rpc_test_wallet, client: Client f"{core_xpub_orig2}", ]) - run_test_e2e(wallet_policy, [core_wallet_name1, core_wallet_name2], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_me_or_3of5(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_me_or_3of5(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): path = "48'/1'/0'/2'" _, core_xpub_orig1 = create_new_wallet() _, core_xpub_orig2 = create_new_wallet() @@ -275,11 +295,13 @@ def test_e2e_miniscript_me_or_3of5(rpc, rpc_test_wallet, client: Client, speculo f"{core_xpub_orig5}", ]) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_me_large_vault(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], model: str): - if (model == "nanos"): +def test_e2e_miniscript_me_large_vault(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + if (firmware.name == "nanos"): pytest.skip("Not supported on Nano S due to memory limitations") path = "48'/1'/0'/2'" @@ -305,10 +327,12 @@ def test_e2e_miniscript_me_large_vault(rpc, rpc_test_wallet, client: Client, spe f"{core_xpub_orig6}", ]) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_me_and_bob_or_me_and_carl_1(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_me_and_bob_or_me_and_carl_1(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # policy: or(and(pk(A1), pk(B)),and(pk(A2), pk(C))) # where A1 and A2 are both internal keys; therefore, two signatures per input must be returned @@ -332,17 +356,19 @@ def test_e2e_miniscript_me_and_bob_or_me_and_carl_1(rpc, rpc_test_wallet, client f"{core_xpub_orig2}", ]) - run_test_e2e(wallet_policy, [core_wallet_name1], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_nanos_large_policy(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], model: str): +def test_e2e_miniscript_nanos_large_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # Nano S has much tighter memory limits. # The policy in this test requires 304 bytes after is parsed, which is larger than the previous 276. # However, it is a kind of policy in the style of the Liana wallet, that it would be nice to support. # reported by pythcoiner - if model != "nanos": + if firmware.name != "nanos": pytest.skip("Test only for Nano S") core_wallet_name1, core_xpub_orig1 = create_new_wallet() @@ -363,11 +389,13 @@ def test_e2e_miniscript_nanos_large_policy(rpc, rpc_test_wallet, client: Client, f"{core_xpub_orig3}", ]) - run_test_e2e(wallet_policy, [core_wallet_name1, core_wallet_name2, core_wallet_name3], rpc, - rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2, + core_wallet_name3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_miniscript_policy_with_a(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_miniscript_policy_with_a(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # versions 2.1.0 and 2.1.1 of the app incorrectly compiled the 'a:' wrapper, producing incorrect addresses _, core_xpub_orig1 = create_new_wallet() @@ -392,10 +420,12 @@ def test_e2e_miniscript_policy_with_a(rpc, rpc_test_wallet, client: Client, spec f"{core_xpub_orig5}", ]) - run_test_e2e(wallet_policy, [core_wallet_name3], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_invalid_miniscript(rpc, client: Client, speculos_globals: SpeculosGlobals): +def test_invalid_miniscript(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, speculos_globals: SpeculosGlobals): path = "48'/1'/0'/2'" _, core_xpub_orig1 = create_new_wallet() _, core_xpub_orig2 = create_new_wallet() diff --git a/tests/test_e2e_multisig.py b/tests/test_e2e_multisig.py index eb11ecc17..4901b080e 100644 --- a/tests/test_e2e_multisig.py +++ b/tests/test_e2e_multisig.py @@ -6,23 +6,33 @@ from hashlib import sha256 from decimal import Decimal -from bitcoin_client.ledger_bitcoin import Client, MultisigWallet, AddressType -from bitcoin_client.ledger_bitcoin.client_base import TransportClient -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy +from ledger_bitcoin import Client, MultisigWallet, AddressType +from ledger_bitcoin.client_base import TransportClient +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys from speculos.client import SpeculosClient -from test_utils.speculos import automation + +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware + +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T from .conftest import AuthServiceProxy -def run_test(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): - with automation(comm, "automations/register_wallet_accept.json"): - wallet_id, wallet_hmac = client.register_wallet(wallet_policy) +def run_test(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, + core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, + speculos_globals: SpeculosGlobals, instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str = ""): + + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") assert wallet_id == wallet_policy.id @@ -95,8 +105,9 @@ def run_test(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: Aut psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac) + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") n_internal_keys = count_internal_keys(speculos_globals.seed, "test", wallet_policy) assert len(hww_sigs) == n_internal_keys * len(psbt.inputs) # should be true as long as all inputs are internal @@ -123,7 +134,8 @@ def run_test(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: Aut rpc.sendrawtransaction(rawtx) -def test_e2e_multisig_2_of_2(rpc: AuthServiceProxy, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_multisig_2_of_2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: + SpeculosGlobals): path = "48'/1'/0'/2'" core_wallet_name, core_xpub_orig = create_new_wallet() @@ -138,10 +150,13 @@ def test_e2e_multisig_2_of_2(rpc: AuthServiceProxy, rpc_test_wallet, client: Cli ], ) - run_test(wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, + speculos_globals, e2e_register_wallet_instruction(firmware), + e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_multisig_multiple_internal_keys(rpc: AuthServiceProxy, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_multisig_multiple_internal_keys(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: SpeculosGlobals): # test an edge case of a multisig where the wallet controls more than one key # 3-of-5 multisig where 2 keys are internal @@ -167,12 +182,14 @@ def test_e2e_multisig_multiple_internal_keys(rpc: AuthServiceProxy, rpc_test_wal ], ) - run_test(wallet_policy, [core_wallet_name_3], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test(navigator, client, wallet_policy, [core_wallet_name_3], + rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), + e2e_sign_psbt_instruction(firmware), test_name) @pytest.mark.timeout(0) # disable timeout -def test_e2e_multisig_16_of_16(rpc: AuthServiceProxy, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], enable_slow_tests: bool): +def test_e2e_multisig_16_of_16(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: SpeculosGlobals, enable_slow_tests: bool): # Largest supported multisig with sortedmulti. # The time for an end-to-end execution on a real Ledger Nano S (including user's input) is about 520 seconds. @@ -198,4 +215,6 @@ def test_e2e_multisig_16_of_16(rpc: AuthServiceProxy, rpc_test_wallet, client: C keys_info=core_xpub_origs + [f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}"], ) - run_test(wallet_policy, core_wallet_names, rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test(navigator, client, wallet_policy, core_wallet_names, rpc, rpc_test_wallet, + speculos_globals. e2e_register_wallet_instruction(firmware), + e2e_sign_psbt_instruction(firmware), test_name) diff --git a/tests/test_e2e_tapscripts.py b/tests/test_e2e_tapscripts.py index e655fbafd..327d368bf 100644 --- a/tests/test_e2e_tapscripts.py +++ b/tests/test_e2e_tapscripts.py @@ -6,25 +6,33 @@ from hashlib import sha256 from decimal import Decimal -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.client_base import TransportClient -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy +from ledger_bitcoin import Client +from ledger_bitcoin.client_base import TransportClient +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys -from speculos.client import SpeculosClient -from test_utils.speculos import automation +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU -from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction from .conftest import AuthServiceProxy +from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T -def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): - with automation(comm, "automations/register_wallet_accept.json"): - wallet_id, wallet_hmac = client.register_wallet(wallet_policy) +def run_test_e2e(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, + core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, + speculos_globals: SpeculosGlobals, instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str = ""): + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") assert wallet_id == wallet_policy.id assert hmac.compare_digest( @@ -96,8 +104,9 @@ def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac) + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") # only correct for taproot policies for i, part_sig in hww_sigs: @@ -131,17 +140,22 @@ def run_test_e2e(wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: rpc.sendrawtransaction(rawtx) -def run_test_invalid(client: Client, descriptor_template: str, keys_info: List[str]): +def run_test_invalid(client: RaggerClient, descriptor_template: str, keys_info: List[str]): wallet_policy = WalletPolicy( name="Invalid wallet", descriptor_template=descriptor_template, keys_info=keys_info) - with pytest.raises((IncorrectDataError, NotSupportedError)): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet_policy) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError or DeviceException.exc.get( + e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_e2e_tapscript_one_of_two_keypath(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_one_of_two_keypath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, + speculos_globals: SpeculosGlobals): # One of two keys, with the foreign key in the key path spend # tr(my_key,pk(foreign_key_1)) @@ -156,10 +170,12 @@ def test_e2e_tapscript_one_of_two_keypath(rpc, rpc_test_wallet, client: Client, f"{core_xpub_orig}", ]) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_one_of_two_scriptpath(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_one_of_two_scriptpath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # One of two keys, with the foreign key in the key path spend # tr(foreign_key,pk(my_key)) @@ -174,10 +190,12 @@ def test_e2e_tapscript_one_of_two_scriptpath(rpc, rpc_test_wallet, client: Clien f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", ]) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_one_of_three_keypath(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_one_of_three_keypath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # One of three keys, with the internal one in the key-path spend # tr(my_key,{pk(foreign_key_1,foreign_key_2)}) @@ -194,11 +212,12 @@ def test_e2e_tapscript_one_of_three_keypath(rpc, rpc_test_wallet, client: Client f"{core_xpub_orig_2}", ]) - run_test_e2e(wallet_policy, [], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_one_of_three_scriptpath(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_one_of_three_scriptpath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # One of three keys, with the internal one in on of the scripts # tr(foreign_key_1,{pk(my_key,foreign_key_2)}) @@ -215,11 +234,12 @@ def test_e2e_tapscript_one_of_three_scriptpath(rpc, rpc_test_wallet, client: Cli f"{core_xpub_orig_2}", ]) - run_test_e2e(wallet_policy, [], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_multi_a_2of2(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_multi_a_2of2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # tr(foreign_key_1,multi_a(2,my_key,foreign_key_2)) path = "499'/1'/0'" @@ -235,18 +255,20 @@ def test_e2e_tapscript_multi_a_2of2(rpc, rpc_test_wallet, client: Client, specul f"{core_xpub_orig_2}", ]) - run_test_e2e(wallet_policy, [core_wallet_name2], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_maxdepth(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], model: str): +def test_e2e_tapscript_maxdepth(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # A taproot tree with maximum supported depth, where the internal key is in the deepest script - MAX_TAPTREE_POLICY_DEPTH = 4 if model == "nanos" else 9 + MAX_TAPTREE_POLICY_DEPTH = 4 if firmware.name == "nanos" else 9 # Make the most unbalanced tree where each script is a simple pk() parts = [f"pk(@{i}/**)" for i in range(1, MAX_TAPTREE_POLICY_DEPTH)] - descriptor_template = "tr(@0/**,{" + ',{'.join(parts) + f",pk(@{MAX_TAPTREE_POLICY_DEPTH}/**)" + "}" * (MAX_TAPTREE_POLICY_DEPTH - 1) + ")" + descriptor_template = "tr(@0/**,{" + ',{'.join(parts) + \ + f",pk(@{MAX_TAPTREE_POLICY_DEPTH}/**)" + "}" * (MAX_TAPTREE_POLICY_DEPTH - 1) + ")" keys_info = [] for _ in range(MAX_TAPTREE_POLICY_DEPTH): @@ -263,15 +285,17 @@ def test_e2e_tapscript_maxdepth(rpc, rpc_test_wallet, client: Client, speculos_g descriptor_template=descriptor_template, keys_info=keys_info) - run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapscript_large(rpc, rpc_test_wallet, client: Client, speculos_globals: - SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], model: str): +def test_e2e_tapscript_large(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: + SpeculosGlobals): # A quite large tapscript with 8 tapleaves and 22 keys in total. # Takes more memory than Nano S can handle - if (model == "nanos"): + if (firmware.name == "nanos"): pytest.skip("Not supported on Nano S due to memory limitations") keys_info = [] @@ -295,10 +319,12 @@ def test_e2e_tapscript_large(rpc, rpc_test_wallet, client: Client, speculos_glob descriptor_template="tr(@0/**,{{{sortedmulti_a(1,@1/**,@2/**,@3/**,@4/**,@5/**),multi_a(2,@6/**,@7/**,@8/**)},{multi_a(2,@9/**,@10/**,@11/**,@12/**),pk(@13/**)}},{{multi_a(2,@14/**,@15/**),multi_a(3,@16/**,@17/**,@18/**)},{multi_a(2,@19/**,@20/**),pk(@21/**)}}})", keys_info=keys_info) - run_test_e2e(wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapminiscript_keypath_or_decaying_3of3(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapminiscript_keypath_or_decaying_3of3(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # The key path is external # The only script path is a decaying 3-of-3 that becomes a 2-of-3 after the timelock. # Only one internal key in the script path. @@ -318,11 +344,12 @@ def test_e2e_tapminiscript_keypath_or_decaying_3of3(rpc, rpc_test_wallet, client f"{core_xpub_orig_3}", ]) - run_test_e2e(wallet_policy, [core_name_2, core_name_3], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_name_2, core_name_3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapminiscript_with_hash256(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapminiscript_with_hash256(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # a taptree containing a hash challenge in a script path (but we're signing for the other script path) path = "499'/1'/0'" _, core_xpub_orig_1 = create_new_wallet() @@ -339,10 +366,12 @@ def test_e2e_tapminiscript_with_hash256(rpc, rpc_test_wallet, client: Client, sp f"{core_xpub_orig_3}", ]) - run_test_e2e(wallet_policy, [core_name_3], rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [core_name_3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_e2e_tapminiscript_mixed_leaves(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapminiscript_mixed_leaves(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): # A leaf has miniscript, a leaf has sortedmulti_a (which is not miniscript) path = "499'/1'/0'" @@ -362,11 +391,12 @@ def test_e2e_tapminiscript_mixed_leaves(rpc, rpc_test_wallet, client: Client, sp f"{core_xpub_orig_4}", ]) - run_test_e2e(wallet_policy, [], - rpc, rpc_test_wallet, client, speculos_globals, comm) + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware), e2e_sign_psbt_instruction(firmware), test_name) -def test_invalid_tapminiscript(client: Client, speculos_globals: SpeculosGlobals): +def test_invalid_tapminiscript(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, speculos_globals: SpeculosGlobals): path = "48'/1'/0'/2'" _, core_xpub_orig1 = create_new_wallet() _, core_xpub_orig2 = create_new_wallet() diff --git a/tests/test_get_extended_pubkey.py b/tests/test_get_extended_pubkey.py index b59d80f13..7bb8c00c8 100644 --- a/tests/test_get_extended_pubkey.py +++ b/tests/test_get_extended_pubkey.py @@ -1,14 +1,17 @@ -import threading - import pytest -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.exception import DenyError, NotSupportedError -from speculos.client import SpeculosClient +from ragger_bitcoin import RaggerClient +from ragger.navigator import Navigator +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU + +from ledger_bitcoin.exception.errors import NotSupportedError, DenyError +from ledger_bitcoin.exception.device_exception import DeviceException +from .instructions import pubkey_instruction_approve, pubkey_instruction_reject_early, pubkey_reject -def test_get_extended_pubkey_standard_display(client: Client, comm: SpeculosClient, is_speculos: - bool, model: str): +def test_get_extended_pubkey_standard_display(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): testcases = { "m/44'/1'/0'": "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", "m/44'/1'/10'": "tpubDCwYjpDhUdPGp21gSpVay2QPJVh6WNySWMXPhbcu1DsxH31dF7mY18oibbu5RxCLBc1Szerjscuc3D5HyvfYqfRvc9mesewnFqGmPjney4d", @@ -19,47 +22,17 @@ def test_get_extended_pubkey_standard_display(client: Client, comm: SpeculosClie "m/86'/1'/4'/1/12": "tpubDHTZ815MvTaRmo6Qg1rnU6TEU4ZkWyA56jA1UgpmMcBGomnSsyo34EZLoctzZY9MTJ6j7bhccceUeXZZLxZj5vgkVMYfcZ7DNPsyRdFpS3f", } - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - event = comm.wait_for_text_event("Confirm public key") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # go back to the Accept screen, then accept - comm.press_and_release("left") - comm.press_and_release("both") - - def ux_thread_stax(): - event = comm.get_next_event() - - while "Approve public key" not in event["text"]: - if "Tap to continue" in event["text"]: - comm.finger_touch(55, 550) - - event = comm.get_next_event() - - comm.finger_touch(55, 550) - for path, pubkey in testcases.items(): - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - x.start() assert pubkey == client.get_extended_pubkey( path=path, - display=True + display=True, + navigator=navigator, + instructions=pubkey_instruction_approve(firmware), + testname=f"{test_name}_{path}" ) - x.join() -def test_get_extended_pubkey_standard_nodisplay(client: Client): +def test_get_extended_pubkey_standard_nodisplay(client: RaggerClient): testcases = { "m/44'/1'/0'": "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", "m/44'/1'/10'": "tpubDCwYjpDhUdPGp21gSpVay2QPJVh6WNySWMXPhbcu1DsxH31dF7mY18oibbu5RxCLBc1Szerjscuc3D5HyvfYqfRvc9mesewnFqGmPjney4d", @@ -82,7 +55,7 @@ def test_get_extended_pubkey_standard_nodisplay(client: Client): ) -def test_get_extended_pubkey_nonstandard_nodisplay(client: Client): +def test_get_extended_pubkey_nonstandard_nodisplay(client: RaggerClient): # as these paths are not standard, the app should reject immediately if display=False testcases = [ "m", # unusual to export the root key @@ -98,148 +71,61 @@ def test_get_extended_pubkey_nonstandard_nodisplay(client: Client): ] for path in testcases: - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path=path, display=False ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_get_extended_pubkey_non_standard(client: Client, comm: SpeculosClient, is_speculos: bool, - model: str): +def test_get_extended_pubkey_non_standard(navigator: Navigator, firmware: Firmware, client: + RaggerClient, + test_name: str): # Test the successful UX flow for a non-standard path (here, root path) # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - event = comm.wait_for_text_event("path is unusual") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # go back to the Accept screen, then accept - comm.press_and_release("left") - comm.press_and_release("both") - - def ux_thread_stax(): - event = comm.get_next_event() - while "Approve" not in event["text"]: - if "Tap to continue" in event["text"] or "Warning" in event["text"]: - comm.finger_touch(55, 550) - - event = comm.get_next_event() - - comm.finger_touch(55, 550) - - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - x.start() - pub_key = client.get_extended_pubkey( path="m", # root pubkey - display=True + display=True, + navigator=navigator, + instructions=pubkey_instruction_approve(firmware), + testname=test_name ) - x.join() - assert pub_key == "tpubD6NzVbkrYhZ4YgUx2ZLNt2rLYAMTdYysCRzKoLu2BeSHKvzqPaBDvf17GeBPnExUVPkuBpx4kniP964e2MxyzzazcXLptxLXModSVCVEV1T" -def test_get_extended_pubkey_non_standard_reject_early(client: Client, comm: SpeculosClient, - is_speculos: bool, model: str): +def test_get_extended_pubkey_non_standard_reject_early(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): # Test rejecting after the "Reject if you're not sure" warning # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - comm.wait_for_text_event("path is unusual") - comm.press_and_release("right") - comm.wait_for_text_event("Confirm public key") - comm.press_and_release("right") - comm.wait_for_text_event("111'/222'/333'") - - comm.press_and_release("right") - comm.wait_for_text_event("not sure") # second line of "Reject if you're not sure" - comm.press_and_release("both") - - def ux_thread_stax(): - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Approve") - comm.finger_touch(55, 650) - - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - x.start() - - with pytest.raises(DenyError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path="m/111'/222'/333'", - display=True + display=True, + navigator=navigator, + instructions=pubkey_instruction_reject_early(firmware), + testname=test_name ) + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 - x.join() - -def test_get_extended_pubkey_non_standard_reject(client: Client, comm: SpeculosClient, is_speculos: - bool, model: str): +def test_get_extended_pubkey_non_standard_reject(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Test rejecting at the end # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - event = comm.wait_for_text_event("path is unusual") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # finally, reject - comm.press_and_release("both") - - def ux_thread_stax(): - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Tap to continue") - comm.finger_touch(55, 550) - comm.wait_for_text_event("Approve") - comm.finger_touch(55, 650) - - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - x.start() - - with pytest.raises(DenyError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path="m/111'/222'/333'", - display=True + display=True, + navigator=navigator, + instructions=pubkey_reject(firmware), + testname=test_name, ) - - x.join() + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 diff --git a/tests/test_get_master_fingerprint.py b/tests/test_get_master_fingerprint.py index 44c55bad3..d909bc1ab 100644 --- a/tests/test_get_master_fingerprint.py +++ b/tests/test_get_master_fingerprint.py @@ -1,6 +1,6 @@ -from bitcoin_client.ledger_bitcoin import Client +from ragger_bitcoin import RaggerClient from .conftest import SpeculosGlobals -def test_get_master_fingerprint(client: Client, speculos_globals: SpeculosGlobals): +def test_get_master_fingerprint(client: RaggerClient, speculos_globals: SpeculosGlobals): assert client.get_master_fingerprint() == speculos_globals.master_key_fingerprint diff --git a/tests/test_get_version.py b/tests/test_get_version.py index 9cfac6a7f..1876c4971 100644 --- a/tests/test_get_version.py +++ b/tests/test_get_version.py @@ -1,7 +1,7 @@ -from bitcoin_client.ledger_bitcoin import Client +from ragger_bitcoin import RaggerClient -def test_get_version(client: Client, app_version: str): +def test_get_version(client: RaggerClient, app_version: str): returned_app_name, returned_app_version, returned_app_flags = client.get_version() assert returned_app_version == app_version, "App version in Makefile did not match the one returned by the app" diff --git a/tests/test_get_wallet_address.py b/tests/test_get_wallet_address.py index 915130084..199b2a82e 100644 --- a/tests/test_get_wallet_address.py +++ b/tests/test_get_wallet_address.py @@ -2,8 +2,11 @@ import hmac import re -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError +from ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy +from ledger_bitcoin.exception.errors import IncorrectDataError +from ledger_bitcoin.exception.device_exception import DeviceException +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient from .conftest import testnet_to_regtest_addr as T @@ -14,7 +17,7 @@ # TODO: add tests with UI -def test_get_wallet_address_singlesig_legacy(client: Client): +def test_get_wallet_address_singlesig_legacy(client: RaggerClient): # legacy address (P2PKH) wallet = WalletPolicy( name="", @@ -27,7 +30,7 @@ def test_get_wallet_address_singlesig_legacy(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" -def test_get_wallet_address_singlesig_wit(client: Client): +def test_get_wallet_address_singlesig_wit(client: RaggerClient): # bech32 address (P2WPKH) wallet = WalletPolicy( name="", @@ -40,7 +43,7 @@ def test_get_wallet_address_singlesig_wit(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "tb1qlrvzyx8jcjfj2xuy69du9trtxnsvjuped7e289" -def test_get_wallet_address_singlesig_sh_wit(client: Client): +def test_get_wallet_address_singlesig_sh_wit(client: RaggerClient): # wrapped segwit addresses (P2SH-P2WPKH) wallet = WalletPolicy( name="", @@ -53,7 +56,7 @@ def test_get_wallet_address_singlesig_sh_wit(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "2NAbM4FSeBQG4o85kbXw2YNfKypcnEZS9MR" -def test_get_wallet_address_singlesig_taproot(client: Client): +def test_get_wallet_address_singlesig_taproot(client: RaggerClient): # test for a native taproot wallet (bech32m addresses, per BIP-0086) wallet = WalletPolicy( @@ -79,9 +82,9 @@ def test_get_wallet_address_singlesig_taproot(client: Client): # Failure cases for default wallets -def test_get_wallet_address_fail_nonstandard(client: Client): +def test_get_wallet_address_fail_nonstandard(client: RaggerClient): # Not empty name should be rejected - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="Not empty", descriptor_template="pkh(@0/**)", @@ -89,17 +92,21 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # 0 keys info should be rejected - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", keys_info=[], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # more than 1 key should be rejected - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -108,9 +115,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong BIP44 purpose should be rejected (here using 84' for a P2PKH address) - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -118,9 +127,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # mismatching pubkey (claiming key origin "44'/1'/0'", but that's the extended pubkey for "84'/1'/0'"") - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -128,9 +139,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong master fingerprint - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -138,9 +151,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[42424242/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # too large address_index, cannot be done non-silently - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -148,9 +163,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 100000, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # missing key origin info - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/**)", @@ -158,9 +175,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # non-standard final derivation steps - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0/<0;2>/*)", @@ -168,9 +187,11 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # taproot single-sig with non-empty script - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="tr(@0,0)", @@ -178,12 +199,14 @@ def test_get_wallet_address_fail_nonstandard(client: Client): f"[f5acc2fd/86'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # Multisig -def test_get_wallet_address_multisig_legacy(client: Client): +def test_get_wallet_address_multisig_legacy(client: RaggerClient): # test for a legacy p2sh multisig wallet wallet = MultisigWallet( @@ -203,7 +226,7 @@ def test_get_wallet_address_multisig_legacy(client: Client): assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" -def test_get_wallet_address_multisig_sh_wit(client: Client): +def test_get_wallet_address_multisig_sh_wit(client: RaggerClient): # test for a wrapped segwit multisig wallet wallet = MultisigWallet( @@ -223,7 +246,7 @@ def test_get_wallet_address_multisig_sh_wit(client: Client): assert res == "2MxAUTJh27foYtyp9dcSxP7RgaSwkkVCHTU" -def test_get_wallet_address_multisig_wit(client: Client): +def test_get_wallet_address_multisig_wit(client: RaggerClient): # test for a native segwit multisig wallet (bech32 address) wallet = MultisigWallet( @@ -243,7 +266,7 @@ def test_get_wallet_address_multisig_wit(client: Client): assert res == "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28" -def test_get_wallet_address_tr_script_pk(client: Client): +def test_get_wallet_address_tr_script_pk(client: RaggerClient): wallet = WalletPolicy( name="Taproot foreign internal key, and our script key", descriptor_template="tr(@0/**,pk(@1/**))", @@ -261,7 +284,7 @@ def test_get_wallet_address_tr_script_pk(client: Client): assert res == "tb1pls9pp5cgcljpkjauxep03lv2c2yc2wcuua26p3ks6j2lq0vl9kjqf5rgm2" -def test_get_wallet_address_tr_script_sortedmulti(client: Client): +def test_get_wallet_address_tr_script_sortedmulti(client: RaggerClient): wallet = WalletPolicy( name="Taproot single-key or multisig 2-of-2", descriptor_template="tr(@0/**,sortedmulti_a(2,@1/**,@2/**))", @@ -280,7 +303,7 @@ def test_get_wallet_address_tr_script_sortedmulti(client: Client): assert res == "tb1pdzk72dnvz3246474p4m5a97u43h6ykt2qcjrrhk6y0fkg8hx2mvswwgvv7" -def test_get_wallet_address_large_addr_index(client: Client): +def test_get_wallet_address_large_addr_index(client: RaggerClient): # 2**31 - 1 is the largest index allowed, per BIP-32 wallet = MultisigWallet( @@ -299,7 +322,7 @@ def test_get_wallet_address_large_addr_index(client: Client): client.get_wallet_address(wallet, wallet_hmac, 0, 2**31 - 1, False) # too large address_index, not allowed for an unhardened step - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(wallet, wallet_hmac, 0, 2**31, False) @@ -408,4 +431,3 @@ def generate_address_and_compare_with_core(desc_tmpl: str): desc_tmpl = f"tr(@{n_keys}/**,and_b(pk(@{n_keys+1}/**),{prepend_a(fr)}))" generate_address_and_compare_with_core(desc_tmpl) - diff --git a/tests/test_get_wallet_address_v1.py b/tests/test_get_wallet_address_v1.py index 2120aeecb..8d7f52e3f 100644 --- a/tests/test_get_wallet_address_v1.py +++ b/tests/test_get_wallet_address_v1.py @@ -1,18 +1,23 @@ # Tests using the V1 version of the wallet policy language, used before version 2.1.0 of the app # Make sure we remain compatible for some time. -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy, WalletType -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError -from speculos.client import SpeculosClient +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy, WalletType +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.exception.errors import IncorrectDataError +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient + +from .instructions import wallet_instruction_approve -import threading import pytest # TODO: add more tests with UI -def test_get_wallet_address_singlesig_legacy_v1(client: Client): +def test_get_wallet_address_singlesig_legacy_v1(client: RaggerClient): # legacy address (P2PKH) wallet = WalletPolicy( name="", @@ -26,7 +31,7 @@ def test_get_wallet_address_singlesig_legacy_v1(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" -def test_get_wallet_address_singlesig_wit_v1(client: Client): +def test_get_wallet_address_singlesig_wit_v1(client: RaggerClient): # bech32 address (P2WPKH) wallet = WalletPolicy( name="", @@ -40,7 +45,7 @@ def test_get_wallet_address_singlesig_wit_v1(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "tb1qlrvzyx8jcjfj2xuy69du9trtxnsvjuped7e289" -def test_get_wallet_address_singlesig_sh_wit_v1(client: Client): +def test_get_wallet_address_singlesig_sh_wit_v1(client: RaggerClient): # wrapped segwit addresses (P2SH-P2WPKH) wallet = WalletPolicy( name="", @@ -54,7 +59,7 @@ def test_get_wallet_address_singlesig_sh_wit_v1(client: Client): assert client.get_wallet_address(wallet, None, 1, 15, False) == "2NAbM4FSeBQG4o85kbXw2YNfKypcnEZS9MR" -def test_get_wallet_address_singlesig_taproot_v1(client: Client): +def test_get_wallet_address_singlesig_taproot_v1(client: RaggerClient): # test for a native taproot wallet (bech32m addresses, per BIP-0086) wallet = WalletPolicy( @@ -81,18 +86,20 @@ def test_get_wallet_address_singlesig_taproot_v1(client: Client): # Failure cases for default wallets -def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): +def test_get_wallet_address_default_fail_wrongkeys_v1(client: RaggerClient): # 0 keys info should be rejected - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", keys_info=[], version=WalletType.WALLET_POLICY_V1 ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # more than 1 key should be rejected - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -101,9 +108,11 @@ def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong BIP44 purpose should be rejected (here using 84' for a P2PKH address) - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -112,9 +121,11 @@ def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): ], version=WalletType.WALLET_POLICY_V1 ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # mismatching pubkey (claiming key origin "44'/1'/0'", but that's the extended dpubkey for "84'/1'/0'"") - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -122,9 +133,11 @@ def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): f"[f5acc2fd/44'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong master fingerprint - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -133,9 +146,11 @@ def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): ], version=WalletType.WALLET_POLICY_V1 ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # too large address_index, cannot be done non-silently - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.get_wallet_address(WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -144,11 +159,13 @@ def test_get_wallet_address_default_fail_wrongkeys_v1(client: Client): ], version=WalletType.WALLET_POLICY_V1 ), None, 0, 100000, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # Multisig -def test_get_wallet_address_multisig_legacy_v1(client: Client): +def test_get_wallet_address_multisig_legacy_v1(client: RaggerClient): # test for a legacy p2sh multisig wallet wallet = MultisigWallet( @@ -169,7 +186,7 @@ def test_get_wallet_address_multisig_legacy_v1(client: Client): assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" -def test_get_wallet_address_multisig_sh_wit_v1(client: Client): +def test_get_wallet_address_multisig_sh_wit_v1(client: RaggerClient): # test for a wrapped segwit multisig wallet wallet = MultisigWallet( @@ -190,7 +207,7 @@ def test_get_wallet_address_multisig_sh_wit_v1(client: Client): assert res == "2MxAUTJh27foYtyp9dcSxP7RgaSwkkVCHTU" -def test_get_wallet_address_multisig_wit_v1(client: Client): +def test_get_wallet_address_multisig_wit_v1(client: RaggerClient): # test for a native segwit multisig wallet (bech32 address) wallet = MultisigWallet( @@ -211,35 +228,9 @@ def test_get_wallet_address_multisig_wit_v1(client: Client): assert res == "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28" -def test_get_wallet_address_singlesig_legacy_v1_ui(client: Client, comm: SpeculosClient, - is_speculos: bool, model: str): +def test_get_wallet_address_singlesig_legacy_v1_ui(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # legacy address (P2PKH) - def ux_thread(): - event = comm.wait_for_text_event("Address") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # go back to the Accept screen, then accept - comm.press_and_release("left") - comm.press_and_release("both") - - def ux_thread_stax(): - while True: - event = comm.get_next_event() - if "Tap to continue" in event["text"] or "Show as QR" in event["text"]: - comm.finger_touch(55, 550) - elif "VERIFIED" in event["text"]: - break - - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - wallet = WalletPolicy( name="", descriptor_template="pkh(@0)", @@ -248,23 +239,17 @@ def ux_thread_stax(): ], version=WalletType.WALLET_POLICY_V1 ) - x.start() - assert client.get_wallet_address(wallet, None, 0, 0, True) == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" - x.join() - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - x.start() - assert client.get_wallet_address(wallet, None, 1, 15, True) == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" - x.join() + assert client.get_wallet_address(wallet, None, 0, 0, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), testname=f"{test_name}_0") == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" + assert client.get_wallet_address(wallet, None, 1, 15, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), testname=f"{test_name}_1") == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" -def test_get_wallet_address_multisig_legacy_v1_ui(client: Client, comm: SpeculosClient, is_speculos: - bool, model: str): - # test for a legacy p2sh multisig wallet +def test_get_wallet_address_multisig_legacy_v1_ui(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): + # test for a legacy p2sh multisig wallet wallet = MultisigWallet( name="Cold storage", address_type=AddressType.LEGACY, @@ -279,33 +264,7 @@ def test_get_wallet_address_multisig_legacy_v1_ui(client: Client, comm: Speculos "1980a07cde99fbdec0d487671d3bb296507e47b3ddfa778600a9d73d501983bc" ) - def ux_thread(): - event = comm.wait_for_text_event("Receive") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # go back to the Accept screen, then accept - comm.press_and_release("left") - comm.press_and_release("both") - - def ux_thread_stax(): - while True: - event = comm.get_next_event() - if "Tap to continue" in event["text"] or "Confirm" in event["text"]: - comm.finger_touch(55, 550) - elif "CONFIRMED" in event["text"]: - break - - if model == "stax": - x = threading.Thread(target=ux_thread_stax) - else: - x = threading.Thread(target=ux_thread) - - x.start() - res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, True) - x.join() + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), + testname=test_name) assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" diff --git a/tests/test_protocol.py b/tests/test_protocol.py index e42bc37e8..670ea6859 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -1,17 +1,20 @@ import pytest -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.client_base import ApduException -from bitcoin_client.ledger_bitcoin.command_builder import BitcoinCommandBuilder, BitcoinInsType, CURRENT_PROTOCOL_VERSION +from ledger_bitcoin.command_builder import BitcoinCommandBuilder, BitcoinInsType, CURRENT_PROTOCOL_VERSION +from ledger_bitcoin.exception.errors import WrongP1P2Error +from ledger_bitcoin.exception.device_exception import DeviceException +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient -def test_high_p1_allowed(client: Client): + +def test_high_p1_allowed(client: RaggerClient): # We reserve p1 for feature flags, so non-zero bits shouldn't be rejected # for forward-compatibility; this allows graceful degradation for optional features. # We can't use the client to send this apdu, so we use raw transport. # We're only testing that no exception is raised. - client.transport_client.apdu_exchange( + client.transport_client.exchange( cla=BitcoinCommandBuilder.CLA_BITCOIN, ins=BitcoinInsType.GET_MASTER_FINGERPRINT, p1=0xff, @@ -20,14 +23,16 @@ def test_high_p1_allowed(client: Client): ) -def test_p2_too_high(client: Client): +def test_p2_too_high(client: RaggerClient): # Tests that sending a p2 > CURRENT_PROTOCOL_VERSION fails with 0x6a86 (WRONG_P1P2) - with pytest.raises(ApduException, match="Exception: invalid status 0x6a86"): + with pytest.raises(ExceptionRAPDU) as e: # We can't use the client to send this apdu, so we use raw transport - client.transport_client.apdu_exchange( + client.transport_client.exchange( cla=BitcoinCommandBuilder.CLA_BITCOIN, ins=BitcoinInsType.GET_MASTER_FINGERPRINT, p1=0, p2=CURRENT_PROTOCOL_VERSION + 1, data=b'' ) + assert DeviceException.exc.get(e.value.status) == WrongP1P2Error + assert len(e.value.data) == 0 diff --git a/tests/test_register_wallet.py b/tests/test_register_wallet.py index bdb2f5a4f..7e7f208d4 100644 --- a/tests/test_register_wallet.py +++ b/tests/test_register_wallet.py @@ -1,17 +1,25 @@ -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError -from bitcoin_client.ledger_bitcoin.exception import DenyError - -from test_utils import has_automation - +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.exception import DenyError +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient + +from .instructions import register_wallet_instruction_approve, register_wallet_instruction_approve_long, register_wallet_instruction_approve_unusual, register_wallet_instruction_reject, Instructions import hmac from hashlib import sha256 import pytest -def run_register_test(client: Client, speculos_globals, wallet_policy: WalletPolicy) -> None: - wallet_policy_id, wallet_hmac = client.register_wallet(wallet_policy) +def run_register_test(navigator: Navigator, client: RaggerClient, speculos_globals, wallet_policy: + WalletPolicy, instructions: Instructions, + test_name: str = "") -> None: + wallet_policy_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions, + testname=test_name) assert wallet_policy_id == wallet_policy.id @@ -21,9 +29,9 @@ def run_register_test(client: Client, speculos_globals, wallet_policy: WalletPol ) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_legacy(client: Client, speculos_globals): - run_register_test(client, speculos_globals, MultisigWallet( +def test_register_wallet_accept_legacy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( name="Cold storage", address_type=AddressType.LEGACY, threshold=2, @@ -31,12 +39,14 @@ def test_register_wallet_accept_legacy(client: Client, speculos_globals): "[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW", "[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm", ], - )) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_sh_wit(client: Client, speculos_globals): - run_register_test(client, speculos_globals, MultisigWallet( +def test_register_wallet_accept_sh_wit(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( name="Cold storage", address_type=AddressType.SH_WIT, threshold=2, @@ -44,12 +54,13 @@ def test_register_wallet_accept_sh_wit(client: Client, speculos_globals): "[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g", "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", ], - )) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_wit(client: Client, speculos_globals): - run_register_test(client, speculos_globals, MultisigWallet( +def test_register_wallet_accept_wit(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( name="Cold storage", address_type=AddressType.WIT, threshold=2, @@ -57,15 +68,17 @@ def test_register_wallet_accept_wit(client: Client, speculos_globals): "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - )) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_with_long_name(client: Client, speculos_globals): +def test_register_wallet_with_long_name(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): name = "Cold storage with a pretty long name that requires 64 characters" assert len(name) == 64 - run_register_test(client, speculos_globals, MultisigWallet( + run_register_test(navigator, client, speculos_globals, MultisigWallet( name=name, address_type=AddressType.WIT, threshold=2, @@ -73,11 +86,13 @@ def test_register_wallet_with_long_name(client: Client, speculos_globals): "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - )) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_reject.json") -def test_register_wallet_reject_header(client: Client): +def test_register_wallet_reject_header(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -88,17 +103,22 @@ def test_register_wallet_reject_header(client: Client): ], ) - with pytest.raises(DenyError): - client.register_wallet(wallet) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_reject(firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_invalid_pubkey_version(client: Client): +def test_register_wallet_invalid_pubkey_version(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # This is the same wallet policy as the test_register_wallet_accept_wit test, # but the external pubkey has the wrong BIP32 version (mainnet xpub instead of testnet tpub). # An older version of the app ignored the version for external pubkeys, while now it rejects it # if the version is wrong, as a sanity check. - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -107,11 +127,13 @@ def test_register_wallet_invalid_pubkey_version(client: Client): "[76223a6e/48'/1'/0'/2']xpub6DjjtjxALtJSP9dKRKuhejeTpZc711gUGZyS9nCM5GAtrNTDuMBZD2FcndJoHst6LYNbJktm4NmJyKqspLi5uRmtnDMAdcPAf2jiSj9gFTX", "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - )) + ), navigator, instructions=register_wallet_instruction_approve(firmware), testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_invalid_names(client: Client): +def test_register_wallet_invalid_names(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): too_long_name = "This wallet name is much too long since it requires 65 characters" assert len(too_long_name) == 65 @@ -131,12 +153,15 @@ def test_register_wallet_invalid_names(client: Client): ], ) - with pytest.raises(IncorrectDataError): - client.register_wallet(wallet) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_missing_key(client: Client): +def test_register_wallet_missing_key(client: RaggerClient): wallet = WalletPolicy( name="Missing a key", descriptor_template="wsh(multi(2,@0/**,@1/**))", @@ -146,26 +171,33 @@ def test_register_wallet_missing_key(client: Client): ], ) - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_unsupported_policy(client: Client): +def test_register_wallet_unsupported_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # valid policies, but not supported (might change in the future) - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Unsupported", descriptor_template="pk(@0/**)", # bare pubkey, not supported keys_info=[ "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", ] - )) + ), + navigator, + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_miniscript_long_policy(client: Client, speculos_globals, model: str): + +def test_register_miniscript_long_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): # This test makes sure that policies longer than 256 bytes work as expected on all devices, # except on Nano S that has 196 bytes as a technical limitation. wallet = WalletPolicy( @@ -177,11 +209,17 @@ def test_register_miniscript_long_policy(client: Client, speculos_globals, model "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", ]) - if (model == "nanos"): - with pytest.raises(IncorrectDataError): # TODO: NotSupportedError would be a better error result + if (firmware.name == "nanos"): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 else: - wallet_id, wallet_hmac = client.register_wallet(wallet) + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_approve_long( + firmware), + testname=test_name) assert wallet_id == wallet.id @@ -191,9 +229,10 @@ def test_register_miniscript_long_policy(client: Client, speculos_globals, model ) -def test_register_wallet_not_sane_policy(client: Client): +def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # pubkeys in the keys vector must be all different - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Unsupported policy", descriptor_template=f"wsh(c:andor(pk(@0/<0;1>/*),pk_k(@1/**),and_v(v:pk(@2/<2;3>/*),pk_k(@3/**))))", @@ -203,18 +242,30 @@ def test_register_wallet_not_sane_policy(client: Client): # the next key is again the internal pubkey, but without key origin information "tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", - ])) + ]), + navigator, + testname=test_name + ) + + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 # Key placeholders referring to the same key must have distinct derivations - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Unsupported policy", descriptor_template="wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@0/**),sln:older(12960)))", keys_info=[ "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", - ])) - with pytest.raises(NotSupportedError): + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Unsupported policy", # even a partial overlap (derivation @0/1 being used twice) is not acceptable @@ -222,20 +273,30 @@ def test_register_wallet_not_sane_policy(client: Client): keys_info=[ "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", - ])) + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 # Miniscript policy with timelock mixing - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Timelock mixing is bad", descriptor_template="wsh(thresh(2,c:pk_k(@0/**),ac:pk_k(@1/**),altv:after(1000000000),altv:after(100)))", keys_info=[ "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", - ])) + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 # Miniscript policy that does not always require a signature - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="No need for sig", descriptor_template="wsh(or_d(multi(1,@0/**),or_b(multi(3,@1/**,@2/**,@3/**),su:after(500000))))", @@ -244,66 +305,85 @@ def test_register_wallet_not_sane_policy(client: Client): "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", "tpubDF6JT5K4izwALMpFv7fQrpWr5bGUMEoWphkzTVJH8jTfgirNEgGZnxsWJDCCxhg2UnW5RcD9Tx8aVAdoM734X5bnRGmJUujz26uQ5gAC1nE", "tpubDF4kujkh5dAhC1pFgBToZybXdvJFXXGX4BWdDxWqP7EUpG8gxkfMQeDjGPDnTr9e4NrkFmDM1ocav3Jz6x79CRZbxGr9dzFokJLuvDDnyRh", - ])) + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 # Malleable policy, even if it requires a signature - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Malleable", descriptor_template="wsh(c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(@0/**),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(@1/**))))", keys_info=[ "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", - ])) + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 # TODO: we can probably not trigger stack and ops limits with the current limits we have on the # miniscript policy size; otherwise it would be worth to add tests for them, too. -@has_automation("automations/register_wallet_accept.json") -def test_register_unusual_singlesig_accounts(client: Client, speculos_globals): +def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): # Tests that it is possible to register policies for single-signature using unusual paths - run_register_test(client, speculos_globals, WalletPolicy( + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Unusual Legacy", descriptor_template="pkh(@0/**)", keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] - )) + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Legacy") - run_register_test(client, speculos_globals, WalletPolicy( + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Unusual Nested SegWit", descriptor_template="sh(wpkh(@0/**))", keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] - )) + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Nested_Segwit") - run_register_test(client, speculos_globals, WalletPolicy( + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Unusual Native SegWit", descriptor_template="wpkh(@0/**)", keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] - )) + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Native_Segwit") - run_register_test(client, speculos_globals, WalletPolicy( + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Unusual Taproot", descriptor_template="tr(@0/**)", keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] - )) + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Taproot") -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_tr_script_pk(client: Client, speculos_globals): - run_register_test(client, speculos_globals, WalletPolicy( +def test_register_wallet_tr_script_pk(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Taproot foreign internal key, and our script key", descriptor_template="tr(@0/**,pk(@1/**))", keys_info=[ "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - )) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_tr_script_sortedmulti(client: Client, speculos_globals): - run_register_test(client, speculos_globals, WalletPolicy( +def test_register_wallet_tr_script_sortedmulti(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, WalletPolicy( name="Taproot single-key or multisig 2-of-2", descriptor_template="tr(@0/**,sortedmulti_a(2,@1/**,@2/**))", keys_info=[ @@ -311,4 +391,6 @@ def test_register_wallet_tr_script_sortedmulti(client: Client, speculos_globals) "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - )) + ), + instructions=register_wallet_instruction_approve_long(firmware), + test_name=test_name) diff --git a/tests/test_register_wallet_v1.py b/tests/test_register_wallet_v1.py index 16f5a8ec7..bcea05160 100644 --- a/tests/test_register_wallet_v1.py +++ b/tests/test_register_wallet_v1.py @@ -1,11 +1,16 @@ # Tests using the V1 version of the wallet policy language, used before version 2.1.0 of the app # Make sure we remain compatible for some time. -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy, WalletType -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError -from bitcoin_client.ledger_bitcoin.exception import DenyError +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy, WalletType +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError, DenyError +from ledger_bitcoin.exception.device_exception import DeviceException -from test_utils import has_automation +from ragger.error import ExceptionRAPDU +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger_bitcoin import RaggerClient + +from .instructions import register_wallet_instruction_approve, register_wallet_instruction_approve_long, register_wallet_instruction_approve_unusual, register_wallet_instruction_reject import hmac from hashlib import sha256 @@ -13,8 +18,8 @@ import pytest -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_legacy_v1(client: Client, speculos_globals): +def test_register_wallet_accept_legacy_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.LEGACY, @@ -26,7 +31,9 @@ def test_register_wallet_accept_legacy_v1(client: Client, speculos_globals): version=WalletType.WALLET_POLICY_V1 ) - wallet_id, wallet_hmac = client.register_wallet(wallet) + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_approve(firmware), + testname=test_name) assert wallet_id == wallet.id @@ -36,8 +43,8 @@ def test_register_wallet_accept_legacy_v1(client: Client, speculos_globals): ) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_sh_wit_v1(client: Client, speculos_globals): +def test_register_wallet_accept_sh_wit_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.SH_WIT, @@ -49,7 +56,9 @@ def test_register_wallet_accept_sh_wit_v1(client: Client, speculos_globals): version=WalletType.WALLET_POLICY_V1 ) - wallet_id, wallet_hmac = client.register_wallet(wallet) + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_approve(firmware), + testname=test_name) assert wallet_id == wallet.id @@ -59,8 +68,8 @@ def test_register_wallet_accept_sh_wit_v1(client: Client, speculos_globals): ) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_wit_v1(client: Client, speculos_globals): +def test_register_wallet_accept_wit_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -72,7 +81,9 @@ def test_register_wallet_accept_wit_v1(client: Client, speculos_globals): version=WalletType.WALLET_POLICY_V1 ) - wallet_id, wallet_hmac = client.register_wallet(wallet) + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_approve(firmware), + testname=test_name) assert wallet_id == wallet.id @@ -82,8 +93,8 @@ def test_register_wallet_accept_wit_v1(client: Client, speculos_globals): ) -@has_automation("automations/register_wallet_reject.json") -def test_register_wallet_reject_header_v1(client: Client): +def test_register_wallet_reject_header_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -95,12 +106,17 @@ def test_register_wallet_reject_header_v1(client: Client): version=WalletType.WALLET_POLICY_V1 ) - with pytest.raises(DenyError): - client.register_wallet(wallet) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_reject(firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_invalid_names_v1(client: Client): +def test_register_wallet_invalid_names_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): for invalid_name in [ "", # empty name not allowed "Very long walletz", # 17 characters is too long @@ -118,15 +134,18 @@ def test_register_wallet_invalid_names_v1(client: Client): version=WalletType.WALLET_POLICY_V1 ) - with pytest.raises(IncorrectDataError): - client.register_wallet(wallet) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_unsupported_policy_v1(client: Client): +def test_register_wallet_unsupported_policy_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # valid policies, but not supported (might change in the future) - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( name="Unsupported", descriptor_template="pk(@0)", # bare pubkey, not supported @@ -134,9 +153,15 @@ def test_register_wallet_unsupported_policy_v1(client: Client): f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", ], version=WalletType.WALLET_POLICY_V1 - )) + ), + navigator, + testname=test_name) - with pytest.raises(NotSupportedError): + # NotSupportedError + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + with pytest.raises(ExceptionRAPDU) as e: # Not supporting keys without wildcard client.register_wallet(MultisigWallet( name="Cold storage", @@ -147,4 +172,10 @@ def test_register_wallet_unsupported_policy_v1(client: Client): f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], version=WalletType.WALLET_POLICY_V1 - )) + ), + navigator, + testname=test_name) + + # NotSupportedError + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 diff --git a/tests/test_sign_message.py b/tests/test_sign_message.py index dd6f3416b..34f422469 100644 --- a/tests/test_sign_message.py +++ b/tests/test_sign_message.py @@ -1,83 +1,103 @@ import pytest -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.exception.errors import DenyError +from ledger_bitcoin.exception.errors import DenyError +from ledger_bitcoin.exception.device_exception import DeviceException +from ragger.navigator import Navigator +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient +from .instructions import message_instruction_approve, message_instruction_approve_long, message_instruction_reject -from test_utils import has_automation - -@has_automation("automations/sign_message_accept.json") -def test_sign_message(client: Client): +def test_sign_message(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): msg = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks." path = "m/44'/1'/0'/0/0" - result = client.sign_message(msg, path) + result = client.sign_message(msg, path, navigator, + instructions=message_instruction_approve(firmware), + testname=test_name) assert result == "IOR4YRVlmJGMx+H7PgQvHzWAF0HAgrUggQeRdnoWKpypfaAberpvF+XbOCM5Cd/ljogNyU3w2OIL8eYCyZ6Ru2k=" -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept(client: Client): +def test_sign_message_accept(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): message = "Hello world!" res = client.sign_message( message, - "m/84'/1'/0'/0/0" + "m/84'/1'/0'/0/0", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name ) assert res == 'IEOK4+JMK7FToR7XMzFCoAYh1nud1IKm9Wq3vXLSVk/lBay8rHCRp9bP6riyR5NDqXYyYf7cXgMQTHNz3SemwZI=' -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept_long(client: Client): +def test_sign_message_accept_long(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # Test with a long message that is split in multiple leaves in the Merkle tree - message = "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible." res = client.sign_message( message, - "m/84'/1'/0'/0/8" + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve_long(firmware), + testname=test_name ) assert res == 'H4frM6TYm5ty1MAf9o/Zz9Qiy3VEldAYFY91SJ/5nYMAZY1UUB97fiRjKW8mJit2+V4OCa1YCqjDqyFnD9Fw75k=' -@has_automation("automations/sign_message_reject.json") -def test_sign_message_reject(client: Client): - with pytest.raises(DenyError): - client.sign_message("Anything", "m/44'/1'/0'/0/0") +def test_sign_message_reject(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + with pytest.raises(ExceptionRAPDU) as e: + client.sign_message("Anything", "m/44'/1'/0'/0/0", + navigator, + instructions=message_instruction_reject(firmware), + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept_non_ascii(client: Client): - # Test with a message that contains non ascii char +def test_sign_message_accept_non_ascii(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # Test with a message that contains non ascii char message = "Hello\nworld!" res = client.sign_message( message, - "m/84'/1'/0'/0/8" + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name ) assert res == 'IGGk2UM12aQGtigJ7XCLJEXQl3bdKgx0G3CIt0ADSWknfAHqs+9+9OPZSjGrjyp46GjztGzUAnCa/DDMrSIAfbg=' -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept_too_long(client: Client): +def test_sign_message_accept_too_long(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # Test with a message that is too long to be displayed - message = "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible. The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible. The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible." res = client.sign_message( message, - "m/84'/1'/0'/0/8" + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name ) assert res == 'IDAl9RThAyunmYuol9DaDs/CScUpiol3FDSjIjyK9y0tc/x1HWrbT/ufdkPFY1Bmi+L9hc3ip1me2RmufprVuNk=' -@has_automation("automations/sign_message_reject.json") -def test_sign_message_hash_reject(client: Client): - with pytest.raises(DenyError): - client.sign_message("Hello\nworld!", "m/44'/1'/0'/0/0") - +def test_sign_message_hash_reject(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + with pytest.raises(ExceptionRAPDU) as e: + client.sign_message("Hello\nworld!", + "m/44'/1'/0'/0/0", + navigator, + instructions=message_instruction_reject(firmware), + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 diff --git a/tests/test_sign_psbt.py b/tests/test_sign_psbt.py index 0c8d39dff..2e3cad1e3 100644 --- a/tests/test_sign_psbt.py +++ b/tests/test_sign_psbt.py @@ -9,137 +9,29 @@ from pathlib import Path -from bitcoin_client.ledger_bitcoin import Client, WalletPolicy, MultisigWallet, AddressType, PartialSignature -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin import WalletPolicy, MultisigWallet, AddressType, PartialSignature +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import AddressType -from speculos.client import SpeculosClient +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import AddressType +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware -from test_utils import has_automation, bip0340, txmaker +from test_utils import bip0340, txmaker from embit.script import Script from embit.networks import NETWORKS +import requests +import json -from test_utils.speculos import automation +from ragger_bitcoin import RaggerClient +from .instructions import * tests_root: Path = Path(__file__).parent -CURRENCY_TICKER = "TEST" - - -def format_amount(ticker: str, amount: int) -> str: - """Formats an amounts in sats as shown in the app: divided by 10_000_000, with no trailing zeroes.""" - assert amount >= 0 - btc_amount = f"{(amount/100_000_000):.8f}".rstrip('0').rstrip('.') - return f"{ticker} {btc_amount}" - - -def should_go_right(event: dict): - """Returns true if the current text event implies a "right" button press to proceed.""" - - if event["text"].startswith("Review"): - return True - elif event["text"].startswith("Amount"): - return True - elif event["text"].startswith("Address"): - return True - elif event["text"].startswith("Confirm"): - return True - elif event["text"].startswith("Fees"): - return True - return False - - -def ux_thread_sign_psbt_stax(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - first_approve = True - - while True: - event = speculos_client.get_next_event() - all_events.append(event) - - if "Tap to continue" in event["text"]: - speculos_client.finger_touch(55, 550) - - elif first_approve and "Hold to sign" in event["text"]: - first_approve = False - speculos_client.finger_touch(55, 550, 3) - - elif "SIGNED" in event["text"]: - break - - -def ux_thread_sign_psbt(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - # press right until the last screen (will press the "right" button more times than needed) - while True: - event = speculos_client.get_next_event() - all_events.append(event) - - if should_go_right(event): - speculos_client.press_and_release("right") - elif "Approve" in event["text"]: - speculos_client.press_and_release("both") - elif event["text"] == "Accept": - speculos_client.press_and_release("both") - break - - -def parse_signing_events(events: List[dict]) -> dict: - ret = dict() - - cur_output_index = -1 - - ret["addresses"] = [] - ret["amounts"] = [] - ret["fees"] = "" - next_step = "" - keywords = ("Amount", "Address", "Fees", "Accept", "Approve") - - for ev in events: - if ev["text"].startswith("output #"): - idx_str = ev["text"][8:] - - assert int(idx_str) - 1 == cur_output_index + 1 # should not skip outputs - - cur_output_index = int(idx_str) - 1 - - ret["addresses"].append("") - ret["amounts"].append("") - next_step = "" - - elif ev["text"].startswith("Tap"): - ret["addresses"].append("") - ret["amounts"].append("") - next_step = "" - continue - - elif ev["text"].startswith(keywords): - next_step = ev["text"] - continue - - if next_step.startswith("Address"): - if len(ret["addresses"]) == 0: - ret["addresses"].append("") - - ret["addresses"][-1] += ev["text"].strip() - - elif next_step.startswith("Fees"): - ret["fees"] += ev["text"].strip() - - elif next_step.startswith("Amount"): - if len(ret["amounts"]) == 0: - ret["amounts"].append("") - - ret["amounts"][-1] += ev["text"].strip() - - return ret - - def open_psbt_from_file(filename: str) -> PSBT: raw_psbt_base64 = open(filename, "r").read() @@ -148,9 +40,7 @@ def open_psbt_from_file(filename: str) -> PSBT: return psbt -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_pkh_1to1(client: Client): - +def test_sign_psbt_singlesig_pkh_1to1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for a legacy 1-input 1-output spend (no change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") @@ -166,8 +56,20 @@ def test_sign_psbt_singlesig_pkh_1to1(client: Client): # #0: # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + print(result) + print([( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) + ) + )]) assert result == [( 0, PartialSignature( @@ -179,8 +81,7 @@ def test_sign_psbt_singlesig_pkh_1to1(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): +def test_sign_psbt_singlesig_sh_wpkh_1to2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") @@ -197,7 +98,9 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): # #0: # "pubkey" : "024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", # "signature" : "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert result == [( 0, @@ -210,8 +113,7 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept_highfee.json") -def test_sign_psbt_highfee(client: Client): +def test_sign_psbt_highfee(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # Transactions with fees higher than 10% of total amount # An aditional warning is shown. @@ -233,14 +135,15 @@ def test_sign_psbt_highfee(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_3(firmware), + testname=test_name) assert len(result) == 1 -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_1to2(client: Client): - +def test_sign_psbt_singlesig_wpkh_1to2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") @@ -252,7 +155,9 @@ def test_sign_psbt_singlesig_wpkh_1to2(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) # expected sigs # #0: @@ -270,8 +175,8 @@ def test_sign_psbt_singlesig_wpkh_1to2(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_2to2(client: Client): +def test_sign_psbt_singlesig_wpkh_2to2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a segwit 2-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") @@ -284,7 +189,9 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # expected sigs # #0: @@ -313,8 +220,8 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): )] -@has_automation("automations/sign_with_default_wallet_missing_nonwitnessutxo_accept.json") -def test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo(client: Client): +def test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo(navigator: Navigator, firmware: + Firmware, client: RaggerClient, test_name: str): # Same as the previous test, but the non-witness-utxo is missing. # The app should sign after a warning. @@ -332,7 +239,9 @@ def test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) # expected sigs # #0: @@ -361,8 +270,9 @@ def test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_selftransfer(client: Client): +def test_sign_psbt_singlesig_wpkh_selftransfer(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # The only output is a change output. # A "self-transfer" screen should be shown before the fees. @@ -375,12 +285,14 @@ def test_sign_psbt_singlesig_wpkh_selftransfer(client: Client): ) psbt = "cHNidP8BAHECAAAAAfcDVJxLN1tzz5vaIy2onFL/ht/OqwKm2jEWGwMNDE/cAQAAAAD9////As0qAAAAAAAAFgAUJfcXOL7SoYGoDC1n6egGa0OTD9/mtgEAAAAAABYAFDXG4N1tPISxa6iF3Kc6yGPQtZPsTTQlAAABAPYCAAAAAAEBCOcYS1aMP1uQcUKTMJbvlsZXsV4yNnVxynyMfxSX//UAAAAAFxYAFGEWho6AN6qeux0gU3BSWnK+Dw4D/f///wKfJwEAAAAAABepFG1IUtrzpUCfdyFtu46j1ZIxLX7ph0DiAQAAAAAAFgAU4e5IJz0XxNe96ANYDugMQ34E0/cCRzBEAiB1b84pX0QaOUrvCdDxKeB+idM6wYKTLGmqnUU/tL8/lQIgbSinpq4jBlo+SIGyh8XNVrWAeMlKBNmoLenKOBugKzcBIQKXsd8NwO+9naIfeI3nkgYjg6g3QZarGTRDs7SNVZfGPJBJJAABAR9A4gEAAAAAABYAFOHuSCc9F8TXvegDWA7oDEN+BNP3IgYCgffBheEUZI8iAFFfv7b+HNM7j4jolv6lj5/n3j68h3kY9azC/VQAAIABAACAAAAAgAAAAAAHAAAAACICAzQZjNnkwXFEhm1F6oC2nk1ADqH6t/RHBAOblLA4tV5BGPWswv1UAACAAQAAgAAAAIABAAAAEgAAAAAiAgJxtbd5rYcIOFh3l7z28MeuxavnanCdck9I0uJs+HTwoBj1rML9VAAAgAEAAIAAAACAAQAAAAAAAAAA" - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_5(firmware), + testname=test_name) assert len(result) == 1 -# def test_sign_psbt_legacy(client: Client): +# def test_sign_psbt_legacy(client: RaggerClient, test_name: str): # # legacy address # # PSBT for a legacy 1-input 1-output spend # unsigned_raw_psbt_base64 = "cHNidP8BAFQCAAAAAbUlIwxFfIt0fsuFCNtL3dHKcOvUPQu2CNcqc8FrNtTyAAAAAAD+////AaDwGQAAAAAAGKkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrAAAAAAAAQD5AgAAAAABATfphYFskBaL7jbWIkU3K7RS5zKr5BvfNHjec1rNieTrAQAAABcWABTkjiMSrvGNi5KFtSy72CSJolzNDv7///8C/y8bAAAAAAAZdqkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrDS2GJ0BAAAAF6kUnEFiBqwsbP0pWpazURx45PGdXkWHAkcwRAIgCxWs2+R6UcpQuD6QKydU0irJ7yNe++5eoOly5VgqrEsCIHUD6t4LNW0292vnP+heXZ6Walx8DRW2TB+IOazzDNcaASEDnQS6zdUebuNm7FuOdKonnlNmPPpUyN66w2CIsX5N+pUhIh4AAAA=" @@ -393,7 +305,7 @@ def test_sign_psbt_singlesig_wpkh_selftransfer(client: Client): # print(result) -# def test_sign_psbt_legacy_p2pkh(client: Client): +# def test_sign_psbt_legacy_p2pkh(client: RaggerClient, test_name: str): # # test from app-bitcoin # # legacy address @@ -410,8 +322,7 @@ def test_sign_psbt_singlesig_wpkh_selftransfer(client: Client): # print(result) -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_multisig_wsh(client: Client): +def test_sign_psbt_multisig_wsh(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -428,7 +339,9 @@ def test_sign_psbt_multisig_wsh(client: Client): psbt = open_psbt_from_file(f"{tests_root}/psbt/multisig/wsh-2of2.psbt") - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_6(firmware), + testname=test_name) assert result == [( 0, @@ -441,8 +354,7 @@ def test_sign_psbt_multisig_wsh(client: Client): )] -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_multisig_sh_wsh(client: Client): +def test_sign_psbt_multisig_sh_wsh(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # wrapped segwit multisig ("sh(wsh(sortedmulti(...)))") wallet = MultisigWallet( name="Cold storage", @@ -460,7 +372,9 @@ def test_sign_psbt_multisig_sh_wsh(client: Client): ) psbt = "cHNidP8BAFUCAAAAAS60cHn6kIlm2wk314ZKiOok2xj++cPoa/K5TXzNk4s6AQAAAAD9////AescAAAAAAAAGXapFFnK2lAxTIKeGfWneG+O4NSYf0KdiKwhlRUAAAEAigIAAAABAaNw+E0toKUlohxkK0YmapPS7uToo7RG7DA2YLrmoD8BAAAAFxYAFAppBymwQTPq8lpFfFWMuPRNdbTX/v///wI7rUIBAAAAABepFJMyNbbbdF4o3zxQhWSJ5ZXY5naHh60dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHbvokAAEBIK0dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHAQQiACAyIOGl/sIPCRep2F4Bude0ME17U2m2dPAiK96XdDCf7wEFR1IhA0fxhNV0BDkMTLzQjBSpKxSeh39pMEcQ+reqlD2a/D20IQPlOZCX7JMMMjUxBLMNtzR+gcVKZaL4J4sf/VRbo03NfFKuIgYDR/GE1XQEOQxMvNCMFKkrFJ6Hf2kwRxD6t6qUPZr8PbQc4kJDtDAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA+U5kJfskwwyNTEEsw23NH6BxUplovgnix/9VFujTc18HPWswv0wAACAAQAAgAAAAIABAACAAAAAAAAAAAAAAA==" - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) assert result == [( 0, @@ -473,8 +387,8 @@ def test_sign_psbt_multisig_sh_wsh(client: Client): )] -@has_automation("automations/sign_with_wallet_missing_nonwitnessutxo_accept.json") -def test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo(client: Client): +def test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): # A transaction spending a wrapped segwit address has a script that appears like a legacy UTXO, but uses # the segwit sighash algorithm. # Therefore, if the non-witness-utxo is missing, we should still sign it while giving the warning for unverified inputs, @@ -496,7 +410,9 @@ def test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo(client: Client): ) psbt = "cHNidP8BAFUCAAAAAS60cHn6kIlm2wk314ZKiOok2xj++cPoa/K5TXzNk4s6AQAAAAD9////AescAAAAAAAAGXapFFnK2lAxTIKeGfWneG+O4NSYf0KdiKwhlRUAAAEBIK0dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHAQQiACAyIOGl/sIPCRep2F4Bude0ME17U2m2dPAiK96XdDCf7wEFR1IhA0fxhNV0BDkMTLzQjBSpKxSeh39pMEcQ+reqlD2a/D20IQPlOZCX7JMMMjUxBLMNtzR+gcVKZaL4J4sf/VRbo03NfFKuIgYDR/GE1XQEOQxMvNCMFKkrFJ6Hf2kwRxD6t6qUPZr8PbQc4kJDtDAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA+U5kJfskwwyNTEEsw23NH6BxUplovgnix/9VFujTc18HPWswv0wAACAAQAAgAAAAIABAACAAAAAAAAAAAAAAA==" - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_8(firmware), + testname=test_name) assert result == [( 0, @@ -509,8 +425,9 @@ def test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_taproot_1to2_sighash_all(client: Client): +def test_sign_psbt_taproot_1to2_sighash_all(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a p2tr 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/tr-1to2-sighash-all.psbt") @@ -523,7 +440,9 @@ def test_sign_psbt_taproot_1to2_sighash_all(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == 1 # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) @@ -548,8 +467,9 @@ def test_sign_psbt_taproot_1to2_sighash_all(client: Client): assert bip0340.schnorr_verify(sighash0, pubkey0_psbt, partial_sig0.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_taproot_1to2_sighash_default(client: Client): +def test_sign_psbt_taproot_1to2_sighash_default(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a p2tr 1-input 2-output spend (1 change address) # Test two times: @@ -557,6 +477,7 @@ def test_sign_psbt_taproot_1to2_sighash_default(client: Client): # - the second PSBT does not specify the sighash type. # The behavior for taproot transactions should be the same, producing 64-byte signatures + index = 0 for psbt_file_name in ["tr-1to2-sighash-default", "tr-1to2-sighash-omitted"]: psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/{psbt_file_name}.psbt") @@ -568,7 +489,10 @@ def test_sign_psbt_taproot_1to2_sighash_default(client: Client): ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=f"{test_name}_{index}") + index += 1 # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) # Therefore, for this testcase we hard-code the sighash (which was validated with Bitcoin Core 22.0 when the @@ -592,14 +516,11 @@ def test_sign_psbt_taproot_1to2_sighash_default(client: Client): assert bip0340.schnorr_verify(sighash0, partial_sig0.pubkey, partial_sig0.signature) -def test_sign_psbt_singlesig_wpkh_4to3(client: Client, comm: SpeculosClient, is_speculos: bool, - model: str): +def test_sign_psbt_singlesig_wpkh_4to3(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a segwit 4-input 3-output spend (1 change address) # this test also checks that addresses, amounts and fees shown on screen are correct - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0/**)", @@ -629,44 +550,17 @@ def test_sign_psbt_singlesig_wpkh_4to3(client: Client, comm: SpeculosClient, is_ assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] - - if model == "stax": - x = threading.Thread(target=ux_thread_sign_psbt_stax, args=[comm, all_events]) - else: - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) - - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_9(firmware), + testname=test_name) assert len(result) == n_ins - parsed_events = parse_signing_events(all_events) - - assert parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount) - shown_out_idx = 0 - for out_idx in range(n_outs): - if out_idx != change_index: - out_amt = psbt.tx.vout[out_idx].nValue - assert parsed_events["amounts"][shown_out_idx] == format_amount(CURRENCY_TICKER, out_amt) - - out_addr = Script(psbt.tx.vout[out_idx].scriptPubKey).address(network=NETWORKS["test"]) - assert parsed_events["addresses"][shown_out_idx] == out_addr - - shown_out_idx += 1 - - -def test_sign_psbt_singlesig_large_amount(client: Client, comm: SpeculosClient, is_speculos: bool, - model: str): +def test_sign_psbt_singlesig_large_amount(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Test with a transaction with an extremely large amount - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0/**)", @@ -685,31 +579,15 @@ def test_sign_psbt_singlesig_large_amount(client: Client, comm: SpeculosClient, assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] - - if model == "stax": - x = threading.Thread(target=ux_thread_sign_psbt_stax, args=[comm, all_events]) - else: - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == 1 - parsed_events = parse_signing_events(all_events) - - assert parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount) - - out_amt = psbt.tx.vout[0].nValue - assert parsed_events["amounts"][0] == format_amount(CURRENCY_TICKER, out_amt) - -@pytest.mark.timeout(0) # disable timeout -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_tr_512to256(client: Client, enable_slow_tests: bool): +def test_sign_psbt_singlesig_wpkh_512to256(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, enable_slow_tests: bool): # PSBT for a transaction with 512 inputs and 256 outputs (maximum currently supported in the app) # Very slow test (esp. with DEBUG enabled), so disabled unless the --enableslowtests option is used @@ -738,23 +616,14 @@ def test_sign_psbt_singlesig_tr_512to256(client: Client, enable_slow_tests: bool [i == 42 for i in range(n_outputs)] ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == n_inputs -def ux_thread_accept_prompt_stax(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - while True: - event = speculos_client.get_next_event() - all_events.append(event) - if "Tap to continue" in event["text"]: - speculos_client.finger_touch(55, 550) - break - - -def test_sign_psbt_fail_11_changes(client: Client, comm: SpeculosClient, model: str): +def test_sign_psbt_fail_11_changes(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for transaction with 11 change addresses; the limit is 10, so it must fail with NotSupportedError # before any user interaction on nanos. @@ -773,23 +642,20 @@ def test_sign_psbt_fail_11_changes(client: Client, comm: SpeculosClient, model: [True] * 11, ) - all_events: List[dict] = [] + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_tap(firmware), + testname=test_name) - if model == "stax": - x = threading.Thread(target=ux_thread_accept_prompt_stax, args=[comm, all_events]) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 - x.start() - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wallet, None) - -def test_sign_psbt_fail_wrong_non_witness_utxo(client: Client, is_speculos: bool): +def test_sign_psbt_fail_wrong_non_witness_utxo(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for transaction with the wrong non-witness utxo for an input. # It must fail with IncorrectDataError before any user interaction. - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0/**)", @@ -812,12 +678,16 @@ def test_sign_psbt_fail_wrong_non_witness_utxo(client: Client, is_speculos: bool psbt.inputs[0].non_witness_utxo = wit client._no_clone_psbt = True - with pytest.raises(IncorrectDataError): - client.sign_psbt(psbt, wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 client._no_clone_psbt = False -def test_sign_psbt_with_opreturn(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_opreturn(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = WalletPolicy( "", "wpkh(@0/**)", @@ -830,13 +700,15 @@ def test_sign_psbt_with_opreturn(client: Client, comm: SpeculosClient): psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert len(hww_sigs) == 1 -def test_sign_psbt_with_naked_opreturn(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_naked_opreturn(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): wallet = WalletPolicy( "", "wpkh(@0/**)", @@ -851,13 +723,14 @@ def test_sign_psbt_with_naked_opreturn(client: Client, comm: SpeculosClient): psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert len(hww_sigs) == 1 -def test_sign_psbt_with_segwit_v16(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_segwit_v16(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # This psbt contains an output with future psbt version 16 (corresponding to address # tb1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq4hu3px). # The app should accept it nonetheless. @@ -874,13 +747,19 @@ def test_sign_psbt_with_segwit_v16(client: Client, comm: SpeculosClient): ], ) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(hww_sigs) == 1 -def test_sign_psbt_with_external_inputs(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_external_inputs(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + + instructions = [sign_psbt_instruction_approve_external_inputs(firmware), + sign_psbt_instruction_approve_external_inputs_2(firmware), + sign_psbt_instruction_approve_external_inputs_2(firmware)] # PSBT obtained by joining pkh-1to1.psbt, tr-1to2.psbt, wpkh-1to2.psbt. # We sign it with each of the respective wallets; therefore it must show the "external inputs" warning each time. psbt_b64 = "cHNidP8BAP0yAQIAAAADobgj0jNtaUtJNO+bblt94XoFUT2oop2wKi7Lx6mm/m0BAAAAAP3///9RIsLN5oI+VXVBdbksnFegqOGsg8OOF4f9Oh/zNI6VEwEAAAAA/f///3oqmXlWwJ+Op/0oGcGph7sU4iv5rc2vIKiXY3Is7uJkAQAAAAD9////BaCGAQAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3dV/6cAAAAAACJRIAuOdIa8MGoK77enwArwQFVC2xrNc+7MqCdxzPX+XrYPeEEPAAAAAAAZdqkUE9fVgWaUbD7AIpNAZtjA0RHRu0GIrHQ4IwAAAAAAFgAU6zj6m4Eo+B8m6V7bDF/66oNpD+Sguw0AAAAAABl2qRQ0Sg9IyhUOwrkDgXZgubaLE6ZwJoisAAAAAAABASunhqkAAAAAACJRINj08dGJltthuxyvVCPeJdih7unJUNN+b/oCMBLV5i4NIRYhLqKFalzxEOZqK+nXNTFHk/28s4iyuPE/K2remC569RkA9azC/VYAAIABAACAAAAAgAEAAAAAAAAAARcgIS6ihWpc8RDmaivp1zUxR5P9vLOIsrjxPytq3pguevUAAQCMAgAAAAHsIw5TCVJWBSokKCcO7ASYlEsQ9vHFePQxwj0AmLSuWgEAAAAXFgAUKBU5gg4t6XOuQbpgBLQxySHE2G3+////AnJydQAAAAAAF6kUyLkGrymMcOYDoow+/C+uGearKA+HQEIPAAAAAAAZdqkUy65bUM+Tnm9TG4prer14j+FLApeIrITyHAAiBgLuhgggfiEChCb2nnZEfX49XgdwSfXmg8MTbCMUdipHGBj1rML9LAAAgAEAAIAAAACAAAAAAAAAAAAAAQB9AgAAAAGvv64GWQ90H/GvWbasRhEmM2pMSoLbVT32/vq3N6wz8wEAAAAA/f///wJwEQEAAAAAACIAIP3uRBxW5bBtDfgsEkxwcBSlyhlli+C5hWvKFvHtMln3pfQwAAAAAAAWABQ6+EKa1ZVKpe6KM8mD/YoehnmSSwAAAAABAR+l9DAAAAAAABYAFDr4QprVlUql7oozyYP9ih6GeZJLIgYD7iw9mOsfk8Chqo5aQAm3Dre0Tq0V8WZvE2sBKtWNMGgY9azC/VQAAIABAACAAAAAgAEAAAAIAAAAAAABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAAIgICKexHcnEx7SWIogxG7amrt9qm9J/VC6/nC5xappYcTswY9azC/VQAAIABAACAAAAAgAEAAAAKAAAAAAA=" @@ -911,15 +790,18 @@ def test_sign_psbt_with_external_inputs(client: Client, comm: SpeculosClient): ) ] - for wallet in wallets: - with automation(comm, "automations/sign_with_wallet_external_inputs_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + index = 0 + for wallet, text in zip(wallets, instructions): + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=text, + testname=f"{test_name}_{index}") + index += 1 assert len(hww_sigs) == 1 -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_miniscript_multikey(client: Client, comm: SpeculosClient): +def test_sign_psbt_miniscript_multikey(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # An earlier (unreleased) version of the app had issues in recognizing the internal key in # wallets with multiple internal keys. This caused the app not to recognize some inputs as # internal and refuse signing. @@ -943,13 +825,15 @@ def test_sign_psbt_miniscript_multikey(client: Client, comm: SpeculosClient): "e139a96195e18bc61e8cda72d11b3f75d3084a5c893990ca74a152206064792d" ) - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) assert len(result) == 2 -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_pkh_1to1_other_encodings(client: Client): +def test_sign_psbt_singlesig_pkh_1to1_other_encodings(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): # same as test_sign_psbt_singlesig_pkh_1to1, but the psbt is passed as bytes or base64 string psbt_obj = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") @@ -965,12 +849,17 @@ def test_sign_psbt_singlesig_pkh_1to1_other_encodings(client: Client): psbt_b64 = psbt_obj.serialize() psbt_bytes = base64.b64decode(psbt_b64) + index = 0 for psbt in [psbt_b64, psbt_bytes]: # expected sigs: # #0: # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" - result = client.sign_psbt(psbt, wallet, None) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=f"{test_name}_{index}") + index += 1 assert result == [( 0, @@ -983,8 +872,8 @@ def test_sign_psbt_singlesig_pkh_1to1_other_encodings(client: Client): )] -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_tr_script_pk_sighash_all(client: Client): +def test_sign_psbt_tr_script_pk_sighash_all(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Transaction signed with SIGHASH_ALL, therefore producing a 65-byte signature wallet = WalletPolicy( @@ -1002,7 +891,9 @@ def test_sign_psbt_tr_script_pk_sighash_all(client: Client): psbt = PSBT() psbt.deserialize("cHNidP8BAFICAAAAAR/BzFdxy4OGDMVtlLz+2ThgjBf2NmJDW0HpxE/8/TFCAQAAAAD9////ATkFAAAAAAAAFgAUqo7zdMr638p2kC3bXPYcYLv9nYUAAAAAAAEBK0wGAAAAAAAAIlEg/AoQ0wjH5BtLvDZC+P2KwomFOxznVaDG0NSV8D2fLaQBAwQBAAAAIhXBUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUjIGsW6MH5efpMwPBbajAK//+UFFm28g3nfeVbAWDvjkysrMAhFlAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1HQB2IjpuMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIRZrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrD0BCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKT1rML9MAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAARcgUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUBGCAJLtoDNhfiEO5/fQ43ikBK6hxItWqhAwIr7Pd0bkcApAAA") - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) assert len(result) == 1 @@ -1024,8 +915,8 @@ def test_sign_psbt_tr_script_pk_sighash_all(client: Client): assert bip0340.schnorr_verify(sighash0, partial_sig0.pubkey, partial_sig0.signature[:64]) -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_against_wrong_tapleaf_hash(client: Client): +def test_sign_psbt_against_wrong_tapleaf_hash(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Versions 2.1.2, 2.1.3 and 2.2.0 incorrectly derived keys for policies with keys whose # derivation doesn't end in /** or /<0;1>/*. wallet = WalletPolicy( @@ -1043,7 +934,9 @@ def test_sign_psbt_against_wrong_tapleaf_hash(client: Client): psbt_b64 = "cHNidP8BAH0CAAAAAYBaTWS0c6cz/bqhz0gkvw2CoOJ9/y4sKh5CovAYdw38AAAAAAD9////ArFTiQAAAAAAIlEgUM92rzrvv69scu7om669/XHG88cGJbYVeMikCkWmlxRAQg8AAAAAABYAFJDl+lvev62lopbLzjGdWRDjAYvgAAAAAAABASuAlpgAAAAAACJRINN8fQAgAcXxI9eoGZhPGUUGNjw4g9EeoiMqhcVBO5VLQhXBw4BHaz5Rb16iJhge9exK1RkvpgSBkmRu83QIUOE6J65bgplv5s8b9DhoURGBxkyWW3v18W8Aes7FLe3lKI+SJUkgIRdstYjTZ0gDOmYhQWnhPLeSgxFVT7+P2Da5rOQ5ofSsIO+9DR1rAsJPsa5gnGaxlTcLz+FasRFEtS1GPP9S4AEHulGdUrLAQhXBw4BHaz5Rb16iJhge9exK1RkvpgSBkmRu83QIUOE6J66x3SqLzSBzMBF+yv8nlwb7y8wznx3ph3mkNbEShEEVdUcgnmRvueBFJGCUTkn4hp+audqQgg2l1ThBr54ScaO8+c6sIEOg+6Z7BaL8AdExL0y1lU+WzQLqlFNMBvCuB5kbfXn6ulKcwCEWIRdstYjTZ0gDOmYhQWnhPLeSgxFVT7+P2Da5rOQ5ofQ9AbHdKovNIHMwEX7K/yeXBvvLzDOfHemHeaQ1sRKEQRV19azC/TAAAIABAACAAAAAgAIAAIACAAAAAwAAACEWQ6D7pnsFovwB0TEvTLWVT5bNAuqUU0wG8K4HmRt9efotAVuCmW/mzxv0OGhREYHGTJZbe/XxbwB6zsUt7eUoj5IlB4DpBQAAAAADAAAAIRaeZG+54EUkYJROSfiGn5q52pCCDaXVOEGvnhJxo7z5zj0BW4KZb+bPG/Q4aFERgcZMllt79fFvAHrOxS3t5SiPkiX1rML9MAAAgAEAAIAAAACAAgAAgAAAAAADAAAAIRbDgEdrPlFvXqImGB717ErVGS+mBIGSZG7zdAhQ4Tonrg0As/NWDAAAAAADAAAAIRbvvQ0dawLCT7GuYJxmsZU3C8/hWrERRLUtRjz/UuABBy0Bsd0qi80gczARfsr/J5cG+8vMM58d6Yd5pDWxEoRBFXUHgOkFAgAAAAMAAAABFyDDgEdrPlFvXqImGB717ErVGS+mBIGSZG7zdAhQ4TonrgEYIALiXeErTe+AoRAtQnHQX7jXI4YbZBhruweZSvu1pjAnAAEFIDUB03lc0pILNyKsR6rhmUOmt4haBLLEqg+PUngRkh1tAQaUAcBGIN2D5P/RpWDLWr8u0Sot1Nvr5XYq9Q/AMKqMEXmB3147rCCnLb87WO/OHvM80hvKtQd/5eDRTyap/Nn6wGXiShz23rpSnAHASCB9x/N9yMHBTLoCp176y3zxfQ4uhFjr2IrFWzh6EZDhV6wgPMPmbiXzWmycjxYW5CemUduJTNaIRBRpeKGxZocLVzu6UZ1SsiEHNQHTeVzSkgs3IqxHquGZQ6a3iFoEssSqD49SeBGSHW0NALPzVgwBAAAAAAAAACEHPMPmbiXzWmycjxYW5CemUduJTNaIRBRpeKGxZocLVzstAQImDD+peKARccErGHSxVp2Aq1+VWjA681kfcLPjYIfHB4DpBQMAAAAAAAAAIQd9x/N9yMHBTLoCp176y3zxfQ4uhFjr2IrFWzh6EZDhVz0BAiYMP6l4oBFxwSsYdLFWnYCrX5VaMDrzWR9ws+Ngh8f1rML9MAAAgAEAAIAAAACAAgAAgAMAAAAAAAAAIQenLb87WO/OHvM80hvKtQd/5eDRTyap/Nn6wGXiShz23i0BWuE6OIQBkBYr0ks+isRVRxvEs10ErP2gC9qtZAt0KE8HgOkFAQAAAAAAAAAhB92D5P/RpWDLWr8u0Sot1Nvr5XYq9Q/AMKqMEXmB3147PQFa4To4hAGQFivSSz6KxFVHG8SzXQSs/aAL2q1kC3QoT/Wswv0wAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA==" - result = client.sign_psbt(psbt_b64, wallet, wallet_hmac) + result = client.sign_psbt(psbt_b64, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) assert len(result) == 2 diff --git a/tests/test_sign_psbt_v1.py b/tests/test_sign_psbt_v1.py index 7dd76c67a..f8af8c5c5 100644 --- a/tests/test_sign_psbt_v1.py +++ b/tests/test_sign_psbt_v1.py @@ -11,137 +11,29 @@ from pathlib import Path -from bitcoin_client.ledger_bitcoin import Client, WalletPolicy, MultisigWallet, AddressType, WalletType, PartialSignature -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin import WalletPolicy, MultisigWallet, AddressType, WalletType, PartialSignature +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import AddressType -from speculos.client import SpeculosClient +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import AddressType -from test_utils import has_automation, bip0340, txmaker +from test_utils import bip0340, txmaker from embit.script import Script from embit.networks import NETWORKS +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware -from test_utils.speculos import automation +import requests +import json -tests_root: Path = Path(__file__).parent - - -CURRENCY_TICKER = "TEST" - - -def format_amount(ticker: str, amount: int) -> str: - """Formats an amounts in sats as shown in the app: divided by 10_000_000, with no trailing zeroes.""" - assert amount >= 0 - btc_amount = f"{(amount/100_000_000):.8f}".rstrip('0').rstrip('.') - return f"{ticker} {btc_amount}" - - -def should_go_right(event: dict): - """Returns true if the current text event implies a "right" button press to proceed.""" - - if event["text"].startswith("Review"): - return True - elif event["text"].startswith("Amount"): - return True - elif event["text"].startswith("Address"): - return True - elif event["text"].startswith("Confirm"): - return True - elif event["text"].startswith("Fees"): - return True - return False - - -def ux_thread_sign_psbt(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - # press right until the last screen (will press the "right" button more times than needed) - - while True: - event = speculos_client.get_next_event() - all_events.append(event) - - if should_go_right(event): - speculos_client.press_and_release("right") - elif event["text"] == "Approve": - speculos_client.press_and_release("both") - elif event["text"] == "Accept": - speculos_client.press_and_release("both") - break - - -def ux_thread_sign_psbt_stax(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - first_approve = True - while True: - event = speculos_client.get_next_event() - all_events.append(event) - - if event["text"] == "Tap to continue": - speculos_client.finger_touch(55, 550) - - elif first_approve and ("Approve" in event["text"] or "Hold" in event["text"]): - first_approve = False - speculos_client.finger_touch(55, 550, 3) - - elif event["text"] == "TRANSACTION": - break - - elif "CONFIRMED" in event["text"]: - first_approve = True - - -def parse_signing_events(events: List[dict]) -> dict: - ret = dict() - - cur_output_index = -1 - - ret["addresses"] = [] - ret["amounts"] = [] - ret["fees"] = "" - next_step = "" - keywords = ("Amount", "Address", "Fees", "Accept", "Approve") - - for ev in events: - if ev["text"].startswith("output #"): - idx_str = ev["text"][8:] - - assert int(idx_str) - 1 == cur_output_index + 1 # should not skip outputs - - cur_output_index = int(idx_str) - 1 - - ret["addresses"].append("") - ret["amounts"].append("") - next_step = "" - - elif ev["text"].startswith("Tap"): - ret["addresses"].append("") - ret["amounts"].append("") - next_step = "" - continue - - elif ev["text"].startswith(keywords): - next_step = ev["text"] - continue - - if next_step.startswith("Address"): - if len(ret["addresses"]) == 0: - ret["addresses"].append("") - - ret["addresses"][-1] += ev["text"].strip() +from ragger_bitcoin import RaggerClient - elif next_step.startswith("Fees"): - ret["fees"] += ev["text"] +from .instructions import * - elif next_step.startswith("Amount"): - if len(ret["amounts"]) == 0: - ret["amounts"].append("") - ret["amounts"][-1] += ev["text"].strip() - - return ret +tests_root: Path = Path(__file__).parent def open_psbt_from_file(filename: str) -> PSBT: @@ -152,9 +44,8 @@ def open_psbt_from_file(filename: str) -> PSBT: return psbt -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_pkh_1to1_v1(client: Client): - +def test_sign_psbt_singlesig_pkh_1to1_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a legacy 1-input 1-output spend (no change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") @@ -171,7 +62,9 @@ def test_sign_psbt_singlesig_pkh_1to1_v1(client: Client): # #0: # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert result == [( 0, @@ -184,9 +77,8 @@ def test_sign_psbt_singlesig_pkh_1to1_v1(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_sh_wpkh_1to2_v1(client: Client): - +def test_sign_psbt_singlesig_sh_wpkh_1to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") @@ -203,7 +95,9 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2_v1(client: Client): # #0: # "pubkey" : "024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", # "signature" : "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert result == [( 0, @@ -216,9 +110,8 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2_v1(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_1to2_v1(client: Client): - +def test_sign_psbt_singlesig_wpkh_1to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a legacy 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") @@ -231,7 +124,9 @@ def test_sign_psbt_singlesig_wpkh_1to2_v1(client: Client): version=WalletType.WALLET_POLICY_V1 ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) # expected sigs # #0: @@ -249,8 +144,8 @@ def test_sign_psbt_singlesig_wpkh_1to2_v1(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_2to2_v1(client: Client): +def test_sign_psbt_singlesig_wpkh_2to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a legacy 2-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") @@ -264,7 +159,9 @@ def test_sign_psbt_singlesig_wpkh_2to2_v1(client: Client): version=WalletType.WALLET_POLICY_V1 ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # expected sigs # #0: @@ -293,8 +190,7 @@ def test_sign_psbt_singlesig_wpkh_2to2_v1(client: Client): )] -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_multisig_wsh_v1(client: Client): +def test_sign_psbt_multisig_wsh_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, @@ -312,7 +208,9 @@ def test_sign_psbt_multisig_wsh_v1(client: Client): psbt = open_psbt_from_file(f"{tests_root}/psbt/multisig/wsh-2of2.psbt") - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_6(firmware), + testname=test_name) assert result == [( 0, @@ -325,8 +223,7 @@ def test_sign_psbt_multisig_wsh_v1(client: Client): )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_taproot_1to2_v1(client: Client): +def test_sign_psbt_taproot_1to2_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for a p2tr 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/tr-1to2-sighash-all.psbt") @@ -340,7 +237,9 @@ def test_sign_psbt_taproot_1to2_v1(client: Client): version=WalletType.WALLET_POLICY_V1 ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == 1 # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) @@ -364,14 +263,11 @@ def test_sign_psbt_taproot_1to2_v1(client: Client): assert bip0340.schnorr_verify(sighash0, pubkey0_psbt, partial_sig0.signature[:-1]) -def test_sign_psbt_singlesig_wpkh_4to3_v1(client: Client, comm: SpeculosClient, is_speculos: bool, - model: str): +def test_sign_psbt_singlesig_wpkh_4to3_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a segwit 4-input 3-output spend (1 change address) # this test also checks that addresses, amounts and fees shown on screen are correct - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0)", @@ -401,44 +297,17 @@ def test_sign_psbt_singlesig_wpkh_4to3_v1(client: Client, comm: SpeculosClient, assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] - - if model == "stax": - x = threading.Thread(target=ux_thread_sign_psbt_stax, args=[comm, all_events]) - else: - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) - - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_9(firmware), + testname=test_name) assert len(result) == n_ins - parsed_events = parse_signing_events(all_events) - - assert parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount) - - shown_out_idx = 0 - for out_idx in range(n_outs): - if out_idx != change_index: - out_amt = psbt.tx.vout[out_idx].nValue - assert parsed_events["amounts"][shown_out_idx] == format_amount(CURRENCY_TICKER, out_amt) - - out_addr = Script(psbt.tx.vout[out_idx].scriptPubKey).address(network=NETWORKS["test"]) - assert parsed_events["addresses"][shown_out_idx] == out_addr - shown_out_idx += 1 - - -def test_sign_psbt_singlesig_large_amount_v1(client: Client, comm: SpeculosClient, is_speculos: - bool, model: str): +def test_sign_psbt_singlesig_large_amount_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Test with a transaction with an extremely large amount - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0)", @@ -458,42 +327,47 @@ def test_sign_psbt_singlesig_large_amount_v1(client: Client, comm: SpeculosClien assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) - if model == "stax": - x = threading.Thread(target=ux_thread_sign_psbt_stax, args=[comm, all_events]) - else: - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) + assert len(result) == 1 - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() - assert len(result) == 1 +def test_sign_psbt_singlesig_wpkh_512to256_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, enable_slow_tests: bool): + # PSBT for a transaction with 512 inputs and 256 outputs (maximum currently supported in the app) + # Very slow test (esp. with DEBUG enabled), so disabled unless the --enableslowtests option is used - parsed_events = parse_signing_events(all_events) + if not enable_slow_tests: + pytest.skip() - assert parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount) + n_inputs = 512 + n_outputs = 256 - out_amt = psbt.tx.vout[0].nValue - assert parsed_events["amounts"][0] == format_amount(CURRENCY_TICKER, out_amt) + wallet = WalletPolicy( + "", + "tr(@0)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + psbt = txmaker.createPsbt( + wallet, + [10000 + 10000 * i for i in range(n_inputs)], + [999 + 99 * i for i in range(n_outputs)], + [i == 42 for i in range(n_outputs)] + ) -def ux_thread_accept_prompt_stax(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" + result = client.sign_psbt(psbt, wallet, None, None) - while True: - event = speculos_client.get_next_event() - all_events.append(event) - if "Tap to continue" in event["text"]: - speculos_client.finger_touch(55, 550) - break + assert len(result) == n_inputs -def test_sign_psbt_fail_11_changes_v1(client: Client, comm: SpeculosClient, is_speculos: bool, - model: str): +def test_sign_psbt_fail_11_changes_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for transaction with 11 change addresses; the limit is 10, so it must fail with NotSupportedError # before any user interaction @@ -513,23 +387,20 @@ def test_sign_psbt_fail_11_changes_v1(client: Client, comm: SpeculosClient, is_s [True] * 11, ) - all_events: List[dict] = [] - - if model == "stax": - x = threading.Thread(target=ux_thread_accept_prompt_stax, args=[comm, all_events]) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_tap(firmware), + testname=test_name) - x.start() - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wallet, None) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_sign_psbt_fail_wrong_non_witness_utxo_v1(client: Client, is_speculos: bool): +def test_sign_psbt_fail_wrong_non_witness_utxo_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for transaction with the wrong non-witness utxo for an input. # It must fail with IncorrectDataError before any user interaction. - if not is_speculos: - pytest.skip("Requires speculos") - wallet = WalletPolicy( "", "wpkh(@0)", @@ -553,12 +424,16 @@ def test_sign_psbt_fail_wrong_non_witness_utxo_v1(client: Client, is_speculos: b psbt.inputs[0].non_witness_utxo = wit client._no_clone_psbt = True - with pytest.raises(IncorrectDataError): - client.sign_psbt(psbt, wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 client._no_clone_psbt = False -def test_sign_psbt_with_opreturn_v1(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_opreturn_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = WalletPolicy( "", "wpkh(@0)", @@ -572,13 +447,15 @@ def test_sign_psbt_with_opreturn_v1(client: Client, comm: SpeculosClient): psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert len(hww_sigs) == 1 -def test_sign_psbt_with_segwit_v16_v1(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_segwit_v16_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # This psbt contains an output with future psbt version 16 (corresponding to address # tb1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq4hu3px). # The app should accept it nonetheless. @@ -596,7 +473,8 @@ def test_sign_psbt_with_segwit_v16_v1(client: Client, comm: SpeculosClient): version=WalletType.WALLET_POLICY_V1 ) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(hww_sigs) == 1 diff --git a/tests/test_sign_psbt_with_sighash_types.py b/tests/test_sign_psbt_with_sighash_types.py index dd2a28dc8..abbcd2beb 100644 --- a/tests/test_sign_psbt_with_sighash_types.py +++ b/tests/test_sign_psbt_with_sighash_types.py @@ -1,10 +1,16 @@ import pytest from pathlib import Path -from bitcoin_client.ledger_bitcoin import Client, WalletPolicy -from bitcoin_client.ledger_bitcoin.exception.errors import NotSupportedError -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from test_utils import has_automation, bip0340 - +from ledger_bitcoin import WalletPolicy +from ledger_bitcoin.exception.errors import NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from test_utils import bip0340 +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware +from ragger_bitcoin import RaggerClient + +from .instructions import sign_psbt_instruction_approve, sign_psbt_instruction_approve_2, sign_psbt_instruction_approve_4, sign_psbt_instruction_approve_10 tests_root: Path = Path(__file__).parent tr_wallet = WalletPolicy( @@ -48,11 +54,13 @@ def open_psbt_from_file(filename: str) -> PSBT: return psbt -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sighash_all_sign_psbt(client: Client): +def test_sighash_all_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -72,12 +80,13 @@ def test_sighash_all_sign_psbt(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sighash_all_input_modified(client: Client): +def test_sighash_all_input_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -90,12 +99,13 @@ def test_sighash_all_input_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sighash_all_output_modified(client: Client): +def test_sighash_all_output_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -108,11 +118,12 @@ def test_sighash_all_output_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_sign_psbt(client: Client): +def test_sighash_none_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -132,12 +143,13 @@ def test_sighash_none_sign_psbt(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_input_modified(client: Client): +def test_sighash_none_input_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 # get the (tweaked) pubkey from the scriptPubKey @@ -151,12 +163,13 @@ def test_sighash_none_input_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_output_modified(client: Client): +def test_sighash_none_output_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 # get the (tweaked) pubkey from the scriptPubKey @@ -170,11 +183,12 @@ def test_sighash_none_output_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_sign_psbt(client: Client): +def test_sighash_single_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -194,12 +208,14 @@ def test_sighash_single_sign_psbt(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_input_modified(client: Client): +def test_sighash_single_input_modified(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") psbt.tx.vin[1].nSequence = psbt.tx.vin[1].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -214,12 +230,14 @@ def test_sighash_single_input_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_output_same_index_modified(client: Client): +def test_sighash_single_output_same_index_modified(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -234,12 +252,14 @@ def test_sighash_single_output_same_index_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_output_different_index_modified(client: Client): +def test_sighash_single_output_different_index_modified(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") psbt.tx.vout[1].nValue = psbt.tx.vout[1].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) # get the (tweaked) pubkey from the scriptPubKey pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] @@ -254,19 +274,23 @@ def test_sighash_single_output_different_index_modified(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_3_ins_2_out(client: Client): +def test_sighash_single_3_ins_2_out(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-3-ins-2-outs.psbt") - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, tr_wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_all_anyone_sign(client: Client): +def test_sighash_all_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -286,12 +310,14 @@ def test_sighash_all_anyone_sign(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_all_anyone_input_changed(client: Client): +def test_sighash_all_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -306,12 +332,14 @@ def test_sighash_all_anyone_input_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_all_anyone_output_changed(client: Client): +def test_sighash_all_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -326,11 +354,12 @@ def test_sighash_all_anyone_output_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_anyone_sign(client: Client): +def test_sighash_none_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -350,12 +379,14 @@ def test_sighash_none_anyone_sign(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_anyone_input_changed(client: Client): +def test_sighash_none_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -370,12 +401,14 @@ def test_sighash_none_anyone_input_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_none_anyone_output_changed(client: Client): +def test_sighash_none_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -390,11 +423,12 @@ def test_sighash_none_anyone_output_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_anyone_sign(client: Client): +def test_sighash_single_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -414,12 +448,14 @@ def test_sighash_single_anyone_sign(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_anyone_input_changed(client: Client): +def test_sighash_single_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -434,12 +470,14 @@ def test_sighash_single_anyone_input_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_single_anyone_output_changed(client: Client): +def test_sighash_single_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 - result = client.sign_psbt(psbt, tr_wallet, None) + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) assert len(result) == 2 @@ -454,87 +492,123 @@ def test_sighash_single_anyone_output_changed(client: Client): assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) -def test_sighash_unsupported(client: Client): +def test_sighash_unsupported(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-unsupported.psbt") - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, tr_wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_sighash_unsupported_for_segwitv0(client: Client): +def test_sighash_unsupported_for_segwitv0(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") psbt.inputs[0].sighash = 0 - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wpkh_wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 psbt.inputs[0].sighash = 0x80 - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wpkh_wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 psbt.inputs[0].sighash = 0x84 - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wpkh_wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_segwitv0_sighash1(client: Client): +def test_sighash_segwitv0_sighash1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): expected_sig = b"0E\x02!\x00\xabD\xf3M\xd7\xe8|\x90TY\x12\x97\xa1\x01\xe8P\n\x06A\xd1\xd5\x91\x87\x8d\r#\xcf\x80\x96\xfay\xe8\x02 ]\x12\xd1\x06-\x92^'\xb5{\xdc\xf9\x94\xec\xf32\xad\n\x8eg\xb8\xfe@{\xab!\x01%]\xa62\xaa\x01" psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") psbt.inputs[0].sighash = 1 - result = client.sign_psbt(psbt, wpkh_wallet, None) + result = client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert result[0][1].signature == expected_sig -@has_automation("automations/sign_with_default_wallet_accept_nondefault_sighash.json") -def test_sighash_segwitv0_sighash2(client: Client): +def test_sighash_segwitv0_sighash2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): expected_sig = b'0D\x02 o\x86>\xd5\x8b\xb5\xa5\xa2KZ\xcez\xb2\x92\xd0\xce\x04!L_\x8f9\xeb#m3\x9e\xb4\x8d\xc6sK\x02 p\x8d\x95\x0b4B\x02^\xf1nB\xd2\xea\x84b\x14\xc7\x00\x88"\xed\x19o