Merge branch 'Q' of github.com:Coldcard/q1firmware into Q

This commit is contained in:
Peter D. Gray 2024-02-28 09:17:11 -05:00
commit 92be45cdad
No known key found for this signature in database
GPG Key ID: A2DCD558C2BE5D7C
5 changed files with 153 additions and 44 deletions

View File

@ -189,7 +189,7 @@ Restore your seed words onto a new Coldcard.''' % num_fails
ch = await ux_show_story(msg, title='I Am Brick!', escape='6')
if ch == '6': break
async def confirm_attempt(self, attempts_left, num_fails, value):
async def confirm_attempt(self, attempts_left, value):
ch = await ux_show_story('''You have %d attempts left before this Coldcard BRICKS \
ITSELF FOREVER.
@ -224,7 +224,7 @@ Press OK to continue, X to stop for now.
if pa.num_fails > 3:
# they are approaching brickage, so warn them each attempt
await self.confirm_attempt(pa.attempts_left, pa.num_fails, pin)
await self.confirm_attempt(pa.attempts_left, pin)
dis.fullscreen("Loading...")
pa.setup(pin)

View File

@ -1801,10 +1801,11 @@ def load_export_and_verify_signature(microsd_path, virtdisk_path, verify_detache
@pytest.fixture
def load_export(need_keypress, cap_story, microsd_path, virtdisk_path, nfc_read_text, nfc_read_json,
load_export_and_verify_signature, is_q1, press_cancel, press_select):
load_export_and_verify_signature, is_q1, press_cancel, press_select, readback_bbqr,
cap_screen_qr):
def doit(way, label, is_json, sig_check=True, addr_fmt=AF_CLASSIC, ret_sig_addr=False,
tail_check=None, sd_key=None, vdisk_key=None, nfc_key=None, ret_fname=False,
fpattern=None):
fpattern=None, qr_key=None):
s_label = None
if label == "Address summary":
@ -1814,6 +1815,7 @@ def load_export(need_keypress, cap_story, microsd_path, virtdisk_path, nfc_read_
"sd": sd_key or "1",
"vdisk": vdisk_key or "2",
"nfc": nfc_key or (KEY_NFC if is_q1 else "3"),
"qr": qr_key or (KEY_QR if is_q1 else "4"),
}
time.sleep(0.2)
title, story = cap_story()
@ -1834,6 +1836,23 @@ def load_export(need_keypress, cap_story, microsd_path, virtdisk_path, nfc_read_
time.sleep(0.3)
press_cancel() # exit NFC animation
return nfc_export
elif way == "qr":
need_keypress(key_map["qr"])
time.sleep(0.3)
try:
file_type, data = readback_bbqr()
if file_type == "J":
return json.loads(data)
elif file_type == "U":
return data
else:
raise NotImplementedError
except:
res = cap_screen_qr().decode('ascii')
try:
return json.loads(res)
except:
return res
else:
# virtual disk
if f"({key_map['vdisk']}) to save to Virtual Disk" not in story:

View File

@ -1,10 +1,15 @@
# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# to run it on both Mk4 and Q:
# python login_settings_tests.py; sleep 10; python --Q login_settings_tests.py
# pytest login_settings_tests.py; sleep 10; pytest --Q login_settings_tests.py
#
# or use test runner:
# python run_sim_tests --login
#
# python run_sim_tests --q1 --login -k countdown --pdb
#
import pytest, time, pdb
from charcodes import KEY_ENTER, KEY_DOWN, KEY_UP, KEY_HOME, KEY_DELETE
from charcodes import KEY_ENTER, KEY_DOWN, KEY_UP, KEY_HOME
from ckcc_protocol.client import ColdcardDevice, CCProtocolPacker, CKCC_SIMULATOR_PATH
from run_sim_tests import ColdcardSimulator, clean_sim_data
@ -149,7 +154,7 @@ def _set_kill_key(device, val, is_Q):
if is_Q:
assert "press this key at any point during login" in story
else:
assert "press this key while the anti-phishing words are shown during login" in story
assert "press this key while the anti- phishing words are shown during login" in story
assert ("Best if this does not match the first number"
" of the second half of your PIN.") in story
@ -167,9 +172,11 @@ def _remap_pin(pin, key_map):
remap_pin += ch
return remap_pin
def _login(device, pin, is_Q, scrambled=False, mk4_kbtn=None):
def _login(device, pin, is_Q, scrambled=False, mk4_kbtn=None, num_failed=None):
orig_pin = pin
scr = _cap_screen(device)
if num_failed:
assert f"{num_failed} failures, {13-num_failed} tries left" in scr
if is_Q:
top = scr.split("\n")[0].split()
is_scrambled = len(top) == 10
@ -215,6 +222,7 @@ def _login(device, pin, is_Q, scrambled=False, mk4_kbtn=None):
_need_keypress(device, ch)
_press_select(device, is_Q)
@pytest.mark.parametrize("nick", [100*"$", "$", 10*"20"+ " "+"8080"+ " " + "XX"+ " "+ "YY"])
def test_set_nickname(nick, request):
is_Q = request.config.getoption('--Q')
@ -244,6 +252,7 @@ def test_set_nickname(nick, request):
assert nick == target
sim.stop()
def test_randomize_pin_keys(request):
is_Q = request.config.getoption('--Q')
clean_sim_data() # remove all from previous
@ -407,6 +416,64 @@ def test_terms_ok(request):
sim.stop()
@pytest.mark.parametrize("brick", [True, False])
def test_wrong_pin_input(request, brick):
is_Q = request.config.getoption('--Q')
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)
time.sleep(.1)
num_attmeptss = 13
for ii, i in enumerate(range(31, 43), start=1):
# pdb.set_trace()
pin = f"{i}-{i}"
scr_num_failed = (ii - 1) if ii > 1 else None
_login(device, pin, is_Q, num_failed=scr_num_failed)
time.sleep(.5)
title, story = _cap_story(device)
if ii > 4:
assert title == "WARNING"
assert pin in story # showing to user to double-check his input
assert "BRICKS ITSELF FOREVER" in story
assert f"{num_attmeptss - ii + 1} attempts left" in story
_press_select(device, is_Q)
time.sleep(.1)
title, story = _cap_story(device)
assert "WRONG PIN" in title
assert f"{num_attmeptss - ii} attempts left" in story
assert f"{ii} failure" in story
_press_select(device, is_Q)
time.sleep(.1)
if brick:
# one more wrong pin
_login(device, "91-11", is_Q, num_failed=12)
time.sleep(.5)
title, story = _cap_story(device)
assert "WARNING" == title
_press_select(device, is_Q)
time.sleep(.1)
title, story = _cap_story(device)
assert title == "I Am Brick!"
assert "After 13 failed PIN attempts this Coldcard is locked forever" in story
assert "no way to reset or recover the secure element" in story
assert "forever inaccessible" in story
assert "Restore your seed words onto a new Coldcard" in story
else:
_login(device, "22-22", is_Q, num_failed=12)
time.sleep(.5)
title, story = _cap_story(device)
assert "WARNING" == title
_press_select(device, is_Q)
time.sleep(.1)
m = _cap_menu(device)
assert "Ready To Sign" in m
sim.stop()
@pytest.mark.parametrize("nick", [None, "In trust we trust NOT"])
@pytest.mark.parametrize("randomize", [False, True])
@pytest.mark.parametrize("login_ctdwn", [None, " 5 minutes", "15 minutes"])

