def point_from_key(key: Key, ec: Curve = secp256k1) -> Point: """Return a point tuple from any possible key representation. It supports: - BIP32 extended keys (bytes, string, or BIP32KeyData) - SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix) - native tuple """ if isinstance(key, tuple): return point_from_pub_key(key, ec) if isinstance(key, int): q, _, _ = prv_keyinfo_from_prv_key(key) return mult(q, ec.G, ec) try: q, net, _ = prv_keyinfo_from_prv_key(key) except BTClibValueError: pass else: if ec != NETWORKS[net].curve: raise BTClibValueError("Curve mismatch") return mult(q, ec.G, ec) return point_from_pub_key(key, ec)
def pub_keyinfo_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: "Return the pub key tuple (SEC-bytes, network) from a private key." q, net, compr = prv_keyinfo_from_prv_key(prv_key, network, compressed) ec = NETWORKS[net].curve pub_key = mult(q, ec.G, ec) return bytes_from_point(pub_key, ec, compr), net
def test_address_from_wif() -> None: q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 test_cases: List[Tuple[bool, str, str, str]] = [ ( False, "mainnet", "5J1geo9kcAUSM6GJJmhYRX1eZEjvos9nFyWwPstVziTVueRJYvW", "1LPM8SZ4RQDMZymUmVSiSSvrDfj1UZY9ig", ), ( True, "mainnet", "Kx621phdUCp6sgEXPSHwhDTrmHeUVrMkm6T95ycJyjyxbDXkr162", "1HJC7kFvXHepkSzdc8RX6khQKkAyntdfkB", ), ( False, "testnet", "91nKEXyJCPYaK9maw7bTJ7ZcCu6dy2gybvNtUWF1LTCYggzhZgy", "mzuJRVe3ERecM6F6V4R6GN9B5fKiPC9HxF", ), ( True, "testnet", "cNT1UjhUuGWN37hnmr754XxvPWwtAJTSq8bcCQ4pUrdxqxbA1iU1", "mwp9QoLuLK65XZUFKhPtvfujBjmgkZnmPx", ), ] for compressed, network, wif, address in test_cases: assert wif == b58.wif_from_prv_key(q, network, compressed) assert prv_keyinfo_from_prv_key(wif) == (q, network, compressed) assert address == b58.p2pkh(wif) script_type, payload, net = b58.h160_from_address(address) assert net == network assert script_type == "p2pkh" if compressed: b32_address = b32.p2wpkh(wif) assert (0, payload, net) == b32.witness_from_address(b32_address) b58_address = b58.p2wpkh_p2sh(wif) script_bin = hash160(b"\x00\x14" + payload) assert ("p2sh", script_bin, net) == b58.h160_from_address(b58_address) else: err_msg = "not a private or compressed public key: " with pytest.raises(BTClibValueError, match=err_msg): b32.p2wpkh(wif) # type: ignore with pytest.raises(BTClibValueError, match=err_msg): b58.p2wpkh_p2sh(wif) # type: ignore
def wif_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> str: "Return the WIF encoding of a private key." q, net, compr = prv_keyinfo_from_prv_key(prv_key, network, compressed) ec = NETWORKS[net].curve payload = b"".join([ NETWORKS[net].wif, q.to_bytes(ec.n_size, byteorder="big", signed=False), b"\x01" if compr else b"", ]) return b58encode(payload).decode("ascii")
def wif_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> str: "Return the WIF encoding of a private key." q, net, compr = prv_keyinfo_from_prv_key(prv_key) # the private key might provide network and compressed informations # e.g., wif or xprv network = net if network is None else network compressed = compr if compressed is None else compressed ec = NETWORKS[network].curve payload = b"".join([ NETWORKS[network].wif, q.to_bytes(ec.n_size, byteorder="big", signed=False), b"\x01" if compressed else b"", ]) return b58encode(payload).decode("ascii")
def sign(msg: Octets, prv_key: PrvKey, addr: Optional[String] = None) -> Sig: "Generate address-based compact signature for the provided message." # first sign the message magic_msg = magic_message(msg) q, network, compressed = prv_keyinfo_from_prv_key(prv_key) dsa_sig = dsa.sign(magic_msg, q) # now calculate the key_id # TODO do the match in Jacobian coordinates avoiding mod_inv pub_keys = dsa.recover_pub_keys(magic_msg, dsa_sig) Q = mult(q) # key_id is in [0, 3] # first two bits in rf are reserved for it key_id = pub_keys.index(Q) pub_key = bytes_from_point(Q, compressed=compressed) if isinstance(addr, str): addr = addr.strip() elif isinstance(addr, bytes): addr = addr.decode("ascii") # finally, calculate the recovery flag if addr is None or addr == p2pkh(pub_key, network, compressed): rf = key_id + 27 # third bit in rf is reserved for the 'compressed' boolean rf += 4 if compressed else 0 # BIP137 elif addr == p2wpkh_p2sh(pub_key, network): rf = key_id + 35 elif addr == p2wpkh(pub_key, network): rf = key_id + 39 else: raise BTClibValueError("mismatch between private key and address") return Sig(rf, dsa_sig)
print(f" r3: {hex(sig3.dsa_sig.r).upper()}") print(f" s3: {hex(sig3.dsa_sig.s).upper()}") bsmsig3 = sig3.serialize() print("4. Serialized signature:") print(" bytes:", bsmsig3) print("hex-string:", bsmsig3.hex().upper()) print("5. Verify signature") print("Bitcoin Core p2pkh:", verify(msg, address1, sig3)) print("BIP137 p2wpkh_p2sh:", verify(msg, address2, sig3)) print("BIP137 p2wpkh :", verify(msg, address3, sig3)) # uncompressed WIF / P2PKH address q, network, _ = prv_keyinfo_from_prv_key(wif) wif2 = wif_from_prv_key(q, network, compressed=False) print("\n1. Uncompressed WIF :", wif2) pubkey, network = pub_keyinfo_from_prv_key(wif2) address4 = p2pkh(pubkey) print("2. Uncompressed P2PKH address:", address4) print("3. Sign message with uncompressed p2pkh:") sig4 = sign(msg, wif2, address4) print(f"rf4: {sig4.rf}") print(f" r4: {hex(sig4.dsa_sig.r).upper()}") print(f" s4: {hex(sig4.dsa_sig.s).upper()}") bsmsig4 = sig4.serialize() print("4. Serialized signature:")
def test_one_prv_key_multiple_addresses() -> None: msg = "Paolo is afraid of ephemeral random numbers".encode() # Compressed WIF wif = "Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C" b58_p2pkh_compressed = b58.p2pkh(wif) b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif) b32_p2wpkh = b32.p2wpkh(wif) # sign with no address sig1 = bms.sign(msg, wif) # True for Bitcoin Core bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1) assert bms.verify(msg, b58_p2pkh_compressed, sig1) # True for Electrum p2wpkh_p2sh bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1) assert bms.verify(msg, b58_p2wpkh_p2sh, sig1) # True for Electrum p2wpkh bms.assert_as_valid(msg, b32_p2wpkh, sig1) assert bms.verify(msg, b32_p2wpkh, sig1) # sign with p2pkh address sig1 = bms.sign(msg, wif, b58_p2pkh_compressed) # True for Bitcoin Core bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1) assert bms.verify(msg, b58_p2pkh_compressed, sig1) # True for Electrum p2wpkh_p2sh bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1) assert bms.verify(msg, b58_p2wpkh_p2sh, sig1) # True for Electrum p2wpkh bms.assert_as_valid(msg, b32_p2wpkh, sig1) assert bms.verify(msg, b32_p2wpkh, sig1) assert sig1 == bms.sign(msg, wif, b58_p2pkh_compressed.encode("ascii")) # sign with p2wpkh_p2sh address (BIP137) sig2 = bms.sign(msg, wif, b58_p2wpkh_p2sh) # False for Bitcoin Core err_msg = "invalid p2pkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2pkh_compressed, sig2) assert not bms.verify(msg, b58_p2pkh_compressed, sig2) # True for BIP137 p2wpkh_p2sh bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig2) assert bms.verify(msg, b58_p2wpkh_p2sh, sig2) # False for BIP137 p2wpkh err_msg = "invalid p2wpkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b32_p2wpkh, sig2) assert not bms.verify(msg, b32_p2wpkh, sig2) assert sig2 == bms.sign(msg, wif, b58_p2wpkh_p2sh.encode("ascii")) # sign with p2wpkh address (BIP137) sig3 = bms.sign(msg, wif, b32_p2wpkh) # False for Bitcoin Core err_msg = "invalid p2pkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2pkh_compressed, sig3) assert not bms.verify(msg, b58_p2pkh_compressed, sig3) # False for BIP137 p2wpkh_p2sh err_msg = "invalid p2wpkh-p2sh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig3) assert not bms.verify(msg, b58_p2wpkh_p2sh, sig3) # True for BIP137 p2wpkh bms.assert_as_valid(msg, b32_p2wpkh, sig3) assert bms.verify(msg, b32_p2wpkh, sig3) assert sig3 == bms.sign(msg, wif, b32_p2wpkh.encode("ascii")) # uncompressed WIF / p2pkh address q, network, _ = prv_keyinfo_from_prv_key(wif) wif2 = b58.wif_from_prv_key(q, network, False) b58_p2pkh_uncompressed = b58.p2pkh(wif2) # sign with uncompressed p2pkh sig4 = bms.sign(msg, wif2, b58_p2pkh_uncompressed) # False for Bitcoin Core compressed p2pkh with pytest.raises(BTClibValueError, match="invalid p2pkh address: "): bms.assert_as_valid(msg, b58_p2pkh_compressed, sig4) assert not bms.verify(msg, b58_p2pkh_compressed, sig4) # False for BIP137 p2wpkh_p2sh err_msg = "invalid p2wpkh-p2sh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig4) assert not bms.verify(msg, b58_p2wpkh_p2sh, sig4) # False for BIP137 p2wpkh err_msg = "invalid p2wpkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b32_p2wpkh, sig4) assert not bms.verify(msg, b32_p2wpkh, sig4) # True for Bitcoin Core uncompressed p2pkh bms.assert_as_valid(msg, b58_p2pkh_uncompressed, sig4) assert bms.verify(msg, b58_p2pkh_uncompressed, sig4) assert sig4 == bms.sign(msg, wif2, b58_p2pkh_uncompressed.encode("ascii")) # unrelated different wif wif3 = "KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617" b58_p2pkh_compressed = b58.p2pkh(wif3) b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif3) b32_p2wpkh = b32.p2wpkh(wif3) # False for Bitcoin Core compressed p2pkh with pytest.raises(BTClibValueError, match="invalid p2pkh address: "): bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1) assert not bms.verify(msg, b58_p2pkh_compressed, sig1) # False for BIP137 p2wpkh_p2sh with pytest.raises(BTClibValueError, match="invalid p2wpkh-p2sh address: "): bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1) assert not bms.verify(msg, b58_p2wpkh_p2sh, sig1) # False for BIP137 p2wpkh with pytest.raises(BTClibValueError, match="invalid p2wpkh address: "): bms.assert_as_valid(msg, b32_p2wpkh, sig1) assert not bms.verify(msg, b32_p2wpkh, sig1) # FIXME: puzzling error message err_msg = "not a private or compressed public key for mainnet: " with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif2, b58_p2pkh_compressed) err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2pkh_uncompressed)
def test_wif_from_prv_key() -> None: q_prv_key = "0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D" wif_prv_keys = [ "KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617", "cMzLdeGd5vEqxB8B6VFQoRopQ3sLAAvEzDAoQgvX54xwofSWj1fx", "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ", "91gGn1HgSap6CbU12F6z3pJri26xzp7Ay1VW6NHCoEayNXwRpu2", " KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617", "KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617 ", ] for alt_prv_key in wif_prv_keys: assert alt_prv_key.strip() == b58.wif_from_prv_key(alt_prv_key) test_vectors: List[Tuple[str, str, bool]] = [ ("KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617", "mainnet", True), ("cMzLdeGd5vEqxB8B6VFQoRopQ3sLAAvEzDAoQgvX54xwofSWj1fx", "testnet", True), ("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ", "mainnet", False), ("91gGn1HgSap6CbU12F6z3pJri26xzp7Ay1VW6NHCoEayNXwRpu2", "testnet", False), (" KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617", "mainnet", True), ("KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617 ", "mainnet", True), ] for v in test_vectors: for prv_key in [q_prv_key] + wif_prv_keys: assert v[0].strip() == b58.wif_from_prv_key(prv_key, v[1], v[2]) q, network, compressed = prv_keyinfo_from_prv_key(v[0]) assert q == int(q_prv_key, 16) assert network == v[1] assert compressed == v[2] bad_q = ec.n.to_bytes(ec.n_size, byteorder="big", signed=False) with pytest.raises(BTClibValueError, match="private key not in 1..n-1: "): b58.wif_from_prv_key(bad_q, "mainnet", True) payload = b"\x80" + bad_q badwif = b58encode(payload) with pytest.raises(BTClibValueError, match="not a private key: "): prv_keyinfo_from_prv_key(badwif) # not a private key: 33 bytes bad_q = 33 * b"\x02" with pytest.raises(BTClibValueError, match="not a private key: "): b58.wif_from_prv_key(bad_q, "mainnet", True) payload = b"\x80" + bad_q badwif = b58encode(payload) with pytest.raises(BTClibValueError, match="not a private key: "): prv_keyinfo_from_prv_key(badwif) # Not a WIF: missing leading 0x80 good_q = 32 * b"\x02" payload = b"\x81" + good_q badwif = b58encode(payload) with pytest.raises(BTClibValueError, match="not a private key: "): prv_keyinfo_from_prv_key(badwif) # Not a compressed WIF: missing trailing 0x01 payload = b"\x80" + good_q + b"\x00" badwif = b58encode(payload) with pytest.raises(BTClibValueError, match="not a private key: "): prv_keyinfo_from_prv_key(badwif) # Not a WIF: wrong size (35) payload = b"\x80" + good_q + b"\x01\x00" badwif = b58encode(payload) with pytest.raises(BTClibValueError, match="not a private key: "): prv_keyinfo_from_prv_key(badwif)
def test_from_prv_key() -> None: secp256r1 = CURVES["secp256r1"] m_c = (q, "mainnet", True) m_unc = (q, "mainnet", False) t_c = (q, "testnet", True) t_unc = (q, "testnet", False) for prv_key in [q, *plain_prv_keys]: assert q == int_from_prv_key(prv_key) assert q == int_from_prv_key(prv_key, secp256r1) assert m_c == prv_keyinfo_from_prv_key(prv_key) assert m_c == prv_keyinfo_from_prv_key(prv_key, "mainnet") assert m_c == prv_keyinfo_from_prv_key(prv_key, "mainnet", compressed=True) assert m_c == prv_keyinfo_from_prv_key(prv_key, compressed=True) assert m_unc == prv_keyinfo_from_prv_key(prv_key, "mainnet", compressed=False) assert m_unc == prv_keyinfo_from_prv_key(prv_key, compressed=False) assert t_c == prv_keyinfo_from_prv_key(prv_key, "testnet") assert t_c == prv_keyinfo_from_prv_key(prv_key, "testnet", compressed=True) assert t_unc == prv_keyinfo_from_prv_key(prv_key, "testnet", compressed=False) for prv_key2 in [xprv_data, *compressed_prv_keys]: assert q == int_from_prv_key(prv_key2) with pytest.raises(BTClibValueError): int_from_prv_key(prv_key2, secp256r1) assert m_c == prv_keyinfo_from_prv_key(prv_key2) assert m_c == prv_keyinfo_from_prv_key(prv_key2, "mainnet") assert m_c == prv_keyinfo_from_prv_key(prv_key2, "mainnet", compressed=True) assert m_c == prv_keyinfo_from_prv_key(prv_key2, compressed=True) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key2, "mainnet", compressed=False) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key2, compressed=False) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key2, "testnet") with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key2, "testnet", compressed=True) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key2, "testnet", compressed=False) for prv_key3 in uncompressed_prv_keys: assert q == int_from_prv_key(prv_key3) with pytest.raises(BTClibValueError): int_from_prv_key(prv_key3, secp256r1) assert m_unc == prv_keyinfo_from_prv_key(prv_key3) assert m_unc == prv_keyinfo_from_prv_key(prv_key3, "mainnet") with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key3, "mainnet", compressed=True) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key3, compressed=True) assert m_unc == prv_keyinfo_from_prv_key(prv_key3, "mainnet", compressed=False) assert m_unc == prv_keyinfo_from_prv_key(prv_key3, compressed=False) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key3, "testnet") with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key3, "testnet", compressed=True) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key3, "testnet", compressed=False) for prv_key4 in [xprv_data, *net_aware_prv_keys]: assert q == int_from_prv_key(prv_key4) with pytest.raises(BTClibValueError): int_from_prv_key(prv_key4, secp256r1) assert prv_keyinfo_from_prv_key(prv_key4) in (m_c, m_unc) assert prv_keyinfo_from_prv_key(prv_key4, "mainnet") in (m_c, m_unc) with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(prv_key4, "testnet") for prv_key5 in [q, *net_unaware_prv_keys]: assert q == int_from_prv_key(prv_key5) assert q == int_from_prv_key(prv_key5, secp256r1) assert prv_keyinfo_from_prv_key(prv_key5) in (m_c, m_unc) assert prv_keyinfo_from_prv_key(prv_key5, "mainnet") in (m_c, m_unc) assert prv_keyinfo_from_prv_key(prv_key5, "testnet") in (t_c, t_unc) for invalid_prv_key in [q0, qn, xprv0_data, xprvn_data, *invalid_prv_keys]: with pytest.raises(BTClibValueError): int_from_prv_key(invalid_prv_key) # type: ignore with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(invalid_prv_key) # type: ignore for not_a_prv_key in [ q0, qn, xprv0_data, xprvn_data, INF, INF_xpub_data, *not_a_prv_keys, Q, *plain_pub_keys, xpub_data, *compressed_pub_keys, *uncompressed_pub_keys, ]: with pytest.raises(BTClibValueError): int_from_prv_key(not_a_prv_key) # type: ignore with pytest.raises(BTClibValueError): prv_keyinfo_from_prv_key(not_a_prv_key) # type: ignore