multiprocess simulator

This commit is contained in:
scgbckbone 2025-04-25 16:24:35 +02:00 committed by doc-hex
parent 89dfe1f6d4
commit 11da344abf
37 changed files with 928 additions and 530 deletions

@ -1 +1 @@
Subproject commit 0e686dbda686f76c4d3e8069558b2a31f9d1c2b1
Subproject commit f87d30f220cb6334eb3c4ace93c1b62e04942022

View File

@ -51,7 +51,7 @@ class Bitcoind:
[
self.bitcoind_path,
# needed for newest master
# TODO legacy wallet will be deprecated in 26
# TODO legacy wallet will be deprecated in 29
"-deprecatedrpc=create_bdb",
"-regtest",
f"-datadir={self.datadir}",
@ -59,6 +59,7 @@ class Bitcoind:
"-fallbackfee=0.0002",
"-server=1",
"-keypool=1",
"-listen=0"
f"-port={self.p2p_port}",
f"-rpcport={self.rpc_port}"
]

View File

@ -5,7 +5,7 @@ from charcodes import KEY_ENTER
from core_fixtures import _pick_menu_item, _cap_story, _press_select
from core_fixtures import _need_keypress, _cap_menu, _sim_exec
from run_sim_tests import ColdcardSimulator, clean_sim_data
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
from ckcc_protocol.client import ColdcardDevice
def _clone(source, target):
@ -18,7 +18,7 @@ def _clone(source, target):
clean_sim_data() # remove all from previous
sim_target = ColdcardSimulator(args=[target_sim_arg, "-l"])
sim_target.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, target_is_Q, "Import Existing")
_pick_menu_item(device, target_is_Q, "Clone Coldcard")
time.sleep(.1)
@ -36,7 +36,7 @@ def _clone(source, target):
sim_source = ColdcardSimulator(args=[source_sim_arg, "--ms", "--p2wsh",
"--set", "nfc=1", "--set", "vidsk=1"])
sim_source.start(start_wait=6)
device_source = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device_source = ColdcardDevice(is_simulator=True)
_pick_menu_item(device_source, source_is_Q, "Advanced/Tools")
time.sleep(.1)
_pick_menu_item(device_source, source_is_Q, "Backup")
@ -89,7 +89,7 @@ def _clone(source, target):
# TARGET again. Killed now - restart and verify settings
sim_target = ColdcardSimulator(args=[target_sim_arg])
sim_target.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, target_is_Q, "Settings")
_pick_menu_item(device, target_is_Q, "Multisig Wallets")
time.sleep(.1)

View File

