def test_exceptions() -> None: script_pub_key: List[Command] = ["OP_2", "OP_3", "OP_ADD", "OP_5", "OP_RETURN_244"] err_msg = "invalid string command: OP_RETURN_244" with pytest.raises(BTClibValueError, match=err_msg): serialize(script_pub_key) with pytest.raises(TypeError): serialize(["OP_2", "OP_3", "OP_ADD", "OP_5", serialize]) # type: ignore err_msg = "too many bytes for OP_PUSHDATA: " with pytest.raises(BTClibValueError, match=err_msg): script_pub_key = ["1f" * 521, "OP_DROP"] serialize(script_pub_key) # A script_pub_key with OP_PUSHDATA4 can't be decoded script_bytes = "4e09020000" + "0A" * 521 + "75" # ['0A'*521, 'OP_DROP'] err_msg = "Invalid pushdata length: " with pytest.raises(BTClibValueError, match=err_msg): parse(script_bytes) # and can't be encoded script_pub_key_ = ["0A" * 521, "OP_DROP"] err_msg = "too many bytes for OP_PUSHDATA: " with pytest.raises(BTClibValueError, match=err_msg): serialize(script_pub_key_)
def test_nulldata() -> None: scripts: List[List[Command]] = [["OP_RETURN", "11" * 79], ["OP_RETURN", "00" * 79]] for script_pub_key in scripts: assert script_pub_key == parse(serialize(script_pub_key)) assert script_pub_key == parse(serialize(script_pub_key).hex())
def test_null_serialization() -> None: empty_script: List[Command] = [] assert empty_script == parse(b"") assert serialize(empty_script) == b"" assert parse(serialize([""])) == ["OP_0"] assert parse(serialize([" "])) == ["OP_0"] assert parse(serialize([b""])) == ["OP_0"] assert parse(serialize([b" "])) == ["20"] with warnings.catch_warnings(): warnings.simplefilter("ignore") assert serialize([0]) == b"\x01\x00" assert parse(serialize([0])) == ["00"] assert serialize([16]) == b"\x01\x10" assert serialize([17]) == b"\x01\x11" assert parse(serialize([16])) == ["10"] assert parse(serialize([17])) == ["11"] assert serialize(["10"]) == b"\x01\x10" assert serialize(["11"]) == b"\x01\x11" assert serialize(["OP_16"]) == b"\x60" assert parse(serialize(["OP_16"])) == ["OP_16"]
def test_simple_scripts() -> None: script_list: List[List[Command]] = [ ["OP_2", "OP_3", "OP_ADD", "OP_5", "OP_EQUAL"], [0x1ADD, "OP_1ADD", 0x1ADE, "OP_EQUAL"], [26, "OP_1NEGATE", "OP_ADD", 26, "OP_EQUAL"], [0x7FFFFFFF, "OP_1NEGATE", "OP_ADD", 0x7FFFFFFF, "OP_EQUAL"], [0x80000000, "OP_1NEGATE", "OP_ADD", 0x7FFFFFFF, "OP_EQUAL"], [0xFFFFFFFF - 1, "OP_1NEGATE", "OP_ADD", 0x7FFFFFFF, "OP_EQUAL"], [0xFFFFFFFF, "OP_1NEGATE", "OP_ADD", 0x7FFFFFFF, "OP_EQUAL"], ["1F" * 250, "OP_DROP"], ["1F" * 520, "OP_DROP"], ] for script_pub_key in script_list: assert script_pub_key == parse(serialize(script_pub_key)) assert script_pub_key == parse(serialize(script_pub_key).hex())
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_regressions() -> None: script_list: List[List[Command]] = [ [1], ["OP_1"], [51], [b"\x01"], ["01"], ["AA"], ["aa"], ["AAAA"], [0], [""], [b""], ["OP_0"], [-1], ["OP_1NEGATE"], [0x81], ["81"], ] with warnings.catch_warnings(): warnings.simplefilter("ignore") for s in script_list: serialized = serialize(s) assert serialize(parse(serialized)) == serialized
def test_op_int_serialization() -> None: for i in range(-1, 17): op_int_str = f"OP_{i}" if i > -1 else "OP_1NEGATE" serialized_op_int = serialize([op_int_str]) assert len(serialized_op_int) == 1 assert [op_int_str] == parse(serialized_op_int)
def test_valid_taproot_script_path() -> None: tx_data = "26dc279d02d8b1a203b653fc4e0f27f408432f3f540136d33f8f930eaeba655910095142980402000000fd697cd4eb5278f1e34545cd57b6670df806fa3a0a064fd8e385a19f1a53d9ce8d8971a30f02000000378d5fb502335dbe02000000001976a9140053a23441c8478caac4c6b769c51f8476cd4b4b88ac58020000000000001976a914f2aae94a43e0d173354201d7832b46c5269c8a2488ac4a08671e" prevouts_data = [ "91ca4c010000000017a9145658b58602cdf7b7e962cfe44e024cb0e366f27087", "cb127401000000002251201ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc", ] witness_data = [ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602", "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba", "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac", "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c", ] tx = Tx.parse(tx_data) prevouts = [TxOut.parse(prevout) for prevout in prevouts_data] index = 1 witness = Witness(witness_data) tx.vin[index].script_witness = witness sighash_type = 0 # all signature = witness.stack[0][:64] if len(witness.stack[0]) == 65: sighash_type = witness.stack[0][-1] assert sighash_type != 0 msg_hash = sig_hash.from_tx(prevouts, tx, index, sighash_type) tapscript = parse(witness.stack[-2]) pub_key = bytes.fromhex(str(tapscript[1])) ssa.assert_as_valid_(msg_hash, pub_key, signature)
def test_non_standard_script_in_p2wsh() -> None: network = "mainnet" fed_pub_keys: List[Command] = ["00" * 33, "11" * 33, "22" * 33] rec_pub_keys: List[Command] = ["77" * 33, "88" * 33, "99" * 33] # fmt: off redeem_script_cmds: List[Command] = [ "OP_IF", "OP_2", *fed_pub_keys, "OP_3", "OP_CHECKMULTISIG", # noqa E131 "OP_ELSE", 500, "OP_CHECKLOCKTIMEVERIFY", "OP_DROP", # noqa E131 "OP_2", *rec_pub_keys, "OP_3", "OP_CHECKMULTISIG", # noqa E131 "OP_ENDIF", ] # fmt: on redeem_script = serialize(redeem_script_cmds) assert redeem_script_cmds == parse(redeem_script) payload = sha256(redeem_script) script_pub_key = ( "00207b5310339c6001f75614daa5083839fa54d46165f6c56025cc54d397a85a5708") assert script_pub_key == ScriptPubKey.p2wsh(redeem_script).script.hex() addr = "bc1q0df3qvuuvqqlw4s5m2jsswpelf2dgct97mzkqfwv2nfe02z62uyq7n4zjj" assert addr == address(script_pub_key, network) assert addr == b32.address_from_witness(0, payload, network)
def test_single_byte_serialization() -> None: for i in range(256): hex_str = hex_string(i) # e.g., "1A" serialized_byte = serialize([hex_str]) assert len(serialized_byte) == 2 assert serialized_byte[0] == 1 assert [hex_str] == parse(serialized_byte)
def test_integer_serialization() -> None: assert ["OP_0"] == parse(b"\x00") with warnings.catch_warnings(): warnings.simplefilter("ignore") assert serialize([0]) != b"\x00" for i in range(1, 17): serialized_int = serialize([i]) assert [hex_string(i)] == parse(serialized_int) for i in range(17, 128): serialized_int = serialize([i]) # e.g., i = 26 assert [hex_string(i)] == parse(serialized_int) for i in range(128, 256): serialized_int = serialize([i])
def convert_script_tree(script_tree): if isinstance(script_tree, list): new_script_tree = [] for x in script_tree: new_script_tree.append(convert_script_tree(x)) return new_script_tree if isinstance(script_tree, dict): leaf = [[script_tree["leafVersion"], parse(script_tree["script"])]] return leaf return []
def legacy_script(script_pub_key: Octets) -> List[bytes]: script_s: List[bytes] = [] current_script: List[Command] = [] for token in parse(script_pub_key)[::-1]: if token == "OP_CODESEPARATOR": # nosec required for python < 3.8 script_s.append(serialize(current_script[::-1])) else: current_script.append(token) script_s.append(serialize(current_script[::-1])) return script_s[::-1]
def test_opcode_length() -> None: err_msg = "Invalid pushdata length" with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x4e\x00") with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x40\x00") assert parse(b"\x01\x00\x50") == [0, "OP_SUCCESS80"] assert parse(b"\x01\x00\x50", exit_on_op_success=True) == ["OP_SUCCESS"]
def test_opcode_length() -> None: err_msg = "Not enough data for pushdata length" with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x4e\x00") err_msg = "Not enough data for pushdata" with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x40\x00") assert parse(b"\x01\x00\x50")[1] == "OP_SUCCESS80" assert parse(b"\x01\x00\x50", exit_on_op_success=True) == ["OP_SUCCESS"]
def witness_v0_script(script_pub_key: Octets) -> List[bytes]: script_type, payload = type_and_payload(script_pub_key) if script_type == "p2wpkh": script = serialize( ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"]) return [script] script_s: List[bytes] = [] current_script: List[Command] = [] for token in parse(script_pub_key)[::-1]: if token == "OP_CODESEPARATOR": # nosec required for python < 3.8 script_s.append(serialize(current_script[::-1])) current_script.append(token) script_s.append(serialize(current_script[::-1])) return script_s[::-1]
def test_nulldata4() -> None: script_: List[Command] = [ "OP_RETURN", "OP_RETURN", "OP_3", "OP_1", "OP_VERIF", "OP_0", "OP_3", ] # FIXME: serialization is not 0x6A{1 byte data-length}{data 6 bytes)} script_pub_key = serialize(script_) assert len(script_pub_key) == 7 assert parse(script_pub_key) == script_ script_type, _ = type_and_payload(script_pub_key) # FIXME: it should be "nulldata" assert script_type == "unknown"
def test_encoding() -> None: script_bytes = b"jKBIP141 \\o/ Hello SegWit :-) keep it strong! LLAP Bitcoin twitter.com/khs9ne" assert serialize(parse(script_bytes)) == script_bytes