What changed, and why it matters
This commit is purely a test-code reorganization. It moves existing WIF-store-related test cases from several scattered test files into a single dedicated test file (test_wif.py), updates test runner configuration, and removes duplicate imports. There is no change to the actual firmware or security-sensitive code.
No security action required; this is a test refactoring commit. Routine review/CI pass is sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff shows only changes under the testing/ directory. Existing tests covering WIF store behavior (hobble restrictions, message signing, multisig signing, ownership verification, and transaction signing) are relocated from test_hobble.py, test_msg.py, test_multisig.py, test_ownership.py, and test_sign.py into test_wif.py. Correspondingly, test_wif.py is added to the simulator argument list requiring vidsk=1, and an import of set_hobble from test_hobble is added in conftest.py. No production firmware code is modified.
Changed components
testing/conftest.pytesting/run_sim_tests.pytesting/test_hobble.pytesting/test_msg.pytesting/test_multisig.pytesting/test_ownership.pytesting/test_sign.pytesting/test_wif.pyInspect captured patch +378 / −373
diff --git a/testing/conftest.py b/testing/conftest.py
index c6d94c8..6bc8737 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 4782cfe..b7c2f69 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 96b129c..b18e499 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 4348521..03b7aee 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 d27ba74..8bca38b 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 4f74e90..d82c2df 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 a8797d6..e93b1de 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 db27772..8f750ae 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
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.