View File

@ -17,6 +17,7 @@ python run_sim_tests.py # same as with '-
python run_sim_tests.py -m all --onetime --veryslow # run all tests (cca 252 minutes)
python run_sim_tests.py -m test_multisig.py -k cosigning # run only tests that match expression from test_multisig.py
python run_sim_tests.py -m test_export.py --pdb # run only export tests and attach debugger
python run_sim_tests.py -m test_attended.py --q1 -w 6 --login # run attended test + all login tests
Onetime/veryslow tests are completely separated form the rest of the test suite.
@ -96,13 +97,11 @@ def is_ok(ec: ExitCode) -> bool:
return False
def _run_tests_with_simulator(test_module: str, simulator_args: List[str], pytest_marks: str,
pytest_k: str, pdb: bool, failed_first: bool, psbt2=False) -> ExitCode:
sim = ColdcardSimulator(args=simulator_args)
sim.start()
time.sleep(1)
def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: bool,
failed_first: bool, psbt2=False, is_Q=False) -> ExitCode:
cmd_list = [
"--cache-clear", "-m", pytest_marks, "--sim", test_module if test_module is not None else ""
"--cache-clear", "-m", pytest_marks, "--sim",
test_module if test_module is not None else ""
]
if pytest_k:
cmd_list += ["-k", pytest_k]
@ -112,28 +111,43 @@ def _run_tests_with_simulator(test_module: str, simulator_args: List[str], pytes
cmd_list.append("--ff")
if psbt2:
cmd_list.append("--psbt2")
if is_Q:
cmd_list.insert(0, "--Q") # only changes behavior in login_settings_test
exit_code = pytest.main(cmd_list)
sim.stop()
time.sleep(1)
clean_sim_data()
return pytest.main(cmd_list)
def _run_coldcard_tests(test_module: str, simulator_args: List[str], pytest_marks: str,
pytest_k: str, pdb: bool, failed_first: bool, psbt2=False,
is_Q=False) -> ExitCode:
if simulator_args:
sim = ColdcardSimulator(args=simulator_args)
sim.start()
time.sleep(1)
exit_code = _run_pytest_tests(test_module, pytest_marks, pytest_k, pdb,
failed_first, psbt2, is_Q)
if simulator_args:
sim.stop()
time.sleep(1)
clean_sim_data()
return exit_code
def run_tests_with_simulator(test_module=None, simulator_args=None, pytest_k=None, pdb=False,
failed_first=False, psbt2=False,
pytest_marks="not onetime and not veryslow and not manual"):
def run_coldcard_tests(test_module=None, simulator_args=None, pytest_k=None, pdb=False,
failed_first=False, psbt2=False, is_Q=False,
pytest_marks="not onetime and not veryslow and not manual"):
failed = []
exit_code = _run_tests_with_simulator(test_module, simulator_args, pytest_marks, pytest_k,
pdb, failed_first, psbt2=psbt2)
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_marks, pytest_k,
pdb, failed_first, psbt2, is_Q)
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_tests_with_simulator(failed_test, simulator_args, pytest_marks,
pytest_k, pdb, failed_first, psbt2=psbt2)
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args, pytest_marks,
pytest_k, pdb, failed_first, psbt2, is_Q)
exit_codes.append(exit_code_2)
if not is_ok(exit_code_2):
failed.append(failed_test)
@ -200,6 +214,8 @@ def main():
parser.add_argument("--onetime", action="store_true", default=False,
help="run tests marked as 'onetime'")
parser.add_argument("--veryslow", action="store_true", default=False,
help="run 'login_settings_tests.py'")
parser.add_argument("--login", action="store_true", default=False,
help="run tests marked as 'veryslow'")
parser.add_argument("--collect", type=str, metavar="MARK",
help="Collect marked test and print them to stdout")
@ -216,7 +232,7 @@ def main():
print(collect_marked_tests(args.collect))
return
if args.module is None and (args.onetime is False and args.veryslow is False):
if args.module is None and (args.onetime is False and args.veryslow is False and args.login is False):
args.module = ["all"]
DEFAULT_SIMULATOR_ARGS = ["--eff", "--set", "nfc=1"]
@ -262,9 +278,9 @@ def main():
if args.q1 and '--q1' not in test_args:
test_args.append('--q1')
ec, failed_tests = run_tests_with_simulator(test_module, simulator_args=test_args,
pytest_k=args.pytest_k, pdb=args.pdb,
failed_first=args.ff, psbt2=args.psbt2)
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)
result.append((test_module, ec, failed_tests))
print("Done", test_module)
print(80 * "=")
@ -272,10 +288,10 @@ def main():
# run veryslow is specified
if args.veryslow:
print("started veryslow tests")
ec, failed_tests = run_tests_with_simulator(test_module=None, pytest_marks="veryslow",
pytest_k=args.pytest_k, pdb=args.pdb,
simulator_args=DEFAULT_SIMULATOR_ARGS,
failed_first=args.ff, psbt2=args.psbt2)
ec, failed_tests = run_coldcard_tests(test_module=None, pytest_marks="veryslow",
pytest_k=args.pytest_k, pdb=args.pdb,
simulator_args=DEFAULT_SIMULATOR_ARGS,
failed_first=args.ff, psbt2=args.psbt2)
result.append(("veryslow", ec, failed_tests))
# run onetime is specified (each test against its own simulator)
@ -283,12 +299,19 @@ def main():
print("started onetime tests")
onetime_tests = collect_marked_tests("onetime")
for onetime_test in onetime_tests:
ec, failed_tests = run_tests_with_simulator(test_module=onetime_test, pdb=args.pdb,
failed_first=args.ff, pytest_marks="onetime",
simulator_args=DEFAULT_SIMULATOR_ARGS,
psbt2=args.psbt2)
ec, failed_tests = run_coldcard_tests(test_module=onetime_test, pdb=args.pdb,
failed_first=args.ff, pytest_marks="onetime",
simulator_args=DEFAULT_SIMULATOR_ARGS,
psbt2=args.psbt2)
result.append((f"onetime: {onetime_test}", ec, failed_tests))
if args.login:
print("start login settings tests")
ec, failed_tests = run_coldcard_tests(test_module="login_settings_tests.py", pdb=args.pdb,
failed_first=args.ff, pytest_k=args.pytest_k,
is_Q=True if args.q1 else False)
result.append((f"login_settings_tests", ec, failed_tests))
print("All done")
any_failed = False

