def test_backup_accept(dev, need_keypress): time.sleep(0.050) r = dev.send_recv(CCProtocolPacker.start_backup()) assert r == None need_keypress('y') while 1: if dev.is_simulator: # work our way thru the password quiz... eventually pressing '1' will work. need_keypress('1') time.sleep(0.10) done = dev.send_recv(CCProtocolPacker.get_backup_file(), timeout=5000) if done: break assert len(done) == 2, done ll, sha = done assert ll > 500 assert len(sha) == 32 result = dev.download_file(ll, sha, file_number=0) assert result[0:2] == b'7z' assert len(set(result)) > 200
def test_b9p_vectors(dev, set_seed_words, need_keypress, vector, pw='RoZert'[::-1].upper()): # Test all BIP39 vectors. Slow. _, words, cooked, xprv = vector seed = Mnemonic.to_seed(words, passphrase=pw) assert seed == a2b_hex(cooked) set_seed_words(words) dev.send_recv(CCProtocolPacker.bip39_passphrase(pw), timeout=None) need_keypress('y') xpub = None while xpub == None: time.sleep(0.050) xpub = dev.send_recv(CCProtocolPacker.get_passphrase_done(), timeout=None) # check our math (ignore testnet vs. mainnet) got = BIP32Node.from_wallet_key(xpub) exp = BIP32Node.from_wallet_key(xprv) assert got.public_pair() == exp.public_pair()
def doit(accept=True, in_psbt=None, finalize=False): if accept != None: need_keypress('y' if accept else 'x') if accept == False: with pytest.raises(CCUserRefused): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) return else: done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) assert len(done) == 2 resp_len, chk = done psbt_out = dev.download_file(resp_len, chk) if not finalize: if in_psbt: assert BasicPSBT().parse(in_psbt) == BasicPSBT().parse(psbt_out) else: from pycoin.tx.Tx import Tx # parse it res = psbt_out assert res[0:4] != b'psbt' t = Tx.from_bin(res) assert t.version in [1, 2] return psbt_out
def test_show_p2sh_addr(dev, hsm_reset, start_hsm, change_hsm, make_myself_wallet, addr_vs_path): # MULTISIG addrs from test_multisig import HARD, make_redeem M = 4 pm = lambda i: [HARD(45), i, 0,0] # can't amke ms wallets inside HSM mode hsm_reset() keys, _ = make_myself_wallet(M) # slow AF permit = ['p2sh', 'm/73'] start_hsm(DICT(share_addrs=permit)) scr, pubkeys, xfp_paths = make_redeem(M, keys, path_mapper=pm) assert len(scr) <= 520, "script too long for standard!" got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address( M, xfp_paths, scr, addr_fmt=AF_P2WSH)) addr_vs_path(got_addr, addr_fmt=AF_P2WSH, script=scr) # turn it off; p2sh must be explicitly allowed for allow in ['m', 'any']: change_hsm(DICT(share_addrs=[allow])) dev.send_recv(CCProtocolPacker.show_address('m', AF_CLASSIC)) with pytest.raises(CCProtoError) as ee: got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address( M, xfp_paths, scr, addr_fmt=AF_P2WSH)) assert 'Not allowed in HSM mode' in str(ee)
def test_sign_msg_good(dev, need_keypress, msg, path, addr_fmt, addr_vs_path): msg = msg.encode('ascii') dev.send_recv(CCProtocolPacker.sign_message(msg, path, addr_fmt=addr_fmt), timeout=None) need_keypress('y') done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None) assert len(done) == 2, done addr, raw = done sig = str(b64encode(raw), 'ascii').replace('\n', '') assert 40 <= len(raw) <= 65 if addr_fmt != AF_CLASSIC: # TODO # - need bech32 decoder here # - pycoin can't do signature decode if addr_fmt & AFC_BECH32: assert '1' in addr return # check expected addr was used sk = addr_vs_path(addr, path, addr_fmt) # verify signature assert verify_message(sk, sig, message=msg.decode('ascii')) == True assert verify_message(addr, sig, message=msg.decode('ascii')) == True
def xxx_test_sign_truncated(dev): ll, sha = dev.upload_file(open('data/truncated.psbt', 'rb').read()) dev.send_recv(CCProtocolPacker.sign_transaction(ll, sha)) with pytest.raises(CCProtoError): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)
def test_b39p_refused(dev, need_keypress, pw='testing 123'): # user can refuse the passphrase (cancel) dev.send_recv(CCProtocolPacker.bip39_passphrase(pw), timeout=None) need_keypress('x') with pytest.raises(CCUserRefused): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_passphrase_done(), timeout=None)
def test_upload_fails(dev): # incorrect file upload cases data = b'3' * 60 with pytest.raises(CCProtoError): # misaligned v = dev.send_recv(CCProtocolPacker.upload(23, 23, data)) with pytest.raises(CCProtoError): # bad position v = dev.send_recv(CCProtocolPacker.upload(1000, 3, data))
def doit(data, pkt_len=2048): from hashlib import sha256 import os for pos in range(0, len(data), pkt_len): v = dev.send_recv( CCProtocolPacker.upload(pos, len(data), data[pos:pos + pkt_len])) assert v == pos chk = dev.send_recv(CCProtocolPacker.sha256()) assert chk == sha256(data[0:pos + pkt_len]).digest(), 'bad hash'
def test_sign_msg_refused(dev, need_keypress, msg=b'testing 123', path='m'): # user can refuse to sign (cancel) dev.send_recv(CCProtocolPacker.sign_message(msg, path), timeout=None) need_keypress('x') with pytest.raises(CCUserRefused): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None)
def test_backup_refuse(dev, need_keypress): time.sleep(0.050) r = dev.send_recv(CCProtocolPacker.start_backup()) assert r == None need_keypress('x') with pytest.raises(CCUserRefused): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_backup_file())
def test_sign1(dev, need_keypress, finalize): in_psbt = a2b_hex(open('data/p2pkh-in-scriptsig.psbt', 'rb').read()) ll, sha = dev.upload_file(in_psbt) dev.send_recv(CCProtocolPacker.sign_transaction(ll, sha, finalize)) #need_keypress('y') with pytest.raises(CCProtoError) as ee: while dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) == None: pass assert 'None of the keys' in str(ee)
def test_upload_short(dev, data_len): # upload a few really short files from hashlib import sha256 data = b'a' * data_len v = dev.send_recv(CCProtocolPacker.upload(0, len(data), data)) assert v == 0 chk = dev.send_recv(CCProtocolPacker.sha256()) assert chk == sha256(data).digest(), 'bad hash' # clear screen / test a degerate case dev.send_recv(CCProtocolPacker.upload(256, 256, b''))
def test_storage_locker(package, count, start_hsm, dev): # read and write (limited) of storage locker. policy = DICT(set_sl=package, allow_sl=count) start_hsm(policy) for t in range(count+3): if t < count: got = dev.send_recv(CCProtocolPacker.get_storage_locker(), timeout=None) assert got == package.encode('ascii') else: with pytest.raises(CCProtoError) as ee: got = dev.send_recv(CCProtocolPacker.get_storage_locker(), timeout=None) assert 'consumed' in str(ee)
def doit(refuse, *args, **kws): tt = kws.pop('timeout', None) dev.send_recv(CCProtocolPacker.sign_message(*args, **kws), timeout=tt) try: done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=tt) assert len(done) == 2 assert refuse == None, "signing didn't fail, but expected to" except CCUserRefused: msg = hsm_status().last_refusal assert refuse != None, "should not have been refused: " + msg assert refuse in msg
def wait_til_signed(dev): result = None while result == None: time.sleep(0.050) result = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) return result
def test_upload_long(dev, pkt_len, count=5, data=None): # upload a larger "file" from hashlib import sha256 import os data = data or os.urandom(pkt_len * count) for pos in range(0, len(data), pkt_len): v = dev.send_recv(CCProtocolPacker.upload(pos, len(data), data[pos:pos+pkt_len])) assert v == pos chk = dev.send_recv(CCProtocolPacker.sha256()) assert chk == sha256(data[0:pos+pkt_len]).digest(), 'bad hash' # clear screen / test a degerate case dev.send_recv(CCProtocolPacker.upload(256, 256, b''))
def test_show_addr_displayed(dev, need_keypress, addr_vs_path, path, addr_fmt, cap_story, show_qr, cap_screen_qr): time.sleep(0.1) addr = dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None) time.sleep(0.1) title, story = cap_story() #need_keypress('x') # check expected addr was used addr_vs_path(addr, path, addr_fmt) print('addr_fmt = 0x%x' % addr_fmt) assert title == 'Address:' assert path in story assert addr in story assert addr in story.split('\n') if show_qr: need_keypress('4') time.sleep(0.1) qr = cap_screen_qr() assert qr == addr or qr == addr.upper()
def test_speed_test(request, fake_txn, is_mark3, start_sign, end_sign, dev, need_keypress): import time # measure time to sign a larger txn if is_mark3: num_in = 20 num_out = 250 else: num_in = 9 num_out = 100 psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=True) open('debug/speed.psbt', 'wb').write(psbt) dt = time.time() start_sign(psbt, finalize=False) tx_time = time.time() - dt need_keypress('y', timeout=None) dt = time.time() done = None while done == None: time.sleep(0.05) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) ready_time = time.time() - dt print(" Tx time: %.1f" % tx_time) print("Sign time: %.1f" % ready_time)
def doit(policy): try: # on simulator, can read screen and provide keystrokes cap_story = request.getfixturevalue('cap_story') except: # real hardware cap_story = None # send policy, start it, approve it data = json.dumps(policy).encode('ascii') ll, sha = dev.upload_file(data) assert ll == len(data) dev.send_recv(CCProtocolPacker.hsm_start(ll, sha)) if cap_story: # capture explanation given user time.sleep(.2) title, body = cap_story() assert title == "Start HSM?" if cap_story: # approve it need_keypress('y') time.sleep(.1) title, body2 = cap_story() assert 'Last chance' in body2 ll = body2.split('\n')[-1] assert ll.startswith("Press ") ch = ll[6] need_keypress(ch) time.sleep(.100) j = hsm_status() assert j.active == True if 'summary' in j: assert not body or j.summary in body else: # do keypresses blindly need_keypress('y') time.sleep(.1) for ch in '12346': need_keypress(ch, timeout=10000) # needs bless firmware step; can take >10 seconds? j = hsm_status(10000) assert j.active == True if 0: for retry in range(30): time.sleep(1) #try: except: pass assert j.active == True return j
def test_show_addr_usb(dev, need_keypress, addr_vs_path, path, addr_fmt,): addr = dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None) need_keypress('y') # check expected addr was used addr_vs_path(addr, path, addr_fmt)
def test_version(dev): # read the version, yawn. v = dev.send_recv(CCProtocolPacker.version()) assert '\n' in v date, label, bl, *extras = v.split('\n') assert '-' in date assert '.' in label assert '.' in bl print("date=%s" % date)
def doit(pw): # reset from previous runs words = reset_seed_words() # optimization if pw == '': return simulator_fixed_xfp print(f"Setting BIP39 pw: {pw}") dev.send_recv(CCProtocolPacker.bip39_passphrase(pw), timeout=None) if pw: time.sleep(0.050) title, body = cap_story() assert pw not in body # verify display of passphrase need_keypress('2') time.sleep(0.050) title, body = cap_story() assert pw in body need_keypress('y') done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_passphrase_done(), timeout=None) xpub = done assert xpub[1:4] == 'pub' got = BIP32Node.from_wallet_key(xpub) # what it should be seed = Mnemonic.to_seed(words, passphrase=pw) expect = BIP32Node.from_master_secret(seed) assert got.public_pair() == expect.public_pair() xfp, = struct.unpack('I', expect.fingerprint()) return xfp
def doit(timeout=1000): txt = dev.send_recv(CCProtocolPacker.hsm_status(), timeout=timeout) assert txt[0] == '{' assert txt[-1] == '}' j = json.loads(txt, object_hook=DICT) assert j.active in {True, False} if 'users' in j or 'wallets' in j: assert 'users' in j assert j.active or ('wallets' in j) assert 'chain' in j return j
def test_sign_msg_good(dev, need_keypress, master_xpub, msg, path, addr_fmt, addr_vs_path): msg = msg.encode('ascii') dev.send_recv(CCProtocolPacker.sign_message(msg, path, addr_fmt=addr_fmt), timeout=None) need_keypress('y') done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None) assert len(done) == 2, done addr, raw = done sig = str(b64encode(raw), 'ascii').replace('\n', '') assert 40 <= len(raw) <= 65 if addr_fmt != AF_CLASSIC: # TODO # - need bech32 decoder here # - pycoin can't do signature decode if addr_fmt & AFC_BECH32: assert '1' in addr return if "'" not in path and 'p' not in path: # check expected addr was used mk = BIP32Node.from_wallet_key(master_xpub) sk = mk.subkey_for_path(path[2:]) addr_vs_path(addr, path, addr_fmt) # verify signature assert verify_message(sk, sig, message=msg.decode('ascii')) == True else: # just verify signature assert verify_message(addr, sig, message=msg.decode('ascii')) == True
def test_low_R_cases(msg, num_iter, expect, dev, set_seed_words, use_mainnet, need_keypress): # Thanks to @craigraw of Sparrow for this test case, copied from: # <https://github.com/sparrowwallet/drongo/blob/master/src/test/java/com/sparrowwallet/drongo/crypto/ECKeyTest.java> set_seed_words( 'absent essay fox snake vast pumpkin height crouch silent bulb excuse razor' ) use_mainnet() path = "m/44'/0'/0'/0/0" # first address, P2PKH addr_fmt = AF_CLASSIC #addr = dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None) #assert addr == '14JmU9a7SzieZNEtBnsZo688rt3mGrw6hr' msg = msg.encode('ascii') dev.send_recv(CCProtocolPacker.sign_message(msg, path, addr_fmt=addr_fmt), timeout=None) need_keypress('y') done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None) assert len(done) == 2, done got_addr, raw = done assert got_addr == '14JmU9a7SzieZNEtBnsZo688rt3mGrw6hr' assert 40 <= len(raw) <= 65 sig = str(b64encode(raw), 'ascii').replace('\n', '') if num_iter != 1: # I have gotten these cases to pass, but I didn't want to keep the code # that grinded for low R in message signing... Ok for txn signing, but # needless delay for message signing. raise pytest.xfail('no code') assert sig == expect
def test_show_addr_usb(dev, need_keypress, addr_vs_path, path, addr_fmt, is_simulator): addr = dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None) need_keypress('y') if "'" in path and not is_simulator(): raise pytest.skip('we cant confirm hardened-derived keypaths') # check expected addr was used addr_vs_path(addr, path, addr_fmt)
def test_xpub_sharing(dev, start_hsm, change_hsm, addr_fmt=AF_CLASSIC): # xpub sharing, but only at certain derivations # - note 'm' is always shared permit = ['m', 'm/73', "m/43/44/*'", 'm/1p/3h/4/5/6/7'] block = ['m/72', 'm/43/44/99', permit[-1][:-2]] policy = DICT(share_xpubs=permit) start_hsm(policy) for p in permit: p = p.replace('*', '99') xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) for p in block: with pytest.raises(CCProtoError) as ee: xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) assert 'Not allowed in HSM mode' in str(ee) policy = DICT(share_xpubs=['any']) change_hsm(policy) for p in block + permit: p = p.replace('*', '99') xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) # default is block all but 'm' policy = DICT() change_hsm(policy) for p in block + permit: if p == 'm': continue p = p.replace('*', '99') with pytest.raises(CCProtoError) as ee: xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) assert 'Not allowed in HSM mode' in str(ee) # 'm' always works xpub = dev.send_recv(CCProtocolPacker.get_xpub('m'), timeout=5000) assert xpub[0:4] == 'tpub'
def test_encryption(dev): "Setup session key and test link encryption works" #dev = ColdcardDevice(sn=force_serial, encrypt=False) #dev.start_encryption() print("Session key: " + str(b2a_hex(dev.session_key), 'utf')) for blen in [4, 8, 60, 128, 256, MAX_MSG_LEN - 4]: rb = dev.send_recv(CCProtocolPacker.ping(bytes(blen)), encrypt=1) assert set(rb) == {0} and len(rb) == blen rb = dev.send_recv(CCProtocolPacker.ping(bytes(blen)), encrypt=0) assert set(rb) == {0} and len(rb) == blen was = dev.session_key assert len(was) == 32 assert len(set(was)) > 8 # rekey dev.start_encryption() assert dev.session_key != was assert len(set(dev.session_key)) > 8
def doit(k): if hasattr(dev.dev, 'pipe'): dev.send_recv(CCProtocolPacker.sim_keypress(k.encode('ascii'))) else: # try to use debug interface to simulate the press # XXX for some reason, picocom must **already** be running for this to work. # - otherwise, this locks up devs = list(glob.glob('/dev/tty.usbmodem*')) if len(devs) == 1: with open(devs[0], 'wb', 0) as fd: fd.write(k.encode('ascii')) else: # need actual user interaction print("NOW, on the Coldcard, press key: %s" % k)