def test_slip132_test_vectors() -> None: """SLIP132 test vector https://github.com/satoshilabs/slips/blob/master/slip-0132.md """ mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" kpath = "m/0/0" test_vectors: List[Tuple[bytes, str, str, str, str]] = [ ( NETWORKS["mainnet"].bip32_prv, "m / 44h / 0h / 0h", "xprv9xpXFhFpqdQK3TmytPBqXtGSwS3DLjojFhTGht8gwAAii8py5X6pxeBnQ6ehJiyJ6nDjWGJfZ95WxByFXVkDxHXrqu53WCRGypk2ttuqncb", "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", ), ( NETWORKS["mainnet"].slip132_p2wpkh_p2sh_prv, "m / 49h / 0h / 0h", "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF", "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", ), ( NETWORKS["mainnet"].slip132_p2wpkh_prv, "m / 84h / 0h / 0h", "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE", "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", ), ] for version, der_path, prv, pub, addr in test_vectors: rxprv = bip39.mxprv_from_mnemonic(mnemonic, "") mxprv = bip32.derive(rxprv, der_path, version) assert prv == mxprv mxpub = bip32.xpub_from_xprv(mxprv) assert pub == mxpub xpub = bip32.derive(mxpub, kpath) address = slip132.address_from_xpub(xpub) assert addr == address address = slip132.address_from_xkey(xpub) assert addr == address xprv = bip32.derive(mxprv, kpath) address = slip132.address_from_xkey(xprv) assert addr == address if version == NETWORKS["mainnet"].bip32_prv: address = b58.p2pkh(xpub) assert addr == address address = b58.p2pkh(xprv) assert addr == address elif version == NETWORKS["mainnet"].slip132_p2wpkh_p2sh_prv: address = b58.p2wpkh_p2sh(xpub) assert addr == address address = b58.p2wpkh_p2sh(xprv) assert addr == address elif version == NETWORKS["mainnet"].slip132_p2wpkh_prv: address = b32.p2wpkh(xpub) assert addr == address address = b32.p2wpkh(xprv) assert addr == address
def test_eq() -> None: pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" script_pub_key = ScriptPubKey.p2pkh(pub_key) addr = b58.p2pkh(pub_key) assert ScriptPubKey.from_address(addr) == script_pub_key addr = b58.p2pkh(pub_key, "testnet") assert ScriptPubKey.from_address(addr) != script_pub_key assert Script(script_pub_key.script) != script_pub_key assert script_pub_key != Script(script_pub_key.script)
def test_exceptions() -> None: pub_key = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" payload = b"\xf5" + hash160(pub_key) invalid_address = b58encode(payload) with pytest.raises(BTClibValueError, match="invalid base58 address prefix: "): b58.h160_from_address(invalid_address) with pytest.raises(BTClibValueError, match="not a private or public key: "): b58.p2pkh(pub_key + "00")
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_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_p2ms_3() -> None: # tx_id 33ac2af1a6f894276713b59ed09ce1a20fed5b36d169f20a3fe831dc45564d57 # output n 0 keys: List[Command] = [ "036D568125A969DC78B963B494FA7ED5F20EE9C2F2FC2C57F86C5DF63089F2ED3A", "03FE4E6231D614D159741DF8371FA3B31AB93B3D28A7495CDAA0CD63A2097015C7", ] cmds: List[Command] = ["OP_1", *keys, "OP_2", "OP_CHECKMULTISIG"] script_pub_key = ScriptPubKey(serialize(cmds)) assert script_pub_key == ScriptPubKey.p2ms(1, keys) pub_keys = script_pub_key.addresses exp_pub_keys = [ "1Ng4YU2e2H3E86syX2qrsmD9opBHZ42vCF", "14XufxyGiY6ZBJsFYHJm6awdzpJdtsP1i3", ] for pub_key, key, exp_pub_key in zip(pub_keys, keys, exp_pub_keys): assert pub_key == b58.p2pkh(key) assert pub_key == exp_pub_key # tx 56214420a7c4dcc4832944298d169a75e93acf9721f00656b2ee0e4d194f9970 # input n 1 cmds_sig: List[Command] = [ "OP_0", "3045022100dba1e9b1c8477fd364edcc1f81845928202daf465a1e2d92904c13c88761cbd002200add6af863dfdb7efb95f334baec041e90811ae9d81624f9f87f33a56761f29401", ] script_sig = Script(serialize(cmds_sig)) script = script_sig + script_pub_key # parse(serialize(*)) is to enforce same string case convention assert script.asm == parse(serialize(cmds_sig + cmds))
def test_derive_from_account() -> None: seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad" rmxprv = rootxprv_from_seed(seed) der_path = "m / 44 h / 0 h" mxpub = xpub_from_xprv(derive(rmxprv, der_path)) test_vectors = [ [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], ] for branch, index in test_vectors: full_path = der_path + f"/{branch}/{index}" addr = p2pkh(derive(rmxprv, full_path)) assert addr == p2pkh(derive_from_account(mxpub, branch, index)) err_msg = "invalid private derivation at branch level" with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 0x80000000, 0, True) err_msg = "too high branch: " with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 0xFFFF + 1, 0) err_msg = "invalid branch: " with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 2, 0) err_msg = "invalid private derivation at address index level" with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 0, 0x80000000) err_msg = "too high address index: " with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 0, 0xFFFF + 1) der_path = "m / 44 h / 0" mxpub = xpub_from_xprv(derive(rmxprv, der_path)) err_msg = "unhardened account/master key" with pytest.raises(BTClibValueError, match=err_msg): derive_from_account(mxpub, 0, 0)
def test_derive() -> None: test_vectors = { "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS": [ ["m / 0 h / 0 h / 463 h", "1DyfBWxhVLmrJ7keyiHeMbt7N3UdeGU4G5"], ["M / 0H / 0h // 267' / ", "11x2mn59Qy43DjisZWQGRResjyQmgthki"], ], "tprv8ZgxMBicQKsPe3g3HwF9xxTLiyc5tNyEtjhBBAk29YA3MTQUqULrmg7aj9qTKNfieuu2HryQ6tGVHse9x7ANFGs3f4HgypMc5nSSoxwf7TK": [ ["m / 0 h / 0 h / 51 h", "mfXYCCsvWPgeCv8ZYGqcubpNLYy5nYHbbj"], ["m / 0 h / 1 h / 150 h", "mfaUnRFxVvf55uD1P3zWXpprN1EJcKcGrb"], ], } for rootxprv, value in test_vectors.items(): for der_path, address in value: assert address == p2pkh(derive(rootxprv, der_path)) indexes = _indexes_from_bip32_path_str(der_path) assert address == p2pkh(derive(rootxprv, indexes)) assert derive(rootxprv, "m") == rootxprv
def test_exceptions() -> None: with pytest.raises(BTClibValueError, match="not a private or public key: "): # invalid checksum xprv = "xppp9s21ZrQH143K2oxHiQ5f7D7WYgXD9h6HAXDBuMoozDGGiYHWsq7TLBj2yvGuHTLSPCaFmUyN1v3fJRiY2A4YuNSrqQMPVLZKt76goL6LP7L" p2pkh(xprv) with pytest.raises(BTClibValueError, match="not a private key: "): xpub = "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" xpub_from_xprv(xpub) seed = "5b56c417303faa3fcba7e57400e120a0" with pytest.raises(BTClibValueError, match="unknown extended key version: "): version = b"\x04\x88\xAD\xE5" rootxprv_from_seed(seed, version) with pytest.raises(BTClibValueError, match="too many bits for seed: "): rootxprv_from_seed(seed * 5) with pytest.raises(BTClibValueError, match="too few bits for seed: "): rootxprv_from_seed(seed[:-2])
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 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_p2pkh() -> None: # self-consistency pub_key = ( "04 " "cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" "f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4") payload = hash160(pub_key) script_pub_key = serialize( ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"]) assert_p2pkh(script_pub_key) assert script_pub_key == ScriptPubKey.p2pkh(pub_key).script assert ("p2pkh", payload) == type_and_payload(script_pub_key) # base58 address network = "mainnet" addr = b58.p2pkh(pub_key, network) assert addr == address(script_pub_key, network) assert addr == b58.address_from_h160("p2pkh", payload, network) # back from the address to the script_pub_key assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network # documented test case: https://learnmeabitcoin.com/guide/p2pkh payload = bytes.fromhex("12ab8dc588ca9d5787dde7eb29569da63c3a238c") script_pub_key = bytes.fromhex("76a914") + payload + bytes.fromhex("88ac") assert_p2pkh(script_pub_key) addr = "12higDjoCCNXSA95xZMWUdPvXNmkAduhWv" assert addr == address(script_pub_key, network) assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network err_msg = "missing final OP_EQUALVERIFY, OP_CHECKSIG" with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(script_pub_key[:-2] + b"\x40\x40") err_msg = "missing leading OP_DUP, OP_HASH160" with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(b"\x40\x40" + script_pub_key[2:]) err_msg = "invalid pub_key hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(script_pub_key[:2] + b"\x40" + script_pub_key[3:])
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] prv_key = 1 + secrets.randbelow(ec.n - 1) wif = wif_from_prv_key(prv_key, network, compressed) return wif, p2pkh(wif)
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)
def test_segwit() -> None: msg = "test".encode() wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK" b58_p2pkh = b58.p2pkh(wif) b32_p2wpkh = b32.p2wpkh(wif) b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif) # p2pkh base58 address (Core, Electrum, BIP137) exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b58_p2pkh, exp_sig) bms_sig = bms.sign(msg, wif) # no address: p2pkh assumed assert bms.verify(msg, b58_p2pkh, bms_sig) assert bms_sig.b64encode() == exp_sig # p2wpkh-p2sh base58 address (Electrum) assert bms.verify(msg, b58_p2wpkh_p2sh, bms_sig) # p2wpkh bech32 address (Electrum) assert bms.verify(msg, b32_p2wpkh, bms_sig) # p2wpkh-p2sh base58 address (BIP137) # different first letter in bms_sig because of different rf exp_sig = "JBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b58_p2wpkh_p2sh, exp_sig) bms_sig = bms.sign(msg, wif, b58_p2wpkh_p2sh) assert bms.verify(msg, b58_p2wpkh_p2sh, bms_sig) assert bms_sig.b64encode() == exp_sig # p2wpkh bech32 address (BIP137) # different first letter in bms_sig because of different rf exp_sig = "KBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b32_p2wpkh, exp_sig) bms_sig = bms.sign(msg, wif, b32_p2wpkh) assert bms.verify(msg, b32_p2wpkh, bms_sig) assert bms_sig.b64encode() == exp_sig
def addresses(script_pub_key: Octets, network: str = "mainnet") -> List[str]: "Return the p2pkh addresses of the pub_keys used in a p2ms script_pub_key." script_pub_key = bytes_from_octets(script_pub_key) # p2ms [m, pub_keys, n, OP_CHECKMULTISIG] length = len(script_pub_key) if length < 37: raise BTClibValueError(f"invalid p2ms length {length}") if script_pub_key[-1] != 0xAE: raise BTClibValueError("missing final OP_CHECKMULTISIG") m = script_pub_key[0] - 80 if not 0 < m < 17: raise BTClibValueError(f"invalid m in m-of-n: {m}") n = script_pub_key[-2] - 80 if not m <= n < 17: raise BTClibValueError(f"invalid m-of-n: {m}-of-{n}") stream = bytesio_from_binarydata(script_pub_key[1:-2]) pub_keys = [var_bytes.parse(stream) for _ in range(n)] if stream.read(1): raise BTClibValueError("invalid p2ms script_pub_key size") return [b58.p2pkh(pub_key, network) for pub_key in pub_keys]
from btclib.b58 import p2pkh, p2wpkh_p2sh from btclib.b58 import wif_from_prv_key from btclib.b32 import p2wpkh from btclib.ecc.bms import sign, verify from btclib.to_prv_key import prv_keyinfo_from_prv_key from btclib.to_pub_key import pub_keyinfo_from_prv_key msg = "Paolo is afraid of ephemeral random numbers".encode() print("\n0. Message:", msg.decode()) wif = b"Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C" print("1. Compressed WIF:", wif.decode()) pubkey, network = pub_keyinfo_from_prv_key(wif) print("2. Addresses") address1 = p2pkh(pubkey) print(" p2pkh:", address1) address2 = p2wpkh_p2sh(pubkey) print("p2wpkh_p2sh:", address2) address3 = p2wpkh(pubkey) print(" p2wpkh:", address3) print( "\n3. Sign message with no address (i.e., with default compressed p2pkh address):" ) sig1 = sign(msg, wif) print(f"rf1: {sig1.rf}") print(f" r1: {hex(sig1.dsa_sig.r).upper()}") print(f" s1: {hex(sig1.dsa_sig.r).upper()}")
def test_exceptions() -> None: msg = "test".encode() wif = "KwELaABegYxcKApCb3kJR9ymecfZZskL9BzVUkQhsqFiUKftb4tu" address = b58.p2pkh(wif) exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=" bms.assert_as_valid(msg, address, exp_sig) bms_sig = bms.Sig.b64decode(exp_sig) err_msg = "not a p2wpkh address: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b32.p2wsh(32 * b"\x00"), exp_sig) err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.Sig(26, bms_sig.dsa_sig) exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLoVc=" err_msg = "invalid decoded length: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) exp_sig = "GpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=" err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) exp_sig = "QpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=" with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) # compressed wif, uncompressed address wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ" address = "19f7adDYqhHSJm2v7igFWZAqxXHj1vUa3T" err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, address) # uncompressed wif, compressed address wif = "5JDopdKaxz5bXVYXcAnfno6oeSL8dpipxtU1AhfKe3Z58X48srn" address = "1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5" err_msg = "not a private or compressed public key for mainnet: " # FIXME puzzling error message with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, address) msg = "test".encode() wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK" b58_p2pkh = b58.p2pkh(wif) b32_p2wpkh = b32.p2wpkh(wif) b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif) wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ" err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2pkh) with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b32_p2wpkh) with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2wpkh_p2sh) # Invalid recovery flag (39) for base58 p2pkh address exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=" bms_sig = bms.Sig.b64decode(exp_sig) bms_sig = bms.Sig(39, bms_sig.dsa_sig, check_validity=False) sig_encoded = bms_sig.b64encode(check_validity=False) err_msg = "invalid p2pkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2pkh, sig_encoded) # Invalid recovery flag (35) for bech32 p2wpkh address exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" bms_sig = bms.Sig.b64decode(exp_sig) bms_sig = bms.Sig(35, bms_sig.dsa_sig, check_validity=False) err_msg = "invalid p2wpkh address recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b32_p2wpkh, bms_sig)
def test_ledger() -> None: """Hybrid ECDSA Bitcoin message signature generated by Ledger""" mnemonic = ( "barely sun snack this snack relief pipe attack disease boss enlist lawsuit" ) # non-standard leading 31 in DER serialization derivation_path = "m/1" msg = b"\xfb\xa3\x1f\x8cd\x85\xe29#K\xb3{\xfd\xa7<?\x95oL\xee\x19\xb2'oh\xa7]\xd9A\xfeU\xd8" dersig_hex_str = "3144022012ec0c174936c2a46dc657252340b2e6e6dd8c31dd059b6f9f33a90c21af2fba022030e6305b3ccf88009d419bf7651afcfcc0a30898b93ae9de9aa6ac03cf8ec56b" # pub_key derivation rprv = bip39.mxprv_from_mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) # the actual message being signed magic_msg = magic_message(msg) # save key_id and patch dersig dersig = bytes.fromhex(dersig_hex_str) key_id = dersig[0] dsa_sig = dsa.Sig.parse(b"\x30" + dersig[1:]) # ECDSA signature verification of the patched dersig dsa.assert_as_valid(magic_msg, xprv, dsa_sig) assert dsa.verify(magic_msg, xprv, dsa_sig) # compressed address addr = b58.p2pkh(xprv) # equivalent Bitcoin Message Signature rec_flag = 27 + 4 + (key_id & 0x01) bms_sig = bms.Sig(rec_flag, dsa_sig) # Bitcoin Message Signature verification bms.assert_as_valid(msg, addr, bms_sig) assert bms.verify(msg, addr, bms_sig) assert not bms.verify(magic_msg, addr, bms_sig) bms.sign(msg, xprv) # standard leading 30 in DER serialization derivation_path = "m/0/0" msg_str = "hello world".encode() dersig_hex_str = "3045022100967dac3262b4686e89638c8219c5761017f05cd87a855edf034f4a3ec6b59d3d0220108a4ef9682b71a45979d8c75c393382d9ccb8eb561d73b8c5fc0b87a47e7d27" # pub_key derivation rprv = bip39.mxprv_from_mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) # the actual message being signed magic_msg = magic_message(msg_str) # save key_id and patch dersig dersig = bytes.fromhex(dersig_hex_str) key_id = dersig[0] dsa_sig = dsa.Sig.parse(b"\x30" + dersig[1:]) # ECDSA signature verification of the patched dersig dsa.assert_as_valid(magic_msg, xprv, dsa_sig, lower_s=True) assert dsa.verify(magic_msg, xprv, dsa_sig) # compressed address addr = b58.p2pkh(xprv) # equivalent Bitcoin Message Signature rec_flag = 27 + 4 + (key_id & 0x01) bms_sig = bms.Sig(rec_flag, dsa_sig) # Bitcoin Message Signature verification bms.assert_as_valid(msg_str, addr, bms_sig) assert bms.verify(msg_str, addr, bms_sig) assert not bms.verify(magic_msg, addr, bms_sig)
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)