def test_sign_msg_microsd_good(sign_on_microsd, master_xpub, msg, path, addr_vs_path, addr_fmt=AF_CLASSIC): # cases we expect to work sig, addr = sign_on_microsd(msg, path) raw = b64decode(sig) 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 path is None: path = 'm' 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) == True else: # just verify signature assert verify_message(addr, sig, message=msg) == True
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 mitm_verify(self, sig, expected_xpub): # First try with Pycoin try: from pycoin.key.BIP32Node import BIP32Node from pycoin.contrib.msg_signing import verify_message from pycoin.encoding import from_bytes_32 from base64 import b64encode mk = BIP32Node.from_wallet_key(expected_xpub) return verify_message(mk, b64encode(sig), msg_hash=from_bytes_32(self.session_key)) except ImportError: pass # If Pycoin is not available, do it using ecdsa from ecdsa import BadSignatureError, SECP256k1, VerifyingKey pubkey, chaincode = decode_xpub(expected_xpub) vk = VerifyingKey.from_string(get_pubkey_string(pubkey), curve=SECP256k1) try: ok = vk.verify_digest(sig[1:], self.session_key) except BadSignatureError: ok = False return ok
def test_sign_msg_microsd_good(sign_on_microsd, msg, path, addr_vs_path, addr_fmt): # cases we expect to work sig, addr = sign_on_microsd(msg, path) raw = b64decode(sig) 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 path is None: path = 'm' # check expected addr was used sk = addr_vs_path(addr, path, addr_fmt) if addr_fmt == AF_P2WPKH: assert addr.startswith('tb1q') # verify signature assert verify_message(sk, sig, message=msg) == True
def test_against_myself(): """ Test code that verifies against ourselves only. Useful but not so great. """ from pycoin.contrib.msg_signing import (parse_signed_message, sign_message, verify_message) from pycoin.encoding import bitcoin_address_to_hash160_sec_with_prefix from pycoin.encoding import wif_to_tuple_of_secret_exponent_compressed for wif, right_addr in [ ('L4gXBvYrXHo59HLeyem94D9yLpRkURCHmCwQtPuWW9m6o1X8p8sp', '1LsPb3D1o1Z7CzEt1kv5QVxErfqzXxaZXv'), ('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', '1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN'), ]: se, comp = wif_to_tuple_of_secret_exponent_compressed(wif) k = Key(secret_exponent=se, is_compressed=comp) assert k.address() == right_addr vk = Key(public_pair=k.public_pair(), is_compressed=comp) assert vk.address() == right_addr h160, pubpre = bitcoin_address_to_hash160_sec_with_prefix(right_addr) vk2 = Key(hash160=h160) assert vk2.address() == right_addr for i in range(1, 30, 10): msg = 'test message %s' % ('A' * i) sig = sign_message(k, msg, verbose=1) assert right_addr in sig # check parsing works m, a, s = parse_signed_message(sig) assert m == msg, m assert a == right_addr, a sig2 = sign_message(k, msg, verbose=0) assert sig2 in sig, (sig, sig2) assert s == sig2, s ok = verify_message(k, sig2, msg) assert ok ok = verify_message(k, sig2.encode('ascii'), msg) assert ok
def test_against_myself(): """ Test code that verifies against ourselves only. Useful but not so great. """ from pycoin.contrib.msg_signing import ( parse_signed_message, sign_message, verify_message) from pycoin.encoding import bitcoin_address_to_hash160_sec_with_prefix from pycoin.encoding import wif_to_tuple_of_secret_exponent_compressed for wif, right_addr in [ ('L4gXBvYrXHo59HLeyem94D9yLpRkURCHmCwQtPuWW9m6o1X8p8sp', '1LsPb3D1o1Z7CzEt1kv5QVxErfqzXxaZXv'), ('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', '1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN'), ]: se, comp = wif_to_tuple_of_secret_exponent_compressed(wif) k = Key(secret_exponent=se, is_compressed=comp) assert k.address() == right_addr vk = Key(public_pair=k.public_pair(), is_compressed=comp) assert vk.address() == right_addr h160, pubpre = bitcoin_address_to_hash160_sec_with_prefix(right_addr) vk2 = Key(hash160=h160) assert vk2.address() == right_addr for i in range(1, 30, 10): msg = 'test message %s' % ('A'*i) sig = sign_message(k, msg, verbose=1) assert right_addr in sig # check parsing works m, a, s = parse_signed_message(sig) assert m == msg, m assert a == right_addr, a sig2 = sign_message(k, msg, verbose=0) assert sig2 in sig, (sig, sig2) assert s == sig2, s ok = verify_message(k, sig2, msg) assert ok ok = verify_message(k, sig2.encode('ascii'), msg) assert ok
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 mitm_verify(self, sig, expected_xpub): # replace this with your own library, as needed. try: from pycoin.key.BIP32Node import BIP32Node from pycoin.contrib.msg_signing import verify_message from pycoin.encoding import from_bytes_32 from base64 import b64encode except ImportError: raise RuntimeError("Missing pycoin for signature checking") mk = BIP32Node.from_wallet_key(expected_xpub) ok = verify_message(mk, b64encode(sig), msg_hash=from_bytes_32(self.session_key)) return ok
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): # create a TXN which has change outputs, which shouldn't be shown to user, and also not fail. xp = dev.master_xpub 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) # should be able to sign, but get warning if not visualized: start_sign(psbt, False) time.sleep(.1) title, story = cap_story() print(repr(story)) assert title == "OK TO SEND?" else: # use new feature to have Coldcard return the 'visualization' of transaction start_sign(psbt, False, stxn_flags=visualized) story = end_sign(accept=None, expect_txn=False) story = story.decode('ascii') if (visualized & STXN_SIGNED): # last line should be signature, using 'm' over the rest from pycoin.contrib.msg_signing import verify_message from pycoin.key.BIP32Node import BIP32Node #def verify_message(key_or_address, signature, message=None, msg_hash=None, netcode=None): assert story[-1] == '\n' last_nl = story[:-1].rindex('\n') msg, sig = story[0:last_nl+1], story[last_nl:] wallet = BIP32Node.from_wallet_key(master_xpub) assert verify_message(wallet, sig, message=msg) == True story = msg assert 'Network fee' in story if couts < num_outs: assert '- to address -' in story else: assert 'Consolidating' in story if couts == 1: assert "- to address -" in story else: assert "- to addresses -" in story val, addrs = parse_change_back(story) assert val > 0 # hard to calc here assert len(addrs) == couts if out_style == 'p2pkh': assert all((i[0] in 'mn') for i in addrs) elif out_style == 'p2wpkh': assert set(i[0:4] for i in addrs) == {'tb1q'} elif out_style == 'p2wpkh-p2sh': assert set(i[0] for i in addrs) == {'2'}
def test_msg_parse(): """ Test against real-world signatures found in the wild. """ from pycoin.contrib.msg_signing import parse_signed_message, verify_message from pycoin.key import Key # Output from brainwallet in "multibit" mode. multibit = ''' -----BEGIN BITCOIN SIGNED MESSAGE----- This is an example of a signed message. -----BEGIN BITCOIN SIGNATURE----- Version: Bitcoin-qt (1.0) Address: 1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN HCT1esk/TWlF/o9UNzLDANqsPXntkMErf7erIrjH5IBOZP98cNcmWmnW0GpSAi3wbr6CwpUAN4ctNn1T71UBwSc= -----END BITCOIN SIGNATURE----- ''' m, a, s = parse_signed_message(multibit) assert m == 'This is an example of a signed message.' assert a == '1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN' assert s == ('HCT1esk/TWlF/o9UNzLDANqsPXntkMErf7erIrjH5IBOZ' 'P98cNcmWmnW0GpSAi3wbr6CwpUAN4ctNn1T71UBwSc=') ok = verify_message(a, s, m, netcode='BTC') assert ok # Sampled from: https://www.bitrated.com/u/Bit2c.txt on Sep 3/2014 bit2c = '''\ Username: Bit2c Public key: 0396267072e597ad5d043db7c73e13af84a77a7212871f1aade607fb0f2f96e1a8 Public key address: 15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej URL: https://www.bitrated.com/u/Bit2c -----BEGIN BITCOIN SIGNED MESSAGE----- We will try to contact both parties to gather information and evidence, and do my best to make rightful judgement. Evidence may be submitted to us on https://www.bit2c.co.il/home/contact or in a private message to [email protected] or in any agreed way. https://www.bit2c.co.il -----BEGIN SIGNATURE----- 15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej H2utKkquLbyEJamGwUfS9J0kKT4uuMTEr2WX2dPU9YImg4LeRpyjBelrqEqfM4QC8pJ+hVlQgZI5IPpLyRNxvK8= -----END BITCOIN SIGNED MESSAGE----- ''' m, a, s = parse_signed_message(bit2c) assert a == '15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej' assert s == ('H2utKkquLbyEJamGwUfS9J0kKT4uuMTEr2WX2dPU9YI' 'mg4LeRpyjBelrqEqfM4QC8pJ+hVlQgZI5IPpLyRNxvK8=') ok = verify_message(a, s, m, netcode='BTC') assert ok # testnet example # Sampled from: http://testnet.bitrated.com/u/bearbin.txt on Sep 3/2014 # NOTE: Testnet3 bearbin = '''\ Username: bearbin Public key: 03fc594c16779054fc5e119c309215c1f40f2ce104b0169cddeb6d20445bd28f67 Public key address: n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM URL: http://testnet.bitrated.com/u/bearbin -----BEGIN BITCOIN SIGNED MESSAGE----- Contact ----------- [email protected] - Email or hangouts (text only). /u/bearbin on reddit (slow response, not preferred for use with the service, just for contact). Resolution Guidelines: ----------------------------- * Evidence is needed. (e.g. pictures w/ proof that it's you). * If anybody fails to respond, money goes to the other person after 2 weeks. * Additional terms available on request. Pricing ---------- * 0.7% Min 0.003 Max 0.15 * Payment in advance. -----BEGIN SIGNATURE----- n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM IEackZgifpBJs3SqQQ6leUwzvakTZgUKTDuCCn6rVMOQgHlIEzWSYZGQu2H+1chvu68uutzt04cGmsHy/kRIaEc= -----END BITCOIN SIGNED MESSAGE----- ''' m, a, s = parse_signed_message(bearbin) assert a == 'n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM' assert s == ('IEackZgifpBJs3SqQQ6leUwzvakTZgUKTDuCCn6rVMOQgH' 'lIEzWSYZGQu2H+1chvu68uutzt04cGmsHy/kRIaEc=') ok = verify_message(a, s, m, netcode='XTN') assert ok
def test_msg_parse(): """ Test against real-world signatures found in the wild. """ from pycoin.contrib.msg_signing import parse_signed_message, verify_message from pycoin.key import Key # Output from brainwallet in "multibit" mode. multibit = """ -----BEGIN BITCOIN SIGNED MESSAGE----- This is an example of a signed message. -----BEGIN BITCOIN SIGNATURE----- Version: Bitcoin-qt (1.0) Address: 1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN HCT1esk/TWlF/o9UNzLDANqsPXntkMErf7erIrjH5IBOZP98cNcmWmnW0GpSAi3wbr6CwpUAN4ctNn1T71UBwSc= -----END BITCOIN SIGNATURE----- """ m, a, s = parse_signed_message(multibit) assert m == "This is an example of a signed message." assert a == "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN" assert s == ("HCT1esk/TWlF/o9UNzLDANqsPXntkMErf7erIrjH5IBOZ" "P98cNcmWmnW0GpSAi3wbr6CwpUAN4ctNn1T71UBwSc=") ok = verify_message(a, s, m, netcode="BTC") assert ok # Sampled from: https://www.bitrated.com/u/Bit2c.txt on Sep 3/2014 bit2c = """\ Username: Bit2c Public key: 0396267072e597ad5d043db7c73e13af84a77a7212871f1aade607fb0f2f96e1a8 Public key address: 15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej URL: https://www.bitrated.com/u/Bit2c -----BEGIN BITCOIN SIGNED MESSAGE----- We will try to contact both parties to gather information and evidence, and do my best to make rightful judgement. Evidence may be submitted to us on https://www.bit2c.co.il/home/contact or in a private message to [email protected] or in any agreed way. https://www.bit2c.co.il -----BEGIN SIGNATURE----- 15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej H2utKkquLbyEJamGwUfS9J0kKT4uuMTEr2WX2dPU9YImg4LeRpyjBelrqEqfM4QC8pJ+hVlQgZI5IPpLyRNxvK8= -----END BITCOIN SIGNED MESSAGE----- """ m, a, s = parse_signed_message(bit2c) assert a == "15etuU8kwLFCBbCNRsgQTvWgrGWY9829ej" assert s == ("H2utKkquLbyEJamGwUfS9J0kKT4uuMTEr2WX2dPU9YI" "mg4LeRpyjBelrqEqfM4QC8pJ+hVlQgZI5IPpLyRNxvK8=") ok = verify_message(a, s, m, netcode="BTC") assert ok # testnet example # Sampled from: http://testnet.bitrated.com/u/bearbin.txt on Sep 3/2014 # NOTE: Testnet3 bearbin = """\ Username: bearbin Public key: 03fc594c16779054fc5e119c309215c1f40f2ce104b0169cddeb6d20445bd28f67 Public key address: n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM URL: http://testnet.bitrated.com/u/bearbin -----BEGIN BITCOIN SIGNED MESSAGE----- Contact ----------- [email protected] - Email or hangouts (text only). /u/bearbin on reddit (slow response, not preferred for use with the service, just for contact). Resolution Guidelines: ----------------------------- * Evidence is needed. (e.g. pictures w/ proof that it's you). * If anybody fails to respond, money goes to the other person after 2 weeks. * Additional terms available on request. Pricing ---------- * 0.7% Min 0.003 Max 0.15 * Payment in advance. -----BEGIN SIGNATURE----- n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM IEackZgifpBJs3SqQQ6leUwzvakTZgUKTDuCCn6rVMOQgHlIEzWSYZGQu2H+1chvu68uutzt04cGmsHy/kRIaEc= -----END BITCOIN SIGNED MESSAGE----- """ m, a, s = parse_signed_message(bearbin) assert a == "n2D9XsQX1mDpFGgYqsfmePTy61LJFQnXQM" assert s == ("IEackZgifpBJs3SqQQ6leUwzvakTZgUKTDuCCn6rVMOQgH" "lIEzWSYZGQu2H+1chvu68uutzt04cGmsHy/kRIaEc=") ok = verify_message(a, s, m, netcode="XTN") assert ok