第4章:构建 SegWit 交易#

参考: code/chapter04/
最后更新: 2025-12-06


隔离见证(Segregated Witness,SegWit)通过将签名数据与交易数据分离,从根本上重构了 Bitcoin 交易。本章通过完整的交易实现演示 SegWit 的核心创新,从创建到广播,使用真实测试网数据追踪使 Taproot 成为可能的见证执行模型。

# 本章环境:bitcoinutils(一次性加载,后续代码块复用)
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey, P2pkhAddress, P2wpkhAddress
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
from bitcoinutils.utils import to_satoshis
from bitcoinutils.script import Script
setup('testnet')
'testnet'

4.1 交易可延展性:SegWit 解决的问题#

Legacy 交易结构与 SegWit#

传统 Bitcoin 交易将所有数据捆绑在一起进行 TXID 计算,而 SegWit 分离见证数据:

Legacy Transaction Structure:
┌─────────────────────────────────────────┐
│ Version │ Inputs │ Outputs │ Locktime   │
│         │ ┌─────┐│         │            │
│         │ │ScSig││         │            │  } All included in TXID
│         │ │     ││         │            │
│         │ └─────┘│         │            │
└─────────────────────────────────────────┘
           ↓
    TXID = SHA256(SHA256(entire_transaction))


SegWit Transaction Structure:
┌─────────────────────────────────────────┐
│ Version │ Inputs │ Outputs │ Locktime   │  } Base Transaction
│         │ ┌─────┐│         │            │
│         │ │Empty││         │            │
│         │ │ScSig││         │            │
│         │ └─────┘│         │            │
└─────────────────────────────────────────┘
                                             } TXID = SHA256(SHA256(base_only))
┌─────────────────────────────────────────┐
│        Witness Data (Separated)         │  } Committed separately
│    ┌─────────────────────────────────┐  │
│    │ Signature │ Public Key         │  │  (For P2WPKH)
│    └─────────────────────────────────┘  │
└─────────────────────────────────────────┘

可延展性问题演示#

在 SegWit 之前,攻击者可以修改签名编码而不影响签名有效性,但会改变 TXID。ECDSA 签名使用的 DER(Distinguished Encoding Rules)格式允许同一签名的多种有效编码。例如:

  • 原始签名304402201234567890abcdef...(71 字节)

  • 可延展版本3045022100001234567890abcdef...(72 字节,带零填充)

两个签名在密码学上相同,都会通过 ECDSA 验证,但它们有不同的字节表示。由于 Legacy Bitcoin 在 TXID 计算中包含整个 scriptSig(包含签名),这些不同的编码为同一经济交易产生不同的交易 ID。

这种可延展性破坏了依赖特定 TXID 的协议,特别是 Lightning Network:

Lightning Channel Setup:
Funding TX (TXID_A) → Commitment TX → Timeout TX
                          ↓              ↓
                     References      References
                       TXID_A         TXID_B

If TXID_A changes due to malleability:
→ Commitment TX becomes invalid
→ Timeout TX becomes invalid  
→ Entire channel unusable

Legacy 与 SegWit 关键代码#

# Legacy: sig 在 scriptSig,参与 TXID
sig = sk.sign_input(tx, 0, previous_locking_script)
txin.script_sig = Script([sig, pk])

# SegWit: sig 在 witness,不参与 TXID
script_code = public_key.get_address().to_script_pub_key()
sig = private_key.sign_segwit_input(tx, 0, script_code, amount)
txin.script_sig = Script([])
tx.witnesses.append(TxWitnessInput([sig, public_key.to_hex()]))
# 示例 1: Legacy vs SegWit 签名对比
# 参考: code/chapter04/01_legacy_vs_segwit_comparison.py

sk = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')

# Legacy: sig 在 scriptSig
prev = Script(["OP_DUP","OP_HASH160", sk.get_public_key().get_address().to_hash160(), "OP_EQUALVERIFY","OP_CHECKSIG"])
tx_legacy = Transaction([TxInput('5e4a294028ea8cb0e156dac36f4444e2c445c7b393e87301b12818b06cee49e0', 0)],
    [TxOutput(to_satoshis(0.00000866), P2pkhAddress('myYHJtG3cyoRseuTwvViGHgP2efAvZkYa4').to_script_pub_key())])
