def test_tx_out() -> None: tx_out = TxOut(0, b"") assert tx_out.value == 0 assert tx_out.script_pub_key.script == b"" assert tx_out.script_pub_key.type == "unknown" assert tx_out.script_pub_key.network == "mainnet" assert tx_out.script_pub_key.addresses == [""] assert tx_out.nValue == tx_out.value assert tx_out.scriptPubKey == tx_out.script_pub_key.script assert tx_out == TxOut.parse(tx_out.serialize()) assert tx_out == TxOut.from_dict(tx_out.to_dict()) value = 3259343370 script = "0020ed8e9600561000f722bd26e850be7d80f24d174fabeff98baef967325e2b5a86" tx_out = TxOut(value, script) assert tx_out.value == value assert tx_out.script_pub_key.script.hex() == script assert tx_out.script_pub_key.type == "p2wsh" assert tx_out.script_pub_key.network == "mainnet" addr = "bc1qak8fvqzkzqq0wg4aym59p0nasrey696040hlnzawl9nnyh3tt2rqzgmhmv" assert tx_out.script_pub_key.addresses == [addr] assert tx_out.nValue == tx_out.value assert tx_out.scriptPubKey == tx_out.script_pub_key.script assert tx_out == TxOut.parse(tx_out.serialize()) assert tx_out == TxOut.from_dict(tx_out.to_dict()) assert tx_out == TxOut.from_address( tx_out.value, tx_out.script_pub_key.addresses[0] )
def test_valid_script_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in data: prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue script_sig = x["success"]["scriptSig"] assert not script_sig witness = Witness(x["success"]["witness"]) if len(witness.stack) >= 2 and witness.stack[-1][0] == 0x50: witness.stack = witness.stack[:-1] # check script paths if len(witness.stack) < 2: continue Q = type_and_payload(prevouts[index].script_pub_key.script)[1] script = witness.stack[-2] control = witness.stack[-1] assert check_output_pubkey(Q, script, control)
def _deserialize_witness_utxo(k: bytes, v: bytes) -> TxOut: "Return the dataclass element from its binary representation." if len(k) != 1: err_msg = f"invalid witness-utxo key length: {len(k)}" raise BTClibValueError(err_msg) return TxOut.parse(v)
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 parse(cls: Type[_Tx], data: BinaryData, check_validity: bool = True) -> _Tx: "Return a Tx by parsing binary data." stream = bytesio_from_binarydata(data) # version is a signed int (int32_t, not uint32_t) version = int.from_bytes(stream.read(4), byteorder="little", signed=True) segwit = stream.read(2) == _SEGWIT_MARKER if not segwit: # Change stream position: seek to byte offset relative to position stream.seek(-2, SEEK_CUR) # current position n = var_int.parse(stream) vin = [TxIn.parse(stream) for _ in range(n)] n = var_int.parse(stream) vout = [TxOut.parse(stream) for _ in range(n)] if segwit: for tx_in in vin: tx_in.script_witness = Witness.parse(stream, check_validity) lock_time = int.from_bytes(stream.read(4), byteorder="little", signed=False) return cls(version, lock_time, vin, vout, check_validity)
def test_invalid_taproot_key_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in filter(lambda x: "failure" in x.keys(), data): tx = Tx.parse(x["tx"]) prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue witness = Witness(x["failure"]["witness"]) tx.vin[index].script_witness = witness # check only key paths if (len(witness.stack) == 1 or len(witness.stack) == 2 and witness.stack[-1][0] == 0x50): with pytest.raises( (BTClibRuntimeError, BTClibValueError, AssertionError)): assert not x["failure"]["scriptSig"] sighash_type = 0 # all signature = witness.stack[0][:64] if len(witness.stack[0]) == 65: sighash_type = witness.stack[0][-1] if sighash_type == 0: raise BTClibValueError( "invalid sighash 0 in 65 bytes signature") msg_hash = sig_hash.from_tx(prevouts, tx, index, sighash_type) pub_key = type_and_payload( prevouts[index].script_pub_key.script)[1] ssa.assert_as_valid_(msg_hash, pub_key, signature)
def test_coinbase_block_1() -> None: coinbase_out = "00f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac" tx_out = TxOut.parse(coinbase_out) assert tx_out.serialize().hex() == coinbase_out coinbase_inp = ( # prev_out "0000000000000000000000000000000000000000000000000000000000000000ffffffff" "0704ffff001d0104" # script_sig "ffffffff" # sequence ) tx_in = TxIn.parse(coinbase_inp) assert tx_in.serialize().hex() == coinbase_inp assert tx_in.prev_out.is_coinbase coinbase = "01000000" "01" + coinbase_inp + "01" + coinbase_out + "00000000" tx = Tx.parse(coinbase) assert tx.serialize(include_witness=True).hex() == coinbase assert tx == Tx.from_dict(tx.to_dict()) assert tx.version == 1 assert tx.lock_time == 0 assert len(tx.vin) == 1 assert len(tx.vout) == 1 assert tx.vin[0].script_sig == tx_in.script_sig assert tx.vout[0].script_pub_key == tx_out.script_pub_key tx_id = "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" assert tx.id.hex() == tx_id assert tx.id == tx.hash assert tx.size == 134 assert tx.vsize == tx.size assert tx.weight == tx.size * 4 assert not tx.is_segwit() assert not any(bool(w) for w in tx.vwitness) assert not any(bool(tx_in.script_witness) for tx_in in tx.vin) assert tx.is_coinbase()
def parse(cls: Type["Tx"], data: BinaryData, check_validity: bool = True) -> "Tx": "Return a Tx by parsing binary data." stream = bytesio_from_binarydata(data) # version is a signed int (int32_t) in bitcoin_core # However there are at least two transactions: # 35e79ee733fad376e76d16d1f10088273c2f4c2eaba1374a837378a88e530005 # c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369 # where the version number is negative if it is considered as a signed # integer. As such in btclib the version is an UNSIGNED integer. # This has been discussed in: https://github.com/bitcoin/bitcoin/pull/16525 version = int.from_bytes(stream.read(4), byteorder="little", signed=False) segwit = stream.read(2) == _SEGWIT_MARKER if not segwit: # Change stream position: seek to byte offset relative to position stream.seek(-2, SEEK_CUR) # current position n = var_int.parse(stream) vin = [TxIn.parse(stream) for _ in range(n)] n = var_int.parse(stream) vout = [TxOut.parse(stream) for _ in range(n)] if segwit: for tx_in in vin: tx_in.script_witness = Witness.parse(stream, check_validity) lock_time = int.from_bytes(stream.read(4), byteorder="little", signed=False) return cls(version, lock_time, vin, vout, check_validity)
def test_valid_taproot_key_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in filter(lambda x: "TAPROOT" in x["flags"], data): tx = Tx.parse(x["tx"]) prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue assert not x["success"]["scriptSig"] witness = Witness(x["success"]["witness"]) tx.vin[index].script_witness = witness if (len(witness.stack) == 1 or len(witness.stack) == 2 and witness.stack[-1][0] == 0x50): 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) pub_key = type_and_payload( prevouts[index].script_pub_key.script)[1] ssa.assert_as_valid_(msg_hash, pub_key, signature)