def test_wif_from_prv_key() -> None: prv_key = "0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D" 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: 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(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_msgsign_p2pkh() -> None: msg = "test message".encode() # sigs are taken from (Electrum and) Bitcoin Core q = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" # uncompressed wif1u = b58.wif_from_prv_key(q, "mainnet", False) assert wif1u == "5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw" add1u = b58.p2pkh(wif1u) assert add1u == "1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD" bms_sig1u = bms.sign(msg, wif1u) assert bms.verify(msg, add1u, bms_sig1u) assert bms_sig1u.rf == 27 exp_sig1u = "G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=" assert bms_sig1u.b64encode() == exp_sig1u # compressed wif1c = b58.wif_from_prv_key(q, "mainnet", True) assert wif1c == "L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk" add1c = b58.p2pkh(wif1c) assert add1c == "14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY" bms_sig1c = bms.sign(msg, wif1c) assert bms.verify(msg, add1c, bms_sig1c) assert bms_sig1c.rf == 31 exp_sig1c = "H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=" assert bms_sig1c.b64encode() == exp_sig1c assert not bms.verify(msg, add1c, bms_sig1u) assert not bms.verify(msg, add1u, bms_sig1c) bms_sig = bms.Sig(bms_sig1c.rf + 1, bms_sig1c.dsa_sig) assert not bms.verify(msg, add1c, bms_sig) # malleate s s = ec.n - bms_sig1c.dsa_sig.s dsa_sig = dsa.Sig(bms_sig1c.dsa_sig.r, s, bms_sig1c.dsa_sig.ec) # without updating rf verification will fail, even with lower_s=False bms_sig = bms.Sig(bms_sig1c.rf, dsa_sig) assert not bms.verify(msg, add1c, bms_sig, lower_s=False) # update rf to satisfy above malleation i = 1 if bms_sig1c.rf % 2 else -1 bms_sig = bms.Sig(bms_sig1c.rf + i, dsa_sig) assert bms.verify(msg, add1c, bms_sig, lower_s=False) # anyway, with lower_s=True malleation does fail verification err_msg = "not a low s" with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, add1c, bms_sig, lower_s=True)
def test_p2pkh_from_wif() -> None: seed = b"\x00" * 32 # better be a documented test case rxprv = bip32.rootxprv_from_seed(seed) path = "m/0h/0h/12" xprv = bip32.derive(rxprv, path) wif = b58.wif_from_prv_key(xprv) assert wif == "L2L1dqRmkmVtwStNf5wg8nnGaRn3buoQr721XShM4VwDbTcn9bpm" pub_key, _ = pub_keyinfo_from_prv_key(wif) address = b58.p2pkh(pub_key) xpub = bip32.xpub_from_xprv(xprv) assert address == slip132.address_from_xpub(xpub) err_msg = "not a private key: " with pytest.raises(BTClibValueError, match=err_msg): b58.wif_from_prv_key(xpub)
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 gen_keys( prv_key: Optional[PrvKey] = None, network: Optional[str] = None, compressed: Optional[bool] = None, ) -> Tuple[str, str]: """Return a private/public key pair. The private key is a WIF, the public key is a base58 P2PKH address. """ if prv_key is None: if network is None: network = "mainnet" ec = NETWORKS[network].curve # q in the range [1, ec.n-1] q = 1 + secrets.randbelow(ec.n - 1) wif = wif_from_prv_key(q, network, compressed) else: wif = wif_from_prv_key(prv_key, network, compressed) address = p2pkh(wif) return wif, address
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:") print(" bytes:", bsmsig4)
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)