sig = sk.sign_input(tx_legacy, 0, prev)
tx_legacy.inputs[0].script_sig = Script([sig, sk.get_public_key().to_hex()])

# SegWit: sig 在 witness
pk = sk.get_public_key()
script_code = pk.get_address().to_script_pub_key()
txin = TxInput('1454438e6f417d710333fbab118058e2972127bdd790134ab74937fa9dddbc48', 0)
txout = TxOutput(to_satoshis(0.00000666), P2wpkhAddress('tb1qckeg66a6jx3xjw5mrpmte5ujjv3cjrajtvm9r4').to_script_pub_key())
tx_sw = Transaction([txin], [txout], has_segwit=True)
sig_sw = sk.sign_segwit_input(tx_sw, 0, script_code, to_satoshis(0.00001))
txin.script_sig = Script([])
tx_sw.witnesses.append(TxWitnessInput([sig_sw, pk.to_hex()]))

print("Legacy scriptSig 含签名;SegWit witness 含签名,scriptSig 为空")
Legacy scriptSig 含签名;SegWit witness 含签名,scriptSig 为空

4.2 创建完整的 SegWit 交易#

让我们逐步构建真实的 SegWit 交易,通过实际实现理解 SegWit 如何解决可延展性。

交易设置#

# 示例 2: SegWit 交易设置
# 参考: code/chapter04/02_create_segwit_transaction.py

sk = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')
pk = sk.get_public_key()
script_code = pk.get_address().to_script_pub_key()
to_addr = P2wpkhAddress('tb1qckeg66a6jx3xjw5mrpmte5ujjv3cjrajtvm9r4')

utxo_txid = '1454438e6f417d710333fbab118058e2972127bdd790134ab74937fa9dddbc48'
txin = TxInput(utxo_txid, 0)
txout = TxOutput(to_satoshis(0.00000666), to_addr.to_script_pub_key())
tx = Transaction([txin], [txout], has_segwit=True)
print(f"From: {to_addr.to_string()}\nTo:   {to_addr.to_string()}")

输出:

From: tb1qckeg66a6jx3xjw5mrpmte5ujjv3cjrajtvm9r4
To:   tb1qckeg66a6jx3xjw5mrpmte5ujjv3cjrajtvm9r4

注意: 此示例使用了成功广播到测试网的真实交易。交易 TXID 是 271cf6285479885a5ffa4817412bfcf55e7d2cf43ab1ede06c4332b46084e3e6,可以在测试网浏览器上查看。

4.3 SegWit 交易构建与分析#

逐步构建交易#

让我们逐步构建交易,观察每个阶段的数据结构变化:

阶段 1:创建未签名交易#

# 示例 3: 创建 SegWit 交易(阶段1和阶段2)
# 参考: code/chapter04/02_create_segwit_transaction.py

sk = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')
pk = sk.get_public_key()
script_code = pk.get_address().to_script_pub_key()
to_addr = P2wpkhAddress('tb1qckeg66a6jx3xjw5mrpmte5ujjv3cjrajtvm9r4')

utxo_txid = '1454438e6f417d710333fbab118058e2972127bdd790134ab74937fa9dddbc48'
utxo_amount = 1000
txin = TxInput(utxo_txid, 0)
txout = TxOutput(to_satoshis(0.00000666), to_addr.to_script_pub_key())
tx = Transaction([txin], [txout], has_segwit=True)
unsigned_tx = tx.serialize()
sig = sk.sign_segwit_input(tx, 0, script_code, to_satoshis(utxo_amount / 100000000))
txin.script_sig = Script([])
tx.witnesses.append(TxWitnessInput([sig, pk.to_hex()]))
signed_tx = tx.serialize()

print(f"未签名: {len(unsigned_tx)//2} bytes; 签名后: {len(signed_tx)//2} bytes")
print("TXID: 271cf6285479885a5ffa4817412bfcf55e7d2cf43ab1ede06c4332b46084e3e6")
未签名: 84 bytes; 签名后: 191 bytes
TXID: 271cf6285479885a5ffa4817412bfcf55e7d2cf43ab1ede06c4332b46084e3e6