@ -13,13 +13,15 @@ from api import bitcoind_d_sim_watch, finalize_v2_v0_convert
from binascii import b2a_hex, a2b_hex
from constants import *
from charcodes import *
from core_fixtures import _need_keypress, _sim_exec, _cap_story, _cap_menu, _cap_screen
from core_fixtures import _need_keypress, _sim_exec, _cap_story, _cap_menu, _cap_screen, _sim_eval
from core_fixtures import _press_select, _pick_menu_item, _enter_complex, _dev_hw_label
# lock down randomness
random.seed(42)
# needs to be run from /testing directory
os.environ["SRC_ROOT"] = os.path.join(os.getcwd().rsplit("/", 1)[0])
if sys.platform == 'darwin':
# BUGFIX: my ARM-based MacOS system uses rosetta to run Python in x86 mode
# and so I needed this?
@ -37,6 +39,7 @@ def pytest_addoption(parser):
default=False, help="operator must press keys on real CC")
parser.addoption("--mk", default=4, help="Assume mark N hardware")
parser.addoption("--sim-socket", "-S", type=str, help="Simulator .socket path", default=None)
parser.addoption("--duress", action="store_true",
default=False, help="assume logged-in with duress PIN")
@ -79,48 +82,42 @@ def simulator(request):
raise pytest.skip('need simulator for this test, have real device')
try:
return ColdcardDevice(sn=SIM_PATH)
except:
return ColdcardDevice(sn=request.config.getoption("--sim-socket"), is_simulator=True)
except Exception as e:
print("Simulator is required for this test")
raise pytest.fail('missing simulator')
@pytest.fixture(scope='module')
@pytest.fixture
def sim_exec(dev):
# run code in the simulator's interpretor
# run code in the simulator's interpreter
# - can work on real product too, if "debug build" is used.
f = functools.partial(_sim_exec, dev)
return f
@pytest.fixture(scope='module')
@pytest.fixture
def sim_eval(dev):
# eval an expression in the simulator's interpretor
# - can work on real product too, if "debug build" is used.
f = functools.partial(_sim_eval, dev)
return f
def doit(cmd, timeout=None):
return dev.send_recv(b'EVAL' + cmd.encode('utf-8'), timeout=timeout).decode('utf-8')
return doit
@pytest.fixture(scope='module')
def sim_execfile(simulator):
@pytest.fixture
def sim_execfile(simulator, src_root_dir):
# run a whole file in the simulator's interpretor
# - requires shared filesystem
import os
def doit(fname, timeout=None):
fn = os.path.realpath(fname)
hook = 'execfile("%s")' % fn
hook = 'execfile("%s")' % (src_root_dir + "/testing/" + fname)
return simulator.send_recv(b'EXEC' + hook.encode('utf-8'), timeout=timeout).decode('utf-8')
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def is_simulator(dev):
def doit():
return hasattr(dev.dev, 'pipe')
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def send_ux_abort(simulator):
def doit():
@ -138,7 +135,7 @@ def OK(is_q1):
def X(is_q1):
return "CANCEL" if is_q1 else "X"
@pytest.fixture(scope='module')
@pytest.fixture
def need_keypress(dev, request):
def doit(k, timeout=1000):
if request.config.getoption("--manual"):
@ -152,7 +149,7 @@ def need_keypress(dev, request):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def enter_number(need_keypress, press_select):
def doit(number):
number = str(number) if not isinstance(number, str) else number
@ -170,7 +167,7 @@ def enter_complex(dev, is_q1):
f = functools.partial(_enter_complex, dev, is_q1)
return f
@pytest.fixture(scope='module')
@pytest.fixture
def enter_hex(need_keypress, enter_text, is_q1):
def doit(hex_str):
if is_q1:
@ -185,7 +182,7 @@ def enter_hex(need_keypress, enter_text, is_q1):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def enter_pin(enter_number, press_select, cap_screen, is_q1):
def doit(pin):
assert '-' in pin
@ -207,7 +204,7 @@ def enter_pin(enter_number, press_select, cap_screen, is_q1):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def do_keypresses(need_keypress):
# do a series of keypresses, any kind
def doit(value):
@ -217,7 +214,7 @@ def do_keypresses(need_keypress):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def enter_text(need_keypress, is_q1):
# enter a text value, might be a number or string ... on Q can be multiline
def doit(value, multiline=False):
@ -260,14 +257,14 @@ def master_xpub(dev):
return r
@pytest.fixture(scope='module')
@pytest.fixture
def unit_test(sim_execfile):
def doit(filename):
rv = sim_execfile(filename)
if rv: pytest.fail(rv)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def get_settings(sim_execfile):
# get all settings
def doit():
@ -278,7 +275,7 @@ def get_settings(sim_execfile):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def get_setting(sim_execfile, sim_exec):
# get an individual setting
def doit(name, default=None):
@ -290,15 +287,15 @@ def get_setting(sim_execfile, sim_exec):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def addr_vs_path(master_xpub):
from bip32 import BIP32Node
from ckcc_protocol.constants import AF_CLASSIC, AFC_PUBKEY, AF_P2WPKH, AFC_SCRIPT
from ckcc_protocol.constants import AF_P2WPKH_P2SH, AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH
from bech32 import bech32_decode, convertbits, Encoding
from hashlib import sha256
def doit(given_addr, path=None, addr_fmt=None, script=None, testnet=True):
from bip32 import BIP32Node
from ckcc_protocol.constants import AF_CLASSIC, AFC_PUBKEY, AF_P2WPKH, AFC_SCRIPT
from ckcc_protocol.constants import AF_P2WPKH_P2SH, AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH
from bech32 import bech32_decode, convertbits, Encoding
from hashlib import sha256
if not script:
try:
# prefer using xpub if we can
@ -369,13 +366,13 @@ def capture_enabled(sim_eval):
# - could be xfail or xskip here
assert sim_eval("'sim_display' in sys.modules") == 'True'
@pytest.fixture(scope='module')
@pytest.fixture
def cap_menu(dev):
"Return menu items as a list"
f = functools.partial(_cap_menu, dev)
return f
@pytest.fixture(scope='module')
@pytest.fixture
def is_ftux_screen(sim_exec):
"are we presenting a view from ftux.py??"
def doit():
@ -402,12 +399,12 @@ def expect_ftux(cap_menu, cap_story, press_select, is_ftux_screen):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def cap_screen(dev):
f = functools.partial(_cap_screen, dev)
return f
@pytest.fixture(scope='module')
@pytest.fixture
def cap_text_box(cap_screen):
# provides text inside a lined box on the screen right now - Q1 only
def doit():
@ -423,15 +420,15 @@ def cap_text_box(cap_screen):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def cap_story(dev):
# returns (title, body) of whatever story is being actively shown
f = functools.partial(_cap_story, dev)
return f
@pytest.fixture(scope='module')
def cap_image(request, sim_exec, is_q1, is_headless):
@pytest.fixture
def cap_image(request, sim_exec, is_q1, is_headless, sim_root_dir):
def flip(raw):
reorg = bytearray(128*64)
@ -451,7 +448,7 @@ def cap_image(request, sim_exec, is_q1, is_headless):
if is_headless:
raise pytest.skip("headless mode: QR tests disabled")
# trigger simulator to capture a snapshot into a named file, read it.
fn = os.path.realpath(f'./debug/snap-{random.randint(int(1E6), int(9E6))}.png')
fn = os.path.realpath(f'{sim_root_dir}/debug/snap-{random.randint(int(1E6), int(9E6))}.png')
try:
sim_exec(f"from glob import dis; dis.dis.save_snapshot({fn!r})")
for _ in range(20):
@ -480,7 +477,7 @@ RV.write(b2a_hex(dis.dis.buffer))'''))
QR_HISTORY = []
@pytest.fixture(scope='session')
def qr_quality_check():
def qr_quality_check(sim_root_dir):
# Use this with cap_screen_qr
print("QR codes will be captured and shown at end of run.")
yield None
@ -529,13 +526,13 @@ def qr_quality_check():
#rv = rv.resize(tuple(c*4 for c in rv.size), resample=Image.NEAREST)
rv.save('debug/all-qrs.png')
rv.save(f'{sim_root_dir}/debug/all-qrs.png')
rv.show()
@pytest.fixture(scope='module')
def cap_screen_qr(cap_image):
@pytest.fixture
def cap_screen_qr(cap_image, sim_root_dir):
def doit(no_history=False):
# NOTE: version=4 QR is pixel doubled to be 66x66 with 2 missing lines at bottom
# LATER: not doing that anymore; v={1,2,3} doubled, all higher 1:1 pixels (tiny)
@ -570,7 +567,7 @@ def cap_screen_qr(cap_image):
w, h = orig_img.size # 320x240
img = orig_img.crop( (0, 0, w, h-5) ).convert('L')
img.save('debug/last-qr.png')
img.save(f'{sim_root_dir}/debug/last-qr.png')
#img.show()
# Above usually works @ zoom=1, but not always!
@ -618,8 +615,8 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
# - skips first line, which on Q shows the index number sometimes
# - insists on some spaces
full = cap_screen()
full_split = full.split("\n")
if is_q1:
full_split = full.split("\n")
if is_change:
for i, (c, line) in enumerate(zip("XXXXCHANGE", full_split)):
if i > 3:
@ -640,14 +637,18 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
for c, line in zip("XXXXXXBACK", full_split):
assert not line.endswith(c)
txt = ''.join(l for l in full.split() if len(l)>4).replace('~', '')
txt = ''.join(l for l in full_split if len(l)>4).replace('~', '')
if txt:
# just index remained
int(txt)
txt = None
else:
if is_change:
assert "CHANGE BACK" in full
elif is_change is False:
assert "CHANGE BACK" not in full
txt = ''.join(full.split("\n")).replace('CHANGE BACK', '')
txt = ''.join(full_split).replace('CHANGE BACK', '')
if txt:
assert txt == qr
@ -662,7 +663,7 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def get_pp_sofar(sim_exec):
# get entry value for bip39 passphrase
def doit():
@ -672,7 +673,7 @@ def get_pp_sofar(sim_exec):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def get_secrets(sim_execfile):
# returns big dict based on what we'd normally put into a backup file.
def doit():
@ -692,48 +693,48 @@ def get_secrets(sim_execfile):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_select(dev, has_qwerty):
f = functools.partial(_press_select, dev, has_qwerty)
return f
@pytest.fixture(scope='module')
@pytest.fixture
def press_cancel(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_CANCEL if has_qwerty else 'x', **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_delete(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_DELETE if has_qwerty else 'x', **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_nfc(need_keypress, has_qwerty):
def doit(num=3, **kws):
need_keypress(KEY_NFC if has_qwerty else str(num), **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_up(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_UP if has_qwerty else "5", **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_down(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_DOWN if has_qwerty else "8", **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_left(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_LEFT if has_qwerty else "7", **kws)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def press_right(need_keypress, has_qwerty):
def doit(**kws):
need_keypress(KEY_RIGHT if has_qwerty else "9", **kws)
@ -775,24 +776,37 @@ def goto_home(cap_menu, press_cancel, press_select, pick_menu_item, cap_screen):
return doit
@pytest.fixture(scope="module")
@pytest.fixture
def pick_menu_item(dev, has_qwerty):
f = functools.partial(_pick_menu_item, dev, has_qwerty)
return f
@pytest.fixture(scope='module')
def virtdisk_path(request, is_simulator, needs_virtdisk):
@pytest.fixture(scope='session')
def src_root_dir():
return os.environ.get("SRC_ROOT")
@pytest.fixture(scope='session')
def sim_root_dir(dev, request, src_root_dir):
if request.config.getoption("--dev"):
return os.path.join(src_root_dir, "unix/work")
cmd = f"import ckcc; RV.write(ckcc.get_sim_root_dirs()[0])"
rv = _sim_exec(dev, cmd)
return rv
@pytest.fixture
def virtdisk_path(request, is_simulator, needs_virtdisk, sim_root_dir):
# get a path to indicated filename on emulated/shared dir
def doit(fn):
# could use: ckcc.get_sim_root_dirs() here
if is_simulator():
get_setting = request.getfixturevalue('get_setting')
if not get_setting('vidsk', False):
raise pytest.xfail('virtdisk disabled')
assert os.path.isdir('../unix/work/VirtDisk')
return '../unix/work/VirtDisk/' + fn
return sim_root_dir + '/VirtDisk/' + fn
elif sys.platform == 'darwin':
# TODO
if not request.config.getoption("--manual"):
raise pytest.fail('must use --manual CLI option')
@ -803,7 +817,7 @@ def virtdisk_path(request, is_simulator, needs_virtdisk):
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def virtdisk_wipe(dev, needs_virtdisk, virtdisk_path):
def doit():
for fn in glob.glob(virtdisk_path('*')):
@ -815,13 +829,12 @@ def virtdisk_wipe(dev, needs_virtdisk, virtdisk_path):
return doit
@pytest.fixture(scope='module')
def microsd_path(simulator):
@pytest.fixture
def microsd_path(simulator, sim_root_dir):
# open a file from the simulated microsd
def doit(fn):
# could use: ckcc.get_sim_root_dirs() here
return '../unix/work/MicroSD/' + fn
return sim_root_dir + '/MicroSD/' + fn
return doit
@ -836,7 +849,7 @@ def microsd_wipe(microsd_path):
os.remove(dir + fname)
return doit
@pytest.fixture(scope='module')
@pytest.fixture
def open_microsd(simulator, microsd_path):
# open a file from the simulated microsd
@ -846,13 +859,12 @@ def open_microsd(simulator, microsd_path):
return doit
@pytest.fixture(scope='module')
def settings_path(simulator):
@pytest.fixture
def settings_path(simulator, sim_root_dir):
# open a file from the simulated microsd
def doit(fn):
# could use: ckcc.get_sim_root_dirs() here
return '../unix/work/settings/' + fn
return sim_root_dir + '/settings/' + fn
return doit
@ -864,7 +876,7 @@ def settings_slots(settings_path):
if fn.endswith(".aes")]
return doit
@pytest.fixture(scope="function")
@pytest.fixture
def set_master_key(sim_exec, sim_execfile, simulator, reset_seed_words):
# load simulator w/ a specific bip32 master key
@ -888,7 +900,7 @@ def set_master_key(sim_exec, sim_execfile, simulator, reset_seed_words):
# - actually need seed words for all tests
reset_seed_words()
@pytest.fixture(scope="function")
@pytest.fixture
def set_xfp(sim_exec):
# set the XFP, without really knowing the private keys
# - won't be able to sign, but should accept PSBT for signing
@ -906,7 +918,7 @@ def set_xfp(sim_exec):
sim_exec('from main import settings; settings.set("xfp", 0x%x);' % simulator_fixed_xfp)
@pytest.fixture(scope="function")
@pytest.fixture
def set_encoded_secret(sim_exec, sim_execfile, simulator, reset_seed_words):
# load simulator w/ a specific secret
@ -932,21 +944,21 @@ def set_encoded_secret(sim_exec, sim_execfile, simulator, reset_seed_words):
# - actually need seed words for all tests
reset_seed_words()
@pytest.fixture(scope="function")
@pytest.fixture
def use_mainnet(settings_set):
def doit():
settings_set('chain', 'BTC')
yield doit
settings_set('chain', 'XTN')
@pytest.fixture(scope="function")
@pytest.fixture
def use_testnet(settings_set):
def doit(do_testnet=True):
settings_set('chain', 'XTN' if do_testnet else 'BTC')
yield doit
settings_set('chain', 'XTN')
@pytest.fixture(scope="function")
@pytest.fixture
def use_regtest(request, settings_set):
if request.config.getoption("--manual"):
def xrt_warn():
@ -960,7 +972,7 @@ def use_regtest(request, settings_set):
settings_set('chain', 'XTN')
@pytest.fixture(scope="function")
@pytest.fixture
def set_seed_words(change_seed_words, reset_seed_words):
def doit(w):
return change_seed_words(w)
@ -971,7 +983,7 @@ def set_seed_words(change_seed_words, reset_seed_words):
reset_seed_words()
@pytest.fixture(scope="function")
@pytest.fixture
def change_seed_words(sim_exec, sim_execfile, simulator):
# load simulator w/ a specific bip32 master key
@ -989,7 +1001,7 @@ def change_seed_words(sim_exec, sim_execfile, simulator):
return doit
@pytest.fixture()
@pytest.fixture
def reset_seed_words(change_seed_words):
# load simulator w/ a specific bip39 seed phrase
@ -1004,7 +1016,7 @@ def reset_seed_words(change_seed_words):
return doit
@pytest.fixture()
@pytest.fixture
def settings_set(sim_exec):
def doit(key, val, prelogin=False):
@ -1014,7 +1026,7 @@ def settings_set(sim_exec):
return doit
@pytest.fixture()
@pytest.fixture
def settings_get(sim_exec):
def doit(key, def_val=None, prelogin=False):
@ -1026,7 +1038,7 @@ def settings_get(sim_exec):
return doit
@pytest.fixture()
@pytest.fixture
def master_settings_get(sim_exec):
def doit(key):
@ -1037,7 +1049,7 @@ def master_settings_get(sim_exec):
return doit
@pytest.fixture()
@pytest.fixture
def settings_remove(sim_exec):
def doit(key):
@ -1046,19 +1058,14 @@ def settings_remove(sim_exec):
return doit
@pytest.fixture(scope='module')
def repl(request):
return request.getfixturevalue('mk4_repl')
@pytest.fixture(scope='module')
def mk4_repl(sim_eval, sim_exec):
@pytest.fixture(scope='session')
def repl(dev, request):
# Provide an interactive connection to the REPL, using the debug build USB commands
class Mk4USBRepl:
def eval(self, cmd, max_time=3):
# send a command, wait for it to finish
resp = sim_eval(cmd)
resp = _sim_eval(dev, cmd)
print(f"eval: {cmd} => {resp}")
if 'Traceback' in resp:
raise RuntimeError(resp)
@ -1066,7 +1073,7 @@ def mk4_repl(sim_eval, sim_exec):
def exec(self, cmd, proc_time=1, raw=False):
# send a (one line) command and read the one-line response
resp = sim_exec(cmd)
resp = _sim_exec(dev, cmd)
print(f"exec: {cmd} => {resp}")
if raw: return resp
return eval(resp) if resp else None
@ -1167,7 +1174,7 @@ def old_mk_repl(dev=None):
return USBRepl()
@pytest.fixture()
@pytest.fixture
def decode_with_bitcoind(bitcoind):
def doit(raw_txn):
@ -1181,7 +1188,7 @@ def decode_with_bitcoind(bitcoind):
return doit
@pytest.fixture()
@pytest.fixture
def decode_psbt_with_bitcoind(bitcoind):
def doit(raw_psbt):
@ -1196,7 +1203,7 @@ def decode_psbt_with_bitcoind(bitcoind):
return doit
@pytest.fixture()
@pytest.fixture
def check_against_bitcoind(bitcoind, use_regtest, sim_exec, sim_execfile):
def doit(hex_txn, fee, num_warn=0, change_outs=None, dests=[]):
@ -1577,7 +1584,7 @@ def has_qwerty(is_q1):
return is_q1
@pytest.fixture(scope='module')
def rf_interface(needs_nfc, sim_exec):
def rf_interface(needs_nfc, dev):
# provide a read/write connection over NFC
# - requires pyscard module and desktop NFC-V reader which doesn't exist
raise pytest.xfail('broken NFC-V challenges')
@ -1629,15 +1636,15 @@ def rf_interface(needs_nfc, sim_exec):
pass
# get the CC into NFC tap mode (but no UX)
sim_exec('glob.NFC.set_rf_disable(0)')
_sim_exec(dev, 'glob.NFC.set_rf_disable(0)')
time.sleep(3)
yield RFHandler()
sim_exec('glob.NFC.set_rf_disable(1)')
_sim_exec(dev, 'glob.NFC.set_rf_disable(1)')
@pytest.fixture()
@pytest.fixture
def nfc_read(request, needs_nfc):
# READ data from NFC chip
# - perfer to do over NFC reader, but can work over USB too
@ -1655,7 +1662,7 @@ def nfc_read(request, needs_nfc):
except:
return doit_usb
@pytest.fixture()
@pytest.fixture
def nfc_read_url(nfc_read, press_cancel):
# gives URL from ndef
@ -1673,7 +1680,7 @@ def nfc_read_url(nfc_read, press_cancel):
return doit
@pytest.fixture()
@pytest.fixture
def nfc_write(request, needs_nfc, is_q1):
# WRITE data into NFC "chip"
def doit_usb(ccfile):
@ -1690,26 +1697,26 @@ def nfc_write(request, needs_nfc, is_q1):
except:
return doit_usb
@pytest.fixture()
@pytest.fixture
def enable_nfc(needs_nfc, sim_exec, settings_set):
def doit():
settings_set('nfc', 1)
sim_exec('import nfc; nfc.NFCHandler.startup()')
return doit
@pytest.fixture()
@pytest.fixture
def nfc_disabled(settings_get):
def doit():
return not bool(settings_get('nfc', 0))
return doit
@pytest.fixture()
@pytest.fixture
def vdisk_disabled(settings_get):
def doit():
return not bool(settings_get('vidsk', 0))
return doit
@pytest.fixture()
@pytest.fixture
def scan_a_qr(sim_exec, is_q1):
# simulate a QR being scanned
# XXX limitation: our USB protocol can't send a v40 QR, limit is more like 30 or so
@ -1742,14 +1749,14 @@ def ccfile_wrap(recs):
return rv
@pytest.fixture()
@pytest.fixture
def nfc_write_text(nfc_write):
def doit(text):
msg = b''.join(ndef.message_encoder([ndef.TextRecord(text), ]))
return nfc_write(ccfile_wrap(msg))
return doit
@pytest.fixture()
@pytest.fixture
def nfc_read_json(nfc_read):
def doit():
import json
@ -1761,7 +1768,7 @@ def nfc_read_json(nfc_read):
return doit
@pytest.fixture()
@pytest.fixture
def nfc_read_text(nfc_read):
def doit():
got = list(ndef.message_decoder(nfc_read()))
@ -1771,7 +1778,7 @@ def nfc_read_text(nfc_read):
return got.text
return doit
@pytest.fixture()
@pytest.fixture
def nfc_read_txn(nfc_read, press_select):
def doit(txid=None, contents=None):
if contents is None:
@ -1807,7 +1814,7 @@ def nfc_read_txn(nfc_read, press_select):
return doit
@pytest.fixture()
@pytest.fixture
def nfc_block4rf(sim_eval):
# wait until RF is enabled and something to read (doesn't read it tho)
def doit(timeout=15):

View File

@ -1,8 +1,6 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
SIM_PATH = '/tmp/ckcc-simulator.sock'
# Simulator normally powers up with this 'wallet'
simulator_fixed_tprv = "tprv8ZgxMBicQKsPeXJHL3vPPgTAEqQ5P2FD9qDeCQT4Cp1EMY5QkwMPWFxHdxHrxZhhcVRJ2m7BNWTz9Xre68y7mX5vCdMJ5qXMUfnrZ2si2X4"
simulator_fixed_tpub = "tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh"

View File

@ -17,6 +17,11 @@ def _sim_exec(device, cmd, binary=False, timeout=60000):
# print(f'sim_exec: {cmd!r} -> {s!r}')
return s.decode('utf-8') if not isinstance(s, str) else s
def _sim_eval(device, cmd, binary=False, timeout=None):
s = device.send_recv(b'EVAL' + cmd.encode('utf-8'), timeout=timeout)
if binary: return s
return s.decode('utf-8')
def _cap_story(device):
cmd = "RV.write('\0'.join(sim_display.story or []))"
rv = _sim_exec(device, cmd)

View File

@ -1,5 +0,0 @@
*.psbt
*.txn
*.txt
*.json
*.png

View File

@ -34,7 +34,7 @@ for fn in ['had_witness', 'num_inputs', 'num_outputs',
if 'destinations' in expect:
for (val, addr), (idx, txo) in zip(expect['destinations'], p.output_iter()):
assert val == txo.nValue
txt = active_request.render_output(txo)
txt, _ = active_request.render_output(txo)
# normalize from display format
address = txt.split("\n")[-2]
assert address[0] == "\x02"

View File

@ -11,7 +11,7 @@
import pytest, time, pdb
from core_fixtures import _pick_menu_item, _cap_menu, _cap_story, _cap_screen
from core_fixtures import _need_keypress, _enter_complex, _press_select
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
from ckcc_protocol.client import ColdcardDevice
from run_sim_tests import ColdcardSimulator, clean_sim_data
@ -138,7 +138,7 @@ def test_set_nickname(nick, request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -150,7 +150,7 @@ def test_set_nickname(nick, request):
sim = ColdcardSimulator(args= ["--q1" if is_Q else "", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
scr = _cap_screen(device)
target = "".join(scr.strip().split("\n"))
@ -167,7 +167,7 @@ def test_randomize_pin_keys(request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -177,7 +177,7 @@ def test_randomize_pin_keys(request):
sim.stop() # power off
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_login(device, is_Q, "22-22", scrambled=True)
time.sleep(3)
m = _cap_menu(device)
@ -190,7 +190,7 @@ def test_login_countdown(lcdwn, request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -200,7 +200,7 @@ def test_login_countdown(lcdwn, request):
sim.stop() # power off
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
secs = int(lcdwn.strip().split()[0])
_login(device, is_Q, "22-22")
time.sleep(.15)
@ -223,7 +223,7 @@ def test_kill_key(kbtn, when, request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -233,7 +233,7 @@ def test_kill_key(kbtn, when, request):
sim.stop() # power off
sim = ColdcardSimulator(args= ["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
if is_Q:
possible_kbtn = [chr(65 + i) for i in range(26)] + [i for i in '\',./']
@ -278,7 +278,7 @@ def test_terms_ok(request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--early-usb", "-w", "--q1" if is_Q else ""])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
time.sleep(.1)
_, story = _cap_story(device)
@ -317,7 +317,7 @@ def test_terms_ok(request):
sim.stop() # power off
sim = ColdcardSimulator(args=["-l", "--q1" if is_Q else "", "--early-usb", "--pin", "22-22"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_login(device, is_Q, "22-22")
time.sleep(3)
m = _cap_menu(device)
@ -331,7 +331,7 @@ def test_wrong_pin_input(request, brick):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--early-usb", "--q1" if is_Q else "", "--pin", "22-22"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
time.sleep(.1)
num_attmeptss = 13
for ii, i in enumerate(range(31, 43), start=1):
@ -394,7 +394,7 @@ def test_login_integration(request, nick, randomize, login_ctdwn, kill_btn, kill
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -416,7 +416,7 @@ def test_login_integration(request, nick, randomize, login_ctdwn, kill_btn, kill
sim.stop() # power off
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
if nick:
scr = _cap_screen(device)
@ -484,7 +484,7 @@ def test_calc_login(request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, is_Q, "Settings")
_pick_menu_item(device, is_Q, "Login Settings")
@ -494,7 +494,7 @@ def test_calc_login(request):
sim.stop() # power off
sim = ColdcardSimulator(args=["--q1", "--pin", "22-22", "--early-usb"])
sim.start(start_wait=6)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
scr = _cap_screen(device)
assert 'ECC Calculator' in scr

View File

@ -30,16 +30,82 @@ python run_sim_tests.py --collect veryslow # just print all
python run_sim_tests.py --collect manual # just print all manual tests to stdout
Make sure to run manual test if you want to state that your changes passed all the tests.
Testing on multiple simulators in parallel
python run_sim_tests.py --q1 --multiproc # to run all Q tests in parallel (default num-proc=14 simulators)
python run_sim_tests.py --multiproc --num-proc 6 # to run all Mk4 tests in parallel max 6 simulators at once
python run_sim_tests.py -m test_addr.py -m test_bbqr.py --multiproc # just desired test
python run_sim_tests.py --q1 -m test_sign.py --multiproc # just desired test
python run_sim_tests --multiproc --turbo # turbo causes both Mk4 & Q tests to run simultaneously (turbo doubles num-procs)
python run_sim_tests --multiproc --turbo # all Mk4 & Q tests run in 60 minutes total!!
python run_sim_tests --multiproc --turbo -m test_addr.py -m test_ux.py # will spawn 4 simulators: one Q and one Mk4 for address tests & one Q and one Mk4 for ux tests
Console output has some useful info:
* when job is started it will print its PID
* when job is done you'll get elapsed time from start (test duration)
* when all is done - complete test session duration
```
$ python run_sim_tests.py -m test_addr.py -m test_drv_entro.py -m test_usb.py --multiproc --turbo
started: Mk4 test_addr.py 38824
started: Q test_addr.py 38935
started: Mk4 test_drv_entro.py 39042
started: Q test_drv_entro.py 39150
started: Mk4 test_usb.py 39257
started: Q test_usb.py 39364
done: Mk4 test_usb.py 0:00:06.043072
done: Q test_usb.py 0:00:06.081147
done: Mk4 test_addr.py 0:00:51.141250
done: Q test_addr.py 0:01:03.185571
done: Mk4 test_drv_entro.py 0:03:24.234521
done: Q test_drv_entro.py 0:03:30.278795
elapsed: 0:03:50.308146
```
After jobs are finished, or even during execution you can inspect `/tmp/cc-simulators` directory:
* contains simulator work directories named as <PID> of specific simulator
* log directories where pytest output is piped
* mk4_logs
* q1_logs
```
$ pwd
/tmp/cc-simulators
$ ls
38824 38935 39042 39150 39257 39364 mk4_logs q1_logs
$ ls 39042/*
39042/debug:
last-qr.png
39042/MicroSD:
drv-hex-idx0-2.txt drv-pw-idx0.txt drv-words-idx0-2.txt drv-words-idx0.txt
drv-hex-idx0.txt drv-wif-idx0.txt drv-words-idx0-3.txt drv-xprv-idx0.txt
39042/settings:
39042/VirtDisk:
README.md
$ ls mk4_logs/
test_addr.py.log test_drv_entro.py.log test_usb.py.log
```
To parse only failures use below cmd in {mk4,q1}_logs directory:
```
for f in $(ls); do x=`grep -n "short test summary info" $f | grep -Eo '^[^:]+'`; if [ -n "$x" ];then tail -n +"$x" $f | grep -E '^FAILED|^ERROR';fi ;done
```
"""
import os, time, glob, json, pytest, atexit, signal, argparse, subprocess, contextlib
import os, time, glob, json, pytest, atexit, signal, argparse, subprocess, contextlib, shutil
from datetime import timedelta
from typing import List
from pytest import ExitCode
SIM_INIT_WAIT = 2 # 2 seconds, can be tweaked via cmdline arguments ( -w 6 )
DEFAULT_PYTEST_MARKS = "not onetime and not veryslow and not manual"
@contextlib.contextmanager
def pushd(new_dir):
@ -50,13 +116,17 @@ def pushd(new_dir):
finally:
os.chdir(previous_dir)
def clean_directory(pth):
for root, dirs, files in os.walk(pth):
for f in files:
os.unlink(os.path.join(root, f))
for d in dirs:
shutil.rmtree(os.path.join(root, d))
def remove_client_sockets():
def remove_all_client_sockets():
with pushd("/tmp"):
for fn in glob.glob("ckcc-client*.sock"):
os.remove(fn)
print("Removed all client sockets")
def remove_cautious(fpath: str) -> None:
if os.path.basename(fpath) in ["README.md", ".gitignore"]:
@ -99,7 +169,7 @@ def is_ok(ec: ExitCode) -> bool:
def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: bool,
failed_first: bool, psbt2=False, is_Q=False, headless=False) -> ExitCode:
failed_first: bool, psbt2=False, is_Q=False, headless=False, sim_socket=None) -> ExitCode:
cmd_list = [
"--cache-clear", "-m", pytest_marks, "--sim",
test_module if test_module is not None else ""
@ -116,42 +186,50 @@ def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: b
cmd_list.insert(0, "--Q") # only changes behavior in login_settings_test
if headless:
cmd_list.append("--headless")
if sim_socket:
cmd_list.append("--sim-socket")
cmd_list.append(sim_socket)
return pytest.main(cmd_list)
def _run_coldcard_tests(test_module: str, simulator_args: List[str], pytest_marks: str,
def _run_coldcard_tests(test_module: str, simulator_args: List[str],
pytest_k: str, pdb: bool, failed_first: bool, psbt2=False,
is_Q=False, headless=False) -> ExitCode:
is_Q=False, headless=False, pytest_marks: str = DEFAULT_PYTEST_MARKS,
sim_segregate=False) -> ExitCode:
sock_path = None
if simulator_args is not None:
sim = ColdcardSimulator(args=simulator_args, headless=headless)
sim = ColdcardSimulator(args=simulator_args, headless=headless, segregate=sim_segregate)
sim.start()
time.sleep(1)
sock_path = sim.socket
exit_code = _run_pytest_tests(test_module, pytest_marks, pytest_k, pdb,
failed_first, psbt2, is_Q, headless)
failed_first, psbt2, is_Q, headless, sock_path)
if simulator_args is not None:
sim.stop()
time.sleep(1)
clean_sim_data()
remove_all_client_sockets()
return exit_code
def run_coldcard_tests(test_module=None, simulator_args=None, pytest_k=None, pdb=False,
failed_first=False, psbt2=False, is_Q=False, headless=False,
pytest_marks="not onetime and not veryslow and not manual"):
pytest_marks=DEFAULT_PYTEST_MARKS):
failed = []
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_marks, pytest_k,
pdb, failed_first, psbt2, is_Q, headless)
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_k,
pdb, failed_first, psbt2, is_Q, headless, pytest_marks)
if not is_ok(exit_code):
# no success, no nothing - give failed another try, each alone with its own simulator
last_failed = get_last_failed()
print("Running failed from last run", last_failed)
exit_codes = []
for failed_test in last_failed:
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args, pytest_marks,
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args,
pytest_k, pdb, failed_first, psbt2, is_Q,
headless)
headless, pytest_marks)
exit_codes.append(exit_code_2)
if not is_ok(exit_code_2):
failed.append(failed_test)
@ -173,11 +251,12 @@ class PytestCollectMarked:
class ColdcardSimulator:
def __init__(self, path=None, args=None, headless=False):
def __init__(self,args=None, headless=False, segregate=False):
self.proc = None
self.args = args
self.path = "/tmp/ckcc-simulator.sock" if path is None else path
self.headless = headless
self.segregate = segregate
self.socket = "/tmp/ckcc-simulator.sock"
def start(self, start_wait=None):
# here we are in testing directory
@ -188,14 +267,20 @@ class ColdcardSimulator:
cmd_list.extend(self.args)
if self.headless:
cmd_list.append("--headless")
if self.segregate:
cmd_list.append("--segregate")
self.proc = subprocess.Popen(
cmd_list,
# this needs to be in firmware/unix - expected to be run from firmware/testing
cwd="../unix",
preexec_fn=os.setsid
preexec_fn=os.setsid,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
time.sleep(start_wait or SIM_INIT_WAIT)
if self.segregate:
self.socket = "/tmp/ckcc-simulator-%d.sock" % self.proc.pid
atexit.register(self.stop)
def stop(self):
@ -205,7 +290,6 @@ class ColdcardSimulator:
os.waitpid(os.getpgid(self.proc.pid), 0)
atexit.unregister(self.stop)
remove_client_sockets()
def main():
@ -233,6 +317,12 @@ def main():
help="only run tests which match the given substring expression")
parser.add_argument("--headless", action="store_true", default=False,
help="run simulator instance in headless mode")
parser.add_argument("--multiproc", action="store_true", default=False,
help="Run tests & simulators in parallel")
parser.add_argument("--num-proc", type=int, default=14,
help="How many executors/simulators to run in parallel in --multiproc mode")
parser.add_argument("--turbo", action="store_true", default=False,
help="Both Mk4 and Q at the same time")
args = parser.parse_args()
if args.sim_init_wait:
@ -258,47 +348,159 @@ def main():
if args.module is None:
test_modules = []
elif len(args.module) == 1 and args.module[0].lower() == "all":
test_modules = sorted(glob.glob("test_*.py"))
test_modules = glob.glob("test_*.py")
assert test_modules, "please run in ../testing subdir"
else:
for fn in args.module:
if not os.path.exists(fn):
raise RuntimeError(f"{fn} does not exist")
test_modules = sorted(args.module)
test_modules = args.module
result = []
for test_module in test_modules:
test_args = DEFAULT_SIMULATOR_ARGS
if test_module in ["test_rng.py", "test_pincodes.py", "test_rolls.py"]:
# test_pincodes.py can only be run against real device
# test_rng.py not needed when using simulator
# test_rolls.py should be run alone as it does not need simulator
print("Skipped", test_module)
continue
# test_pincodes.py can only be run against real device
# test_rng.py not needed when using simulator
# test_rolls.py should be run alone as it does not need simulator
# set diff
test_modules = set(test_modules) - {"test_rng.py", "test_pincodes.py", "test_rolls.py"}
print("Started", test_module)
module_args = []
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_args = DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
sim_args = DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
if test_module == "test_vdisk.py":
test_args = ["--eject"] + DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
sim_args = ["--eject"] + DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
if test_module == "test_bip39pw.py":
test_args = []
sim_args = []
if test_module in ["test_unit.py", "test_se2.py", "test_backup.py", "test_teleport.py"]:
# test_nvram_mk4 needs to run without --eff
# se2 duress wallet activated as ephemeral seed requires proper `settings.load`
test_args = ["--set", "nfc=1"]
sim_args = ["--set", "nfc=1"]
if test_module in ["test_ephemeral.py", "test_notes.py", "test_ccc.py"]:
# proper `settings.load` _ virtual disk
test_args = ["--set", "nfc=1", "--set", "vidsk=1"]
sim_args = ["--set", "nfc=1", "--set", "vidsk=1"]
if args.q1 and '--q1' not in test_args:
test_args.append('--q1')
if args.q1 and '--q1' not in sim_args:
sim_args.append('--q1')
ec, failed_tests = run_coldcard_tests(test_module, simulator_args=test_args,
pytest_k=args.pytest_k, pdb=args.pdb,
failed_first=args.ff, psbt2=args.psbt2,
headless=args.headless)
module_args.append((test_module, sim_args, args.pytest_k, args.pdb,
args.ff, args.psbt2, args.q1, args.headless))
if args.multiproc:
start_time = time.time()
def add_to_queue(module_name, simulator_args, queue):
if module_name == "test_multisig.py":
# split takes too much time
queue.append((0, [module_name, simulator_args, "not tutorial and not airgapped and not ms_address and not descriptor_export", ""]))
queue.append((0, [module_name, simulator_args, "airgapped", "-sep1"]))
queue.append((0, [module_name, simulator_args, "tutorial", "-sep2"]))
queue.append((0, [module_name, simulator_args, "ms_address", "-sep3"]))
queue.append((0, [module_name, simulator_args, "descriptor_export", "-sep4"]))
elif module_name == "test_seed_xor.py":
# split takes too much time
queue.append((0, [module_name, simulator_args, "test_import_xor", "-sep1"]))
queue.append((0, [module_name, simulator_args, "not test_import_xor", ""]))
elif module_name in ["test_export.py", "test_ephemeral.py", "test_sign.py", "test_msg.py",
"test_backup.py"]:
# higher priority
queue.append((1, [module_name, simulator_args, None, ""]))
else:
# standard priority
queue.append((2, [module_name, simulator_args, None, ""]))
# will clear everything there from previous runs
tmp_dir = "/tmp/cc-simulators"
clean_directory(tmp_dir) # clean it
mk4_log_dir = f"{tmp_dir}/mk4_logs"
q1_log_dir = f"{tmp_dir}/q1_logs"
os.makedirs(mk4_log_dir, exist_ok=True)
os.makedirs(q1_log_dir, exist_ok=True)
q = [] # build priority queue
for mod_name, sim_args, *_ in module_args:
if args.turbo:
if "--q1" in sim_args:
add_to_queue(mod_name, sim_args, q)
add_to_queue(mod_name, [i for i in sim_args if i == "--q1"], q)
else:
add_to_queue(mod_name, sim_args, q)
add_to_queue(mod_name, sim_args + ["--q1"], q)
else:
add_to_queue(mod_name, sim_args, q)
# sort queue by priority, highest priority elements at the end
q = [i[1] for i in sorted(q, reverse=True)]
num_proc = args.num_proc
if args.turbo:
# double num-proc
num_proc *= 2
procs = []
while True:
# create as many processes as allowed by --num-proc (default=14)
if q and (len(procs) < num_proc):
# start simulators first
q_chunks = []
for _ in range (num_proc - len(procs)):
try:
mn, sim_args, k, mod_add = q.pop() # remove element
except IndexError:
# priority queue is empty
break
sim = ColdcardSimulator(sim_args, segregate=True)
sim.start(start_wait=0)
ld = q1_log_dir if "--q1" in sim_args else mk4_log_dir
q_chunks.append((sim, mn, mod_add, k, ld))
time.sleep(5)
for sim, mn, mod_add, k, log_dir in q_chunks:
assert sim.socket
out_log_path = f"{log_dir}/%s.log" % (mn + mod_add)
out_fd = open(out_log_path, "w")
cmd_list = ["pytest", "--cache-clear", "-m", DEFAULT_PYTEST_MARKS, "--sim",
mn, "--sim-socket", sim.socket]
if k:
cmd_list.extend(["-k", k])
p = subprocess.Popen(cmd_list, preexec_fn=os.setsid, stdout=out_fd, stderr=out_fd)
mark = "Q" if "q1" in log_dir else "Mk4"
procs.append((mn+mod_add, p, out_fd, sim, mark, time.time()))
print(f'started: {mark:<6}{mn+mod_add:<30}{sim.socket.split("-")[-1].split(".")[0]:<10}')
if not procs and not q:
# done
break
i = 0
while i < len(procs):
mn, p, out_fd, sim, mark, st = procs[i]
if p.poll() is None:
# still running
i += 1
continue
else:
# done
p.communicate()
out_fd.close()
sim.stop()
del procs[i]
print(f"done: {mark:<6}{mn:<30}{str(timedelta(seconds=time.time()-st)):<15}")
time.sleep(3)
# multiprocess done
print(f"\n\nelapsed: {str(timedelta(seconds=time.time()-start_time))}")
return
result = []
for arguments in module_args:
test_module = arguments[0]
print("Started", test_module)
ec, failed_tests = run_coldcard_tests(*arguments)
result.append((test_module, ec, failed_tests))
print("Done", test_module)
print(80 * "=")
@ -363,5 +565,8 @@ def main():
if __name__ == "__main__":
main()
# sim = ColdcardSimulator(args=["--eff", "--segregate"])
# sim.start()
# import pdb;pdb.set_trace()
# x = 5
# EOF

View File

@ -3,9 +3,9 @@
import pytest, pdb, time, random, os
from charcodes import KEY_CANCEL
from core_fixtures import _pick_menu_item, _press_select
from core_fixtures import _need_keypress, _cap_screen, _sim_exec
from core_fixtures import _need_keypress, _sim_exec
from run_sim_tests import ColdcardSimulator, clean_sim_data
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
from ckcc_protocol.client import ColdcardDevice
def test_status_bar_rewrite_after_restore_master(request):
@ -13,7 +13,7 @@ def test_status_bar_rewrite_after_restore_master(request):
clean_sim_data() # remove all from previous
sim = ColdcardSimulator(args=["--q1", "-l"])
sim.start(start_wait=3)
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
device = ColdcardDevice(is_simulator=True)
_pick_menu_item(device, True, "Advanced/Tools")
_pick_menu_item(device, True, "Temporary Seed")

View File

@ -570,13 +570,14 @@ def test_seed_vault_backup_frozen(reset_seed_words, settings_set, repl, build_te
assert target in bk
def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home):
sd_dir = "../unix/work/MicroSD"
def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home, src_root_dir,
sim_root_dir):
sd_dir = f"{sim_root_dir}/MicroSD"
num_7z = len([i for i in os.listdir(sd_dir) if i.endswith(".7z")])
fname = "ccbk-start.json"
reset_seed_words()
goto_home()
shutil.copy(f"data/{fname}", sd_dir)
shutil.copy(f"{src_root_dir}/testing/data/{fname}", sd_dir)
pick_menu_item("Advanced/Tools")
pick_menu_item("Backup")
pick_menu_item("Clone Coldcard")
@ -593,18 +594,23 @@ def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home):
def test_bkpw_override(reset_seed_words, override_bkpw, goto_home, pick_menu_item,
cap_story, press_select, garbage_collector, microsd_path,
restore_backup_cs):
restore_backup_cs, is_q1):
reset_seed_words() # clean slate
old_pw = None
test_cases = [
"arm prob slot merc hub fiel wing aver tale undo diar boos army cabl mous teac drif risk frow achi poet ecol boss grit",
" ".join(12 * ["elevator"]),
" ".join(12 * ["fever"]),
32 * "a",
(16 * "0") + " " + (16 *"1"),
64 * "Q",
(26 * "?") + "!@#$%^&*()",
]
if is_q1:
# not needed on mk4 - even tho works (takes too much time)
# Mk4 display is not suitable for these type of passwords anyways
test_cases += [
"arm prob slot merc hub fiel wing aver tale undo diar boos army cabl mous teac drif risk frow achi poet ecol boss grit",
" ".join(12 * ["elevator"]),
" ".join(12 * ["fever"]),
64 * "Q",
]
fnames = []
for pw in test_cases:
override_bkpw(pw, old_pw)

View File

@ -219,14 +219,14 @@ def test_show_bbqr_sizes(size, cap_screen_qr, sim_exec, render_bbqr):
assert ft == 'U'
@pytest.mark.parametrize('src', [ 'rng', 'gpu', 'bigger'] )
def test_show_bbqr_contents(src, cap_screen_qr, sim_exec, render_bbqr, load_shared_mod):
def test_show_bbqr_contents(src, cap_screen_qr, sim_exec, render_bbqr, load_shared_mod, src_root_dir):
args = dict(msg=f'Test {src}', file_type='B')
if src == 'rng':
args['data'] = expect = prandom(500) # limited by simulated USB path
elif src in { 'gpu', 'bigger' }:
args['setup'] = 'from gpu_binary import BINARY'
cc_gpu_bin = load_shared_mod('cc_gpu_bin', '../shared/gpu_binary.py')
cc_gpu_bin = load_shared_mod('cc_gpu_bin', f'{src_root_dir}/shared/gpu_binary.py')
if src == 'gpu':
args['str_expr'] = 'BINARY'
expect = cc_gpu_bin.BINARY
@ -254,7 +254,7 @@ def test_bbqr_psbt(size, encoding, max_ver, partial, segwit, scan_a_qr, readback
cap_screen_qr, render_bbqr, goto_home, use_regtest, cap_story,
decode_psbt_with_bitcoind, decode_with_bitcoind, fake_txn, dev,
start_sign, end_sign, press_cancel, press_select, need_keypress,
base64str, try_sign_bbqr, signing_artifacts_reexport):
base64str, try_sign_bbqr, signing_artifacts_reexport, sim_root_dir):
num_in = size
num_out = size*10
@ -274,7 +274,8 @@ def test_bbqr_psbt(size, encoding, max_ver, partial, segwit, scan_a_qr, readback
if base64str:
psbt = base64.b64encode(psbt).decode()
open('debug/last.psbt', 'w' if base64str else 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w' if base64str else 'wb') as f:
f.write(psbt)
_, file_type, rb = try_sign_bbqr(psbt, type_code="U" if base64str else "P",
max_version=max_ver, encoding=encoding)

View File

@ -50,7 +50,7 @@ def test_b9p_basic(pw, set_bip39_pw):
set_bip39_pw(pw)
@pytest.fixture()
@pytest.fixture
def set_bip39_pw(dev, need_keypress, reset_seed_words, cap_story,
sim_execfile, press_select):

View File

@ -284,13 +284,13 @@ def setup_ccc(goto_home, pick_menu_item, cap_story, press_select, pass_word_quiz
press_select()
pick_menu_item(vel_mi)
pick_menu_item(vel) # actually a full menu item
if vel == "Unlimited":
target = 0
else:
target = int(vel.split()[0])
time.sleep(.2)
pick_menu_item(vel) # actually a full menu item
time.sleep(.3)
assert settings_get("ccc")["pol"]["vel"] == target
if whitelist:
@ -304,10 +304,14 @@ def setup_ccc(goto_home, pick_menu_item, cap_story, press_select, pass_word_quiz
pick_menu_item("Scan QR")
for i, addr in enumerate(whitelist, start=1):
scan_a_qr(addr)
time.sleep(.5)
scr = cap_screen()
assert f"Got {i} so far" in scr
assert "ENTER to apply" in scr
for _ in range(10):
scr = cap_screen()
if (f"Got {i} so far" in scr) and ("ENTER to apply" in scr):
break
time.sleep(.2)
else:
assert False, "updating whitelist failed"
press_select()
else:
@ -385,7 +389,7 @@ def enter_enabled_ccc(goto_home, pick_menu_item, cap_story, press_select, is_q1,
@pytest.fixture
def ccc_ms_setup(microsd_path, virtdisk_path, scan_a_qr, is_q1, cap_menu, pick_menu_item,
cap_story, press_select, need_keypress, enter_number, press_cancel,
garbage_collector):
garbage_collector, cap_screen):
def doit(N=3, b_words=12, way="sd", addr_fmt=AF_P2WSH, ftype="cc", bbqr=True):
@ -449,56 +453,72 @@ def ccc_ms_setup(microsd_path, virtdisk_path, scan_a_qr, is_q1, cap_menu, pick_m
time.sleep(.1)
title, story = cap_story()
if is_q1:
assert title == "QR or SD Card?"
if way in ("sd", "vdisk"):
if way in ("sd", "vdisk"):
if is_q1:
assert "ENTER to use SD card" in story
press_select()
if addr_fmt == AF_P2WSH:
press_select()
else:
need_keypress(KEY_QR)
time.sleep(.1)
title, story = cap_story()
assert title == "Address Format"
assert "Press ENTER for default address format (P2WSH" in story
assert "press (1) for P2SH-P2WSH" in story
if addr_fmt == AF_P2WSH:
press_select()
else:
need_keypress("1")
need_keypress("1")
else:
assert way == "qr"
if not is_q1:
raise pytest.skip("mk4 no qr")
for d, dd in res:
if ftype == "cc":
conts = json.dumps(dd)
tc = "J"
else:
deriv = dd[f"{label}_deriv"].replace("m/", "")
conts = f"[{dd['xfp']}/{deriv}]{dd[label]}"
tc = "U"
if bbqr:
_, parts = split_qrs(conts, tc, max_version=20)
for p in parts:
scan_a_qr(p)
time.sleep(.1)
else:
scan_a_qr(conts)
time.sleep(.1)
time.sleep(.5)
press_cancel() # after we're done scanning keys, exit QR animation to proceed
# casual on-device multisig create
if way != "qr":
assert title == "QR or SD Card?"
need_keypress(KEY_QR)
time.sleep(.1)
title, story = cap_story()
assert title == "Address Format"
assert "Press ENTER for default address format (P2WSH" in story
assert "press (1) for P2SH-P2WSH" in story
if addr_fmt == AF_P2WSH:
press_select()
else:
need_keypress("1")
for i, (d, dd) in enumerate(res, start=1):
if ftype == "cc":
conts = json.dumps(dd)
tc = "J"
else:
deriv = dd[f"{label}_deriv"].replace("m/", "")
conts = f"[{dd['xfp']}/{deriv}]{dd[label]}"
tc = "U"
if bbqr:
_, parts = split_qrs(conts, tc, max_version=20)
for p in parts:
scan_a_qr(p)
time.sleep(.25)
else:
scan_a_qr(conts)
for _ in range(10):
time.sleep(.2)
scr = cap_screen()
if ("Number of keys scanned: %d" % i) in scr:
break
else:
assert False, f"failed to scan ms xpubs ({i})"
press_cancel() # after we're done scanning keys, exit QR animation to proceed
time.sleep(.1)
# CCC C key account number
enter_number("0")
time.sleep(.1)
title, story = cap_story()
assert "Create new multisig wallet" in story
for _ in range(5):
time.sleep(.1)
title, story = cap_story()
if "Create new multisig wallet" in story:
break
else:
press_cancel()
assert False, "failed to create ms wallet"
assert f"Policy: 2 of {N}" in story
if is_q1:
assert "Coldcard Co-sign" in story

View File

@ -122,7 +122,7 @@ def enable_hsm_commands(dev, sim_exec, only_mk4):
sim_exec(cmd)
@pytest.fixture(scope='function')
@pytest.fixture
def hsm_reset(dev, sim_exec):
# filename for the policy file, as stored on simulated CC
@ -476,10 +476,11 @@ def wait_til_signed(dev):
return result
@pytest.fixture
def attempt_psbt(hsm_status, start_sign, dev):
def attempt_psbt(hsm_status, start_sign, dev, sim_root_dir):
def doit(psbt, refuse=None, remote_error=None):
open('debug/attempt.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/attempt.psbt', 'wb') as f:
f.write(psbt)
start_sign(psbt)
try:
@ -780,7 +781,7 @@ def test_multiple_signings(dev, quick_start_hsm, is_simulator,
def test_multiple_signings_multisig(cc_first, M_N, dev, quick_start_hsm,
is_simulator, attempt_psbt, fake_txn,
load_hsm_users, auth_user, bitcoind,
request):
request, sim_root_dir):
# signs 400 different PSBTs in loop beaing one leg of multisig
# CC must be on regtest if testing with real thing
af = "bech32"
@ -850,7 +851,9 @@ def test_multiple_signings_multisig(cc_first, M_N, dev, quick_start_hsm,
# uploading only external to CC
file_len, sha = dev.upload_file(desc_ext.encode('ascii'))
open('debug/last-config.txt', 'wt').write(desc_ext)
with open(f'{sim_root_dir}/debug/last-config.txt', 'wt') as f:
f.write(desc_ext)
dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha), timeout=30000)
time.sleep(.2)

View File

@ -45,7 +45,7 @@ def str2ipath(s):
yield here
@pytest.fixture(scope='function')
@pytest.fixture
def has_ms_checks(request, sim_exec):
# Add this fixture to any test that should FAIL if ms checks are disabled
# - in other words, tests that test the checks which are disabled.
@ -64,7 +64,7 @@ def has_ms_checks(request, sim_exec):
return danger_mode
@pytest.fixture()
@pytest.fixture
def bitcoind_p2sh(bitcoind):
# Use bitcoind to generate a p2sh addres based on public keys.
@ -142,12 +142,13 @@ def make_multisig(dev, sim_execfile):
return doit
@pytest.fixture
def offer_ms_import(cap_story, dev):
def offer_ms_import(cap_story, dev, sim_root_dir):
def doit(config, allow_non_ascii=False):
# upload the file, trigger import
file_len, sha = dev.upload_file(config.encode('utf-8' if allow_non_ascii else 'ascii'))
open('debug/last-config.txt', 'wt').write(config)
with open(f'{sim_root_dir}/debug/last-config.txt', 'wt') as f:
f.write(config)
dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha))
@ -249,12 +250,12 @@ def import_multisig(request, is_q1, need_keypress, offer_ms_import):
@pytest.fixture
def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
is_q1, request, need_keypress, import_multisig,
settings_set):
settings_set, sim_root_dir):
def doit(M, N, addr_fmt=None, name=None, unique=0, accept=False, common=None,
keys=None, do_import=True, derivs=None, descriptor=False,
int_ext_desc=False, dev_key=False, way=None, bip67=True,
force_unsort_ms=True, netcode="XTN"):
force_unsort_ms=True, netcode="XTN", return_desc=False):
# param: bip67 if false, only usable together with descriptor=True
if not bip67:
assert descriptor, "needs descriptor=True"
@ -308,7 +309,8 @@ def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
xfp2str(xfp), dd.hwif(as_private=False))
#print(config)
open('debug/last-ms.txt', 'wt').write(config)
with open(f'{sim_root_dir}/debug/last-ms.txt', 'wt') as f:
f.write(config)
title, story = import_multisig(data=config, way=way)
@ -331,6 +333,9 @@ def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
xor ^= xfp
assert dev.send_recv(CCProtocolPacker.multisig_check(M, N, xor)) == 1
if return_desc and descriptor:
return config
return keys
return doit
@ -560,7 +565,7 @@ def test_import_ranges(m_of_n, use_regtest, addr_fmt, clear_ms, import_ms_wallet
@pytest.mark.ms_danger
def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
test_ms_show_addr, has_ms_checks,
fake_ms_txn, try_sign):
fake_ms_txn, try_sign, sim_root_dir):
# detect when pubkeys are not in order in the redeem script
clear_ms()
M, N = 1, 15
@ -578,7 +583,7 @@ def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
change_outputs=[1],
violate_script_key_order=True)
with open('debug/last.psbt', 'wb') as f:
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
with pytest.raises(Exception) as e:
@ -588,7 +593,8 @@ def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
@pytest.mark.parametrize("has_change", [True, False])
def test_violate_import_order_multi(has_change, clear_ms, import_ms_wallet,
fake_ms_txn, try_sign, test_ms_show_addr):
fake_ms_txn, try_sign, test_ms_show_addr,
sim_root_dir):
clear_ms()
M, N = 3, 5
keys = import_ms_wallet(M, N, accept=True, descriptor=True, bip67=False)
@ -601,7 +607,7 @@ def test_violate_import_order_multi(has_change, clear_ms, import_ms_wallet,
change_outputs=[1] if has_change else [],
bip67=False, violate_script_key_order=True)
with open('debug/last.psbt', 'wb') as f:
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
with pytest.raises(Exception) as e:
@ -1167,8 +1173,6 @@ def test_import_dup_xfp_fails(m_of_n, use_regtest, addr_fmt, clear_ms,
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
def test_ms_cli(dev, addr_fmt, clear_ms, import_ms_wallet, addr_vs_path, desc):
# exercise the p2sh command of ckcc:cli ... hard to do manually.
from subprocess import check_output
M, N = 2, 3
clear_ms()
bip67, descriptor = (False, True) if desc == "multi" else (True, False)
@ -1180,41 +1184,17 @@ def test_ms_cli(dev, addr_fmt, clear_ms, import_ms_wallet, addr_vs_path, desc):
scr, pubkeys, xfp_paths = make_redeem(M, keys, pmapper, bip67=bip67)
def decode_path(p):
return '/'.join(str(i) if i < 0x80000000 else "%d'"%(i& 0x7fffffff) for i in p)
addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
scr[0] - 80, xfp_paths, scr, addr_fmt=addr_fmt), timeout=None
)
if 1:
args = ['ckcc']
if dev.is_simulator:
args += ['-x']
addr_vs_path(addr, addr_fmt=addr_fmt, script=scr)
args += ['p2sh', '-q']
if addr_fmt == AF_P2WSH:
args += ['-s']
elif addr_fmt == AF_P2WSH_P2SH:
args += ['-s', '-w']
args += [B2A(scr)]
args += [xfp2str(x)+'/'+decode_path(path) for x,*path in xfp_paths]
import shlex
print('CMD: ' + (' '.join(shlex.quote(i) for i in args)))
addr = check_output(args, encoding='ascii').strip()
print(addr)
addr_vs_path(addr, addr_fmt=addr_fmt, script=scr)
# test case for make_ms_address really.
expect_addr, _, scr2, _ = make_ms_address(M, keys, path_mapper=pmapper,
addr_fmt=addr_fmt, bip67=bip67)
assert expect_addr == addr
assert scr2 == scr
# need to re-start our connection once ckcc has talked to simulator
dev.start_encryption()
dev.check_mitm()
# test case for make_ms_address really.
expect_addr, _, scr2, _ = make_ms_address(M, keys, path_mapper=pmapper,
addr_fmt=addr_fmt, bip67=bip67)
assert expect_addr == addr
assert scr2 == scr
clear_ms()
@ -1446,7 +1426,7 @@ def fake_ms_txn(pytestconfig):
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet,
addr_vs_path, fake_ms_txn, try_sign, try_sign_microsd, transport,
has_change, settings_set, desc):
has_change, settings_set, desc, sim_root_dir):
M, N = M_N
num_outs = num_ins-1
descriptor, bip67 = (True, False) if desc == "multi" else (False, True)
@ -1470,7 +1450,8 @@ def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, impor
outstyles=ADDR_STYLES_MS, change_outputs=[1] if has_change else [],
bip67=bip67)
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
if transport == 'sd':
try_sign_microsd(psbt, encoding=('binary', 'hex', 'base64')[random.randint(0,2)])
@ -1484,7 +1465,7 @@ def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, impor
@pytest.mark.parametrize('segwit', [True, False])
@pytest.mark.parametrize('incl_xpubs', [ True, False ])
def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
fake_ms_txn, try_sign, incl_xpubs, bitcoind):
fake_ms_txn, try_sign, incl_xpubs, bitcoind, sim_root_dir):
# IMPORTANT: wont work if you start simulator with --ms flag. Use no args
@ -1502,11 +1483,13 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs,
outstyles=all_out_styles, change_outputs=list(range(1,num_outs)))
open(f'debug/myself-before.psbt', 'w').write(b64encode(psbt).decode())
with open(f'{sim_root_dir}/debug/myself-before.psbt', 'w') as f:
f.write(b64encode(psbt).decode())
for idx in range(M):
select_wallet(idx)
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs)
open(f'debug/myself-after.psbt', 'w').write(b64encode(updated).decode())
with open(f'{sim_root_dir}/debug/myself-after.psbt', 'w') as f:
f.write(b64encode(updated).decode())
assert updated != psbt
aft = BasicPSBT().parse(updated)
@ -1522,9 +1505,12 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
assert all(inp['next'] in {'finalizer','updater'} for inp in anal['inputs']), "other issue: %r" % anal
except:
# XXX seems to be a bug in analyzepsbt function ... not fully studied
pprint(anal, stream=open('debug/analyzed.txt', 'wt'))
with open(f'{sim_root_dir}/debug/analyzed.txt', 'wt') as f:
pprint(anal, stream=f)
decode = bitcoind.rpc.decodepsbt(b64encode(psbt).decode('ascii'))
pprint(decode, stream=open('debug/decoded.txt', 'wt'))
with open(f'{sim_root_dir}/debug/decoded.txt', 'wt') as f:
pprint(decode, stream=f)
if M==N or segwit:
# as observed, bug not trigged, so raise if it *does* happen
@ -1749,7 +1735,7 @@ def test_make_airgapped(addr_fmt, acct_num, M_N, goto_home, cap_story, pick_menu
@pytest.mark.parametrize('addr_style', ["legacy", "p2sh-segwit", "bech32"])
@pytest.mark.parametrize('cc_sign_first', [True, False])
def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clear_ms, try_sign,
press_cancel, addr_style, use_regtest, is_q1):
press_cancel, addr_style, use_regtest, is_q1, sim_root_dir):
# Make a P2SH wallet with local bitcoind as a co-signer (and simulator)
# - send an receive various
# - following text of <https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md>
@ -1851,7 +1837,8 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
# assert resp['changepos'] == -1
psbt = b64decode(resp['psbt'])
open('debug/funded.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/funded.psbt', 'wb') as f:
f.write(psbt)
# patch up the PSBT a little ... bitcoind doesn't know the path for the CC's key
ex = BasicPSBT().parse(psbt)
@ -1863,11 +1850,13 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
psbt = ex.as_bytes()
open('debug/patched.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/patched.psbt', 'wb') as f:
f.write(psbt)
_, updated = try_sign(psbt, finalize=False)
open('debug/cc-updated.psbt', 'wb').write(updated)
with open(f'{sim_root_dir}/debug/cc-updated.psbt', 'wb') as f:
f.write(updated)
if cc_sign_first:
# cc signed first - bitcoind is now second
@ -1879,7 +1868,9 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
# finalize and send
rr = bitcoind.supply_wallet.finalizepsbt(both_signed, True)
open('debug/bc-final-txn.txn', 'wt').write(rr['hex'])
with open(f'{sim_root_dir}/debug/bc-final-txn.txn', 'wt') as f:
f.write(rr['hex'])
assert rr['complete']
tx_hex = rr["hex"]
res = bitcoind.supply_wallet.testmempoolaccept([tx_hex])
@ -1894,8 +1885,9 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
@pytest.mark.parametrize('out_style', ['p2wsh'])
@pytest.mark.parametrize('bitrot', list(range(0,6)) + [98, 99, 100] + list(range(-5, 0)))
@pytest.mark.ms_danger
def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet, addr_vs_path,
fake_ms_txn, start_sign, end_sign, out_style, cap_story, bitrot, has_ms_checks):
def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet,
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story,
bitrot, has_ms_checks, sim_root_dir):
M = 1
N = 3
num_outs = 2
@ -1926,7 +1918,8 @@ def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_
assert len(track) == 1
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
start_sign(psbt)
with pytest.raises(Exception) as ee:
@ -1947,7 +1940,8 @@ def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_
@pytest.mark.parametrize('pk_num', range(4))
@pytest.mark.parametrize('case', ['pubkey', 'path'])
def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, make_multisig,
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story):
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story,
sim_root_dir):
M = 1
N = 3
@ -1982,7 +1976,8 @@ def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xp
hack_change_out=lambda idx: dict(tweak_pubkeys=
lambda data: tweak(case, pk_num, data)))
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
with pytest.raises(Exception) as ee:
start_sign(psbt)
@ -1999,7 +1994,7 @@ def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xp
@pytest.mark.parametrize('repeat', range(2) )
def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign):
def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign, sim_root_dir):
# from SomberNight <https://github.com/spesmilo/electrum/issues/6743#issuecomment-729965813>
psbt_b4 = bytes.fromhex('70736274ff0100520200000001bde05be36069e2e0fe44793c68ad8244bb1a52cc37f152e0fa5b75e40169d7f70000000000fdffffff018b1e000000000000160014ed5180f05c7b1dc980732602c50cda40530e00ad4de11c004f01024289ef0000000000000000007dd565da7ee1cf05c516e89a608968fed4a2450633a00c7b922df66b27afd2e1033a0a4fa4b0a997738ac2f142a395c1f02afcb31d7ffd46a90a0c927a4c411fd704094ef7844f01024289ef0431fcbdcc8000000112d4aaea7292e7870c7eeb3565fa1c1fa8f957fa7c4c24b411d5b4f5710d359a023e63d1e54063525bea286ccb2a0ad7b14560aa31ec4be826afa883141dfe1d53145c9e228d300000800100008000000080010000804f01024289ef04e44b38f1800000014a1960f3a3c86ba355a16a66a548cfb62eeb25663311f7cd662a192896f3777e038cc595159a395e4ec35e477c9523a1512f873e74d303fb03fc9a1503b1ba45271434652fae30000080010000800000008001000080000100df02000000000101d1a321707660769c7f8604d04c9ae2db58cf1ec7a01f4f285cdcbb25ce14bdfd0300000000fdffffff02401f00000000000017a914bfd0b8471a3706c1e17870a4d39b0354bcea57b687c864000000000000160014e608b171d63ec24d9fa252d5c1e45624b14e44700247304402205133eb96df167b895f657cce31c6882840a403013682d9d4651aed2730a7dad502202aaacc045d85d9c711af0c84e7f355cc18bf2f8e6d91774d42ba24de8418a39e012103a58d8eb325abb412eaf927cf11d8b7641c4a468ce412057e47892ca2d13ed6144de11c000104220020a3c65c4e376d82fb3ca45596feee5b08313ad64f38590c1b08bc530d1c0bbfea010569522102ab84641359fa22461b8461515231da63c196614cd22b26e556ed878e30db4da621034211ab0f75c3a307a2f6bf6f09a9e05d3c8edd0ba7a2ac31f432d1045ef6381921039690cf74941da5db291fa8be7348abe3807786732d969eac5d27e0afa909a55f53ae220602ab84641359fa22461b8461515231da63c196614cd22b26e556ed878e30db4da60c094ef78400000000030000002206034211ab0f75c3a307a2f6bf6f09a9e05d3c8edd0ba7a2ac31f432d1045ef638191c5c9e228d3000008001000080000000800100008000000000030000002206039690cf74941da5db291fa8be7348abe3807786732d969eac5d27e0afa909a55f1c34652fae3000008001000080000000800100008000000000030000000000')
# pre 3.2.0 result
@ -2033,7 +2028,8 @@ def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign):
assert out_psbt != psbt_wrong
assert BasicPSBT().parse(out_psbt) == BasicPSBT().parse(psbt_right)
open('debug/i6.psbt', 'wt').write(out_psbt.hex())
with open(f'{sim_root_dir}/debug/i6.psbt', 'wt') as f:
f.write(out_psbt.hex())
@pytest.mark.parametrize('N', [ 3, 15])
@pytest.mark.parametrize('xderiv', [ None, 'any', 'unknown', '*', '', 'none'])
@ -2128,7 +2124,8 @@ def test_ms_import_many_derivs(M, N, way, make_multisig, clear_ms, offer_ms_impo
@pytest.mark.ms_danger
@pytest.mark.parametrize('descriptor', [True, False])
def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_story, fake_ms_txn, start_sign, sim_exec):
def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_story, fake_ms_txn,
start_sign, sim_exec, sim_root_dir):
# note: cant use has_ms_checks fixture here
danger_mode = (request.config.getoption('--ms-danger'))
sim_exec(f'from multisig import MultisigWallet; MultisigWallet.disable_checks={danger_mode}')
@ -2138,7 +2135,8 @@ def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_sto
keys = import_ms_wallet(M, N, accept=1, descriptor=descriptor, addr_fmt="p2wsh")
psbt = fake_ms_txn(1, 1, M, keys, incl_xpubs=True)
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
start_sign(psbt)
title, story = cap_story()
@ -2306,13 +2304,17 @@ def test_dup_ms_wallet_bug(goto_home, pick_menu_item, press_select, import_ms_wa
@pytest.mark.parametrize('int_ext_desc', [True, False])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, goto_home, pick_menu_item,
press_select, clear_ms, cap_story, microsd_path, virtdisk_path,
nfc_read_text, load_export, is_q1, desc):
def test_import_descriptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, goto_home, pick_menu_item,
press_select, clear_ms, cap_story, microsd_path, virtdisk_path,
nfc_read_text, load_export, is_q1, desc):
clear_ms()
M, N = M_N
import_ms_wallet(M, N, addr_fmt=addr_fmt, accept=1, descriptor=True,
int_ext_desc=int_ext_desc, bip67=False if desc == "multi" else True)
desc_import = import_ms_wallet(
M, N, addr_fmt=addr_fmt, accept=True, descriptor=True,
int_ext_desc=int_ext_desc, bip67=False if desc == "multi" else True,
return_desc=True
)
desc_import = desc_import.strip()
goto_home()
pick_menu_item('Settings')
@ -2322,8 +2324,7 @@ def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, go
pick_menu_item('Export')
contents = load_export(way, label="Descriptor multisig setup", is_json=False)
desc_export = contents.strip()
with open("debug/last-ms.txt", "r") as f:
desc_import = f.read().strip()
normalized = parse_desc_str(desc_export)
# as new format is not widely supported we only allow to import it - no export yet
if int_ext_desc:
@ -2342,7 +2343,7 @@ def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, go
@pytest.mark.parametrize("start_idx", [2147483540, MAX_BIP32_IDX, 0])
@pytest.mark.parametrize('M_N', [(2, 2), (3, 5), (15, 15)])
@pytest.mark.parametrize('addr_fmt', [AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "nfc"]) # vdisk
def test_bitcoind_ms_address(change, M_N, addr_fmt, clear_ms, goto_home, need_keypress,
pick_menu_item, cap_menu, cap_story, make_multisig, import_ms_wallet,
microsd_path, bitcoind_d_wallet_w_sk, use_regtest, load_export, way,
@ -3042,14 +3043,13 @@ def test_ms_wallet_ordering(clear_ms, import_ms_wallet, try_sign_microsd, fake_m
psbt = fake_ms_txn(5, 5, 3, keys3, outstyles=all_out_styles, segwit_in=True, incl_xpubs=True)
open('debug/last.psbt', 'wb').write(psbt)
try_sign_microsd(psbt, encoding='base64')
@pytest.mark.parametrize("descriptor", [True, False])
@pytest.mark.parametrize("m_n", [(2, 3), (3, 5), (5, 10)])
def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wallet, try_sign_microsd, fake_ms_txn):
def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wallet,
try_sign_microsd, fake_ms_txn):
clear_ms()
M, N = m_n
all_out_styles = list(unmap_addr_fmt.keys())
@ -3063,13 +3063,11 @@ def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wa
addr_fmt="p2wsh", descriptor=descriptor)
psbt = fake_ms_txn(5, 5, M, opt, outstyles=all_out_styles,
segwit_in=True, incl_xpubs=True)
open('debug/last.psbt', 'wb').write(psbt)
try_sign_microsd(psbt, encoding='base64')
for opt_1 in all_options:
# create PSBT with original keys order
psbt = fake_ms_txn(5, 5, M, opt_1, outstyles=all_out_styles,
segwit_in=True, incl_xpubs=True)
open('debug/last.psbt', 'wb').write(psbt)
try_sign_microsd(psbt, encoding='base64')

View File

@ -15,7 +15,7 @@ from charcodes import KEY_NFC, KEY_QR
@pytest.mark.parametrize('case', range(6))
def test_ndef(case, load_shared_mod):
def test_ndef(case, load_shared_mod, src_root_dir):
# NDEF unit tests -- runs in cpython
def get_body(efile):
@ -36,7 +36,7 @@ def test_ndef(case, load_shared_mod):
def decode(msg):
return list(ndef.message_decoder(get_body(msg)))
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
n = cc_ndef.ndefMaker()
if case == 0:
@ -101,13 +101,13 @@ def test_ndef(case, load_shared_mod):
'short',
'long',
])
def test_ndef_ccfile(ccfile, load_shared_mod):
def test_ndef_ccfile(ccfile, load_shared_mod, src_root_dir):
# NDEF unit tests
def decode(body):
return list(ndef.message_decoder(body))
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
txt_msg = None
if ccfile == 'rx':
@ -149,7 +149,8 @@ def test_ndef_ccfile(ccfile, load_shared_mod):
@pytest.fixture
def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
sim_exec, nfc_read, nfc_write, nfc_block4rf, press_select,
press_cancel, press_nfc, nfc_read_txn, ndef_parse_txn_psbt):
press_cancel, press_nfc, nfc_read_txn, ndef_parse_txn_psbt,
sim_root_dir):
# like "try_sign" but use NFC to send/receive PSBT/results
@ -182,7 +183,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
ndef.TextRecord('some text'),
]
with open('debug/nfc-sent.psbt', 'wb') as f:
with open(f'{sim_root_dir}/debug/nfc-sent.psbt', 'wb') as f:
f.write(ip)
# wrap in a CCFile
@ -274,7 +275,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
sim_exec('from pyb import SDCard; SDCard.ejected = False')
@pytest.fixture
def ndef_parse_txn_psbt(press_cancel):
def ndef_parse_txn_psbt(press_cancel, sim_root_dir):
def doit(contents, txid=None, orig=None, expect_finalized=True):
# from NFC data read, what did we get?
got_txid = None
@ -310,12 +311,14 @@ def ndef_parse_txn_psbt(press_cancel):
assert got_txid == txid
assert expect_finalized
result = got_txn
open("debug/nfc-result.txn", 'wb').write(result)
with open(f"{sim_root_dir}/debug/nfc-result.txn", 'wb') as f:
f.write(result)
else:
assert not expect_finalized
result = got_psbt
open("debug/nfc-result.psbt", 'wb').write(result)
with open(f"{sim_root_dir}/debug/nfc-result.psbt", 'wb') as f:
f.write(result)
# read back final product
if got_txn:
@ -434,9 +437,9 @@ def test_rf_uid(rf_interface, cap_story, goto_home, pick_menu_item):
print(uid)
def test_ndef_roundtrip(load_shared_mod):
def test_ndef_roundtrip(load_shared_mod, src_root_dir):
# specific failing case
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
r = open('data/ms-import.ndef', 'rb').read()
@ -605,10 +608,11 @@ def test_share_by_pushtx(goto_home, cap_story, pick_menu_item, settings_set,
])
def test_nfc_share_files(fname, mode, ftype, nfc_read_json, nfc_read_text,
need_keypress, goto_home, pick_menu_item, is_q1,
cap_menu, nfc_read, nfc_block4rf, press_select):
cap_menu, nfc_read, nfc_block4rf, press_select,
src_root_dir, sim_root_dir):
goto_home()
fpath = "data/" + fname
shutil.copy2(fpath, '../unix/work/MicroSD')
fpath = f"{src_root_dir}/testing/data/" + fname
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
pick_menu_item("Advanced/Tools")
pick_menu_item("File Management")
pick_menu_item("NFC File Share")
@ -659,6 +663,6 @@ def test_nfc_share_files(fname, mode, ftype, nfc_read_json, nfc_read_text,
res = json.loads(res)
assert res == contents
os.remove('../unix/work/MicroSD/' + fname)
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
# EOF

View File

@ -159,7 +159,8 @@ def test_ux(valid, testnet, method,
sim_exec, wipe_cache, make_myself_wallet, use_testnet, goto_home, pick_menu_item,
press_cancel, press_select, settings_set, is_q1, nfc_write, need_keypress,
cap_screen, cap_story, load_shared_mod, scan_a_qr, skip_if_useless_way,
sign_msg_from_address, multisig, import_ms_wallet, clear_ms, verify_qr_address
sign_msg_from_address, multisig, import_ms_wallet, clear_ms, verify_qr_address,
src_root_dir, sim_root_dir
):
skip_if_useless_way(method)
addr_fmt = AF_CLASSIC
@ -201,7 +202,7 @@ def test_ux(valid, testnet, method,
elif method == 'nfc':
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
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()
@ -211,7 +212,8 @@ def test_ux(valid, testnet, method,
pick_menu_item('Advanced/Tools')
pick_menu_item('NFC Tools')
pick_menu_item('Verify Address')
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
f.write(ccfile)
nfc_write(ccfile)
#press_select()
@ -255,7 +257,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
pick_menu_item, need_keypress, sim_exec, clear_ms,
import_ms_wallet, press_select, goto_home, nfc_write,
load_shared_mod, load_export_and_verify_signature,
cap_story, is_q1):
cap_story, is_q1, src_root_dir, sim_root_dir):
goto_home()
wipe_cache()
settings_set('accts', [])
@ -290,7 +292,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
if idx == 200:
addr = addr
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
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()
@ -300,7 +302,9 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
pick_menu_item('Advanced/Tools')
pick_menu_item('NFC Tools')
pick_menu_item('Verify Address')
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
f.write(ccfile)
nfc_write(ccfile)
time.sleep(1)
@ -317,7 +321,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nfc_write, cap_story,
need_keypress, load_shared_mod, use_mainnet):
need_keypress, load_shared_mod, use_mainnet, src_root_dir, sim_root_dir):
# testing bug in chains.possible_address_fmt
# allowed regtest addresses to be allowed on main chain
goto_home()
@ -335,7 +339,7 @@ def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nf
need_keypress('1')
else:
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
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()
@ -344,7 +348,8 @@ def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nf
pick_menu_item('Advanced/Tools')
pick_menu_item('NFC Tools')
pick_menu_item('Verify Address')
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
f.write(ccfile)
nfc_write(ccfile)
# press_select()

View File

@ -18,7 +18,7 @@ from ckcc_protocol.constants import *
@pytest.mark.parametrize('netcode', ["XTN", "BTC"])
def test_generate(mode, pdf, netcode, dev, cap_menu, pick_menu_item, goto_home, cap_story,
need_keypress, microsd_path, verify_detached_signature_file, settings_set,
press_select):
press_select, src_root_dir):
# test UX and operation of the 'bitcoin core' wallet export
mx = "Don't make PDF"
@ -47,7 +47,7 @@ def test_generate(mode, pdf, netcode, dev, cap_menu, pick_menu_item, goto_home,
if pdf:
assert mx in cap_menu()
shutil.copy('../docs/paperwallet.pdf', microsd_path('paperwallet.pdf'))
shutil.copy(f'{src_root_dir}/docs/paperwallet.pdf', microsd_path('paperwallet.pdf'))
pick_menu_item(mx)
time.sleep(0.2)

View File

@ -6,7 +6,12 @@ import pytest, time, os, shutil
from binascii import a2b_hex
from constants import simulator_fixed_tprv
SIM_FNAME = '../unix/work/MicroSD/.tmp.tmp'
@pytest.fixture
def simulator_db_file(sim_root_dir):
def doit():
return sim_root_dir + "/MicroSD/.tmp.tmp"
return doit
@pytest.fixture
def set_pw_phrase(pick_menu_item, word_menu_entry):
@ -31,8 +36,9 @@ def set_pw_phrase(pick_menu_item, word_menu_entry):
'ab'*25,
])
def test_first_time(pws, need_keypress, cap_story, pick_menu_item, enter_complex,
cap_menu, go_to_passphrase, reset_seed_words, press_select):
try: os.unlink(SIM_FNAME)
cap_menu, go_to_passphrase, reset_seed_words, press_select,
simulator_db_file):
try: os.unlink(simulator_db_file())
except: pass
pws = pws.split()
@ -85,7 +91,7 @@ def test_first_time(pws, need_keypress, cap_story, pick_menu_item, enter_complex
reset_seed_words()
def test_crypto_unittest(sim_eval, sim_exec, simulator):
def test_crypto_unittest(sim_exec, simulator, simulator_db_file):
# unit test for AES key generation from SDCard and master secret
card = sim_exec('import files; from h import b2a_hex; cs = files.CardSlot().__enter__(); RV.write(b2a_hex(cs.get_id_hash())); cs.__exit__()')
@ -121,7 +127,7 @@ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()'''
# check that key works for decrypt and that the file was actually encrypted
with open(SIM_FNAME, 'rb') as fd:
with open(simulator_db_file(), 'rb') as fd:
raw = fd.read()
import pyaes
@ -137,7 +143,7 @@ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()'''
assert j[0]['xfp']
def test_delete_one_by_one(go_to_passphrase, pick_menu_item, cap_menu,
cap_story, press_select):
cap_story, press_select, src_root_dir, sim_root_dir):
# delete it one by one
# when all deleted - we must be back in Passphrase
# menu without Restore Saved option visible
@ -145,7 +151,7 @@ def test_delete_one_by_one(go_to_passphrase, pick_menu_item, cap_menu,
time.sleep(.1)
m = cap_menu()
if 'Restore Saved' not in m:
shutil.copy2('data/pwsave.tmp', '../unix/work/MicroSD/.tmp.tmp')
shutil.copy2(f'{src_root_dir}/testing/data/pwsave.tmp', f'{sim_root_dir}/MicroSD/.tmp.tmp')
go_to_passphrase()
pick_menu_item('Restore Saved')
m = cap_menu()

View File

@ -65,7 +65,7 @@ def decode_slot(data):
assert len(data) == 128
return SlotInfo(*struct.unpack(TRICK_FMT, data))
@pytest.fixture(scope='function')
@pytest.fixture
def se2_gate(sim_exec):
# not-so-low-level method: include auth data for main PIN
def doit(method_num, obj=None, buf=None):
@ -252,7 +252,7 @@ def test_ux_trick_menus(goto_trick_menu, pick_menu_item, cap_menu,
# all clear now
@pytest.fixture(scope='function')
@pytest.fixture
def new_trick_pin(goto_trick_menu, pick_menu_item, cap_menu, press_select,
cap_story, enter_pin, se2_gate, is_simulator, is_q1):
# using menus and UX, setup a new trick PIN

View File

@ -3,7 +3,7 @@
# test Seed XOR features
#
import pytest, time, itertools
import pytest, time, itertools, random
from mnemonic import Mnemonic
from constants import simulator_fixed_words
from xor import prepare_test_pairs, xor
@ -54,7 +54,7 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
word_menu_entry, verify_ephemeral_secret_ui,
confirm_tmp_seed, seed_vault_enable, press_select,
scan_a_qr, is_q1, cap_screen_qr, cap_screen, OK):
def doit(parts, expect, incl_self=False, save_to_vault=False,
def doit(parts, expect, incl_self=False, save_to_vault=None,
is_master_tmp_fail=False, way=None):
if expect is None:
parts, expect = prepare_test_pairs(*parts)
@ -66,6 +66,9 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
elif incl_self is False:
set_seed_words(proper[num_words])
if save_to_vault is None:
save_to_vault = random.getrandbits(1)
seed_vault_enable(save_to_vault)
time.sleep(.2)
@ -171,7 +174,6 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
@pytest.mark.parametrize('way', ["qr", "seedqr", "classic"])
@pytest.mark.parametrize('incl_self', [False, True])
@pytest.mark.parametrize('seed_vault', [False, True])
@pytest.mark.parametrize('parts, expect', [
# 24words - 3 parts
(['romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room',
@ -191,10 +193,10 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
# random generated
*random_test_cases()
])
def test_import_xor(seed_vault, incl_self, parts, expect, restore_seed_xor, way, is_q1):
def test_import_xor(incl_self, parts, expect, restore_seed_xor, way, is_q1):
if not is_q1 and "qr" in way:
raise pytest.skip("Q only")
restore_seed_xor(parts, expect, incl_self, seed_vault, way=way)
restore_seed_xor(parts, expect, incl_self, way=way)
@pytest.mark.parametrize('incl_self', [False, True])

View File

@ -119,14 +119,14 @@ def xxx_test_sign_truncated(dev):
'data/worked-combined.psbt',
'data/worked-7.psbt',
])
def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec, src_root_dir, sim_root_dir):
# unit test: parsing by the psbt proxy object
sim_exec('import main; main.FILENAME = %r; ' % ('../../testing/'+fn))
sim_exec('import main; main.FILENAME = %r; ' % (f'{src_root_dir}/testing/'+fn))
rv = sim_execfile('devtest/unit_psbt.py')
assert not rv, rv
rb = '../unix/work/readback.psbt'
rb = f'{sim_root_dir}/readback.psbt'
oo = BasicPSBT().parse(open(fn, 'rb').read())
rb = BasicPSBT().parse(open(rb, 'rb').read())
@ -134,7 +134,7 @@ def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
@pytest.mark.unfinalized
def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
press_select, press_cancel):
press_select, press_cancel, sim_root_dir):
# measure time to sign a larger txn
if is_mark4:
# Mk4: expect
@ -151,7 +151,9 @@ def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=True)
open('debug/speed.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/speed.psbt', 'wb') as f:
f.write(psbt)
dt = time.time()
start_sign(psbt, finalize=False)
@ -193,7 +195,7 @@ if 0:
@pytest.mark.veryslow
@pytest.mark.parametrize('segwit', [True, False])
def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
start_sign, end_sign, dev, segwit, accept = True):
start_sign, end_sign, dev, segwit, sim_root_dir):
# try a bunch of different bigger sized txns
# - important to test on real device, due to it's limited memory
@ -212,7 +214,8 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit, outstyles=ADDR_STYLES)
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
start_sign(psbt, finalize=True)
@ -225,9 +228,10 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
except:
cap_story = None
signed = end_sign(accept, finalize=True)
signed = end_sign(accept=True, finalize=True)
open('debug/signed.txn', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/signed.txn', 'wb') as f:
f.write(signed)
decoded = decode_with_bitcoind(signed)
@ -263,12 +267,14 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
@pytest.mark.bitcoind
@pytest.mark.parametrize('num_ins', [ 2, 7, 15 ])
@pytest.mark.parametrize('segwit', [True, False])
def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit, decode_with_bitcoind):
def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit,
decode_with_bitcoind, sim_root_dir):
# create a TXN using actual addresses that are correct for DUT
xp = dev.master_xpub
psbt = fake_txn(num_ins, 1, xp, segwit_in=segwit)
open('debug/real-%d.psbt' % num_ins, 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/real-%d.psbt' % num_ins, 'wb') as f:
f.write(psbt)
_, txn = try_sign(psbt, accept=True, finalize=True)
@ -288,7 +294,7 @@ def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit, dec
@pytest.mark.parametrize('num_dests', [ 1, 10, 25 ])
@pytest.mark.bitcoind
def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
start_sign, end_sign, we_finalize, num_dests):
start_sign, end_sign, we_finalize, num_dests, sim_root_dir):
wallet_xfp = match_key
use_regtest()
@ -344,7 +350,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
fee = resp['fee']
chg_pos = resp['changepos']
open('debug/vs.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/vs.psbt', 'wb') as f:
f.write(psbt)
# check some basics
mine = BasicPSBT().parse(psbt)
@ -366,14 +373,17 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
assert mine.version == 2
signed = end_sign(accept=True, finalize=we_finalize)
open('debug/vs-signed.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/vs-signed.psbt', 'wb') as f:
f.write(signed)
if not we_finalize:
b4 = BasicPSBT().parse(psbt)
aft = BasicPSBT().parse(signed)
assert b4 != aft, "signing didn't change anything?"
open('debug/signed.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/signed.psbt', 'wb') as f:
f.write(signed)
resp = bitcoind.supply_wallet.finalizepsbt(str(b64encode(signed), 'ascii'), True)
#combined_psbt = b64decode(resp['psbt'])
@ -385,7 +395,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
# assert resp['complete']
print("Final txn: %r" % network)
open('debug/finalized-by-btcd.txn', 'wb').write(network)
with open(f'{sim_root_dir}/debug/finalized-by-btcd.txn', 'wb') as f:
f.write(network)
# try to send it
txed = bitcoind.supply_wallet.sendrawtransaction(B2A(network))
@ -394,7 +405,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
else:
assert signed[0:4] != b'psbt', "expecting raw bitcoin txn"
#print("Final txn: %s" % B2A(signed))
open('debug/finalized-by-cc.txn', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/finalized-by-cc.txn', 'wb') as f:
f.write(signed)
txed = bitcoind.supply_wallet.sendrawtransaction(B2A(signed))
print("Final txn hash: %r" % txed)
@ -426,7 +438,7 @@ def test_sign_example(set_master_key, sim_execfile, start_sign, end_sign):
@pytest.mark.bitcoind
@pytest.mark.unfinalized
def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind):
def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind, sim_root_dir):
# Check we can finalize p2sh_p2wpkh inputs right.
# TODO fix this
@ -442,7 +454,8 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
start_sign(psbt, finalize=True)
signed = end_sign(accept=True)
#signed = end_sign(None)
open('debug/p2sh-signed.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/p2sh-signed.psbt', 'wb') as f:
f.write(signed)
#print('my finalization: ' + B2A(signed))
@ -450,7 +463,9 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
signed_psbt = end_sign(accept=True)
# use bitcoind to combine
open('debug/signed.psbt', 'wb').write(signed_psbt)
with open(f'{sim_root_dir}/debug/signed.psbt', 'wb') as f:
f.write(signed_psbt)
resp = bitcoind.rpc.finalizepsbt(str(b64encode(signed_psbt), 'ascii'), True)
assert resp['complete'] == True, "bitcoind wasn't able to finalize it"
@ -463,7 +478,8 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
@pytest.mark.bitcoind
@pytest.mark.unfinalized
def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign, end_sign,
decode_psbt_with_bitcoind, offer_ms_import, press_select, clear_ms):
decode_psbt_with_bitcoind, offer_ms_import, press_select, clear_ms,
sim_root_dir):
# Use the private key given in BIP 174 and do similar signing
# as the examples.
@ -504,7 +520,8 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
start_sign(psbt)
part_signed = end_sign(True)
open('debug/ex-signed-part.psbt', 'wb').write(part_signed)
with open(f'{sim_root_dir}/debug/ex-signed-part.psbt', 'wb') as f:
f.write(part_signed)
b4 = BasicPSBT().parse(psbt)
aft = BasicPSBT().parse(part_signed)
@ -514,7 +531,9 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
start_sign(part_signed, finalize=False)
signed = end_sign(True, finalize=False)
open('debug/ex-signed.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/ex-signed.psbt', 'wb') as f:
f.write(signed)
aft2 = BasicPSBT().parse(signed)
decode = decode_psbt_with_bitcoind(signed)
@ -542,7 +561,8 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
@pytest.mark.bitcoind
def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind, cap_story):
def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind, cap_story,
sim_root_dir):
# is change shown/hidden at right times. no fraud checks
# NOTE: out#1 is change:
@ -562,7 +582,8 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[1,])
signed = end_sign(True)
open('debug/chg-signed.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/chg-signed.psbt', 'wb') as f:
f.write(signed)
# modify it: remove bip32 path
b4.outputs[1].bip32_paths = {}
@ -581,14 +602,17 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[])
signed2 = end_sign(True)
open('debug/chg-signed2.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/chg-signed2.psbt', 'wb') as f:
f.write(signed)
aft = BasicPSBT().parse(signed)
aft2 = BasicPSBT().parse(signed2)
assert aft.txn == aft2.txn
@pytest.mark.parametrize('case', [ 1, 2])
@pytest.mark.bitcoind
def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_against_bitcoind, cap_story):
def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_against_bitcoind,
cap_story, sim_root_dir):
# fraud: BIP-32 path of output doesn't lead to pubkey indicated
# NOTE: out#1 is change:
@ -612,7 +636,8 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
b4.serialize(fd)
mod_psbt = fd.getvalue()
open('debug/mod-%d.psbt' % case, 'wb').write(mod_psbt)
with open(f'{sim_root_dir}/debug/mod-%d.psbt' % case, 'wb') as f:
f.write(mod_psbt)
if case == 1:
start_sign(mod_psbt)
@ -631,7 +656,8 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
end_sign(True)
@pytest.mark.bitcoind
def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitcoind, cap_story):
def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitcoind, cap_story,
sim_root_dir):
# fraud: BIP-32 path of output doesn't match TXO address
# NOTE: out#1 is change:
#chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
@ -653,7 +679,8 @@ def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitc
b4.serialize(fd)
mod_psbt = fd.getvalue()
open('debug/mod-addr.psbt', 'wb').write(mod_psbt)
with open(f'{sim_root_dir}/debug/mod-addr.psbt', 'wb') as f:
f.write(mod_psbt)
start_sign(mod_psbt)
with pytest.raises(CCProtoError) as ee:
@ -664,13 +691,15 @@ def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitc
@pytest.mark.parametrize('case', ['p2sh-p2wpkh', 'p2wpkh', 'p2sh', 'p2sh-p2pkh'])
@pytest.mark.bitcoind
def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_regtest,
cap_story, case):
cap_story, case, sim_root_dir):
# not fraud: output address encoded in various equiv forms
use_regtest()
# NOTE: out#1 is change:
#chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
psbt = open('data/example-change.psbt', 'rb').read()
with open('data/example-change.psbt', 'rb') as f:
psbt = f.read()
b4 = BasicPSBT().parse(psbt)
t = CTransaction()
@ -720,7 +749,8 @@ def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_re
b4.serialize(fd)
mod_psbt = fd.getvalue()
open('debug/mod-%s.psbt' % case, 'wb').write(mod_psbt)
with open(f'{sim_root_dir}/debug/mod-%s.psbt' % case, 'wb') as f:
f.write(mod_psbt)
start_sign(mod_psbt)
@ -817,7 +847,8 @@ def test_sign_multisig_partial_fail(start_sign, end_sign):
assert 'None of the keys involved' in str(ee)
@pytest.mark.unfinalized
def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, sim_execfile):
def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, sim_execfile,
sim_root_dir):
# Example from SomberNight: we can sign it, but signature won't be accepted by
# network because the PSBT lies about the UTXO amount and tries to give away to miners,
@ -852,11 +883,13 @@ def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, s
signed = end_sign(True, finalize=fin)
open('debug/sn-signed.'+ ('txn' if fin else 'psbt'), 'wt').write(B2A(signed))
with open(f'{sim_root_dir}/debug/sn-signed.'+ ('txn' if fin else 'psbt'), 'wt') as f:
f.write(B2A(signed))
@pytest.mark.parametrize('fee_max', [ 10, 25, 50])
@pytest.mark.parametrize('under', [ False, True])
def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, settings_set, sim_exec, cap_story):
def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, settings_set,
sim_exec, cap_story, sim_root_dir):
settings_set('fee_limit', fee_max)
@ -866,7 +899,8 @@ def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, s
psbt = fake_txn(1, 1, dev.master_xpub, fee=None, outvals=[outval])
open('debug/fee.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/fee.psbt', 'wb') as f:
f.write(psbt)
if not under:
with pytest.raises(CCProtoError) as ee:
@ -885,7 +919,8 @@ def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, s
settings_set('fee_limit', 10)
def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set, cap_story):
def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set, cap_story,
sim_root_dir):
settings_set('fee_limit', -1)
@ -894,7 +929,8 @@ def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set
psbt = fake_txn(1, 1, dev.master_xpub, fee=None, outvals=[outval])
open('debug/fee-un.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/fee-un.psbt', 'wb') as f:
f.write(psbt)
# should be able to sign, but get warning
start_sign(psbt, False)
@ -917,15 +953,17 @@ def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set
@pytest.mark.parametrize('out_style', ADDR_STYLES_SINGLE)
@pytest.mark.parametrize('visualized', [0, STXN_VISUALIZE, STXN_VISUALIZE|STXN_SIGNED])
def test_change_outs(fake_txn, start_sign, end_sign, cap_story, dev, num_outs, master_xpub,
act_outs, segwit, out_style, visualized, add_xpub, num_ins=3):
act_outs, segwit, out_style, visualized, add_xpub, sim_root_dir):
# create a TXN which has change outputs, which shouldn't be shown to user, and also not fail.
xp = dev.master_xpub
num_ins = 3
couts = num_outs if act_outs == -1 else num_ins-act_outs
psbt = fake_txn(num_ins, num_outs, xp, segwit_in=segwit,
outstyles=[out_style], change_outputs=range(couts), add_xpub=add_xpub)
open('debug/change.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/change.psbt', 'wb') as f:
f.write(psbt)
# should be able to sign, but get warning
if not visualized:
@ -1001,7 +1039,8 @@ def KEEP_test_random_psbt(try_sign, sim_exec, fname="data/ .psbt"):
@pytest.mark.bitcoind
@pytest.mark.unfinalized
@pytest.mark.parametrize('num_dests', [ 1, 10, 25 ])
def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind, start_sign, end_sign, num_dests):
def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
start_sign, end_sign, num_dests, sim_root_dir):
# Compare how we finalize vs bitcoind ... should be exactly the same txn
wallet_xfp = match_key
# has to be after match key
@ -1030,7 +1069,8 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
fee = resp['fee']
chg_pos = resp['changepos']
open('debug/vs.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/vs.psbt', 'wb') as f:
f.write(psbt)
# check some basics
mine = BasicPSBT().parse(psbt)
@ -1053,13 +1093,15 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
signed_final = end_sign(accept=True, finalize=True)
assert signed_final[0:4] != b'psbt', "expecting raw bitcoin txn"
open('debug/finalized-by-ckcc.txn', 'wt').write(B2A(signed_final))
with open(f'{sim_root_dir}/debug/finalized-by-ckcc.txn', 'wt') as f:
f.write(B2A(signed_final))
# Sign again, but don't finalize it.
start_sign(psbt, finalize=False)
signed = end_sign(accept=True)
open('debug/vs-signed-unfin.psbt', 'wb').write(signed)
with open(f'{sim_root_dir}/debug/vs-signed-unfin.psbt', 'wb') as f:
f.write(signed)
# Use bitcoind to finalize it this time.
resp = bitcoind.supply_wallet.finalizepsbt(str(b64encode(signed), 'ascii'), True)
@ -1069,7 +1111,8 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
# assert resp['complete']
#print("Final txn: %r" % network)
open('debug/finalized-by-btcd.txn', 'wt').write(B2A(network))
with open(f'{sim_root_dir}/debug/finalized-by-btcd.txn', 'wt') as f:
f.write(B2A(network))
assert network == signed_final, "Finalized differently"
@ -1091,7 +1134,7 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
("44'/1'/0'/3000/5", '2nd last component'),
("44'/1'/0'/3/5", '2nd last component'),
])
def test_change_troublesome(dev, start_sign, cap_story, try_path, expect):
def test_change_troublesome(dev, start_sign, cap_story, try_path, expect, sim_root_dir):
# NOTE: out#1 is change:
# addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
# path = (m=4369050F)/44'/1'/0'/1/5
@ -1115,7 +1158,8 @@ def test_change_troublesome(dev, start_sign, cap_story, try_path, expect):
b4.serialize(fd)
mod_psbt = fd.getvalue()
open('debug/troublesome.psbt', 'wb').write(mod_psbt)
with open(f'{sim_root_dir}/debug/troublesome.psbt', 'wb') as f:
f.write(mod_psbt)
start_sign(mod_psbt)
time.sleep(0.1)
@ -1200,8 +1244,6 @@ def spend_outputs(funding_psbt, finalized_txn, tweaker=None):
with BytesIO() as rv:
nn.serialize(rv)
raw = rv.getvalue()
open('debug/spend_outs.psbt', 'wb').write(raw)
return nn, raw
@ -1231,7 +1273,7 @@ def txid_from_export_prompt(cap_story, cap_screen_qr, cap_screen, need_keypress)
@pytest.mark.parametrize('segwit_in', [False, True])
def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, settings_set,
settings_get, cap_story, sim_exec, hist_count,
txid_from_export_prompt, press_cancel):
txid_from_export_prompt, press_cancel, sim_root_dir):
# cleanup prev runs, if very first time thru
sim_exec('import history; history.OutptValueCache.clear()')
@ -1243,7 +1285,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
outstyles=(['p2wpkh']*num_utxo) + ['p2wpkh-p2sh', 'p2pkh'])
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
open('debug/funding.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/funding.psbt', 'wb') as f:
f.write(psbt)
num_inp_utxo = (1 if segwit_in else 0)
@ -1266,6 +1309,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
assert all_utxo == hist_b4+num_utxo+num_inp_utxo
# build a new PSBT based on those change outputs
psbt2, raw = spend_outputs(psbt, txn)
with open(f'{sim_root_dir}/debug/spend_outs.psbt', 'wb') as f:
f.write(raw)
# try to sign that ... should work fine
try_sign(raw, accept=True, finalize=True)
@ -1281,6 +1326,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
spendables[0][1].nValue += amt
psbt3, raw = spend_outputs(psbt, txn, tweaker=value_tweak)
with open(f'{sim_root_dir}/debug/spend_outs.psbt', 'wb') as f:
f.write(raw)
with pytest.raises(CCProtoError) as ee:
orig, result = try_sign(raw, accept=True, finalize=True)
@ -1352,7 +1399,8 @@ def test_sdcard_signing(encoding, num_outs, del_after, partial, try_sign_microsd
@pytest.mark.unfinalized
@pytest.mark.parametrize('num_ins', [2,3,8])
@pytest.mark.parametrize('num_outs', [1,2,8])
def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_sign, cap_story):
def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_sign,
cap_story, sim_root_dir):
# Try to simulate a PSBT that might be involved in a Payjoin (BIP-78 txn)
@ -1362,7 +1410,8 @@ def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_
psbt = fake_txn(num_ins, num_outs, segwit_in=True, psbt_hacker=hack)
open('debug/payjoin.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/payjoin.psbt', 'wb') as f:
f.write(psbt)
ip = start_sign(psbt, finalize=False)
time.sleep(.1)
@ -1414,7 +1463,7 @@ def test_wrong_xfp(fake_txn, try_sign, segwit):
assert 'found 12345678' in str(ee)
@pytest.mark.parametrize('segwit', [False, True])
def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
def test_wrong_xfp_multi(fake_txn, try_sign, segwit, sim_root_dir):
# A PSBT which is unsigned and doesn't involve our XFP value
# - but multiple wrong XFP values
@ -1431,7 +1480,9 @@ def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
psbt = fake_txn(7, 2, segwit_in=segwit, psbt_hacker=hack)
open('debug/wrong-xfp.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/wrong-xfp.psbt', 'wb') as f:
f.write(psbt)
with pytest.raises(CCProtoError) as ee:
orig, result = try_sign(psbt, accept=True)
@ -1445,7 +1496,8 @@ def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
@pytest.mark.parametrize('out_style', ADDR_STYLES_SINGLE)
@pytest.mark.parametrize('segwit', [False, True])
@pytest.mark.parametrize('outval', ['.5', '.788888', '0.92640866'])
def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign, dev):
def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign, dev,
sim_root_dir):
# check how we render the value of outputs
# - works on simulator and connected USB real-device
xp = dev.master_xpub
@ -1454,7 +1506,8 @@ def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign,
psbt = fake_txn(1, 2, dev.master_xpub, segwit_in=segwit, outvals=[oi, int(1E8-oi)],
outstyles=[out_style], change_outputs=[1])
open('debug/render.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/render.psbt', 'wb') as f:
f.write(psbt)
# should be able to sign, but get warning
@ -1501,7 +1554,8 @@ def test_negative_fee(dev, fake_txn, try_sign):
( 5, 'mXTN'),
( 2, 'bits'),
( 0, 'sats')])
def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set, settings_remove):
def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
settings_remove, sim_root_dir):
# Check we are rendering values in right units.
decimal, units = units
@ -1517,7 +1571,8 @@ def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
need = sum(outputs)
psbt = fake_txn(1, len(outputs), dev.master_xpub, segwit_in=True, outvals=outputs, invals=[need])
open('debug/values.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/values.psbt', 'wb') as f:
f.write(psbt)
ip = start_sign(psbt, finalize=False)
time.sleep(.1)
@ -1542,12 +1597,13 @@ def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
@pytest.mark.parametrize('num_out', [1,2,3])
@pytest.mark.parametrize('segwit', [True, False])
def test_qr_txn(num_in, num_out, segwit, fake_txn, try_sign, dev, cap_screen_qr,
qr_quality_check, cap_story, need_keypress, is_q1, press_cancel):
qr_quality_check, cap_story, need_keypress, is_q1, press_cancel,
sim_root_dir):
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit)
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
with open('debug/last.txn', 'wb') as f:
with open(f'{sim_root_dir}/debug/last.txn', 'wb') as f:
f.write(txn)
title, story = cap_story()
@ -1833,7 +1889,7 @@ def test_op_return_signing(op_return_data, dev, fake_txn, bitcoind_d_sim_watch,
({b"x" * 64: b"y" * 128}, {b"q" * 64: b"p" * 128}, {b"w" * 90: b"z" * 256}),
({b"x" * 32: b"y" * 256}, {b"q" * 32: b"p" * 256, b"f" * 15: 32 * b"\x01"}, {b"w": b"z"}),
])
def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn, sim_root_dir):
unknown_global, unknown_ins, unknown_outs = unknowns
def hack(psbt):
psbt.unknown = unknown_global
@ -1843,7 +1899,8 @@ def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
o.unknown = unknown_outs
psbt = fake_txn(5, 5, dev.master_xpub, segwit_in=True, psbt_hacker=hack)
open('debug/last.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
f.write(psbt)
psbt_o = BasicPSBT().parse(psbt)
assert psbt_o.unknown == unknown_global
for inp in psbt_o.inputs:
@ -1860,7 +1917,7 @@ def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
for out in res.outputs:
assert out.unknown == unknown_outs
def test_read_write_prop_attestation_keys(try_sign, fake_txn):
def test_read_write_prop_attestation_keys(try_sign, fake_txn, sim_root_dir):
from psbt import ser_prop_key, PSBT_PROP_CK_ID
def attach_attest_to_outs(psbt):
for idx, o in enumerate(psbt.outputs):
@ -1869,7 +1926,8 @@ def test_read_write_prop_attestation_keys(try_sign, fake_txn):
o.proprietary[key] = value
psbt = fake_txn(2, 2, psbt_hacker=attach_attest_to_outs)
open('debug/propkeys.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/propkeys.psbt', 'wb') as f:
f.write(psbt)
orig, signed = try_sign(psbt)
res = BasicPSBT().parse(signed)
@ -1910,7 +1968,7 @@ def test_duplicate_unknow_values_in_psbt(dev, start_sign, end_sign, fake_txn):
@pytest.fixture
def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
bitcoind, bitcoind_d_dev_watch, settings_set,
finalize_v2_v0_convert, pytestconfig):
finalize_v2_v0_convert, pytestconfig, sim_root_dir):
def doit(addr_fmt, sighash, num_inputs=2, num_outputs=2, consolidation=False, sh_checks=False,
psbt_v2=None, tx_check=True):
@ -1984,7 +2042,7 @@ def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
psbt_sh = x.as_b64_str()
# make useful reference psbt along the way
with open(f'debug/sighash-{sighash[0] if len(sighash) == 1 else "MIX"}.psbt'\
with open(f'{sim_root_dir}/debug/sighash-{sighash[0] if len(sighash) == 1 else "MIX"}.psbt'\
.replace('|', '-'), 'wt') as f:
f.write(psbt_sh)
@ -2847,7 +2905,7 @@ def random_nLockTime_test_cases(num=10):
*random_nLockTime_test_cases()
])
def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
bitcoind_d_sim_watch, nLockTime):
bitcoind_d_sim_watch, nLockTime, sim_root_dir):
# - works on simulator and connected USB real-device
nLockTime, expect_ux = nLockTime
num_ins = 10
@ -2883,7 +2941,8 @@ def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
)
psbt = base64.b64decode(psbt_resp.get("psbt"))
open('debug/locktimes.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/locktimes.psbt', 'wb') as f:
f.write(psbt)
# should be able to sign, but get warning
@ -2905,7 +2964,7 @@ def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
def test_base64_psbt_qr(in_out, partial, segwit, scan_a_qr, readback_bbqr,
goto_home, use_regtest, cap_story, fake_txn, dev,
decode_psbt_with_bitcoind, decode_with_bitcoind,
press_cancel, press_select, need_keypress):
press_cancel, press_select, need_keypress, sim_root_dir):
def hack(psbt):
if partial:
# change first input to not be ours
@ -2923,7 +2982,8 @@ def test_base64_psbt_qr(in_out, partial, segwit, scan_a_qr, readback_bbqr,
psbt = base64.b64encode(psbt).decode()
open('debug/last.psbt', 'w').write(psbt)
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
f.write(psbt)
goto_home()
need_keypress(KEY_QR)
@ -3104,8 +3164,7 @@ def test_txout_explorer_op_return(finalize, data, fake_txn, start_sign, cap_stor
assert s == dd0
assert e == dd1
qr = qr_list[i - 20]
assert qr.startswith(s)
assert qr.endswith(e)
assert qr == ""
press_cancel() # exit txn out explorer
end_sign(finalize=finalize)
@ -3180,7 +3239,7 @@ def test_zero_value_outputs(num_outs, change, fake_txn, start_sign, end_sign, re
psbt = fake_txn(1, num_outs, outvals=num_outs*[0], change_outputs=change_outs, input_amount=1)
start_sign(psbt, False, stxn_flags=STXN_VISUALIZE)
story = end_sign(accept=None, expect_txn=False).decode()
assert f"Zero Value: Non-standard zero value outputs: {num_outs}" in story
assert f"Zero Value: Non-standard zero value output(s)" in story
assert "1 input" in story
assert f"{num_outs} output{'' if num_outs == 1 else 's'}" in story
assert 'Network fee 0.00000001 XTN' in story

View File

@ -20,7 +20,7 @@ def THIS_FILE_requires_q1(is_q1, is_headless):
if not is_q1 or is_headless:
raise pytest.skip('Q1 only (not headless)')
@pytest.fixture()
@pytest.fixture
def rx_start(grab_payload, goto_home, pick_menu_item):
def doit(**kws):
goto_home()
@ -31,7 +31,7 @@ def rx_start(grab_payload, goto_home, pick_menu_item):
return doit
@pytest.fixture()
@pytest.fixture
def main_do_over(unit_test, settings_get, settings_set):
# reset all contents, including master secret ... except ktrx
# - so you can test backup-restore onto blank unit
@ -42,7 +42,7 @@ def main_do_over(unit_test, settings_get, settings_set):
return doit
@pytest.fixture()
@pytest.fixture
def grab_payload(press_select, need_keypress, press_cancel, nfc_read_url, cap_story, nfc_block4rf, cap_screen_qr, readback_bbqr):
# started the process; capture pw/code and QR contents, verify NFC works
@ -108,7 +108,7 @@ def grab_payload(press_select, need_keypress, press_cancel, nfc_read_url, cap_s
return doit
@pytest.fixture()
@pytest.fixture
def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr, enter_complex, cap_screen, goto_home, split_scan_bbqr):
# finish the teleport by doing QR and getting data
def doit(data, pw, expect_fail=False, expect_xfp=None):
@ -126,10 +126,13 @@ def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr,
if expect_fail:
time.sleep(.200)
return
for retries in range(20):
for _ in range(10):
scr = cap_screen()
if 'Teleport Password' in scr: break
time.sleep(.200)
time.sleep(.2)
else:
assert False, "Teleport Password not in screen"
if expect_xfp:
assert xfp2str(expect_xfp) in scr
@ -140,7 +143,7 @@ def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr,
return doit
@pytest.fixture()
@pytest.fixture
def tx_start(press_select, need_keypress, press_cancel, goto_home, pick_menu_item, cap_story, scan_a_qr, enter_complex, cap_screen):
# start the Tx process, capturing password and leaving you are picker menu
@ -150,13 +153,15 @@ def tx_start(press_select, need_keypress, press_cancel, goto_home, pick_menu_ite
time.sleep(.250) # required
scan_a_qr(rx_qr)
time.sleep(.250) # required
scr = cap_screen()
if expect_fail:
assert expect_fail in scr
return
assert 'Teleport Password (number)' in scr
for _ in range(10):
scr = cap_screen()
if expect_fail and expect_fail in scr:
return
elif 'Teleport Password (number)' in scr:
break
time.sleep(.2)
else:
assert False, "Teleport Password not in screen"
enter_complex(rx_code)
time.sleep(.150) # required
@ -416,10 +421,10 @@ def test_tx_wrong_pub(rx_start, tx_start, cap_menu, enter_complex, pick_menu_ite
@pytest.mark.parametrize('segwit', [True])
@pytest.mark.parametrize('incl_xpubs', [ False ])
def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
fake_ms_txn, try_sign, incl_xpubs, bitcoind, cap_story, need_keypress,
fake_ms_txn, try_sign, incl_xpubs, bitcoind, cap_story, need_keypress,
cap_menu, pick_menu_item, grab_payload, rx_complete, press_select,
ndef_parse_txn_psbt, press_nfc, nfc_read, settings_get, settings_set,
txid_from_export_prompt):
txid_from_export_prompt, sim_root_dir):
# IMPORTANT: won't work if you start simulator with --ms flag. Use no args
all_out_styles = list(unmap_addr_fmt.keys())
@ -436,13 +441,15 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs,
outstyles=all_out_styles, change_outputs=list(range(1,num_outs)))
open(f'debug/myself-before.psbt', 'wb').write(psbt)
with open(f'{sim_root_dir}/debug/myself-before.psbt', 'wb') as f:
f.write(psbt)
cur_wallet = 0
my_xfp = select_wallet(cur_wallet)
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs, exit_export_loop=False)
open(f'debug/myself-after-1.psbt', 'wb').write(updated)
with open(f'{sim_root_dir}/debug/myself-after-1.psbt', 'wb') as f:
f.write(updated)
assert updated != psbt
title, body = cap_story()
@ -484,7 +491,8 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
assert len(pw) == 8
nn = xfp2str(next_xfp)
open(f'debug/next_qr_{nn}.txt', 'wt').write(f'{nn}\n\n{pw}\n\n{data}')
with open(f'{sim_root_dir}/debug/next_qr_{nn}.txt', 'wt') as f:
f.write(f'{nn}\n\n{pw}\n\n{data}')
time.sleep(.1)
title, story = cap_story()
@ -628,8 +636,7 @@ def test_teleport_real_ms(dev, fake_ms_txn):
# py.test test_teleport.py --dev --manual -k test_teleport_real_ms
#
from bip32 import BIP32Node
from struct import unpack
from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError
from ckcc_protocol.protocol import CCProtocolPacker
M = N = 2

View File

@ -25,13 +25,13 @@ def upload_file(dev):
return doit
@pytest.fixture
def make_firmware():
def doit(hw_compat, fname='../stm32/firmware-signed.bin', outname='tmp-firmware.bin'):
def make_firmware(src_root_dir):
def doit(hw_compat, fname=f'{src_root_dir}/stm32/firmware-signed.bin', outname='tmp-firmware.bin'):
# os.system(f'signit sign 3.0.99 --keydir ../stm32/keys -r {fname} -o {outname} --hw-compat=0x{hw_compat:02x}')
p = subprocess.run(
[
'signit', 'sign', '3.0.99',
'--keydir', '../stm32/keys',
'--keydir', f'{src_root_dir}/stm32/keys',
'-r', f'{fname}',
'-o', f'{outname}',
f'--hw-compat={hw_compat}'
@ -50,7 +50,7 @@ def make_firmware():
return doit
@pytest.fixture
def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_select, microsd_path, sim_exec):
def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_select, microsd_path, sim_exec, src_root_dir):
# send a firmware file over the microSD card
@ -64,7 +64,7 @@ def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_sele
# create DFU file (wrapper)
open(f'{fname}.bin', 'wb').write(data)
dfu = microsd_path('tmp-firmware.dfu')
cmd = f'../external/micropython/tools/dfu.py -b 0x08008000:{fname}.bin {dfu}'
cmd = f'{src_root_dir}/external/micropython/tools/dfu.py -b 0x08008000:{fname}.bin {dfu}'
print(cmd)
os.system(cmd)

View File

@ -826,7 +826,8 @@ def test_sign_file_from_list_files(f_len, goto_home, cap_story, pick_menu_item,
def test_bip39_pw_signing_xfp_ux(pick_menu_item, press_select, cap_story, enter_complex,
reset_seed_words, cap_menu, go_to_passphrase):
reset_seed_words, cap_menu, go_to_passphrase, microsd_wipe):
microsd_wipe() # need to wipe all PSBT on SD card so we do not proceed to signing
go_to_passphrase()
enter_complex("21coinkite21", apply=True)
time.sleep(0.3)
@ -834,6 +835,7 @@ def test_bip39_pw_signing_xfp_ux(pick_menu_item, press_select, cap_story, enter_
assert title == "[0C9DC99D]"
assert 'Above is the master key fingerprint of the new wallet' in story
press_select() # confirm passphrase
time.sleep(0.1)
m = cap_menu()
assert m[0] == "[0C9DC99D]"
pick_menu_item("Ready To Sign")
@ -973,8 +975,8 @@ def test_custom_pushtx_url(goto_home, pick_menu_item, press_select, enter_comple
("coldcard-export.json", "J"),
("coldcard-export.sig", "U"),
])
def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
goto_home, pick_menu_item, is_q1, cap_menu):
def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress, src_root_dir,
goto_home, pick_menu_item, is_q1, cap_menu, sim_root_dir):
goto_home()
if not is_q1:
pick_menu_item("Advanced/Tools")
@ -982,8 +984,8 @@ def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
assert "BBQr File Share" not in cap_menu()
return
fpath = "data/" + fname
shutil.copy2(fpath, '../unix/work/MicroSD')
fpath = f"{src_root_dir}/testing/data/" + fname
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
pick_menu_item("Advanced/Tools")
pick_menu_item("File Management")
pick_menu_item("BBQr File Share")
@ -995,14 +997,15 @@ def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
res = f.read()
assert res == rb
os.remove('../unix/work/MicroSD/' + fname)
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
@pytest.mark.parametrize("fname", [
"ccbk-start.json",
"devils-txn.txn",
"payjoin.psbt", # base64 string in file
])
def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_screen_qr):
def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_screen_qr,
src_root_dir, sim_root_dir):
goto_home()
if not is_q1:
pick_menu_item("Advanced/Tools")
@ -1010,8 +1013,8 @@ def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_s
assert "QR File Share" not in cap_menu()
return
fpath = "data/" + fname
shutil.copy2(fpath, '../unix/work/MicroSD')
fpath = f"{src_root_dir}/testing/data/" + fname
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
pick_menu_item("Advanced/Tools")
pick_menu_item("File Management")
pick_menu_item("QR File Share")
@ -1022,7 +1025,7 @@ def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_s
res = f.read()
assert res == qr.decode()
os.remove('../unix/work/MicroSD/' + fname)
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
@pytest.mark.onetime

View File

@ -30,7 +30,7 @@ def test_vd_basics(dev, virtdisk_path, is_simulator):
@pytest.fixture
def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, press_cancel,
pick_menu_item, goto_home):
pick_menu_item, goto_home, sim_root_dir):
# like "try_sign" but use Virtual Disk to send/receive PSBT/results
# - on real dev, need user to manually say yes ... alot
@ -142,10 +142,9 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
assert expect_finalize
assert got_txn
assert got_txid == txid == result_txid
with open("debug/vd-result.txn", 'wb') as f:
with open(f"{sim_root_dir}/debug/vd-result.txn", 'wb') as f:
f.write(got_txid)
# check output encoding matches input (for PSBT only)
if got_psbt:
if encoding == 'hex':
@ -170,7 +169,7 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
if got_psbt:
assert got_psbt[0:5] == b'psbt\xff'
with open("debug/vd-result.psbt", 'wb') as f:
with open(f"{sim_root_dir}/debug/vd-result.psbt", 'wb') as f:
f.write(got_psbt)
from psbt import BasicPSBT

View File

@ -14,7 +14,7 @@ from serialize import uint256_from_str
from ctransaction import CTransaction, COutPoint, CTxIn, CTxOut
@pytest.fixture()
@pytest.fixture
def fake_txn(dev, pytestconfig):
# make various size txn's ... completely fake and pointless values
# - but has UTXO's to match needs

View File

@ -60,6 +60,7 @@ wallet (on testnet, always with the same seed). But there are other options:
- `--scan` => (Q) use attached serial port connected to a QR scanner module (not simulation)
- `--battery` => (Q) assume the USB cable is NOT connected (ie. on battery power)
- `--early-usb` => start simulated USB interface even before user is login (useful for login testing)
- `--segregate` => scroll down to `Running simulators in parallel` section
See `variant/sim_settings.py` for the details of settings-related options.
@ -78,7 +79,7 @@ See `variant/sim_settings.py` for the details of settings-related options.
## Requirements
- uses good olde `xterm` for console input and output
- uses good old `xterm` for console input and output
- this directory has additional `requirements.txt` (a superset of other requirements of the project)
- run "brew install sdl2" before/after doing python requirements
- run "make setup" then "make"
@ -99,4 +100,39 @@ See `variant/sim_settings.py` for the details of settings-related options.
- linux supported (only tested on debian based Ubuntu 20.04), please check main README.md
- Windows can work under WSL but is not supported by our team. Follow instructions on <https://www.reddit.com/r/coldcard/comments/14etq8i/coldcard_simulator_for_windows_mac_and_linux_to/>
# Running simulators in parallel
Normally, when simulator is spwned with `./simulator.py --eff --q1` (or similar)
the default socket file is produced (`/tmp/ckcc-simulator.sock`) for simulator to be able to emulate various types of comms.
Each time new simulator is spwned (while som older still running) the socket file gets claimed by the most recently opened simulator.
You can continue to use older simulator manually, but it is no longer possible to communicate via the socket file.
Besides shared socket file, all simulators share some working directories (`work` & previously `testing/debug`, currently `work/debug`).
To enable full parallel operation on multiple simulators use `--segregate` simulator flag that:
* creates unique simulator socket file `/tmp/ckcc-simulator-<PID>.sock` for every simulator spwned with the flag
* creates separate simulator work directory in `/tmp/cc-simulators/<PID>` (every simulator has its own debug dir in work dir)
Spawn two simulators:
```shell
./simulator.py --eff --segregate # Mk4
./simulator.py --eff --segregate --q1 # Q
```
Two directories were created inside `/tmp/cc-simulators` and two socket files in `/tmp` with same PID in names as directory names created.
To operate above simulators new `--socket`/`-c` flag needs to be used with client: `ckcc -c /tmp/ckcc-simulator-35156.sock ...`
```shell
ckcc -c /tmp/ckcc-simulator-35156.sock addr -s
ckcc -c /tmp/ckcc-simulator-35291.sock addr -s
```
Simulator socket path is dumped to STDOUT after simulator is started:
```shell
Coldcard Simulator: Commands (over simulated window):
- Control-Q to quit
- ^Z to snapshot screen.
- ^S/^E to start/end movie recording
- ^N to capture NFC data (tap it)
- socket: /tmp/ckcc-simulator-35291.sock
```

View File

@ -1,18 +1,16 @@
diff --git a/unix/variant/pyb.py b/unix/variant/pyb.py
index d22bb1b..fe8e7ca 100644
index 5e108da3..5a15fc9c 100644
--- a/unix/variant/pyb.py
+++ b/unix/variant/pyb.py
@@ -36,10 +36,10 @@ class USB_HID:
import usocket as socket
@@ -40,9 +40,9 @@ class USB_HID:
sfp_b = SOCKET_FILE_PATH.encode()
self.pipe = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
# If on linux, try commenting the following line
- addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
+ # addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
- addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
+ #addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
# If on linux, try uncommenting the following two lines
- #import struct
- #addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
+ import struct
+ addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
- # addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
+ addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
while 1:
try:
self.pipe.bind(addr)

View File

@ -6,6 +6,11 @@
import machine, pyb, sys
socket_path = sys.argv.pop() # last arg must be a socket path - remove
assert ("ckcc-simulator" in socket_path) and (".sock" in socket_path)
pyb.SOCKET_FILE_PATH = socket_path
print("socket:", pyb.SOCKET_FILE_PATH)
if '--metal' in sys.argv:
# next in argv will be two open file descriptors to use for serial I/O to a real Coldcard
import bare_metal

View File

@ -14,12 +14,10 @@
# Limitations:
# - USB light not fully implemented, because happens at irq level on real product
#
import os, sys, signal, time, pdb, tempfile, struct, zlib
import subprocess, asyncio
import os, sys, signal, time, pdb, tempfile, struct, zlib, subprocess, shutil
from dataclasses import dataclass
import sdl2.ext
import PIL
from PIL import Image, ImageSequence, ImageOps
from PIL import Image, ImageOps
from select import select
import fcntl
from bare import BareMetal
@ -31,6 +29,7 @@ UNIX_SOCKET_PATH = '/tmp/ckcc-simulator.sock'
current_led_state = 0x0
def activate_file(filename):
# see <https://stackoverflow.com/questions/17317219>
if sys.platform == "win32":
@ -745,6 +744,14 @@ def handle_q1_key_events(event, numpad_tx, data_tx):
def start():
is_q1 = ('--q1' in sys.argv)
segregate = ("--segregate" in sys.argv)
pid = os.getpid()
# for compatibility with old clients
# UNIX_SOCKET_PATH is always used if not segregate
socket_path = UNIX_SOCKET_PATH
if segregate:
socket_path = '/tmp/ckcc-simulator-%d.sock' % pid
if "--headless" in sys.argv:
sys.argv.remove("--headless")
is_headless = True
@ -760,6 +767,7 @@ def start():
- ^S/^E to start/end movie recording
- ^N to capture NFC data (tap it)'''
)
print(" - socket: %s" % socket_path)
if is_q1:
print('''\
Q1 specials:
@ -818,10 +826,10 @@ Q1 specials:
# manage unix socket cleanup for client
def sock_cleanup():
import os
fp = UNIX_SOCKET_PATH
fp = socket_path
if os.path.exists(fp):
os.remove(fp)
sock_cleanup()
import atexit
atexit.register(sock_cleanup)
@ -849,11 +857,30 @@ Q1 specials:
scan_args = [ '--scan', str(port.fileno()) ]
sys.argv.remove('--scan')
os.chdir('./work')
cc_cmd = ['../coldcard-mpy',
'-X', 'heapsize=9m',
'-i', '../sim_boot.py'] + [str(i) for i in pass_fds] \
+ metal_args + scan_args + sys.argv[1:]
# unix
cwd = os.getcwd()
# abs paths
cc_mpy = os.path.join(cwd, "coldcard-mpy")
sim_boot = os.path.join(cwd, "sim_boot.py")
if segregate:
os.makedirs("/tmp/cc-simulators", exist_ok=True)
os.chdir("/tmp/cc-simulators")
# our new work /tmp/cc-simulators/<PID>
os.mkdir(str(pid))
os.chdir(str(pid))
os.mkdir("MicroSD")
os.mkdir("settings")
os.mkdir("VirtDisk")
os.mkdir("debug")
# needed for VirtDisk test
shutil.copy(os.path.join(cwd, "work", "VirtDisk", "README.md"),
os.path.join(os.getcwd(), "VirtDisk", "README.md"))
else:
os.chdir('./work')
cc_cmd = [cc_mpy, '-X', 'heapsize=9m', '-i', sim_boot] + [str(i) for i in pass_fds] \
+ metal_args + scan_args + sys.argv[1:] + [socket_path]
if is_headless:
pass_fds.remove("-1")

View File

@ -198,10 +198,9 @@ def is_simulator():
def is_debug_build():
return True
def get_sim_root_dirs():
# return a single path and list of files to pretend to find there
import ffilib, os
import ffilib
libc = ffilib.libc()
b = bytearray(500)

View File

@ -2,9 +2,10 @@
#
import utime as time
import uerrno as errno
import sys
import usocket as socket
import sys, struct, os
from machine import Pin
SOCKET_FILE_PATH = None
class USB_VCP:
@staticmethod
@ -28,22 +29,20 @@ def usb_mode(nm=UNSET, **kws):
return _umode
class USB_HID:
fn = b'/tmp/ckcc-simulator.sock'
def __init__(self):
self.pipe = None
self.last_from = None
self._open()
def _open(self):
import sys
import usocket as socket
assert SOCKET_FILE_PATH # has to be set in sim_boot.py by caller
sfp_b = SOCKET_FILE_PATH.encode()
self.pipe = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
# If on linux, try commenting the following line
addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
# If on linux, try uncommenting the following two lines
#import struct
#addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
# addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
while 1:
try:
self.pipe.bind(addr)
@ -51,8 +50,7 @@ class USB_HID:
except OSError as exc:
if exc.args[0] == errno.EADDRINUSE:
# handle restart after first run
import os
os.remove(self.fn)
os.remove(SOCKET_FILE_PATH)
continue
def recv(self, buf, timeout=0):

11
unix/work/debug/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
*.7z
*.txt
*.json
*.psbt
*.csv
*.pdf
*.dfu
*.txn
*.sig
*.png
.tmp.tmp