Chris,
These examples should work and could be adapted to a different funding input (I stubbed out the actual signature with just a preimage reveal for simplicity).
funding witnessScript: a820ff76f4f923cd475f27097b3822640a9b441894f3d4f742fe70e745e986da388e87
funding scriptPubKey: 002080c153db1ea3461a2b70126f853189fc34d7d9926f083dfb2bffae8ae2bb1130
funding txid: 166ef40e58f1a62e01d610023ca7935eef372cf3c1239d81430dffbd1fe8fe9e
funding tx hex: 02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff020101ffffffff01a08601000000000022002080c153db1ea3461a2b70126f853189fc34d7d9926f083dfb2bffae8ae2bb113000000000
182 |
spending output scriptPubKey: 020002b2
spending stripped size: 64
spending full size: 114
spending txid: 44c752f499326a59a78259b455875d296108184b7f25b9a0b41bc018af7aedd8
spending wtxid: 1e6eb4de698f079cbaa0fdf47d7ab5e1bb4939fae42652f295dd05d15930074d
spending tx stripped hex: 02000000019efee81fbdff0d43819d23c1f32c37ef5e93a73c0210d6012ea6f1580ef46e160000000000ffffffff01905f01000000000004020002b200000000
spending tx full hex: 020000000001019efee81fbdff0d43819d23c1f32c37ef5e93a73c0210d6012ea6f1580ef46e160000000000ffffffff01905f01000000000004020002b2020a62697035342064656d6f23a820ff76f4f923cd475f27097b3822640a9b441894f3d4f742fe70e745e986da388e8700000000
$ decodrawtransaction $(cat funding_tx.hex)
{
"txid": "166ef40e58f1a62e01d610023ca7935eef372cf3c1239d81430dffbd1fe8fe9e",
"hash": "166ef40e58f1a62e01d610023ca7935eef372cf3c1239d81430dffbd1fe8fe9e",
"version": 2,
"size": 96,
"vsize": 96,
"weight": 384,
"locktime": 0,
"vin": [
{
"coinbase": "0101",
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00100000,
"n": 0,
"scriptPubKey": {
"asm": "0 80c153db1ea3461a2b70126f853189fc34d7d9926f083dfb2bffae8ae2bb1130",
"desc": "addr(bcrt1qsrq48kc75drp52mszfhc2vvfls6d0kvjduyrm7etl7hg4c4mzycqmw604m)#2rrnsnlc",
"hex": "002080c153db1ea3461a2b70126f853189fc34d7d9926f083dfb2bffae8ae2bb1130",
"address": "bcrt1qsrq48kc75drp52mszfhc2vvfls6d0kvjduyrm7etl7hg4c4mzycqmw604m",
"type": "witness_v0_scripthash"
}
}
]
}
$ decodrawtransaction $(cat spending_tx_stripped.hex)
{
"txid": "44c752f499326a59a78259b455875d296108184b7f25b9a0b41bc018af7aedd8",
"hash": "44c752f499326a59a78259b455875d296108184b7f25b9a0b41bc018af7aedd8",
"version": 2,
"size": 64,
"vsize": 64,
"weight": 256,
"locktime": 0,
"vin": [
{
"txid": "166ef40e58f1a62e01d610023ca7935eef372cf3c1239d81430dffbd1fe8fe9e",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00090000,
"n": 0,
"scriptPubKey": {
"asm": "512 OP_CHECKSEQUENCEVERIFY",
"desc": "raw(020002b2)#vceh5z0j",
"hex": "020002b2",
"type": "nonstandard"
}
}
]
}
$ decodrawtransaction $(cat spending_tx_full.hex)
{
"txid": "44c752f499326a59a78259b455875d296108184b7f25b9a0b41bc018af7aedd8",
"hash": "1e6eb4de698f079cbaa0fdf47d7ab5e1bb4939fae42652f295dd05d15930074d",
"version": 2,
"size": 114,
"vsize": 77,
"weight": 306,
"locktime": 0,
"vin": [
{
"txid": "166ef40e58f1a62e01d610023ca7935eef372cf3c1239d81430dffbd1fe8fe9e",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"62697035342064656d6f",
"a820ff76f4f923cd475f27097b3822640a9b441894f3d4f742fe70e745e986da388e87"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00090000,
"n": 0,
"scriptPubKey": {
"asm": "512 OP_CHECKSEQUENCEVERIFY",
"desc": "raw(020002b2)#vceh5z0j",
"hex": "020002b2",
"type": "nonstandard"
}
}
]
}
```python3
#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import struct
OP_SHA256 = 0xA8
OP_EQUAL = 0x87
OP_CHECKSEQUENCEVERIFY = 0xB2
def sha256(b: bytes) -> bytes:
return hashlib.sha256(b).digest()
def hash256(b: bytes) -> bytes:
return hashlib.sha256(hashlib.sha256(b).digest()).digest()
def hash256_be_hex(b: bytes) -> str:
return hash256(b)[::-1].hex()
def compact_size(n: int) -> bytes:
if n < 0:
raise ValueError("negative compact size")
if n < 253:
return bytes([n])
if n <= 0xFFFF:
return b"\xfd" + struct.pack("<H", n)
if n <= 0xFFFFFFFF:
return b"\xfe" + struct.pack("<I", n)
return b"\xff" + struct.pack("<Q", n)
def i32(n: int) -> bytes:
return struct.pack("<i", n)
def u32(n: int) -> bytes:
return struct.pack("<I", n)
def u64(n: int) -> bytes:
return struct.pack("<Q", n)
def ser_output(value_sat: int, script_pubkey: bytes) -> bytes:
return u64(value_sat) + compact_size(len(script_pubkey)) + script_pubkey
def ser_input(prev_txid_be_hex: str, vout: int, script_sig: bytes = b"", sequence: int = 0xFFFFFFFF) -> bytes:
# Transaction outpoints serialize txids little-endian.
prev_txid_le = bytes.fromhex(prev_txid_be_hex)[::-1]
return (
prev_txid_le
+ u32(vout)
+ compact_size(len(script_sig))
+ script_sig
+ u32(sequence)
)
# --------------------------------------------------------------------
# Funding transaction
# --------------------------------------------------------------------
#
# This is a coinbase-like funding transaction for a decode/test vector.
# It pays 100_000 sats to a native P2WSH output.
#
# witnessScript:
#
# OP_SHA256 <sha256("bip54 demo")> OP_EQUAL
#
# The spending transaction must reveal the preimage "bip54 demo" in witness.
secret = b"bip54 demo"
secret_hash = sha256(secret)
witness_script = (
bytes([OP_SHA256])
+ bytes([0x20]) # push 32 bytes
+ secret_hash
+ bytes([OP_EQUAL])
)
# Native v0 P2WSH scriptPubKey:
#
# OP_0 <sha256(witness_script)>
#
funding_script_pubkey = b"\x00\x20" + sha256(witness_script)
coinbase_script_sig = b"\x01\x01" # BIP34-style height=1 push; enough for a decode vector.
funding_tx = (
i32(2)
+ compact_size(1)
+ bytes(32) # coinbase prevout txid = 0x00..00
+ u32(0xFFFFFFFF) # coinbase prevout index
+ compact_size(len(coinbase_script_sig))
+ coinbase_script_sig
+ u32(0xFFFFFFFF)
+ compact_size(1)
+ ser_output(100_000, funding_script_pubkey)
+ u32(0)
)
funding_txid = hash256_be_hex(funding_tx)
# --------------------------------------------------------------------
# Spending transaction
# --------------------------------------------------------------------
#
# This spends funding_tx output 0.
#
# The sole output scriptPubKey is:
#
# <512> OP_CHECKSEQUENCEVERIFY
#
# Minimal ScriptNum encoding for decimal 512 is little-endian 00 02,
# so the full script is:
#
# 02 00 02 b2
#
# This spending transaction has:
#
# witness-stripped size = exactly 64 bytes
# full witness serialization = larger than 64 bytes
csv512_script_pubkey = bytes([0x02, 0x00, 0x02, OP_CHECKSEQUENCEVERIFY])
spending_input = ser_input(
prev_txid_be_hex=funding_txid,
vout=0,
script_sig=b"",
sequence=0xFFFFFFFF,
)
spending_output = ser_output(90_000, csv512_script_pubkey)
spending_tx_stripped = (
i32(2)
+ compact_size(1)
+ spending_input
+ compact_size(1)
+ spending_output
+ u32(0)
)
# P2WSH witness stack:
#
# <secret> <witness_script>
#
spending_witness = (
compact_size(2)
+ compact_size(len(secret))
+ secret
+ compact_size(len(witness_script))
+ witness_script
)
spending_tx_full = (
i32(2)
+ b"\x00\x01" # SegWit marker + flag
+ compact_size(1)
+ spending_input
+ compact_size(1)
+ spending_output
+ spending_witness
+ u32(0)
)
assert len(spending_tx_stripped) == 64
assert csv512_script_pubkey.hex() == "020002b2"
print("funding witnessScript:", witness_script.hex())
print("funding scriptPubKey: ", funding_script_pubkey.hex())
print("funding txid: ", funding_txid)
print("funding tx hex: ", funding_tx.hex())
print()
print("spending output scriptPubKey:", csv512_script_pubkey.hex())
print("spending stripped size: ", len(spending_tx_stripped))
print("spending full size: ", len(spending_tx_full))
print("spending txid: ", hash256_be_hex(spending_tx_stripped))
print("spending wtxid: ", hash256_be_hex(spending_tx_full))
print("spending tx stripped hex: ", spending_tx_stripped.hex())
print("spending tx full hex: ", spending_tx_full.hex())
```