def test_duplicate_xfp(N, offer_ms_import, need_keypress, test_ms_show_addr): # it's legit to have duplicate XFP values! Not hard to make either! # new wallet will all having same XFP, but different xpubs pk = BIP32Node.from_wallet_key(simulator_fixed_xprv) keys = [(simulator_fixed_xfp, pk, pk.subkey(45, is_hardened=True, as_private=False))] lst = [keys[0][-1]] for idx in range(N-1): h = BIP32Node.from_hwif(pk.hwif(as_private=True)) # deepcopy h._chain_code = b'chain code is 32 bytes: %08d' % idx subkey = h.subkey(45, is_hardened=True, as_private=False) lst.append(subkey) xfp = unpack("<I", pk.fingerprint())[0] keys.append( (xfp, h, subkey) ) #print(lst) # bare, no fingerprints # - no xfps # - no meta data config = '\n'.join(sk.hwif(as_private=False) for sk in lst) title, story = offer_ms_import(config) assert f'Policy: {N} of {N}\n' in story need_keypress('y') test_ms_show_addr(N, keys)
def test_streams(self): m0 = BIP32Node.from_master_secret("foo bar baz".encode("utf8")) pm0 = m0.public_copy() self.assertEqual(m0.wallet_key(), pm0.wallet_key()) m1 = m0.subkey() pm1 = pm0.subkey() for i in range(4): m = m1.subkey(i=i) pm = pm1.subkey(i=i) self.assertEqual(m.wallet_key(), pm.wallet_key()) self.assertEqual(m.bitcoin_address(), pm.bitcoin_address()) m2 = BIP32Node.from_wallet_key(m.wallet_key(as_private=True)) m3 = m2.public_copy() self.assertEqual(m.wallet_key(as_private=True), m2.wallet_key(as_private=True)) self.assertEqual(m.wallet_key(), m3.wallet_key()) print(m.wallet_key(as_private=True)) for j in range(2): k = m.subkey(i=j) k2 = BIP32Node.from_wallet_key(k.wallet_key(as_private=True)) k3 = BIP32Node.from_wallet_key(k.wallet_key()) k4 = k.public_copy() self.assertEqual(k.wallet_key(as_private=True), k2.wallet_key(as_private=True)) self.assertEqual(k.wallet_key(), k2.wallet_key()) self.assertEqual(k.wallet_key(), k3.wallet_key()) self.assertEqual(k.wallet_key(), k4.wallet_key()) print(" %s %s" % (k.bitcoin_address(), k.wif()))
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 test_public(sim_execfile): "verify contents of public 'dump' file" from pycoin.key.BIP32Node import BIP32Node from pycoin.contrib.segwit_addr import encode as sw_encode from pycoin.contrib.segwit_addr import decode as sw_decode from pycoin.encoding import a2b_hashed_base58, hash160 pub = sim_execfile('devtest/dump_public.py') assert 'Error' not in pub #print(pub) pub, dev = pub.split('#DEBUG#', 1) assert 'pub' in pub assert 'prv' not in pub assert 'prv' in dev lines = [i.strip() for i in pub.split('\n')] for ln in lines: if ln[1:4] == 'pub': node_pub = BIP32Node.from_wallet_key(ln) break node_prv = BIP32Node.from_wallet_key(dev.strip()) # pub and private are linked assert node_prv.hwif(as_private=False) == node_pub.hwif() # check every path we derived count = 0 for ln in lines: if ln[0:1] == 'm' and '=>' in ln: subpath, result = ln.split(' => ', 1) sk = node_prv.subkey_for_path(subpath[2:]) if result[0:2] in {'tp', 'xp'}: expect = BIP32Node.from_wallet_key(result) assert sk.hwif(as_private=False) == result elif result[0] in '1mn': assert result == sk.address(False) elif result[0:3] in {'bc1', 'tb1'}: h20 = sk.hash160() assert result == sw_encode(result[0:2], 0, h20) elif result[0] in '23': h20 = hash160(b'\x00\x14' + sk.hash160()) assert h20 == a2b_hashed_base58(result)[1:] else: raise ValueError(result) count += 1 print("OK: %s" % ln) assert count > 12
def doit(given_addr, path=None, addr_fmt=None, script=None): if not script: try: # prefer using xpub if we can mk = BIP32Node.from_wallet_key(master_xpub) sk = mk.subkey_for_path(path[2:]) except PublicPrivateMismatchError: mk = BIP32Node.from_wallet_key(simulator_fixed_xprv) sk = mk.subkey_for_path(path[2:]) if addr_fmt == AF_CLASSIC: # easy assert sk.address() == given_addr elif addr_fmt & AFC_PUBKEY: pkh = sk.hash160(use_uncompressed=False) if addr_fmt == AF_P2WPKH: hrp, data = bech32_decode(given_addr) decoded = convertbits(data[1:], 5, 8, False) assert hrp in {'tb', 'bc' } assert bytes(decoded[-20:]) == pkh else: assert addr_fmt == AF_P2WPKH_P2SH assert given_addr[0] in '23' expect = a2b_hashed_base58(given_addr)[1:] assert len(expect) == 20 assert hash160(b'\x00\x14' + pkh) == expect elif addr_fmt & AFC_SCRIPT: assert script, 'need a redeem/witness script' if addr_fmt == AF_P2SH: assert given_addr[0] in '23' expect = a2b_hashed_base58(given_addr)[1:] assert hash160(script) == expect elif addr_fmt == AF_P2WSH: hrp, data = bech32_decode(given_addr) assert hrp in {'tb', 'bc' } decoded = convertbits(data[1:], 5, 8, False) assert bytes(decoded[-32:]) == sha256(script).digest() elif addr_fmt == AF_P2WSH_P2SH: assert given_addr[0] in '23' expect = a2b_hashed_base58(given_addr)[1:] assert hash160(b'\x00\x20' + sha256(script).digest()) == expect else: raise pytest.fail(f'not ready for {addr_fmt:x} yet') else: raise ValueError(addr_fmt) return sk if not script else None
def test_export_electrum(mode, dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path): # lightly test electrum wallet export goto_home() pick_menu_item('Advanced') pick_menu_item('MicroSD Card') pick_menu_item('Export Wallet') pick_menu_item('Electrum Wallet') time.sleep(0.1) title, story = cap_story() assert 'This saves a skeleton Electrum wallet' in story need_keypress('y') time.sleep(0.1) pick_menu_item(mode) time.sleep(0.1) title, story = cap_story() assert 'wallet file written' in story fname = story.split('\n')[-1] need_keypress('y') path = microsd_path(fname) with open(path, 'rt') as fp: obj = json.load(fp) ks = obj['keystore'] assert ks['ckcc_xfp'] == simulator_fixed_xfp assert ks['hw_type'] == 'coldcard' assert ks['type'] == 'hardware' deriv = ks['derivation'] assert deriv.startswith('m/') assert int(deriv.split("/")[1][:-1]) in {44, 84, 49} # weak xpub = ks['xpub'] assert xpub[1:4] == 'pub' if xpub[0] in 'tx': # no slip132 here got = BIP32Node.from_wallet_key(xpub) expect = BIP32Node.from_wallet_key( simulator_fixed_xprv).subkey_for_path(deriv[2:]) assert got.sec() == expect.sec() os.unlink(path)
def from_text(class_, text, is_compressed=True): """ This function will accept a BIP0032 wallet string, a WIF, or a bitcoin address. The "is_compressed" parameter is ignored unless a public address is passed in. """ data = a2b_hashed_base58(text) netcode, key_type = netcode_and_type_for_data(data) data = data[1:] if key_type in ("pub32", "prv32"): # TODO: fix this... it doesn't belong here from pycoin.key.BIP32Node import BIP32Node return BIP32Node.from_wallet_key(text) if key_type == 'wif': is_compressed = (len(data) > 32) if is_compressed: data = data[:-1] return Key( secret_exponent=from_bytes_32(data), prefer_uncompressed=not is_compressed, netcode=netcode) if key_type == 'address': return Key(hash160=data, is_compressed=is_compressed, netcode=netcode) raise EncodingError("unknown text: %s" % text)
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 master_xpub(dev): r = dev.send_recv(CCProtocolPacker.get_xpub('m'), timeout=None, encrypt=1) assert r[1:4] == 'pub', r if r[0:4] == dev.master_xpub[0:4]: assert r == dev.master_xpub elif dev.master_xpub: # testnet vs. mainnet difference from pycoin.key.BIP32Node import BIP32Node a = BIP32Node.from_wallet_key(r) b = BIP32Node.from_wallet_key(dev.master_xpub) assert a.secret_exponent() == b.secret_exponent() return r
def from_text(class_, text, is_compressed=True): """ This function will accept a BIP0032 wallet string, a WIF, or a bitcoin address. The "is_compressed" parameter is ignored unless a public address is passed in. """ data = a2b_hashed_base58(text) netcode, key_type, length = netcode_and_type_for_data(data) data = data[1:] if key_type in ("pub32", "prv32"): # TODO: fix this... it doesn't belong here from pycoin.key.BIP32Node import BIP32Node return BIP32Node.from_wallet_key(text) if key_type == 'wif': is_compressed = (len(data) > 32) if is_compressed: data = data[:-1] return Key( secret_exponent=from_bytes_32(data), prefer_uncompressed=not is_compressed, netcode=netcode) if key_type == 'address': return Key(hash160=data, is_compressed=is_compressed, netcode=netcode) raise EncodingError("unknown text: %s" % text)
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_stub_menu(sim_execfile, goto_address_explorer, need_keypress, cap_menu, mk_common_derivations, parse_display_screen, validate_address): # For a given wallet, ensure the explorer shows the correct stub addresses node_prv = BIP32Node.from_wallet_key( sim_execfile('devtest/dump_private.py').strip()) common_derivs = mk_common_derivations(node_prv.netcode()) # capture menu address stubs goto_address_explorer() need_keypress('4') time.sleep(.01) m = cap_menu() for idx, (path, addr_format) in enumerate(common_derivs): # derive index=0 address subpath = path.format(account=0, change=0, idx=0) # e.g. "m/44'/1'/0'/0/0" sk = node_prv.subkey_for_path(subpath[2:]) # capture full index=0 address from display screen & validate it goto_address_explorer(click_idx=idx) addr_dict = parse_display_screen(0, 10) if subpath not in addr_dict: raise Exception( 'Subpath ("%s") not found in address explorer display' % subpath) expected_addr = addr_dict[subpath] validate_address(expected_addr, sk) # validate that stub is correct [start, end] = m[idx].split('-') assert expected_addr.startswith(start) assert expected_addr.endswith(end)
def doit(given_addr, path, addr_fmt): mk = BIP32Node.from_wallet_key(master_xpub) sk = mk.subkey_for_path(path[2:]) if addr_fmt == AF_CLASSIC: # easy assert sk.address() == given_addr elif addr_fmt & AFC_PUBKEY: pkh = sk.hash160(use_uncompressed=False) if addr_fmt == AF_P2WPKH: hrp, data = bech32_decode(given_addr) decoded = convertbits(data[1:], 5, 8, False) assert hrp in {'tb', 'bc'} assert bytes(decoded[-20:]) == pkh else: assert addr_fmt == AF_P2WPKH_P2SH assert given_addr[0] in '23' expect = a2b_hashed_base58(given_addr)[1:] assert len(expect) == 20 assert hash160(b'\x00\x14' + pkh) == expect elif addr_fmt & AFC_SCRIPT: raise pytest.fail('multisig/p2sh addr not handled') else: raise ValueError(addr_fmt)
def test_account_menu(account_num, sim_execfile, pick_menu_item, goto_address_explorer, need_keypress, cap_menu, mk_common_derivations, parse_display_screen, validate_address): # Try a few sub-accounts node_prv = BIP32Node.from_wallet_key( sim_execfile('devtest/dump_private.py').strip()) common_derivs = mk_common_derivations(node_prv.netcode()) # capture menu address stubs goto_address_explorer() time.sleep(.01) # skip warning need_keypress('4') time.sleep(.01) m = cap_menu() pick_menu_item([i for i in m if i.startswith('Account')][0]) # enter account number time.sleep(0.1) for d in str(account_num): need_keypress(d) need_keypress('y') time.sleep(0.1) m = cap_menu() assert f'Account: {account_num}' in m which = 0 for idx, (path, addr_format) in enumerate(common_derivs[1:]): # derive index=0 address assert '{account}' in path subpath = path.format(account=account_num, change=0, idx=0) # e.g. "m/44'/1'/X'/0/0" sk = node_prv.subkey_for_path(subpath[2:]) # capture full index=0 address from display screen & validate it # go down menu to expected derivation spot m = cap_menu() pick_menu_item(m[idx]) time.sleep(0.1) addr_dict = parse_display_screen(0, 10) if subpath not in addr_dict: raise Exception( 'Subpath ("%s") not found in address explorer display' % subpath) expected_addr = addr_dict[subpath] validate_address(expected_addr, sk) # validate that stub is correct [start, end] = m[idx].split('-') assert expected_addr.startswith(start) assert expected_addr.endswith(end) need_keypress('x')
def doit(num_ins, num_outs, master_xpub, subpath="0/%d", fee=10000): psbt = BasicPSBT() txn = Tx(2, [], []) # we have a key; use it to provide "plausible" value inputs from pycoin.key.BIP32Node import BIP32Node mk = BIP32Node.from_wallet_key(master_xpub) xfp = mk.fingerprint() psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)] psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)] for i in range(num_ins): # make a fake txn to supply each of the inputs # - each input is 1BTC # addr where the fake money will be stored. subkey = mk.subkey_for_path(subpath % i) sec = subkey.sec() assert len(sec) == 33, "expect compressed" assert subpath[0:2] == '0/' psbt.inputs[i].bip32_paths[sec] = xfp + pack('<II', 0, i) # UTXO that provides the funding for to-be-signed txn supply = Tx(2, [TxIn(pack('4Q', 0xdead, 0xbeef, 0, 0), 73)], []) scr = bytes([0x76, 0xa9, 0x14]) + subkey.hash160() + bytes( [0x88, 0xac]) supply.txs_out.append(TxOut(1E8, scr)) with BytesIO() as fd: supply.stream(fd) psbt.inputs[i].utxo = fd.getvalue() if 0: with BytesIO() as fd: supply.stream(fd, include_witness_data=True) psbt.inputs[i].witness_utxo = fd.getvalue() spendable = TxIn(supply.hash(), 0) txn.txs_in.append(spendable) for i in range(num_outs): # random P2PKH scr = bytes([0x76, 0xa9, 0x14]) + pack( 'I', i + 1) + bytes(16) + bytes([0x88, 0xac]) h = TxOut(round(((1E8 * num_ins) - fee) / num_outs, 4), scr) txn.txs_out.append(h) with BytesIO() as b: txn.stream(b) psbt.txn = b.getvalue() rv = BytesIO() psbt.serialize(rv) assert rv.tell() <= MAX_TXN_LEN, 'too fat' return rv.getvalue()
def test_export_public_txt(dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path, addr_vs_path): from pycoin.contrib.segwit_addr import encode as sw_encode # test UX and values produced. goto_home() pick_menu_item('Advanced') pick_menu_item('MicroSD Card') pick_menu_item('Dump Summary') time.sleep(0.1) title, story = cap_story() assert 'Saves a text file to' in story need_keypress('y') time.sleep(0.1) title, story = cap_story() assert 'Summary file' in story fname = story.split('\n')[-1] assert 'public' in fname xfp = xfp2str(simulator_fixed_xfp).upper() root = BIP32Node.from_wallet_key(simulator_fixed_xprv) path = microsd_path(fname) with open(path, 'rt') as fp: for ln in fp.readlines(): if 'fingerprint' in ln: assert ln.strip().endswith(xfp) if '=>' not in ln: continue lhs, rhs = ln.strip().split(' => ') assert lhs.startswith('m/') rhs = rhs.split('#')[0].strip() if 'SLIP-132' in ln: rhs, _, f, _ = slip132undo(rhs) else: f = None if rhs[1:4] == 'pub': expect = root.subkey_for_path(lhs[2:]) assert expect.hwif(as_private=False) == rhs continue if not f: if rhs[0] in 'mn': f = AF_CLASSIC elif rhs[0:3] == 'tb1': f = AF_P2WPKH elif rhs[0] == '2': f = AF_P2WPKH_P2SH else: raise ValueError(rhs) addr_vs_path(rhs, path=lhs, addr_fmt=f)
def test_export_wasbi(dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path): # test UX and operation of the 'wasabi wallet export' goto_home() pick_menu_item('Advanced') pick_menu_item('MicroSD Card') pick_menu_item('Wasabi Wallet') time.sleep(0.1) title, story = cap_story() assert 'This saves a skeleton Wasabi' in story need_keypress('y') time.sleep(0.1) title, story = cap_story() assert 'wallet file written' in story fname = story.split('\n')[-1] need_keypress('y') path = microsd_path(fname) with open(path, 'rt') as fp: obj = json.load(fp) assert 'MasterFingerprint' in obj assert 'ExtPubKey' in obj assert 'BlockchainState' in obj assert obj['BlockchainState']['Network'] xpub = obj['ExtPubKey'] assert xpub.startswith('xpub') # even for testnet assert int(obj['MasterFingerprint'], 16) == simulator_fixed_xfp got = BIP32Node.from_wallet_key(xpub) expect = BIP32Node.from_wallet_key( simulator_fixed_xprv).subkey_for_path("84'/0'/0'.pub") assert got.sec() == expect.sec() os.unlink(path)
def cosign_spend_request(xprvkey_or_wallet, req_keys, inputs, xpub_check): ''' Sign the inputs of a transaction, given the sighashs and subkey paths for each input Args: xprvkey_or_wallet = 111-char base58 encoded serialization of BIP32 wallet or pycoin.key.BIP32Node object (w/ private key) req_keys = dictionary: key is subpath ('a/b/c', but only 'a' for now as a string) value is tuple: (address, public pair) ... optional checking data inputs = list of by transaction input: (subpath, sighash_all value) Returns: list of 3-tuples: (der-encoded signature, sighash, subpath) ''' # We need just these features from pycoin <https://github.com/richardkiss/pycoin> from pycoin import ecdsa from pycoin.key.BIP32Node import BIP32Node from pycoin.tx.script import der # We need a BIP32 "wallet" for the root of all keys. if isinstance(xprvkey_or_wallet, basestring): wallet = BIP32Node.from_wallet_key(xprvkey_or_wallet.strip()) else: wallet = xprvkey_or_wallet # Verify we are looking at the right extended private key check = wallet.hwif(as_private=False)[-len(xpub_check):] if check != xpub_check: raise ValueError("This private key isn't the right one for xpub...%s" % xpub_check) # Make the right subkey for each inputs wallets = {} for sp, (addr_check, ppair) in req_keys.items(): w = wallet.subkey_for_path(sp) assert w.bitcoin_address() == addr_check assert w.public_pair() == tuple(ppair) wallets[sp] = w # Generate a signature for each input required sigs = [] SIGHASH_ALL = 1 order = ecdsa.generator_secp256k1.order() for sp, sighash in inputs: sighash_int = int(sighash, 16) r, s = ecdsa.sign(ecdsa.generator_secp256k1, wallets[sp].secret_exponent(), sighash_int) if s + s > order: s = order - s sig = der.sigencode_der(r, s) + chr(SIGHASH_ALL) sigs.append((sig.encode('hex'), sighash, sp)) return sigs
def pycoinWallet(public_key=None, public=True, testnet=settings.BTC_TESTNET, password=None): netcode = 'XTN' if testnet else 'BTC' if public: return BIP32Node.from_wallet_key(public_key) else: assert not password is None return BIP32Node.from_master_secret(password, netcode=netcode)
def cosign_spend_request(xprvkey_or_wallet, req_keys, inputs, xpub_check): """ Sign the inputs of a transaction, given the sighashs and subkey paths for each input Args: xprvkey_or_wallet = 111-char base58 encoded serialization of BIP32 wallet or pycoin.key.BIP32Node object (w/ private key) req_keys = dictionary: key is subpath ('a/b/c', but only 'a' for now as a string) value is tuple: (address, public pair) ... optional checking data inputs = list of by transaction input: (subpath, sighash_all value) Returns: list of 3-tuples: (der-encoded signature, sighash, subpath) """ # We need just these features from pycoin <https://github.com/richardkiss/pycoin> from pycoin import ecdsa from pycoin.key.BIP32Node import BIP32Node from pycoin.tx.script import der # We need a BIP32 "wallet" for the root of all keys. if isinstance(xprvkey_or_wallet, basestring): wallet = BIP32Node.from_wallet_key(xprvkey_or_wallet.strip()) else: wallet = xprvkey_or_wallet # Verify we are looking at the right extended private key check = wallet.hwif(as_private=False)[-len(xpub_check) :] if check != xpub_check: raise ValueError("This private key isn't the right one for xpub...%s" % xpub_check) # Make the right subkey for each inputs wallets = {} for sp, (addr_check, ppair) in req_keys.items(): w = wallet.subkey_for_path(sp) assert w.bitcoin_address() == addr_check assert w.public_pair() == tuple(ppair) wallets[sp] = w # Generate a signature for each input required sigs = [] SIGHASH_ALL = 1 order = ecdsa.generator_secp256k1.order() for sp, sighash in inputs: sighash_int = int(sighash, 16) r, s = ecdsa.sign(ecdsa.generator_secp256k1, wallets[sp].secret_exponent(), sighash_int) if s + s > order: s = order - s sig = der.sigencode_der(r, s) + chr(SIGHASH_ALL) sigs.append((sig.encode("hex"), sighash, sp)) return sigs
def test_export_unchained(dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path): # test UX and operation of the 'unchained capital export' goto_home() pick_menu_item('Advanced') pick_menu_item('MicroSD Card') pick_menu_item('Export Wallet') pick_menu_item('Unchained Capital') time.sleep(0.1) title, story = cap_story() assert 'Unchained Capital' in story need_keypress('y') time.sleep(0.1) title, story = cap_story() assert 'Unchained Capital file' in story fname = story.split('\n')[-1] assert 'unchained' in fname need_keypress('y') root = BIP32Node.from_wallet_key(simulator_fixed_xprv) path = microsd_path(fname) with open(path, 'rt') as fp: obj = json.load(fp) assert obj['xfp'] == xfp2str(simulator_fixed_xfp) assert obj['account'] == 0 assert obj['p2sh_deriv'] == "m/45'" for k in ['p2sh_p2wsh', 'p2sh', 'p2wsh']: xpub = slip132undo(obj[k])[0] if k != 'p2sh' else obj[k] node = BIP32Node.from_wallet_key(xpub) assert xpub == node.hwif(as_private=False) sk = root.subkey_for_path(obj[f'{k}_deriv'][2:] + '.pub') #assert node.chain_code() == sk.chain_code() assert node.hwif() == sk.hwif()
def master_xpub(dev): if hasattr(dev.dev, 'pipe'): # this works better against simulator in HSM mode, where the xpub cmd may be disabled return simulator_fixed_xpub r = dev.send_recv(CCProtocolPacker.get_xpub('m'), timeout=None, encrypt=1) assert r[1:4] == 'pub', r if r[0:4] == dev.master_xpub[0:4]: assert r == dev.master_xpub elif dev.master_xpub: # testnet vs. mainnet difference from pycoin.key.BIP32Node import BIP32Node a = BIP32Node.from_wallet_key(r) b = BIP32Node.from_wallet_key(dev.master_xpub) assert a.secret_exponent() == b.secret_exponent() return r
def get_address_by_path(key, path): ''' gets key = xpub or xpriv and path returns JSON xprv: {"address":"1qwerty...", "priv_key":"Kqwert...", "path":"1/2"} xpub: {"address":"1qwerty...", "priv_key":None, "path":"1/2"} ''' da_key = BIP32Node.from_wallet_key(key) btc_address = da_key.subkey_for_path(path).bitcoin_address() btc_private = da_key.subkey_for_path(path).wif() return {"address":btc_address, "priv_key":btc_private, "path":path}
def test_crypto_unittest(sim_eval, sim_exec): # 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__()' ) # known value for simulator, generally unknown on random SD cards assert card == '95a60b9ff0c944ec2c23a28e599f794e95bb376a451b6037b054f8230b405fb0' salt = a2b_hex(card) # read key simulator calculates key = sim_exec('''\ import files; from h import b2a_hex; \ from pwsave import PassphraseSaver; \ cs = files.CardSlot().__enter__(); \ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()''' ) assert len(key) == 64 #assert key == '234af2aa2ab43af83667dfc6e11d08223e0f486ef34539b41a045dd9eb3ea664' from pycoin.key.BIP32Node import BIP32Node from pycoin.encoding import from_bytes_32, to_bytes_32 from hashlib import sha256 mk = BIP32Node.from_wallet_key(simulator_fixed_xprv) sk = mk.subkey_for_path('2147431408p/0p') md = sha256() md.update(salt) md.update(to_bytes_32(sk.secret_exponent())) md.update(salt) expect = sha256(md.digest()).hexdigest() assert expect == key # check that key works for decrypt / that the file was actually encrypted with open(SIM_FNAME, 'rb') as fd: raw = fd.read() import pyaes d = pyaes.AESModeOfOperationCTR(a2b_hex(expect), pyaes.Counter(0)).decrypt txt = str(bytearray(d(raw)), 'utf8') print(txt) assert txt[0] == '[' and txt[-1] == ']' import json j = json.loads(txt) assert isinstance(j, list) assert j[0]['pw'] assert j[0]['xfp']
def test_xpub_good(dev, master_xpub, path): # get some xpubs and validate the derivations xpub = dev.send_recv(CCProtocolPacker.get_xpub(path), timeout=None) assert xpub[1:4] == 'pub' assert len(xpub) > 100 # check the derive using pycoin k = BIP32Node.from_wallet_key(xpub) assert k.hwif() == xpub if "'" not in path: mk = BIP32Node.from_wallet_key(master_xpub) sk = mk.subkey_for_path(path[2:]) assert sk.hwif() == xpub if len(path) <= 2: assert mk.fingerprint() == struct.pack('<I', dev.master_fingerprint)
def test_export_airgap(goto_home, cap_story, pick_menu_item, cap_menu, need_keypress, microsd_path): # test UX and math for bip45 export import json goto_home() pick_menu_item('Settings') pick_menu_item('Multisig Wallets') pick_menu_item('Export XPUB') time.sleep(.1) title, story = cap_story() assert 'BIP45' in story assert "m/45'" in story assert "m/48'/" in story need_keypress('y') time.sleep(.1) title, story = cap_story() fname = story.split('\n')[-1] assert fname.startswith('ccxp-') assert fname.endswith('.json') with open(microsd_path(fname), 'rt') as fp: rv = json.load(fp) assert 'xfp' in rv assert len(rv) >= 7 n = BIP32Node.from_wallet_key(rv['p2sh']) assert n.tree_depth() == 1 assert n.child_index() == 45 | (1 << 31) mxfp = unpack("<I", n.parent_fingerprint())[0] assert hex(mxfp) == hex(simulator_fixed_xfp) e = BIP32Node.from_wallet_key(simulator_fixed_xprv) expect = e.subkey_for_path("45'.pub") assert expect.hwif() == n.hwif()
def doit(M, addr_fmt=None, do_import=True): passwords = ['Me', 'Myself', 'And I', ''] if 0: # WORKING, but slow .. and it's constant data keys = [] for pw in passwords: xfp = set_bip39_pw(pw) sk = dev.send_recv(CCProtocolPacker.get_xpub("m/45'")) node = BIP32Node.from_wallet_key(sk) keys.append((xfp, None, node)) assert len(set(x for x,_,_ in keys)) == 4, keys pprint(keys) else: # Much, FASTER! assert dev.is_simulator keys = [(3503269483, None, BIP32Node.from_hwif('tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9')), (2389277556, None, BIP32Node.from_hwif('tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc')), (3190206587, None, BIP32Node.from_hwif('tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa')), (1130956047, None, BIP32Node.from_hwif('tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n')), ] if do_import: # render as a file for import config = f"name: Myself-{M}\npolicy: {M} / 4\n\n" if addr_fmt: config += f'format: {addr_fmt.upper()}\n' config += '\n'.join('%s: %s' % (xfp2str(xfp), sk.hwif()) for xfp, _, sk in keys) #print(config) title, story = offer_ms_import(config) #print(story) # dont care if update or create; accept it. time.sleep(.1) need_keypress('y') def select_wallet(idx): # select to specific pw xfp = set_bip39_pw(passwords[idx]) assert xfp == keys[idx][0] return (keys, select_wallet)
def test_dump_addresses(generate_addresses_file, mk_common_derivations, sim_execfile, validate_address, click_idx): # Validate addresses dumped to text file node_prv = BIP32Node.from_wallet_key( sim_execfile('devtest/dump_private.py').strip()) common_derivs = mk_common_derivations(node_prv.netcode()) # Generate the addresses file and get each line in a list for subpath, addr in generate_addresses_file(click_idx): # derive the subkey and validate the corresponding address sk = node_prv.subkey_for_path(subpath[2:]) validate_address(addr, sk)
def olsign(key, proposal, url, upload, html, output): if not url and not proposal: raise click.BadParameter( "Need a URL to fetch proposal from (--url), or the file itself (-i file.json)" ) # get the proposal JSON try: if url: proposal = requests.get(url).json() else: proposal = simplejson.load(proposal) except JSONDecodeError: raise click.UsageError("Does not contain valid JSON") # unwrap signature, checking it as we go proposal = check_sig_and_unwrap(proposal) click.echo(''' Co-signing as: {cosigner} Required xpubkey: ...{xpubkey_check} '''.format(**proposal)) # unpack their private key (to test if suitable) wallet = BIP32Node.from_wallet_key(key.read().strip()) check = wallet.hwif(as_private=False)[-8:] if check != proposal['xpubkey_check']: raise click.UsageError( 'This private key is not the one we need as this co-signer.') #pprint(proposal.keys()) # present a summary of what will happen if html: show_page(proposal) sigs = do_signing(wallet, proposal['req_keys'], proposal['inputs']) package = package_for_ck(wallet, proposal, sigs) if output: output.write(package) click.echo("Wrote result to: %s" % output.name) if upload: upload_to_ck(package) if not output and not upload: click.echo("JSON response:\n\n%s" % package)
def doit(M, N, unique=0): keys = [] for i in range(N-1): pk = BIP32Node.from_master_secret(b'CSW is a fraud %d - %d' % (i, unique), 'XTN') xfp = unpack("<I", pk.fingerprint())[0] sub = pk.subkey(45, is_hardened=True, as_private=True) keys.append((xfp, pk, sub)) pk = BIP32Node.from_wallet_key(simulator_fixed_xprv) keys.append((simulator_fixed_xfp, pk, pk.subkey(45, is_hardened=True, as_private=True))) return keys
def olsign(key, proposal, url, upload, html, output): if not url and not proposal: raise click.BadParameter( "Need a URL to fetch proposal from (--url), or the file itself (-i file.json)") # get the proposal JSON try: if url: proposal = requests.get(url).json() else: proposal = simplejson.load(proposal) except JSONDecodeError: raise click.UsageError("Does not contain valid JSON") # unwrap signature, checking it as we go proposal = check_sig_and_unwrap(proposal) click.echo(''' Co-signing as: {cosigner} Required xpubkey: ...{xpubkey_check} '''.format(**proposal)) # unpack their private key (to test if suitable) wallet = BIP32Node.from_wallet_key(key.read().strip()) check = wallet.hwif(as_private = False)[-8:] if check != proposal['xpubkey_check']: raise click.UsageError('This private key is not the one we need as this co-signer.') #pprint(proposal.keys()) # present a summary of what will happen if html: show_page(proposal) sigs = do_signing(wallet, proposal['req_keys'], proposal['inputs']) package = package_for_ck(wallet, proposal, sigs) if output: output.write(package) click.echo("Wrote result to: %s" % output.name) if upload: upload_to_ck(package) if not output and not upload: click.echo("JSON response:\n\n%s" % package)
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 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 main(): parser = argparse.ArgumentParser(description="Generate a private wallet key. WARNING: obsolete. Use ku instead.") parser.add_argument('-a', "--address", help='show as Bitcoin address', action='store_true') parser.add_argument('-i', "--info", help='show metadata', action='store_true') parser.add_argument('-j', "--json", help='output metadata as JSON', action='store_true') parser.add_argument('-w', "--wif", help='show as Bitcoin WIF', action='store_true') parser.add_argument('-f', "--wallet-key-file", help='initial wallet key', type=argparse.FileType('r')) parser.add_argument('-k', "--wallet-key", help='initial wallet key') parser.add_argument('-g', "--gpg", help='use gpg --gen-random to get additional entropy', action='store_true') parser.add_argument('-u', "--dev-random", help='use /dev/random to get additional entropy', action='store_true') parser.add_argument('-n', "--uncompressed", help='show in uncompressed form', action='store_true') parser.add_argument('-p', help='generate wallet key from passphrase. NOT RECOMMENDED', metavar='passphrase') parser.add_argument('-s', "--subkey", help='subkey path (example: 0p/2/1)') parser.add_argument('-t', help='generate test key', action="store_true") parser.add_argument('inputfile', help='source of entropy. stdin by default', type=argparse.FileType(mode='r+b'), nargs='?') args = parser.parse_args() # args.inputfile doesn't like binary when "-" is passed in. Deal with this. if args.inputfile == sys.stdin and hasattr(sys.stdin, "buffer"): args.inputfile = sys.stdin.buffer network = 'XTN' if args.t else 'BTC' entropy = bytearray() if args.gpg: entropy.extend(gpg_entropy()) if args.dev_random: entropy.extend(dev_random_entropy()) if args.inputfile: entropy.extend(args.inputfile.read()) if args.p: entropy.extend(args.p.encode("utf8")) if len(entropy) == 0 and not args.wallet_key and not args.wallet_key_file: parser.error("you must specify at least one source of entropy") if args.wallet_key and len(entropy) > 0: parser.error("don't specify both entropy and a wallet key") if args.wallet_key_file: wallet = BIP32Node.from_wallet_key(args.wallet_key_file.readline()[:-1]) elif args.wallet_key: wallet = BIP32Node.from_wallet_key(args.wallet_key) else: wallet = BIP32Node.from_master_secret(bytes(entropy), netcode=network) try: if args.subkey: wallet = wallet.subkey_for_path(args.subkey) if wallet.child_index() >= 0x80000000: wc = wallet.child_index() - 0x80000000 child_index = "%dp (%d)" % (wc, wallet.child_index()) else: child_index = "%d" % wallet.child_index() if args.json: d = dict( wallet_key=wallet.wallet_key(as_private=wallet.is_private()), public_pair_x=wallet.public_pair[0], public_pair_y=wallet.public_pair[1], tree_depth=wallet.depth, fingerprint=b2h(wallet.fingerprint()), parent_fingerprint=b2h(wallet.parent_fingerprint), child_index=child_index, chain_code=b2h(wallet.chain_code), bitcoin_addr=wallet.bitcoin_address(), bitcoin_addr_uncompressed=wallet.bitcoin_address(compressed=False), network="test" if wallet.is_test else "main", ) if wallet.is_private(): d.update(dict( key="private", secret_exponent=wallet.secret_exponent, WIF=wallet.wif(), WIF_uncompressed=wallet.wif(compressed=False) )) else: d.update(dict(key="public")) print(json.dumps(d, indent=3)) elif args.info: print(wallet.wallet_key(as_private=wallet.is_private())) print(full_network_name_for_netcode(wallet.netcode())) if wallet.is_private(): print("private key") print("secret exponent: %d" % wallet.secret_exponent()) else: print("public key only") print("public pair x: %d\npublic pair y: %d" % wallet.public_pair()) print("tree depth: %d" % wallet.tree_depth()) print("fingerprint: %s" % b2h(wallet.fingerprint())) print("parent f'print: %s" % b2h(wallet.parent_fingerprint())) print("child index: %s" % child_index) print("chain code: %s" % b2h(wallet.chain_code())) if wallet.is_private(): print("WIF: %s" % wallet.wif()) print(" uncompressed: %s" % wallet.wif(use_uncompressed=True)) print("Bitcoin address: %s" % wallet.bitcoin_address()) print(" uncompressed: %s" % wallet.bitcoin_address(use_uncompressed=True)) elif args.address: print(wallet.bitcoin_address(use_uncompressed=args.uncompressed)) elif args.wif: print(wallet.wif(compressed=not args.uncompressed)) else: print(wallet.wallet_key(as_private=wallet.is_private())) except PublicPrivateMismatchError as ex: print(ex.args[0])
#!/usr/bin/env python import unittest from pycoin.key import bip32 from pycoin.serialize import h2b from pycoin.key.BIP32Node import BIP32Node def Wallet(*args, **kwargs): return BIP32Node(*args, **kwargs) Wallet.from_master_secret = lambda *args, **kwargs: BIP32Node.from_master_secret(*args, **kwargs) Wallet.from_wallet_key = lambda *args, **kwargs: BIP32Node.from_wallet_key(*args, **kwargs) bip32.Wallet = Wallet class Bip0032TestCase(unittest.TestCase): def test_vector_1(self): master = bip32.Wallet.from_master_secret(h2b("000102030405060708090a0b0c0d0e0f")) self.assertEqual(master.wallet_key(as_private=True), "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi") self.assertEqual(master.bitcoin_address(), "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma") self.assertEqual(master.wif(), "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW") self.assertEqual(master.wallet_key(), "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8") m0p = master.subkey(is_hardened=True) self.assertEqual(m0p.wallet_key(), "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw") self.assertEqual(m0p.wallet_key(as_private=True), "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7") self.assertEqual(master.subkey_for_path("0p").wallet_key(), m0p.wallet_key()) pub_mp0 = master.subkey(is_hardened=True, as_private=False)
def generate_bip32_address_from_extended_pubkey(extended_pubkey, branch, index): ext_key = BIP32Node.from_wallet_key(extended_pubkey) return ext_key.subkey_for_path('%d/%d' % (branch, index)).address()