未签名交易输出: 0200000000010148bcdd9dfa3749b74a1390d7bd272197e2588011abfb3303717d416f8e4354140000000000fdffffff019a02000000000000160014c5b28d6bba91a2693a9b1876bcd3929323890fb200000000

解析组件:

Version:      02000000
Marker:       00 (SegWit indicator)
Flag:         01 (SegWit version)
Input Count:  01
TXID:         1454438e6f417d710333fbab118058e2972127bdd790134ab74937fa9dddbc48
VOUT:         00000000
ScriptSig:    00 (empty, 0 bytes)
Sequence:     fffffffd (RBF enabled - Replace-By-Fee)
Output Count: 01
Value:        9a02000000000000 (666 sats)
Script Len:   16 (22 bytes)
ScriptPubKey: 0014c5b28d6bba91a2693a9b1876bcd3929323890fb2
Locktime:     00000000

关键观察:

  • 标准 Bitcoin 交易结构

  • ScriptSig 为空(00)——这对 SegWit 是正常的

  • 还没有见证数据

阶段 2:添加 SegWit 签名#

阶段 2 输出:

ScriptSig: ''
Witness: [3044022015098d26...e33c0301 (sig), 02898711e6bf...c8519 (pk)]
Signed TX: 191 bytes

已验证的交易: 此交易已成功广播到测试网。TXID: 271cf6285479885a5ffa4817412bfcf55e7d2cf43ab1ede06c4332b46084e3e6

关键变化:

  • ScriptSig 保持为空

  • 出现见证数据

  • 交易变长(添加了见证部分)

交易结构对比#

签名前(阶段 1):

Standard Bitcoin Transaction Format (with SegWit marker/flag)
├── Version: 02000000
├── Marker: 00 (SegWit indicator)
├── Flag: 01 (SegWit version)
├── Input Count: 01
├── Input Data: 48bcdd9d...00fdffffff (ScriptSig empty)
├── Output Count: 01  
├── Output Data: 9a020000...3890fb2
└── Locktime: 00000000

Total: 84 bytes (base transaction)

签名后(阶段 2):

SegWit Transaction Format
├── Version: 02000000
├── Marker: 00 (SegWit indicator)
├── Flag: 01 (SegWit version)  
├── Input Count: 01
├── Input Data: 48bcdd9d...00fdffffff (ScriptSig still empty)
├── Output Count: 01
├── Output Data: 9a020000...3890fb2
├── Witness Data: 0247304402...c8519 (NEW - authorization data)
└── Locktime: 00000000

Total: 191 bytes (added witness section: 82 bytes)

注意: Sequence 0xfffffffd 表示启用了 RBF(Replace-By-Fee,按费用替换),允许在需要时用更高的费用替换交易。这就是为什么链上浏览器在交易特征中显示 “RBF”。

注意:标记/标志(00 01)仅出现在序列化形式中以指示 SegWit,不参与 txid(它们参与 wtxid)。

原始交易解析组件#

带标签组件的完整签名交易:

[VERSION]       02000000
[MARKER]        00      (SegWit indicator)
[FLAG]          01      (SegWit version)
[INPUT_COUNT]   01
[TXID]          1454438e6f417d710333fbab118058e2972127bdd790134ab74937fa9dddbc48
[VOUT]          00000000
[SCRIPTSIG_LEN] 00      (Empty - authorization moved to witness)
[SEQUENCE]      fffffffd
[OUTPUT_COUNT]  01
[VALUE]         9a02000000000000  (666 satoshis)
[SCRIPT_LEN]    16      (22 bytes)
[SCRIPTPUBKEY]  0014c5b28d6bba91a2693a9b1876bcd3929323890fb2
[WITNESS_ITEMS] 02      (2 items: signature + public key)
[SIG_LEN]       47      (71 bytes)
[SIGNATURE]     3044022015098d26...e33c0301
[PK_LEN]        21      (33 bytes)
[PUBLIC_KEY]    02898711e6bf...c8519
[LOCKTIME]      00000000
# 可运行:解析 SegWit 交易关键字段(标准库)
import struct
tx_hex = "0200000000010148bcdd9dfa3749b74a1390d7bd272197e2588011abfb3303717d416f8e4354140000000000fdffffff019a02000000000000160014c5b28d6bba91a2693a9b1876bcd3929323890fb202473044022015098d26918b46ab36b0d1b50ee502b33d5c5b5257c76bd6d00ccb31452c25ae0220256e82d4df10981f25f91e5273be39fced8fe164434616c94fa48f3549e33c03012102898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c851900000000"
b = bytes.fromhex(tx_hex)
ver = struct.unpack("<I", b[0:4])[0]
marker, flag = b[4], b[5]
nin = b[6]
print(f"Version: {ver}, Marker: {marker:02x}, Flag: {flag:02x}, Inputs: {nin}")
print(f"SegWit: {marker == 0 and flag == 1}")
Version: 2, Marker: 00, Flag: 01, Inputs: 1
SegWit: True

