def test_infinity_point_from_octets() -> None: curve_size = CURVES["secp256k1"].p_size inf_bytes = b"\x04" inf_bytes += INF[0].to_bytes(curve_size, byteorder="big", signed=False) inf_bytes += INF[1].to_bytes(curve_size, byteorder="big", signed=False) with pytest.raises(BTClibValueError, match="no bytes representation for infinity point"): point_from_octets(inf_bytes)
def _assert_valid_partial_sigs(partial_sigs: Mapping[bytes, bytes]) -> None: "Raise an exception if the dataclass element is not valid." for pub_key, sig in partial_sigs.items(): try: # pub_key must be a valid secp256k1 Point in SEC representation sec_point.point_from_octets(pub_key) except BTClibValueError as e: err_msg = "invalid partial signature pub_key: {pub_key!r}" raise BTClibValueError(err_msg) from e try: dsa.Sig.parse(sig) except BTClibValueError as e: err_msg = f"invalid partial signature: {sig!r}" raise BTClibValueError(err_msg) from e
def test_p2wpkh() -> None: # https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki # leading/trailing spaces should be tolerated pub = " 02 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" addr = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" assert addr == b32.p2wpkh(pub) addr = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" assert addr == b32.p2wpkh(pub, "testnet") # http://bitcoinscri.pt/pages/segwit_native_p2wpkh pub = "02 530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def7" addr = "bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck" assert addr == b32.p2wpkh(pub) _, wit_prg, _ = b32.witness_from_address(addr) assert wit_prg == hash160(pub) uncompr_pub = bytes_from_point(point_from_octets(pub), compressed=False) err_msg = "not a private or compressed public key: " with pytest.raises(BTClibValueError, match=err_msg): b32.p2wpkh(uncompr_pub) with pytest.raises(BTClibValueError, match=err_msg): b32.p2wpkh(pub + "00") err_msg = "invalid size: " with pytest.raises(BTClibValueError, match=err_msg): b32.address_from_witness(0, hash160(pub) + b"\x00")
def test_p2pkh_from_pub_key() -> None: # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses pub_key = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" address = "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs" assert address == b58.p2pkh(pub_key) assert address == b58.p2pkh(pub_key, compressed=True) _, h160, _ = b58.h160_from_address(address) assert h160 == hash160(pub_key) # trailing/leading spaces in address string assert address == b58.p2pkh(" " + pub_key) assert h160 == hash160(" " + pub_key) assert address == b58.p2pkh(pub_key + " ") assert h160 == hash160(pub_key + " ") uncompr_pub_key = bytes_from_point(point_from_octets(pub_key), compressed=False) uncompr_address = "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM" assert uncompr_address == b58.p2pkh(uncompr_pub_key, compressed=False) assert uncompr_address == b58.p2pkh(uncompr_pub_key) _, uncompr_h160, _ = b58.h160_from_address(uncompr_address) assert uncompr_h160 == hash160(uncompr_pub_key) err_msg = "not a private or uncompressed public key: " with pytest.raises(BTClibValueError, match=err_msg): assert uncompr_address == b58.p2pkh(pub_key, compressed=False) err_msg = "not a private or compressed public key: " with pytest.raises(BTClibValueError, match=err_msg): assert address == b58.p2pkh(uncompr_pub_key, compressed=True)
def test_forge_hash_sig() -> None: """forging valid hash signatures""" # pylint: disable=protected-access ec = CURVES["secp256k1"] # see https://twitter.com/pwuille/status/1063582706288586752 # Satoshi's key key = "03 11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" Q = point_from_octets(key, ec) # pick u1 and u2 at will u1 = 1 u2 = 2 R = double_mult(u2, Q, u1, ec.G, ec) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n s = ec.n - s if s > ec.n / 2 else s e = s * u1 % ec.n dsa._assert_as_valid_(e, (Q[0], Q[1], 1), r, s, lower_s=True, ec=ec) # pick u1 and u2 at will u1 = 1234567890 u2 = 987654321 R = double_mult(u2, Q, u1, ec.G, ec) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n s = ec.n - s if s > ec.n / 2 else s e = s * u1 % ec.n dsa._assert_as_valid_(e, (Q[0], Q[1], 1), r, s, lower_s=True, ec=ec)
def __init__( self, version: Octets, depth: int, parent_fingerprint: Octets, index: int, chain_code: Octets, key: Octets, check_validity: bool = True, ) -> None: super().__init__(version, depth, parent_fingerprint, index, chain_code, key, False) if self.is_private: self.prv_key_int = int.from_bytes(self.key[1:], "big", signed=False) self.pub_key_point = INF else: self.prv_key_int = 0 self.pub_key_point = point_from_octets(self.key, ec) if check_validity: self.assert_valid()
def _point_from_xpub(xpub: BIP32Key, ec: Curve) -> Point: "Return an elliptic curve point tuple from a xpub key." if isinstance(xpub, BIP32KeyData): xpub.assert_valid() else: xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] in (2, 3): ec2 = curve_from_xkeyversion(xpub.version) if ec != ec2: raise BTClibValueError(f"ec/xpub version ({xpub.version.hex()}) mismatch") return point_from_octets(xpub.key, ec) raise BTClibValueError(f"not a public key: {xpub.key.hex()}")
def assert_p2pk(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, (35, 67)) # p2pk [pub_key, OP_CHECKSIG] # 0x41{65-byte pub_key}AC # or # 0x21{33-byte pub_key}AC if script_pub_key[-1] != 0xAC: raise BTClibValueError("missing final OP_CHECKSIG") len_marker = script_pub_key[0] length = len(script_pub_key) if length == 35: if len_marker != 0x21: err_msg = f"invalid pub_key length marker: {len_marker}" err_msg += f" instead of {0x21}" raise BTClibValueError(err_msg) elif length == 67: if len_marker != 0x41: err_msg = f"invalid pub_key length marker: {len_marker}" err_msg += f" instead of {0x41}" raise BTClibValueError(err_msg) pub_key = script_pub_key[1:-1] point_from_octets(pub_key)
def point_from_pub_key(pub_key: PubKey, ec: Curve = secp256k1) -> Point: "Return an elliptic curve point tuple from a public key." if isinstance(pub_key, tuple): if ec.is_on_curve(pub_key) and pub_key[1] != 0: return pub_key raise BTClibValueError(f"not a valid public key: {pub_key}") if isinstance(pub_key, BIP32KeyData): return _point_from_xpub(pub_key, ec) try: return _point_from_xpub(pub_key, ec) except (TypeError, BTClibValueError): pass # it must be octets try: return point_from_octets(pub_key, ec) except (TypeError, ValueError) as e: raise BTClibValueError(f"not a public key: {pub_key!r}") from e
def pub_keyinfo_from_pub_key(pub_key: PubKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: "Return the pub key tuple (SEC-bytes, network) from a public key." compr = True if compressed is None else compressed net = "mainnet" if network is None else network ec = NETWORKS[net].curve if isinstance(pub_key, tuple): return bytes_from_point(pub_key, ec, compr), net if isinstance(pub_key, BIP32KeyData): return _pub_keyinfo_from_xpub(pub_key, network, compressed) try: return _pub_keyinfo_from_xpub(pub_key, network, compressed) except (TypeError, BTClibValueError): pass # it must be octets try: if compressed is None: pub_key = bytes_from_octets(pub_key, (ec.p_size + 1, 2 * ec.p_size + 1)) compr = False if len(pub_key) == ec.p_size + 1: compr = True else: size = ec.p_size + 1 if compressed else 2 * ec.p_size + 1 pub_key = bytes_from_octets(pub_key, size) compr = compressed except (TypeError, ValueError) as e: err_msg = f"not a public key: {pub_key!r}" raise BTClibValueError(err_msg) from e # verify that it is a valid point Q = point_from_octets(pub_key, ec) return bytes_from_point(Q, ec, compr), net
def test_octets2point() -> None: for ec in all_curves.values(): G_bytes = bytes_from_point(ec.G, ec) G_point = point_from_octets(G_bytes, ec) assert ec.G == G_point G_bytes = bytes_from_point(ec.G, ec, False) G_point = point_from_octets(G_bytes, ec) assert ec.G == G_point # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) Q_bytes = b"\x03" if Q[1] & 1 else b"\x02" Q_bytes += Q[0].to_bytes(ec.p_size, byteorder="big", signed=False) Q_point = point_from_octets(Q_bytes, ec) assert Q_point == Q assert bytes_from_point(Q_point, ec) == Q_bytes Q_hex_str = Q_bytes.hex() Q_point = point_from_octets(Q_hex_str, ec) assert Q_point == Q Q_bytes = b"\x04" + Q[0].to_bytes( ec.p_size, byteorder="big", signed=False) Q_bytes += Q[1].to_bytes(ec.p_size, byteorder="big", signed=False) Q_point = point_from_octets(Q_bytes, ec) assert Q_point == Q assert bytes_from_point(Q_point, ec, False) == Q_bytes Q_hex_str = Q_bytes.hex() Q_point = point_from_octets(Q_hex_str, ec) assert Q_point == Q Q_bytes = b"\x01" + b"\x01" * ec.p_size with pytest.raises(BTClibValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x01" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x04" + b"\x01" * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for uncompressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x02" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x03" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) # invalid x_Q coordinate ec = CURVES["secp256k1"] x_Q = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 xstr = format(x_Q, "32X") with pytest.raises(BTClibValueError, match="invalid x-coordinate: "): point_from_octets("03" + xstr, ec) with pytest.raises(BTClibValueError, match="point not on curve: "): point_from_octets("04" + 2 * xstr, ec) with pytest.raises(BTClibValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec) with pytest.raises(BTClibValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec, False)