def test_address_witness() -> None: wit_ver = 0 wit_prg = 20 * b"\x05" for net in ("mainnet", "testnet"): addr = b32.address_from_witness(wit_ver, wit_prg, net) assert (wit_ver, wit_prg, net) == b32.witness_from_address(addr) wit_ver = 0 wit_prg = 32 * b"\x05" for net in ("mainnet", "testnet"): addr = b32.address_from_witness(wit_ver, wit_prg, net) assert (wit_ver, wit_prg, net) == b32.witness_from_address(addr) addr = "bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck" assert b32.address_from_witness(*b32.witness_from_address(addr)) == addr wit_prg_ints = list(wit_prg) wit_prg_ints[0] = -1 with pytest.raises(BTClibValueError, match="invalid value: "): b32.power_of_2_base_conversion(wit_prg_ints, 8, 5) addr = "tb1qq5zs2pg9q5zs2pg9q5zs2pg9q5zs2pg9q5mpvsef" err_msg = "invalid size: " with pytest.raises(BTClibValueError, match=err_msg): b32.witness_from_address(addr)
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_valid_address() -> None: "Test whether valid addresses decode to the correct output." valid_bc_addresses: List[Tuple[str, str]] = [ ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6", ), ( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", ), ("BC1SW50QGDZ25J", "6002751e"), ( "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323", ), ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", ), ] valid_tb_addresses: List[Tuple[str, str]] = [ ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", ), ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", ), ( "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", ), ] for address, hexscript in valid_bc_addresses + valid_tb_addresses: addr = b32.address_from_witness(*b32.witness_from_address(address)) assert address.lower().strip() == addr wit_ver, wit_prg, network = b32.witness_from_address( address.strip().encode("ascii")) assert addr == b32.address_from_witness(wit_ver, wit_prg, network) script_pub_key: List[Command] = [op_int(wit_ver), wit_prg] assert serialize(script_pub_key).hex() == hexscript
def test_p2tr() -> None: key = 1 pubkey = output_pubkey(key)[0] addr = b32.p2tr(key) _, wit_prg, _ = b32.witness_from_address(addr) assert wit_prg == pubkey
def test_valid_address() -> None: "Test whether valid addresses decode to the correct output." valid_bc_addresses: List[Tuple[str, str]] = [ ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6", ), ( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", ), ("BC1SW50QA3JX3S", "6002751e"), ( "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323", ), ( " bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", # extra leading space "5210751e76e8199196d454941c45d1b3a323", ), ] valid_tb_addresses: List[Tuple[str, str]] = [ ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", ), ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", ), ] for address, hexscript in valid_bc_addresses + valid_tb_addresses: addr = b32.address_from_witness(*b32.witness_from_address(address)) assert address.lower().strip() == addr wit_ver, wit_prg, network = b32.witness_from_address( address.strip().encode("ascii")) assert addr == b32.address_from_witness(wit_ver, wit_prg, network) script_pub_key: List[Command] = [op_int(wit_ver), wit_prg] assert serialize(script_pub_key).hex() == hexscript
def test_p2wsh() -> None: # https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki pub = "02 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" script_pub_key: List[Command] = [pub, "OP_CHECKSIG"] witness_script_bytes = serialize(script_pub_key) addr = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7" assert addr == b32.p2wsh(witness_script_bytes, "testnet") _, wit_prg, _ = b32.witness_from_address(addr) assert wit_prg == sha256(witness_script_bytes) addr = "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" assert addr == b32.p2wsh(witness_script_bytes) _, wit_prg, _ = b32.witness_from_address(addr) assert wit_prg == sha256(witness_script_bytes) err_msg = "invalid size: " with pytest.raises(BTClibValueError, match=err_msg): b32.address_from_witness(0, witness_script_bytes)
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 assert_as_valid(msg: Octets, addr: String, sig: Union[Sig, String], lower_s: bool = True) -> None: # Private function for test/dev purposes # It raises Errors, while verify should always return True or False if isinstance(sig, Sig): sig.assert_valid() else: sig = Sig.b64decode(sig) # first two bits in rf are reserved for key_id # key_id = 00; key_id = 01; key_id = 10; key_id = 11 # 27-27 = 000000; 28-27 = 000001; 29-27 = 000010; 30-27 = 000011 # 31-27 = 000100; 32-27 = 000101; 33-27 = 000110; 34-27 = 000111 # 35-27 = 001000; 36-27 = 001001; 37-27 = 001010; 38-27 = 001011 # 39-27 = 001100; 40-27 = 001101; 41-27 = 001110; 42-27 = 001111 key_id = sig.rf - 27 & 0b11 magic_msg = magic_message(msg) Q = dsa.recover_pub_key(key_id, magic_msg, sig.dsa_sig, lower_s, sha256) compressed = sig.rf > 30 # signature is valid only if the provided address is matched pub_key = bytes_from_point(Q, compressed=compressed) if has_segwit_prefix(addr): wit_ver, h160, _ = witness_from_address(addr) if wit_ver != 0 or len(h160) != 20: raise BTClibValueError(f"not a p2wpkh address: {addr!r}") if not (30 < sig.rf < 35 or sig.rf > 38): raise BTClibValueError( f"invalid p2wpkh address recovery flag: {sig.rf}") if hash160(pub_key) != h160: raise BTClibValueError(f"invalid p2wpkh address: {addr!r}") return script_type, h160, _ = h160_from_address(addr) if script_type == "p2pkh": if sig.rf > 34: raise BTClibValueError( f"invalid p2pkh address recovery flag: {sig.rf}") if hash160(pub_key) != h160: raise BTClibValueError(f"invalid p2pkh address: {addr!r}") return # must be P2WPKH-P2SH if not 30 < sig.rf < 39: raise BTClibValueError( f"invalid p2wpkh-p2sh address recovery flag: {sig.rf}") script_pk = b"\x00\x14" + hash160(pub_key) if hash160(script_pk) != h160: raise BTClibValueError(f"invalid p2wpkh-p2sh address: {addr!r}")
def test_invalid_address() -> None: "Test whether invalid addresses fail to decode." invalid_addresses: List[Tuple[str, str]] = [ ("tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", "invalid hrp: "), ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "invalid checksum: "), ("BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", "invalid witness version: "), ("bc1rw5uspcuh", "invalid size: "), ( "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", "invalid size: ", ), ("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "invalid size: "), ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", "mixed case: ", ), ( "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "zero padding of more than 4 bits in 5-to-8 conversion", ), ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", "non-zero padding in 5-to-8 conversion", ), ("bc1gmk9yu", "empty data in bech32 address: "), ( "bc1qqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqv0jstn5", "invalid bech32 address length: ", ), ] for address, err_msg in invalid_addresses: with pytest.raises(BTClibValueError, match=err_msg): b32.witness_from_address(address)
def from_address(cls: Type["ScriptPubKey"], addr: String, check_validity: bool = True) -> "ScriptPubKey": "Return the ScriptPubKey of the input bech32/base58 address." if b32.has_segwit_prefix(addr): wit_ver, wit_prg, network = b32.witness_from_address(addr) return cls(serialize([op_int(wit_ver), wit_prg]), network, check_validity) script_type, h160, network = b58.h160_from_address(addr) if script_type == "p2sh": commands: List[Command] = ["OP_HASH160", h160, "OP_EQUAL"] else: # it must be "p2pkh" commands = [ "OP_DUP", "OP_HASH160", h160, "OP_EQUALVERIFY", "OP_CHECKSIG", ] return cls(serialize(commands), network, check_validity)
def test_invalid_address() -> None: "Test whether invalid addresses fail to decode." invalid_addresses: List[Tuple[str, str]] = [ ("tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", "invalid hrp: "), ( "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", "invalid hrp: ", ), ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "invalid checksum: "), ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", "invalid checksum: ", ), ( "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", "invalid checksum: ", ), ( "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", "invalid checksum: ", ), ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", "invalid checksum: "), ( "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", "invalid checksum: ", ), ( "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", "invalid character in checksum", ), ( "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", "invalid witness version: ", ), ("BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", "invalid checksum: "), ("bc1pw5dgrnzv", "invalid size: "), ("bc1rw5uspcuh", "invalid checksum: "), ( "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", "invalid checksum: ", ), ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", "invalid size: ", ), ("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "invalid size: "), ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", "mixed case: ", ), ( "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", "mixed case: ", ), ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", "zero padding of more than 4 bits in 5-to-8 conversion", ), ( "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "invalid checksum: ", ), ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", "non-zero padding in 5-to-8 conversion", ), ( "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", "non-zero padding in 5-to-8 conversion", ), ("bc1gmk9yu", "empty data in bech32 address"), ( "bc1qqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqv0jstn5", "invalid bech32 address length: ", ), ] for address, err_msg in invalid_addresses: with pytest.raises(BTClibValueError, match=err_msg): print(address) b32.witness_from_address(address)