4.4 P2WPKH 栈执行分析#

ScriptPubKey: 0014c5b28d6b...890fb2(OP_0 + 20B hash)
Witness: [3044022015098d26...e33c0301 (sig), 02898711e6bf...c8519 (pk)]

等价脚本:OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG

栈执行简要#

│ (empty) │ → 见证压栈 → │ 02898711e6bf...c8519 (pk)        │
                        │ 304402201509...33c0301 (sig)      │
                        └───────────────────────────────────┘
→ OP_DUP → OP_HASH160 → 哈希匹配 → OP_CHECKSIG → │ 1 (TRUE) │

txid 排除见证;wtxid 含见证。矿工通过 coinbase 中的见证承诺提交。

4.5 SegWit 到 Taproot 的演化#

SegWit 通过三个关键创新建立了使 Taproot 成为可能的架构基础:

见证版本框架#

Version 0: P2WPKH (OP_0 <20-bytes>) and P2WSH (OP_0 <32-bytes>)
Version 1: P2TR (OP_1 <32-bytes>) - Taproot

可延展性抗性#

稳定的交易 ID 支持:

  • Lightning Network 通道

  • 复杂的预签名交易链

  • 可靠的 Layer 2 协议

经济激励#

SegWit 引入基于权重的费用计算:

Transaction Weight = (Base Size × 4) + Witness Size
Virtual Size = Weight ÷ 4

直觉:见证字节按 1 权重单位/字节收费,而基础字节为 4 wu/字节。节省取决于有多少授权数据移动到见证(取决于结构,不是固定的 25%/75%)。

通过分离实现空间效率:

在 Legacy 交易中,2-of-3 多重签名需要大的 scriptSig:

scriptSig: <empty> <sig1> <sig2> <redeemScript>
Total: ~300 bytes in scriptSig (counted at full weight)

在 SegWit P2WSH 中,相同的授权移动到见证:

scriptSig: <empty> (0 bytes)
witness: <empty> <sig1> <sig2> <witnessScript>
Total: ~300 bytes in witness (75% discount)

这种架构变化意味着复杂脚本在经济上变得可行。SegWit 多重签名交易支付的费用比其 Legacy 等价物大约少 25%,而简单的单签名交易节省较少。

Taproot 放大: SegWit 的经济框架为 Taproot 更大的效率奠定了基础。Taproot 通过密钥聚合的单签名链上可以使复杂的多方安排与单签名交易一样便宜,充分利用 SegWit 建立的见证折扣结构。

章节总结#

本章通过完整的交易实现演示了 SegWit 的核心创新:

见证结构:将签名数据与交易逻辑分离,为 Taproot 的脚本树和通过密钥聚合的单签名链上创建基础。

可延展性抗性:稳定的交易 ID 支持 Taproot 通过更高效的授权方案优化的 Layer 2 生态系统。

栈执行模型:SegWit 解释器对见证程序的模式识别为 Taproot 的 OP_1 执行模型提供模板。

经济框架:权重单位折扣为高级脚本设计创造激励,Taproot 通过签名聚合和脚本树效率最大化这些设计。

理解 SegWit 的见证架构和执行模型对于掌握 Taproot 至关重要,因为 P2TR 直接建立在这些基础之上,同时添加 Schnorr 签名、密钥聚合和 Merkle 脚本树。

在下一章中,我们将探索 Schnorr 签名如何提供使 Taproot 的密钥聚合和签名效率成为可能的数学原语,在 SegWit 的见证架构基础上创建 Bitcoin 最先进的授权方案。