From 12e0af3f5cd5854f0781df39cfb8b3fa753de750 Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Thu, 5 Mar 2026 18:46:35 +0100 Subject: [PATCH] WIF store testing --- testing/conftest.py | 1 + testing/run_sim_tests.py | 2 +- testing/test_hobble.py | 33 ---- testing/test_msg.py | 62 ------- testing/test_multisig.py | 64 ------- testing/test_ownership.py | 61 ------ testing/test_sign.py | 149 --------------- testing/test_wif.py | 379 +++++++++++++++++++++++++++++++++++++- 8 files changed, 378 insertions(+), 373 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index c6d94c85..6bc87375 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -3024,6 +3024,7 @@ from test_drv_entro import derive_bip85_secret, activate_bip85_ephemeral from test_ephemeral import generate_ephemeral_words, import_ephemeral_xprv, goto_eph_seed_menu from test_ephemeral import ephemeral_seed_disabled_ui, restore_main_seed, confirm_tmp_seed from test_ephemeral import verify_ephemeral_secret_ui, get_identity_story, get_seed_value_ux, seed_vault_enable +from test_hobble import set_hobble from test_msg import verify_msg_sign_story, sign_msg_from_text, msg_sign_export, sign_msg_from_address from test_multisig import import_ms_wallet, make_multisig, offer_ms_import, fake_ms_txn from test_multisig import make_ms_address, clear_ms, make_myself_wallet, import_multisig diff --git a/testing/run_sim_tests.py b/testing/run_sim_tests.py index 4782cfe0..b7c2f69c 100644 --- a/testing/run_sim_tests.py +++ b/testing/run_sim_tests.py @@ -367,7 +367,7 @@ def main(): for test_module in sorted(list(test_modules)): sim_args = DEFAULT_SIMULATOR_ARGS if test_module in ["test_bsms.py", "test_address_explorer.py", "test_export.py", - "test_multisig.py", "test_ux.py"]: + "test_multisig.py", "test_ux.py", "test_wif.py"]: sim_args = DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"] if test_module == "test_vdisk.py": sim_args = ["--eject"] + DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"] diff --git a/testing/test_hobble.py b/testing/test_hobble.py index 96b129c5..b18e4993 100644 --- a/testing/test_hobble.py +++ b/testing/test_hobble.py @@ -18,7 +18,6 @@ # import pytest, time, os, pdb from bip32 import BIP32Node -from base58 import encode_base58_checksum from constants import simulator_fixed_words, simulator_fixed_xprv from test_ephemeral import SEEDVAULT_TEST_DATA, WORDLISTS from test_ephemeral import confirm_tmp_seed, verify_ephemeral_secret_ui @@ -395,38 +394,6 @@ def test_h_tempseeds(mode, set_hobble, pick_menu_item, cap_menu, settings_set, i press_select() -@pytest.mark.parametrize('en_okeys', [ True, False]) -def test_h_wif_store(en_okeys, set_hobble, settings_remove, import_wif_to_store, goto_home, - cap_menu, pick_menu_item): - - settings_remove("wifs") - - wif_list = [ - encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') - for _ in range(3) - ] - - import_wif_to_store(wif_list) - goto_home() - - set_hobble(True, {'okeys'} if en_okeys else {}) - pick_menu_item("Advanced/Tools") - - if en_okeys: - pick_menu_item("WIF Store") - time.sleep(.1) - menu = cap_menu() - # check it is read-only - assert "Import WIF" not in menu - assert "Clear All" not in menu - pick_menu_item(menu[0]) - time.sleep(.1) - menu = cap_menu() - assert "Delete" not in menu - else: - assert "WIF Store" not in cap_menu() - - @pytest.mark.parametrize('en_okeys', [ True, False]) def test_h_usbcmds(en_okeys, set_hobble, dev): # test various usb commands are blocked during hobble diff --git a/testing/test_msg.py b/testing/test_msg.py index 4348521b..03b7aeeb 100644 --- a/testing/test_msg.py +++ b/testing/test_msg.py @@ -6,7 +6,6 @@ import pytest, time, os, itertools, hashlib, json from bip32 import BIP32Node from msg import verify_message, RFC_SIGNATURE_TEMPLATE, sign_message, parse_signed_message from base64 import b64encode, b64decode -from base58 import encode_base58_checksum from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError, CCUserRefused from ckcc_protocol.constants import * from constants import addr_fmt_names, msg_sign_unmap_addr_fmt @@ -1041,65 +1040,4 @@ def test_verify_scanned_signed_msg(msg, scan_a_qr, need_keypress, goto_home, cap assert "Good signature by address" in story assert addr == addr_from_display_format(story.split("\n")[-1]) - -@pytest.mark.parametrize("way,af", [ - ("sd", "P2SH-Segwit"), - ("input", "Segwit P2WPKH"), - ("nfc", "Classic P2PKH") -]) -def test_sign_msg_with_wif_store_key(way, af, settings_remove, import_wif_to_store, cap_menu, - pick_menu_item, cap_story, need_keypress, press_nfc, - enter_complex, garbage_collector, microsd_path, nfc_write_text, - verify_msg_sign_story, msg_sign_export, press_select, goto_home): - settings_remove("wifs") - msg = "Coinkite" - - n = BIP32Node.from_master_secret(os.urandom(32)) - privkey = n.node.private_key - import_wif_to_store([encode_base58_checksum(bytes([239]) + bytes(privkey) + b'\x01')]) - - menu = cap_menu() - assert len(menu) == 2 - pick_menu_item(menu[1]) - pick_menu_item("Sign MSG") - pick_menu_item(af) - - if way == "input": - need_keypress("0") - enter_complex(msg, apply=False, b39pass=False) - - elif way == "sd": - name = "msg_to_sign.txt" - pth = microsd_path(name) - with open(pth, "w") as f: - f.write(msg) - - need_keypress("1") - pick_menu_item(name) - - elif way == "nfc": - press_nfc() - time.sleep(0.2) - nfc_write_text(msg) - time.sleep(0.3) - - else: - raise NotImplementedError - - time.sleep(.1) - title, story = cap_story() - addr_fmt = {"P2SH-Segwit": "p2sh-p2wpkh", - "Segwit P2WPKH": "p2wpkh", - "Classic P2PKH": "p2pkh"}[af] - - target_addr = n.address(addr_fmt=addr_fmt) - verify_msg_sign_story(story, msg, "m", addr=target_addr) - press_select() - res = msg_sign_export(way if way != "input" else "sd") - assert target_addr in res - pmsg, addr, sig = parse_signed_message(res) - assert pmsg == msg - assert verify_message(addr, sig, msg) is True - goto_home() - # EOF diff --git a/testing/test_multisig.py b/testing/test_multisig.py index d27ba749..8bca38b5 100644 --- a/testing/test_multisig.py +++ b/testing/test_multisig.py @@ -4275,68 +4275,4 @@ def test_txin_explorer_our_sig(dev, fake_ms_txn, start_sign, settings_set, clear start_sign(psbt) txin_explorer(num_ins, [(af, inp_amount, 0, "XTN", (M,N), None, None, False, [my_xfp])]) - -@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh", "p2sh"]) -def test_wif_store(addr_fmt, dev, fake_ms_txn, start_sign, settings_set, clear_ms, - cap_story, pytestconfig, import_ms_wallet, end_sign, settings_remove): - # TODO This test MUST be run with --psbt2 flag on and off - clear_ms() - settings_remove("wifs") - M, N = 3, 5 - - if addr_fmt == AF_P2SH: - dd = "m/45h" - elif addr_fmt == AF_P2WSH: - dd = "m/48h/1h/0h/2h" - else: - dd = "m/48h/1h/0h/1h" - - def path_mapper(idx): - kk = str_to_path(dd) - return kk + [0,0] - - keys = import_ms_wallet(M, N, name='wif_store', accept=True, netcode="XTN", - descriptor=True, addr_fmt=addr_fmt, common=dd) - - psbt = fake_ms_txn(1, 1, M, keys, inp_af=unmap_addr_fmt[addr_fmt], - path_mapper=path_mapper, psbt_v2=pytestconfig.getoption('psbt2')) - - # sign with master key first - nothing in WIF store - # without warning - # one signature from master added - start_sign(psbt) - title, story = cap_story() - assert "warning" not in story - signed = end_sign() - - po = BasicPSBT().parse(signed) - assert len(po.inputs[0].part_sigs) == 1 - - # add privkey from 0th & 1st node to WIF store - der_node0 = keys[0][1].subkey_for_path(dd[2:] + "/0/0") - sk0 = bytes(der_node0.node.private_key).hex() - pk0 = der_node0.node.private_key.K.sec().hex() - der_node1 = keys[1][1].subkey_for_path(dd[2:] + "/0/0") - sk1 = bytes(der_node1.node.private_key).hex() - pk1 = der_node1.node.private_key.K.sec().hex() - settings_set("wifs", [(pk0,sk0), (pk1,sk1)]) - - # ofe of the private keys will be used for signing - # only one as we cannot sign with 2 keys in one sitting - start_sign(signed) - title, story = cap_story() - assert "warning" in story - assert "WIF store" in story - signed = end_sign() - po = BasicPSBT().parse(signed) - assert len(po.inputs[0].part_sigs) == 2 - - # sign with other key - keys that already have signatures are ignored - # that is why we can proceed with this iterative method - start_sign(signed, finalize=True) - title, story = cap_story() - assert "warning" in story - assert "WIF store" in story - end_sign(finalize=True) - # EOF diff --git a/testing/test_ownership.py b/testing/test_ownership.py index 4f74e908..d82c2dfa 100644 --- a/testing/test_ownership.py +++ b/testing/test_ownership.py @@ -699,65 +699,4 @@ def test_named_wallet_search(wname, valid, method, clear_ms, import_ms_wallet, i assert "1 wallet(s)" in story assert 'without finding a match' in story - -@pytest.mark.parametrize("addr_fmt", ["p2wpkh", "p2sh-p2wpkh", "p2pkh"]) -@pytest.mark.parametrize("idx", [1, 3]) -def test_wif_store(addr_fmt, idx, is_q1, goto_home, pick_menu_item, scan_a_qr, cap_story, need_keypress, - src_root_dir, sim_root_dir, nfc_write, settings_remove, import_wif_to_store, - load_shared_mod, cap_screen_qr, press_cancel): - - settings_remove("wifs") - - n = BIP32Node.from_master_secret(os.urandom(32)) - privkey = n.node.private_key - addr = n.address(addr_fmt=addr_fmt) - wif = encode_base58_checksum(bytes([239]) + bytes(privkey) + b'\x01') - wif1 = encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') - wif2 = encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') - - if idx == 1: - wif_list = [wif, wif1, wif2] - else: - wif_list = [wif1, wif2, wif] - - import_wif_to_store(wif_list) - - goto_home() - - if is_q1: - pick_menu_item('Scan Any QR Code') - scan_a_qr(addr) - time.sleep(1) - - title, story = cap_story() - - assert addr == addr_from_display_format(story.split("\n\n")[0]) - assert '(1) to verify ownership' in story - need_keypress('1') - - else: - cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py') - n = cc_ndef.ndefMaker() - n.add_text(addr) - ccfile = n.bytes() - - pick_menu_item('Advanced/Tools') - pick_menu_item('NFC Tools') - pick_menu_item('Verify Address') - with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f: - f.write(ccfile) - nfc_write(ccfile) - - time.sleep(1) - title, story = cap_story() - assert addr == addr_from_display_format(story.split("\n\n")[0]) - assert f"Found in WIF store at index {idx}" in story - need_keypress(KEY_QR if is_q1 else '1') - addr_qr = cap_screen_qr().decode() - if addr_fmt == "p2wpkh": - addr_qr = addr_qr.lower() - - assert addr == addr_qr - press_cancel() - # EOF diff --git a/testing/test_sign.py b/testing/test_sign.py index a8797d6b..e93b1de5 100644 --- a/testing/test_sign.py +++ b/testing/test_sign.py @@ -21,7 +21,6 @@ from txn import * from ctransaction import CTransaction, CTxOut, CTxIn, COutPoint from ckcc_protocol.constants import STXN_VISUALIZE, STXN_SIGNED from charcodes import KEY_QR, KEY_RIGHT, KEY_LEFT -from bip322 import bip322_msg_hash SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) @@ -3667,152 +3666,4 @@ def test_txid_qr(fake_txn, start_sign, cap_story, press_cancel, press_select): assert "(6) for QR Code of TXID" in story press_cancel() -@pytest.mark.parametrize("num_ins", [1, 5]) -@pytest.mark.parametrize("addr_fmt", ["p2pkh", "p2wpkh", "p2sh-p2wpkh"]) -def test_wif_store_signing(num_ins, addr_fmt, fake_txn, goto_home, pick_menu_item, need_keypress, - start_sign, end_sign, cap_menu, cap_story, press_cancel, settings_remove, - press_select, import_wif_to_store): - - settings_remove("wifs") - - wrap = False - if addr_fmt == "p2pkh": - sw = False - elif addr_fmt == "p2wpkh": - sw = True - elif addr_fmt == "p2sh-p2wpkh": - wrap = True - sw = True - else: - raise ValueError - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(num_ins, 1, segwit_in=sw, wrapped=wrap, master_xpub=node.hwif()) - - wifs = [] - privkeys = [] - for i in range(num_ins): - n = node.subkey_for_path("0/%d" % i) - sk = n.node.private_key - privkeys.append(sk) - wifs.append(n.node.private_key.wif(testnet=True)) - - import_wif_to_store(wifs) - - menu = cap_menu() - assert menu[0] == "Import WIF" - - start_sign(psbt, finalize=True) - time.sleep(.1) - title, story = cap_story() - assert "warning" in story - if num_ins == 1: - assert "WIF store: 0" in story - else: - assert f"WIF store: {', '.join([str(i) for i in range(num_ins)])}" in story - end_sign(finalize=True) - - -@pytest.mark.parametrize("der_paths", [True, False]) -@pytest.mark.parametrize("complete", [True, False]) -def test_wif_store_multi(der_paths, complete, fake_txn, start_sign, end_sign, cap_story, settings_set): - wifs = [] - - hack = None - if der_paths: - def hack(psbt): - new_paths = {} - for k, v in psbt.inputs[0].bip32_paths.items(): - new_paths[k] = b"\x01" * 8 # garbage (do not use zero xfp here) - - psbt.inputs[0].bip32_paths = new_paths - - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(1, 1, segwit_in=True, master_xpub=node.hwif(), psbt_v2=True, outvals=[1E8*3], - psbt_hacker=hack) - po = BasicPSBT().parse(psbt) - n = node.subkey_for_path("0/0") - sk = bytes(n.node.private_key).hex() - pk = n.node.private_key.K.sec().hex() - wifs.append((pk, sk)) - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(1, 1, segwit_in=False, master_xpub=node.hwif(), psbt_v2=True, psbt_hacker=hack) - tmp = BasicPSBT().parse(psbt) - po.inputs += tmp.inputs - po.input_count += 1 - n = node.subkey_for_path("0/0") - sk = bytes(n.node.private_key).hex() - pk = n.node.private_key.K.sec().hex() - wifs.append((pk, sk)) - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(1, 1, segwit_in=True, wrapped=True, master_xpub=node.hwif(), psbt_v2=True, - psbt_hacker=hack) - tmp = BasicPSBT().parse(psbt) - po.inputs += tmp.inputs - po.input_count += 1 - n = node.subkey_for_path("0/0") - sk = bytes(n.node.private_key).hex() - pk = n.node.private_key.K.sec().hex() - wifs.append((pk, sk)) - - # pretend we have those imported - if not complete: - wifs = wifs[:-1] - - settings_set("wifs", wifs) - - start_sign(po.as_bytes(), finalize=complete) - title, story = cap_story() - assert "warning" in story - if complete: - assert "WIF store: 0, 1, 2" in story - else: - assert "WIF store: 0, 1" in story - assert "Limited Signing" in story - - end_sign(finalize=complete) - - -def test_wif_store_with_master(fake_txn, start_sign, end_sign, cap_story, settings_set): - # signs both master key and keys from WIF store - wifs = [] - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(1, 1, segwit_in=True, master_xpub=node.hwif(), psbt_v2=True, outvals=[1E8*3]) - po = BasicPSBT().parse(psbt) - n = node.subkey_for_path("0/0") - sk = bytes(n.node.private_key).hex() - pk = n.node.private_key.K.sec().hex() - wifs.append((pk, sk)) - - node = BIP32Node.from_master_secret(os.urandom(32)) - psbt = fake_txn(1, 1, segwit_in=False, master_xpub=node.hwif(), psbt_v2=True) - tmp = BasicPSBT().parse(psbt) - po.inputs += tmp.inputs - po.input_count += 1 - n = node.subkey_for_path("0/0") - sk = bytes(n.node.private_key).hex() - pk = n.node.private_key.K.sec().hex() - wifs.append((pk, sk)) - - # add simulator input - psbt = fake_txn(1, 1, segwit_in=True, psbt_v2=True) - tmp = BasicPSBT().parse(psbt) - po.inputs += tmp.inputs - po.input_count += 1 - - - settings_set("wifs", wifs) - - # convert to v0 PSBT just for fun - start_sign(po.to_v0(), finalize=True) - title, story = cap_story() - assert "warning" in story - assert "WIF store: 0, 1" in story - - end_sign(finalize=True) - # EOF diff --git a/testing/test_wif.py b/testing/test_wif.py index db277729..8f750aef 100644 --- a/testing/test_wif.py +++ b/testing/test_wif.py @@ -1,10 +1,14 @@ # (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -import pytest, time, os, re, hashlib, shutil +import pytest, time, os from helpers import prandom, addr_from_display_format -from charcodes import KEY_DOWN, KEY_QR, KEY_NFC, KEY_DELETE, KEY_UP +from charcodes import KEY_QR, KEY_NFC, KEY_UP +from constants import unmap_addr_fmt, AF_P2WSH, AF_P2SH from bip32 import BIP32Node, PrivateKey from base58 import encode_base58_checksum +from msg import verify_message, parse_signed_message +from psbt import BasicPSBT +from helpers import str_to_path def make_fake_wif(prefix=239): @@ -352,7 +356,7 @@ def test_wif_store_import_duplicate(settings_remove, import_wif_to_store, settin @pytest.mark.parametrize("way", ["qr", "sd", "nfc"]) def test_wif_store_export_all(way, goto_home, settings_remove, import_wif_to_store, pick_menu_item, - load_export): + load_export, press_cancel): goto_home() settings_remove("wifs") @@ -364,5 +368,374 @@ def test_wif_store_export_all(way, goto_home, settings_remove, import_wif_to_sto conts = load_export(way, "WIF Store", is_json=False, sig_check=False) assert wif_list == conts.split("\n") + press_cancel() +@pytest.mark.parametrize('en_okeys', [ True, False]) +def test_hobbled_wif_store(en_okeys, set_hobble, settings_remove, import_wif_to_store, goto_home, + cap_menu, pick_menu_item): + goto_home() + settings_remove("wifs") + + wif_list = [ + encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') + for _ in range(3) + ] + + import_wif_to_store(wif_list) + goto_home() + + set_hobble(True, {'okeys'} if en_okeys else {}) + pick_menu_item("Advanced/Tools") + + if en_okeys: + pick_menu_item("WIF Store") + time.sleep(.1) + menu = cap_menu() + # check it is read-only + assert "Import WIF" not in menu + assert "Clear All" not in menu + pick_menu_item(menu[0]) + time.sleep(.1) + menu = cap_menu() + assert "Delete" not in menu + else: + assert "WIF Store" not in cap_menu() + + +@pytest.mark.parametrize("way,af", [ + ("sd", "P2SH-Segwit"), + ("input", "Segwit P2WPKH"), + ("nfc", "Classic P2PKH") +]) +def test_sign_msg_with_wif_store_key(way, af, settings_remove, import_wif_to_store, cap_menu, + pick_menu_item, cap_story, need_keypress, press_nfc, + enter_complex, garbage_collector, microsd_path, nfc_write_text, + verify_msg_sign_story, msg_sign_export, press_select, goto_home): + settings_remove("wifs") + msg = "Coinkite" + + n = BIP32Node.from_master_secret(os.urandom(32)) + privkey = n.node.private_key + import_wif_to_store([encode_base58_checksum(bytes([239]) + bytes(privkey) + b'\x01')]) + + menu = cap_menu() + assert len(menu) == 2 + pick_menu_item(menu[1]) + pick_menu_item("Sign MSG") + pick_menu_item(af) + + if way == "input": + need_keypress("0") + enter_complex(msg, apply=False, b39pass=False) + + elif way == "sd": + name = "msg_to_sign.txt" + pth = microsd_path(name) + with open(pth, "w") as f: + f.write(msg) + + need_keypress("1") + pick_menu_item(name) + + elif way == "nfc": + press_nfc() + time.sleep(0.2) + nfc_write_text(msg) + time.sleep(0.3) + + else: + raise NotImplementedError + + time.sleep(.1) + title, story = cap_story() + addr_fmt = {"P2SH-Segwit": "p2sh-p2wpkh", + "Segwit P2WPKH": "p2wpkh", + "Classic P2PKH": "p2pkh"}[af] + + target_addr = n.address(addr_fmt=addr_fmt) + verify_msg_sign_story(story, msg, "m", addr=target_addr) + press_select() + res = msg_sign_export(way if way != "input" else "sd") + assert target_addr in res + pmsg, addr, sig = parse_signed_message(res) + assert pmsg == msg + assert verify_message(addr, sig, msg) is True + goto_home() + + +@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh", "p2sh"]) +def test_multisig_wif_store(addr_fmt, dev, fake_ms_txn, start_sign, settings_set, clear_ms, + cap_story, pytestconfig, import_ms_wallet, end_sign, settings_remove): + # TODO This test MUST be run with --psbt2 flag on and off + clear_ms() + settings_remove("wifs") + M, N = 3, 5 + + if addr_fmt == AF_P2SH: + dd = "m/45h" + elif addr_fmt == AF_P2WSH: + dd = "m/48h/1h/0h/2h" + else: + dd = "m/48h/1h/0h/1h" + + def path_mapper(idx): + kk = str_to_path(dd) + return kk + [0,0] + + keys = import_ms_wallet(M, N, name='wif_store', accept=True, netcode="XTN", + descriptor=True, addr_fmt=addr_fmt, common=dd) + + psbt = fake_ms_txn(1, 1, M, keys, inp_af=unmap_addr_fmt[addr_fmt], + path_mapper=path_mapper, psbt_v2=pytestconfig.getoption('psbt2')) + + # sign with master key first - nothing in WIF store + # without warning + # one signature from master added + start_sign(psbt) + title, story = cap_story() + assert "warning" not in story + signed = end_sign() + + po = BasicPSBT().parse(signed) + assert len(po.inputs[0].part_sigs) == 1 + + # add privkey from 0th & 1st node to WIF store + der_node0 = keys[0][1].subkey_for_path(dd[2:] + "/0/0") + sk0 = bytes(der_node0.node.private_key).hex() + pk0 = der_node0.node.private_key.K.sec().hex() + der_node1 = keys[1][1].subkey_for_path(dd[2:] + "/0/0") + sk1 = bytes(der_node1.node.private_key).hex() + pk1 = der_node1.node.private_key.K.sec().hex() + settings_set("wifs", [(pk0,sk0), (pk1,sk1)]) + + # ofe of the private keys will be used for signing + # only one as we cannot sign with 2 keys in one sitting + start_sign(signed) + title, story = cap_story() + assert "warning" in story + assert "WIF store" in story + signed = end_sign() + po = BasicPSBT().parse(signed) + assert len(po.inputs[0].part_sigs) == 2 + + # sign with other key - keys that already have signatures are ignored + # that is why we can proceed with this iterative method + start_sign(signed, finalize=True) + title, story = cap_story() + assert "warning" in story + assert "WIF store" in story + end_sign(finalize=True) + + +@pytest.mark.parametrize("addr_fmt", ["p2wpkh", "p2sh-p2wpkh", "p2pkh"]) +@pytest.mark.parametrize("idx", [1, 3]) +def test_wif_store_ownership(addr_fmt, idx, is_q1, goto_home, pick_menu_item, scan_a_qr, cap_story, + need_keypress, src_root_dir, sim_root_dir, nfc_write, settings_remove, + import_wif_to_store, load_shared_mod, cap_screen_qr, press_cancel): + + settings_remove("wifs") + + n = BIP32Node.from_master_secret(os.urandom(32)) + privkey = n.node.private_key + addr = n.address(addr_fmt=addr_fmt) + wif = encode_base58_checksum(bytes([239]) + bytes(privkey) + b'\x01') + wif1 = encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') + wif2 = encode_base58_checksum(bytes([239]) + os.urandom(32) + b'\x01') + + if idx == 1: + wif_list = [wif, wif1, wif2] + else: + wif_list = [wif1, wif2, wif] + + import_wif_to_store(wif_list) + + goto_home() + + if is_q1: + pick_menu_item('Scan Any QR Code') + scan_a_qr(addr) + time.sleep(1) + + title, story = cap_story() + + assert addr == addr_from_display_format(story.split("\n\n")[0]) + assert '(1) to verify ownership' in story + need_keypress('1') + + else: + cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py') + n = cc_ndef.ndefMaker() + n.add_text(addr) + ccfile = n.bytes() + + pick_menu_item('Advanced/Tools') + pick_menu_item('NFC Tools') + pick_menu_item('Verify Address') + with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f: + f.write(ccfile) + nfc_write(ccfile) + + time.sleep(1) + title, story = cap_story() + assert addr == addr_from_display_format(story.split("\n\n")[0]) + assert f"Found in WIF store at index {idx}" in story + need_keypress(KEY_QR if is_q1 else '1') + addr_qr = cap_screen_qr().decode() + if addr_fmt == "p2wpkh": + addr_qr = addr_qr.lower() + + assert addr == addr_qr + press_cancel() + + +@pytest.mark.parametrize("num_ins", [1, 5]) +@pytest.mark.parametrize("addr_fmt", ["p2pkh", "p2wpkh", "p2sh-p2wpkh"]) +def test_wif_store_signing(num_ins, addr_fmt, fake_txn, goto_home, pick_menu_item, need_keypress, + start_sign, end_sign, cap_menu, cap_story, press_cancel, settings_remove, + press_select, import_wif_to_store): + + settings_remove("wifs") + + wrap = False + if addr_fmt == "p2pkh": + sw = False + elif addr_fmt == "p2wpkh": + sw = True + elif addr_fmt == "p2sh-p2wpkh": + wrap = True + sw = True + else: + raise ValueError + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(num_ins, 1, segwit_in=sw, wrapped=wrap, master_xpub=node.hwif()) + + wifs = [] + privkeys = [] + for i in range(num_ins): + n = node.subkey_for_path("0/%d" % i) + sk = n.node.private_key + privkeys.append(sk) + wifs.append(n.node.private_key.wif(testnet=True)) + + import_wif_to_store(wifs) + + menu = cap_menu() + assert menu[0] == "Import WIF" + + start_sign(psbt, finalize=True) + time.sleep(.1) + title, story = cap_story() + assert "warning" in story + if num_ins == 1: + assert "WIF store: 0" in story + else: + assert f"WIF store: {', '.join([str(i) for i in range(num_ins)])}" in story + end_sign(finalize=True) + + +@pytest.mark.parametrize("der_paths", [True, False]) +@pytest.mark.parametrize("complete", [True, False]) +def test_wif_store_signing_multi(der_paths, complete, fake_txn, start_sign, end_sign, cap_story, + settings_set): + wifs = [] + + hack = None + if der_paths: + def hack(psbt): + new_paths = {} + for k, v in psbt.inputs[0].bip32_paths.items(): + new_paths[k] = b"\x01" * 8 # garbage (do not use zero xfp here) + + psbt.inputs[0].bip32_paths = new_paths + + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(1, 1, segwit_in=True, master_xpub=node.hwif(), psbt_v2=True, outvals=[1E8*3], + psbt_hacker=hack) + po = BasicPSBT().parse(psbt) + n = node.subkey_for_path("0/0") + sk = bytes(n.node.private_key).hex() + pk = n.node.private_key.K.sec().hex() + wifs.append((pk, sk)) + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(1, 1, segwit_in=False, master_xpub=node.hwif(), psbt_v2=True, psbt_hacker=hack) + tmp = BasicPSBT().parse(psbt) + po.inputs += tmp.inputs + po.input_count += 1 + n = node.subkey_for_path("0/0") + sk = bytes(n.node.private_key).hex() + pk = n.node.private_key.K.sec().hex() + wifs.append((pk, sk)) + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(1, 1, segwit_in=True, wrapped=True, master_xpub=node.hwif(), psbt_v2=True, + psbt_hacker=hack) + tmp = BasicPSBT().parse(psbt) + po.inputs += tmp.inputs + po.input_count += 1 + n = node.subkey_for_path("0/0") + sk = bytes(n.node.private_key).hex() + pk = n.node.private_key.K.sec().hex() + wifs.append((pk, sk)) + + # pretend we have those imported + if not complete: + wifs = wifs[:-1] + + settings_set("wifs", wifs) + + start_sign(po.as_bytes(), finalize=complete) + title, story = cap_story() + assert "warning" in story + if complete: + assert "WIF store: 0, 1, 2" in story + else: + assert "WIF store: 0, 1" in story + assert "Limited Signing" in story + + end_sign(finalize=complete) + + +def test_wif_store_signing_with_master(fake_txn, start_sign, end_sign, cap_story, settings_set): + # signs both master key and keys from WIF store + wifs = [] + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(1, 1, segwit_in=True, master_xpub=node.hwif(), psbt_v2=True, outvals=[1E8*3]) + po = BasicPSBT().parse(psbt) + n = node.subkey_for_path("0/0") + sk = bytes(n.node.private_key).hex() + pk = n.node.private_key.K.sec().hex() + wifs.append((pk, sk)) + + node = BIP32Node.from_master_secret(os.urandom(32)) + psbt = fake_txn(1, 1, segwit_in=False, master_xpub=node.hwif(), psbt_v2=True) + tmp = BasicPSBT().parse(psbt) + po.inputs += tmp.inputs + po.input_count += 1 + n = node.subkey_for_path("0/0") + sk = bytes(n.node.private_key).hex() + pk = n.node.private_key.K.sec().hex() + wifs.append((pk, sk)) + + # add simulator input + psbt = fake_txn(1, 1, segwit_in=True, psbt_v2=True) + tmp = BasicPSBT().parse(psbt) + po.inputs += tmp.inputs + po.input_count += 1 + + + settings_set("wifs", wifs) + + # convert to v0 PSBT just for fun + start_sign(po.to_v0(), finalize=True) + title, story = cap_story() + assert "warning" in story + assert "WIF store: 0, 1" in story + + end_sign(finalize=True) + +# EOF \ No newline at end of file