From 5b0cd6fc5ff8817f73ee2eae9ac453208aeb4622 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Tue, 7 Sep 2021 08:20:47 -0400 Subject: [PATCH] mk4 changes --- testing/conftest.py | 37 ++++++++++-- testing/devtest/backups.py | 2 +- testing/devtest/clear_seed.py | 2 +- testing/devtest/get-setting.py | 2 +- testing/devtest/get-settings.py | 2 +- testing/devtest/nvram.py | 2 +- testing/devtest/set_encoded_secret.py | 2 +- testing/devtest/set_raw_secret.py | 2 +- testing/devtest/set_seed.py | 2 +- testing/devtest/set_tprv.py | 2 +- testing/devtest/unit_psbt.py | 2 +- testing/devtest/wipe_ms.py | 2 +- testing/pytest.ini | 5 ++ testing/requirements.txt | 3 + testing/test_sign.py | 80 +++++++++++++++++++++++--- testing/test_unit.py | 81 +++++++++++++++++++++++++++ 16 files changed, 204 insertions(+), 24 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index 3f820ca1..aa618c59 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -20,7 +20,7 @@ def pytest_addoption(parser): parser.addoption("--manual", action="store_true", default=False, help="operator must press keys on real CC") - parser.addoption("--mk", default=3, help="Assume mark N hardware") + parser.addoption("--mk", default=4, help="Assume mark N hardware") parser.addoption("--duress", action="store_true", default=False, help="assume logged-in with duress PIN") @@ -64,8 +64,9 @@ def simulator(request): def sim_exec(dev): # run code in the simulator's interpretor - def doit(cmd): + def doit(cmd, binary=False): s = dev.send_recv(b'EXEC' + cmd.encode('utf-8')) + if binary: return s return s.decode('utf-8') if not isinstance(s, str) else s return doit @@ -574,7 +575,7 @@ def reset_seed_words(sim_exec, sim_execfile, simulator): def settings_set(sim_exec): def doit(key, val): - x = sim_exec("nvstore.settings.set('%s', %r)" % (key, val)) + x = sim_exec("settings.set('%s', %r)" % (key, val)) assert x == '' return doit @@ -583,7 +584,7 @@ def settings_set(sim_exec): def settings_get(sim_exec): def doit(key): - cmd = f"RV.write(repr(nvstore.settings.get('{key}')))" + cmd = f"RV.write(repr(settings.get('{key}')))" resp = sim_exec(cmd) assert 'Traceback' not in resp, resp return eval(resp) @@ -594,7 +595,7 @@ def settings_get(sim_exec): def settings_remove(sim_exec): def doit(key): - x = sim_exec("nvstore.settings.remove_key('%s')" % key) + x = sim_exec("settings.remove_key('%s')" % key) assert x == '' return doit @@ -1000,8 +1001,34 @@ def is_mark2(request): @pytest.fixture(scope='session') def is_mark3(request): + # better: ask it return int(request.config.getoption('--mk')) == 3 +@pytest.fixture(scope='session') +def is_mark4(request): + # better: ask it + return int(request.config.getoption('--mk')) == 4 + +@pytest.fixture() +def nfc_read(sim_exec): + def doit(): + rv = sim_exec('RV.write(NFC.dump_ndef())', binary=True) + if b'Traceback' in rv: raise pytest.fail(rv.decode('utf-8')) + return rv + return doit + +@pytest.fixture +def load_shared_mod(): + # load indicated file.py as a module + # from + def doit(name, path): + import importlib.util + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + return doit + # useful fixtures related to multisig from test_multisig import (import_ms_wallet, make_multisig, offer_ms_import, fake_ms_txn, make_ms_address, clear_ms, make_myself_wallet) diff --git a/testing/devtest/backups.py b/testing/devtest/backups.py index cecf9340..c8661855 100644 --- a/testing/devtest/backups.py +++ b/testing/devtest/backups.py @@ -10,7 +10,7 @@ from ubinascii import hexlify as b2a_hex from ubinascii import unhexlify as a2b_hex import ngu, ustruct -from nvstore import settings +from glob import settings if 1: # test file contents: completeness, syntax diff --git a/testing/devtest/clear_seed.py b/testing/devtest/clear_seed.py index a730f131..d2e6d053 100644 --- a/testing/devtest/clear_seed.py +++ b/testing/devtest/clear_seed.py @@ -3,7 +3,7 @@ # quickly main wipe seed; don't install anything new from glob import numpad, dis from pincodes import pa -from nvstore import settings +from glob import settings from pincodes import AE_SECRET_LEN, PA_IS_BLANK if not pa.is_secret_blank(): diff --git a/testing/devtest/get-setting.py b/testing/devtest/get-setting.py index ab67ae9d..fec81ff2 100644 --- a/testing/devtest/get-setting.py +++ b/testing/devtest/get-setting.py @@ -1,7 +1,7 @@ # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # import main -from nvstore import settings +from glob import settings from ujson import dumps RV.write(dumps(settings.get(main.SKEY))) diff --git a/testing/devtest/get-settings.py b/testing/devtest/get-settings.py index 047b5165..d113971b 100644 --- a/testing/devtest/get-settings.py +++ b/testing/devtest/get-settings.py @@ -1,6 +1,6 @@ # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -from nvstore import settings +from glob import settings from ujson import dumps RV.write(dumps(settings.current)) diff --git a/testing/devtest/nvram.py b/testing/devtest/nvram.py index 0d1a2fb1..54f7f028 100644 --- a/testing/devtest/nvram.py +++ b/testing/devtest/nvram.py @@ -11,7 +11,7 @@ from ubinascii import unhexlify as a2b_hex import ustruct from sflash import SF -from nvstore import SLOTS, settings +from glob import SLOTS, settings # reset whatever's there SF.chip_erase() diff --git a/testing/devtest/set_encoded_secret.py b/testing/devtest/set_encoded_secret.py index 75597b55..b49b74aa 100644 --- a/testing/devtest/set_encoded_secret.py +++ b/testing/devtest/set_encoded_secret.py @@ -5,7 +5,7 @@ from sim_settings import sim_defaults import stash, chains from h import b2a_hex from pincodes import pa -from nvstore import settings +from glob import settings from stash import SecretStash, SensitiveValues from utils import xfp2str diff --git a/testing/devtest/set_raw_secret.py b/testing/devtest/set_raw_secret.py index 10a7efe3..2c44270f 100644 --- a/testing/devtest/set_raw_secret.py +++ b/testing/devtest/set_raw_secret.py @@ -5,7 +5,7 @@ from sim_settings import sim_defaults import stash, chains from h import b2a_hex from pincodes import pa -from nvstore import settings +from glob import settings from stash import SecretStash, SensitiveValues from utils import xfp2str diff --git a/testing/devtest/set_seed.py b/testing/devtest/set_seed.py index c3317780..e8556412 100644 --- a/testing/devtest/set_seed.py +++ b/testing/devtest/set_seed.py @@ -5,7 +5,7 @@ from sim_settings import sim_defaults import stash, chains from h import b2a_hex from pincodes import pa -from nvstore import settings +from glob import settings import stash from seed import set_seed_value from utils import xfp2str diff --git a/testing/devtest/set_tprv.py b/testing/devtest/set_tprv.py index 944728a5..dfa104d8 100644 --- a/testing/devtest/set_tprv.py +++ b/testing/devtest/set_tprv.py @@ -6,7 +6,7 @@ from sim_settings import sim_defaults import stash, chains from h import b2a_hex from pincodes import pa -from nvstore import settings +from glob import settings from stash import SecretStash, SensitiveValues from utils import xfp2str, swab32 diff --git a/testing/devtest/unit_psbt.py b/testing/devtest/unit_psbt.py index 12570832..fb8519a8 100644 --- a/testing/devtest/unit_psbt.py +++ b/testing/devtest/unit_psbt.py @@ -10,7 +10,7 @@ from ubinascii import hexlify as b2a_hex from ubinascii import unhexlify as a2b_hex import ustruct -from nvstore import settings +from glob import settings from public_constants import MAX_TXN_LEN # load PSBT into simulated SPI Flash diff --git a/testing/devtest/wipe_ms.py b/testing/devtest/wipe_ms.py index 296a23c0..293b6125 100644 --- a/testing/devtest/wipe_ms.py +++ b/testing/devtest/wipe_ms.py @@ -1,7 +1,7 @@ # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # quickly clear all multisig wallets installed -from nvstore import settings +from glob import settings from ux import restore_menu if settings.get('multisig'): diff --git a/testing/pytest.ini b/testing/pytest.ini index 205da0e1..3037e818 100644 --- a/testing/pytest.ini +++ b/testing/pytest.ini @@ -5,3 +5,8 @@ markers = bitcoind: indicates local bitcoind (testnet) will be needed onetime: test cant be combined with any others, likely needs board reset veryslow: test takes more than 30 minutes realtime + +# this isn't working FIXME +filterwarnings = + ignore:.*DeprecationWarning.* + diff --git a/testing/requirements.txt b/testing/requirements.txt index 688d7d4b..47bbb905 100644 --- a/testing/requirements.txt +++ b/testing/requirements.txt @@ -10,3 +10,6 @@ onetimepass==1.0.1 # for QR scanning (pulls in numpy) zbar-py==1.0.4 + +nfcpy==1.0.3 +ndef==0.3.3 diff --git a/testing/test_sign.py b/testing/test_sign.py index 4e6c9c83..1b0e3c87 100644 --- a/testing/test_sign.py +++ b/testing/test_sign.py @@ -155,10 +155,27 @@ def test_speed_test(request, fake_txn, is_mark3, start_sign, end_sign, dev, need print(" Tx time: %.1f" % tx_time) print("Sign time: %.1f" % ready_time) +if 0: + # TODO: attempt to re-create the mega transaction: 5,569 inputs, one out + # see + # - how big woudl PSBT be? + # - not a great test case because so slow. + def test_mega_txn(fake_txn, is_mark4, start_sign, end_sign, dev): + if not is_mark4: + raise pytest.xfail('no way') + + psbt = fake_txn(5569, 1, dev.master_xpub) + + open('debug/mega.psbt', 'wb').write(psbt) + + _, txn = try_sign(psbt, accept=True, finalize=True) + + open('debug/mega.txn', 'wb').write(txn) + @pytest.mark.parametrize('segwit', [True, False]) @pytest.mark.parametrize('out_style', ADDR_STYLES) -def test_io_size(request, decode_with_bitcoind, fake_txn, is_mark3, +def test_io_size(request, decode_with_bitcoind, fake_txn, is_mark3, is_mark4, start_sign, end_sign, dev, segwit, out_style, accept = True): # try a bunch of different bigger sized txns @@ -170,14 +187,15 @@ def test_io_size(request, decode_with_bitcoind, fake_txn, is_mark3, # - only mk3 can do full amounts # - time on mk3, v4.0.0 firmware: 13 minutes - if not is_mark3: - num_in = 10 - num_out = 10 - else: + num_in = 10 + num_out = 10 + + if is_mark3: num_in = 20 num_out = 250 - - + elif is_mark4: + num_in = 250 + num_out = 2000 psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit, outstyles=[out_style]) @@ -196,6 +214,8 @@ def test_io_size(request, decode_with_bitcoind, fake_txn, is_mark3, signed = end_sign(accept, finalize=True) + open('debug/signed.txn', 'wb').write(signed) + decoded = decode_with_bitcoind(signed) #print("Bitcoin code says:", end=''); pprint(decoded) @@ -1170,7 +1190,7 @@ def test_txid_calc(num_ins, fake_txn, try_sign, dev, segwit, decode_with_bitcoin title, story = cap_story() assert '0' in story assert 'TXID' in title, story - txid = story.strip() + txid = story.split()[0] if 1: # compare to PyCoin @@ -1395,4 +1415,48 @@ def test_value_render(units, fake_txn, start_sign, cap_story, settings_set, sett settings_remove('rz') + +@pytest.mark.parametrize('num_outs', [ 1, 20, 250]) +def test_nfc_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_story): + # Read signing result over NFC, decode it. + import ndef + from hashlib import sha256 + psbt = fake_txn(1, num_outs) + orig, result = try_sign(psbt, accept=True, finalize=True) + + too_big = len(result) > 8000 + + if too_big: assert num_outs > 100 + if num_outs > 100: assert too_big + + time.sleep(.1) + title, story = cap_story() + assert 'TXID' in title, story + txid = a2b_hex(story.split()[0]) + assert 'Press 3' in story + need_keypress('3') + + if too_big: + title, story = cap_story() + assert 'is too large' in story + return + + contents = nfc_read() + #need_keypress('x') + + #print("contents = " + B2A(contents)) + for got in ndef.message_decoder(contents): + if got.type == 'urn:nfc:wkt:T': + assert 'Transaction' in got.text + assert b2a_hex(txid).decode() in got.text + elif got.type == 'urn:nfc:ext:bitcoin.org:txid': + assert got.data == txid + elif got.type == 'urn:nfc:ext:bitcoin.org:txn': + assert got.data == result + elif got.type == 'urn:nfc:ext:bitcoin.org:sha256': + assert got.data == sha256(result).digest() + else: + raise ValueError(got.type) + + # EOF diff --git a/testing/test_unit.py b/testing/test_unit.py index 43a06459..b04d2c13 100644 --- a/testing/test_unit.py +++ b/testing/test_unit.py @@ -239,5 +239,86 @@ def test_match_deriv_path(patterns, paths, answers, sim_exec): rv = sim_exec(cmd) assert rv == str(bool(ans)) +@pytest.mark.parametrize('case', range(6)) +def test_ndef(case, load_shared_mod): + # NDEF unit tests + import ndef + from struct import pack, unpack + from binascii import b2a_hex + + def get_body(efile): + # unwrap CC_FILE and cruft + assert efile[-1] == 0xfe + assert efile[0] == 0xE2 + st = len(cc_ndef.CC_FILE) + if efile[st] == 0xff: + xl = unpack('>H', efile[st+1:st+3])[0] + st += 3 + else: + xl = efile[st] + st += 1 + body = efile[st:-1] + assert len(body) == xl + return body + + def decode(msg): + return list(ndef.message_decoder(get_body(msg))) + + cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py') + n = cc_ndef.ndefMaker() + + if case == 0: + n.add_text("Hello world") + + got, = decode(n.bytes()) + assert got.type == 'urn:nfc:wkt:T' + assert got.text == 'Hello world' + assert got.language == 'en' + assert got.encoding == 'UTF-8' + + elif case == 1: + n.add_text("Hello world") + n.add_url("store.coinkite.com/store/coldcard") + + txt,url = decode(n.bytes()) + assert txt.text == 'Hello world' + + assert url.type == 'urn:nfc:wkt:U' + assert url.uri == 'https://store.coinkite.com/store/coldcard' == url.iri + + elif case == 2: + hx = b2a_hex(bytes(range(32))) + n.add_text("Title") + n.add_custom('bitcoin.org:sha256', hx) + + txt,sha = decode(n.bytes()) + assert txt.text == 'Title' + assert sha.data == hx + + elif case == 3: + psbt = b'psbt\xff' + bytes(5000) + n.add_text("Title") + n.add_custom('bitcoin.org:psbt', psbt) + n.add_text("Footer") + + txt,p,ft = decode(n.bytes()) + assert txt.text == 'Title' + assert ft.text == 'Footer' + assert p.data == psbt + assert p.type == 'urn:nfc:ext:bitcoin.org:psbt' + + elif case == 4: + hx = b2a_hex(bytes(range(32))) + n.add_custom('bitcoin.org:txid', hx) + got, = decode(n.bytes()) + assert got.type == 'urn:nfc:ext:bitcoin.org:txid' + assert got.data == hx + + elif case == 5: + hx = bytes(2000) + n.add_custom('bitcoin.org:txn', hx) + got, = decode(n.bytes()) + assert got.type == 'urn:nfc:ext:bitcoin.org:txn' + assert got.data == hx # EOF