def p2pkh(key: Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> str: "Return the p2pkh base58 address corresponding to a public key." pub_key, network = pub_keyinfo_from_key(key, network, compressed=compressed) return address_from_h160("p2pkh", hash160(pub_key), network)
def fingerprint(key: Key, network: Optional[str] = None) -> bytes: """Return the public key fingerprint from a private/public key. The fingerprint is the last four bytes of the compressed public key HASH160. """ pub_key, _ = pub_keyinfo_from_key(key, network, compressed=True) return hash160(pub_key)[:4]
def hash160_from_key(key: Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> H160_Net: """Return (public key HASH160, nettwork) from a private/public key. HASH160 is RIPEMD160(SHA256). """ pub_key, network = pub_keyinfo_from_key(key, network, compressed) return hash160(pub_key), network
def test_derive_exceptions() -> None: # root key, zero depth rootmxprv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" xprv = BIP32KeyData.b58decode(rootmxprv) # FIXME # assert xprv == _derive(xprv, "m") assert rootmxprv == derive(xprv, "m") assert rootmxprv == derive(xprv, "") fingerprint = hashes.hash160(pub_keyinfo_from_key(xprv)[0])[:4] assert fingerprint == _derive(xprv, bytes.fromhex("80000000")).parent_fingerprint for der_path in ("/1", "800000", "80000000"): xkey = _derive(xprv, der_path) assert fingerprint == xkey.parent_fingerprint err_msg = "invalid literal for int" for der_path in (";/0", "invalid index"): with pytest.raises(ValueError, match=err_msg): derive(xprv, der_path) with pytest.raises(BTClibValueError, match="depth greater than 255: "): derive(xprv, "m" + 256 * "/0") with pytest.raises(BTClibValueError, match="index are not a multiple of 4-bytes: "): derive(xprv, b"\x00" * 5) for index in (2**32, 0x8000000000): with pytest.raises(OverflowError, match="int too big to convert"): derive(xprv, index) xprv = _derive(xprv, "1") err_msg = "final depth greater than 255: " with pytest.raises(BTClibValueError, match=err_msg): derive(xprv, "m" + 255 * "/0") rootxprv = "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS" temp = base58.b58decode(rootxprv) bad_xprv = base58.b58encode(temp[:45] + b"\x02" + temp[46:], 78) err_msg = "invalid private key prefix: " with pytest.raises(BTClibValueError, match=err_msg): derive(bad_xprv, 0x80000000) xpub = xpub_from_xprv(rootxprv) temp = base58.b58decode(xpub) bad_xpub = base58.b58encode(temp[:45] + b"\x00" + temp[46:], 78) err_msg = r"invalid public key prefix not in \(0x02, 0x03\): " with pytest.raises(BTClibValueError, match=err_msg): derive(bad_xpub, 0x80000000) err_msg = "hardened derivation from public key" with pytest.raises(BTClibValueError, match=err_msg): derive(xpub, 0x80000000)
def p2pk( cls: Type[_ScriptPubKey], key: Key, network: Optional[str] = None, check_validity: bool = True, ) -> _ScriptPubKey: "Return the p2pk ScriptPubKey of the provided Key." payload, network = pub_keyinfo_from_key(key, network) script = serialize([payload, "OP_CHECKSIG"]) return cls(script, network, check_validity)
def p2wpkh( cls: Type["ScriptPubKey"], key: Key, check_validity: bool = True, ) -> "ScriptPubKey": """Return the p2wpkh ScriptPubKey of the provided key. If the provided key is a public one, it must be compressed. """ pub_key, network = pub_keyinfo_from_key(key, compressed=True) script = serialize(["OP_0", hash160(pub_key)]) return cls(script, network, check_validity)
def p2ms( cls: Type["ScriptPubKey"], m: int, keys: Sequence[Key], network: Optional[str] = None, compressed: Optional[bool] = None, lexi_sort: bool = True, check_validity: bool = True, ) -> "ScriptPubKey": """Return the m-of-n multi-sig ScriptPubKey of the provided keys. BIP67 endorses lexicographica key sorting according to compressed key representation. Note that sorting uncompressed keys (leading 0x04 byte) results in a different order than sorting the same keys in compressed (leading 0x02 or 0x03 bytes) representation. https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki """ n = len(keys) if not 0 < n < 17: raise BTClibValueError(f"invalid n in m-of-n: {n}") if not 0 < m <= n: raise BTClibValueError(f"invalid m in m-of-n: {m}-of-{n}") # if network is None, then first key sets the network pub_key, network = pub_keyinfo_from_key(keys[0], network, compressed) pub_keys = [pub_key] + [ pub_keyinfo_from_key(k, network, compressed)[0] for k in keys[1:] ] if lexi_sort: pub_keys = sorted(pub_keys) script = serialize( [op_int(m), *pub_keys, op_int(n), "OP_CHECKMULTISIG"]) return cls(script, network, check_validity)
def test_p2w_p2sh() -> None: pub_key_str = "03 a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f" pub_key, network = pub_keyinfo_from_key(pub_key_str, compressed=True) witness_program = hash160(pub_key) b58addr = b58.p2wpkh_p2sh(pub_key, network) assert b58addr == "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g" script_pub_key = serialize([ "OP_DUP", "OP_HASH160", witness_program, "OP_EQUALVERIFY", "OP_CHECKSIG" ]) b58addr = b58.p2wsh_p2sh(script_pub_key, network) assert b58addr == "3QHRam4Hvp1GZVkgjoKWUC1GEd8ck8e4WX"
def input_script_sig( internal_pubkey: Optional[Key], script_tree: TaprootScriptTree, script_num: int ) -> Tuple[bytes, bytes]: parity_bit = output_pubkey(internal_pubkey, script_tree)[1] if internal_pubkey: pub_key_bytes = pub_keyinfo_from_key(internal_pubkey, compressed=True)[0][1:] else: h_str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" pub_key_bytes = bytes.fromhex(h_str) (leaf_version, script), path = tree_helper(script_tree)[0][script_num] control = (parity_bit + leaf_version).to_bytes(1, "big") control += pub_key_bytes control += path return script, control
def p2pkh( cls: Type["ScriptPubKey"], key: Key, compressed: Optional[bool] = None, network: Optional[str] = None, check_validity: bool = True, ) -> "ScriptPubKey": "Return the p2pkh ScriptPubKey of the provided key." pub_key, network = pub_keyinfo_from_key(key, network, compressed=compressed) script = serialize([ "OP_DUP", "OP_HASH160", hash160(pub_key), "OP_EQUALVERIFY", "OP_CHECKSIG" ]) return cls(script, network, check_validity)
def output_pubkey( internal_pubkey: Optional[Key] = None, script_tree: Optional[TaprootScriptTree] = None, ec: Curve = secp256k1, ) -> Tuple[bytes, int]: if not internal_pubkey and not script_tree: raise BTClibValueError("Missing data") if internal_pubkey: pubkey = pub_keyinfo_from_key(internal_pubkey, compressed=True)[0][1:] else: h_str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" pubkey = bytes.fromhex(h_str) if script_tree: _, h = tree_helper(script_tree) else: h = tagged_hash(b"TapTweak", pubkey) t = int.from_bytes(tagged_hash(b"TapTweak", pubkey + h), "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover x = int.from_bytes(pubkey, "big") Q = ec.add((x, ec.y_even(x)), mult(t)) return Q[0].to_bytes(32, "big"), Q[1] % 2
def p2wpkh_p2sh(key: Key, network: Optional[str] = None) -> str: "Return the p2wpkh-p2sh base58 address corresponding to a public key." pub_key, network = pub_keyinfo_from_key(key, network, compressed=True) witness_program = hash160(pub_key) return _address_from_v0_witness(witness_program, network)
def test_from_key() -> None: secp256r1 = CURVES["secp256r1"] m_c = bytes_from_point(Q, compressed=True), "mainnet" m_unc = bytes_from_point(Q, compressed=False), "mainnet" t_c = bytes_from_point(Q, compressed=True), "testnet" t_unc = bytes_from_point(Q, compressed=False), "testnet" for pub_key in [Q, *plain_pub_keys]: assert Q == point_from_pub_key(pub_key) with pytest.raises(BTClibValueError): point_from_pub_key(pub_key, secp256r1) assert m_c == pub_keyinfo_from_pub_key(pub_key) assert m_c == pub_keyinfo_from_pub_key(pub_key, "mainnet") assert m_c == pub_keyinfo_from_pub_key(pub_key, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_pub_key(pub_key, compressed=True) assert m_unc == pub_keyinfo_from_pub_key(pub_key, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_pub_key(pub_key, compressed=False) assert t_c == pub_keyinfo_from_pub_key(pub_key, "testnet") assert t_c == pub_keyinfo_from_pub_key(pub_key, "testnet", compressed=True) assert t_unc == pub_keyinfo_from_pub_key(pub_key, "testnet", compressed=False) for prv_key2 in [xpub_data, *compressed_pub_keys]: assert Q == point_from_pub_key(prv_key2) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key2, secp256r1) assert m_c == pub_keyinfo_from_pub_key(prv_key2) assert m_c == pub_keyinfo_from_pub_key(prv_key2, "mainnet") assert m_c == pub_keyinfo_from_pub_key(prv_key2, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_pub_key(prv_key2, compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, "mainnet", compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, "testnet", compressed=False) for prv_key3 in uncompressed_pub_keys: assert Q == point_from_pub_key(prv_key3) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key3, secp256r1) assert m_unc == pub_keyinfo_from_pub_key(prv_key3) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, "mainnet") with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, "mainnet", compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, compressed=True) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, "testnet", compressed=True) for prv_key4 in [xpub_data, *net_aware_pub_keys]: assert Q == point_from_pub_key(prv_key4) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key4, secp256r1) assert pub_keyinfo_from_pub_key(prv_key4) in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key4, "mainnet") in (m_c, m_unc) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key4, "testnet") for prv_key5 in net_unaware_pub_keys: assert Q == point_from_pub_key(prv_key5) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key5, secp256r1) assert pub_keyinfo_from_pub_key(prv_key5) in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key5, "mainnet") in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key5, "testnet") in (t_c, t_unc) for invalid_pub_key in [INF, INF_xpub_data, *invalid_pub_keys]: with pytest.raises(BTClibValueError): point_from_pub_key(invalid_pub_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(invalid_pub_key) # type: ignore for not_a_pub_key in [ INF, INF_xpub_data, *not_a_pub_keys, q, q0, qn, *plain_prv_keys, xprv_data, xprv0_data, xprvn_data, *compressed_prv_keys, *uncompressed_prv_keys, ]: with pytest.raises(BTClibValueError): point_from_pub_key(not_a_pub_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(not_a_pub_key) # type: ignore for key in [Q, *plain_pub_keys, q, *plain_prv_keys]: assert Q == point_from_key(key) assert m_c == pub_keyinfo_from_key(key) assert m_c == pub_keyinfo_from_key(key, "mainnet") assert m_c == pub_keyinfo_from_key(key, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_key(key, compressed=True) assert m_unc == pub_keyinfo_from_key(key, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_key(key, compressed=False) assert t_c == pub_keyinfo_from_key(key, "testnet") assert t_c == pub_keyinfo_from_key(key, "testnet", compressed=True) assert t_unc == pub_keyinfo_from_key(key, "testnet", compressed=False) for key2 in [ *compressed_pub_keys, xpub_data, xprv_data, *compressed_prv_keys ]: assert Q == point_from_key(key2) with pytest.raises(BTClibValueError): point_from_key(key2, secp256r1) assert m_c == pub_keyinfo_from_key(key2) assert m_c == pub_keyinfo_from_key(key2, "mainnet") assert m_c == pub_keyinfo_from_key(key2, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_key(key2, compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key2, "mainnet", compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key2, compressed=False) for key3 in [*uncompressed_pub_keys, *uncompressed_prv_keys]: assert Q == point_from_key(key3) with pytest.raises(BTClibValueError): point_from_key(key3, secp256r1) assert m_unc == pub_keyinfo_from_key(key3) assert m_unc == pub_keyinfo_from_key(key3, "mainnet") with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key3, "mainnet", compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key3, compressed=True) assert m_unc == pub_keyinfo_from_key(key3, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_key(key3, compressed=False) for key4 in [ *net_aware_pub_keys, xpub_data, xprv_data, *net_aware_prv_keys ]: assert Q == point_from_key(key4) with pytest.raises(BTClibValueError): point_from_key(key4, secp256r1) assert pub_keyinfo_from_key(key4) in (m_c, m_unc) assert pub_keyinfo_from_key(key4, "mainnet") in (m_c, m_unc) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key4, "testnet") for key5 in [q, *net_unaware_prv_keys, *net_unaware_pub_keys]: assert Q == point_from_key(key5) assert pub_keyinfo_from_key(key5) in (m_c, m_unc) assert pub_keyinfo_from_key(key5, "mainnet") in (m_c, m_unc) assert pub_keyinfo_from_key(key5, "testnet") in (t_c, t_unc) for invalid_key in [ INF, INF_xpub_data, *invalid_pub_keys, q0, qn, xprv0_data, xprvn_data, *invalid_prv_keys, ]: with pytest.raises(BTClibValueError): point_from_key(invalid_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_key(invalid_key) # type: ignore for not_a_key in [ q0, qn, xprv0_data, xprvn_data, INF, INF_xpub_data, *not_a_pub_keys, ]: with pytest.raises(BTClibValueError): point_from_key(not_a_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_key(not_a_key) # type: ignore
def p2wpkh(key: Key, network: Optional[str] = None) -> str: "Return the p2wpkh bech32 address corresponding to a public key." pub_key, network = pub_keyinfo_from_key(key, network, compressed=True) return address_from_witness(0, hash160(pub_key), network)