View File

@ -21,7 +21,7 @@ from charcodes import KEY_NFC
@pytest.mark.bitcoind
@pytest.mark.parametrize('acct_num', [None, '0', '99', '123'])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
def test_export_core(way, dev, use_regtest, acct_num, pick_menu_item, goto_home, cap_story,
need_keypress, microsd_path, virtdisk_path, bitcoind_wallet, bitcoind_d_wallet,
enter_number, nfc_read_text, load_export, bitcoind, press_select):
@ -160,7 +160,7 @@ def test_export_core(way, dev, use_regtest, acct_num, pick_menu_item, goto_home,
#assert x['hdkeypath'] == f"m/84'/1'/{acct_num}'/0/%d" % (len(addrs)-1)
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
def test_export_wasabi(way, dev, pick_menu_item, goto_home, cap_story, press_select, microsd_path,
nfc_read_json, virtdisk_path, testnet, use_mainnet, load_export):
@ -199,7 +199,7 @@ def test_export_wasabi(way, dev, pick_menu_item, goto_home, cap_story, press_sel
@pytest.mark.parametrize('mode', [ "Classic P2PKH", "P2SH-Segwit", "Segwit P2WPKH"])
@pytest.mark.parametrize('acct_num', [ None, '0', '9897'])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
def test_export_electrum(way, dev, mode, acct_num, pick_menu_item, goto_home, cap_story, need_keypress,
microsd_path, nfc_read_json, virtdisk_path, use_mainnet, testnet, load_export,
@ -266,7 +266,7 @@ def test_export_electrum(way, dev, mode, acct_num, pick_menu_item, goto_home, ca
@pytest.mark.parametrize('acct_num', [ None, '99', '1236'])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
@pytest.mark.parametrize('app', [
("Generic JSON", "Generic Export"),
@ -351,7 +351,7 @@ def test_export_coldcard(way, dev, acct_num, app, pick_menu_item, goto_home, cap
else:
assert False
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
@pytest.mark.parametrize('acct_num', [None, '0', '99', '123'])
def test_export_unchained(way, dev, pick_menu_item, goto_home, cap_story, need_keypress, acct_num,
@ -403,7 +403,7 @@ def test_export_unchained(way, dev, pick_menu_item, goto_home, cap_story, need_k
assert node.hwif() == sk.hwif()
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
def test_export_public_txt(way, dev, pick_menu_item, goto_home, press_select, microsd_path,
addr_vs_path, virtdisk_path, nfc_read_text, cap_story, use_mainnet,
@ -546,7 +546,7 @@ def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home
press_cancel()
@pytest.mark.parametrize("chain", ["BTC", "XTN", "XRT"])
@pytest.mark.parametrize("way", ["sd", "vdisk", "nfc"])
@pytest.mark.parametrize("way", ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize("addr_fmt", [AF_P2WPKH, AF_P2WPKH_P2SH, AF_CLASSIC])
@pytest.mark.parametrize("acct_num", [None, 0, 1, (2 ** 31) - 1])
@pytest.mark.parametrize("int_ext", [True, False])