第3章:P2SH 脚本工程#
参考:
code/chapter03/
最后更新: 2025-12-06
Bitcoin 的第一个真正的可编程性出现在支付到脚本哈希(Pay-to-Script-Hash,P2SH)中,复杂的花费条件优雅地隐藏在简单的 20 字节哈希后面。本章连接基础 P2PKH 交易和 Taproot 复杂脚本树之间的桥梁,展示 Bitcoin 的脚本系统如何支持企业资金管理和时间锁定继承规划等实际应用。
# 本章环境:bitcoinutils(一次性加载,后续代码块复用)
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey, P2pkhAddress, P2shAddress
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, Sequence
from bitcoinutils.utils import to_satoshis
from bitcoinutils.constants import TYPE_RELATIVE_TIMELOCK
setup('testnet')
'testnet'
3.1 P2SH 架构:哈希背后的脚本#
P2SH 使任何脚本都可以用紧凑的 20 字节哈希表示,将脚本复杂性从 UTXO 集转移到花费时。
两阶段验证模型#
P2SH 通过两个不同阶段运行:
阶段 1:哈希验证
OP_HASH160 <script_hash> OP_EQUAL
阶段 2:脚本执行
<revealed_script> → Execute as Bitcoin Script
P2SH 地址生成过程#
P2SH 遵循第一章中介绍的 Hash160 → Base58Check 模式,但哈希的是脚本而非公钥:
Script Serialization → hex_encoded_script
Hash160(script) → 20_bytes_script_hash
Version + Base58Check → 3...address (mainnet)
所有 P2SH 地址在主网上以”3”开头,在测试网上以”2”开头,立即将它们与 P2PKH 地址区分开来。
ScriptSig 构建模式#
P2SH 的解锁脚本(ScriptSig)遵循特定模式:
<script_data> <serialized_redeem_script>
其中 <script_data> 包含满足赎回脚本条件所需的值,<serialized_redeem_script> 是哈希与锁定脚本匹配的原始脚本。
3.2 多重签名资金库:2-of-3 企业安全#
企业 Bitcoin 托管通常需要多方授权以防止单点故障。2-of-3 多重签名方案确保没有单个人可以单方面访问资金,同时保持操作灵活性。
业务场景:初创公司资金管理#
考虑一个有三个关键利益相关者的区块链初创公司:
Alice:具有运营权限的 CEO
Bob:具有技术监督的 CTO
Carol:具有财务控制的 CFO
他们的资金政策要求任何资金流动都需要两个签名,既防止单人风险,又不需要一致共识。
# 示例 1: 创建多重签名 P2SH
# 参考: code/chapter03/01_create_multisig_p2sh.py
alice_pk = '02898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519'
bob_pk = '0284b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5'
carol_pk = '0317aa89b43f46a0c0cdbd9a302f2508337ba6a06d123854481b52de9c20996011'
redeem_script = Script(['OP_2', alice_pk, bob_pk, carol_pk, 'OP_3', 'OP_CHECKMULTISIG'])
p2sh_addr = P2shAddress.from_script(redeem_script)
print(f"Redeem Script: {redeem_script.to_hex()[:32]}...{redeem_script.to_hex()[-8:]}")
print(f"P2SH Address: {p2sh_addr.to_string()}")
Redeem Script: 522102898711e6bf63f5cbe1b38c05e8...601153ae
P2SH Address: 2NDSSi5n5knjFqNMQMxyCezn6i18UQEh8Nj
关键函数#
Script([...]):opcode 和数据列表 → Script 对象P2shAddress.from_script(script):Hash160(script) → Base58Check序列化:
522102898711...601153ae(OP_2 + 3×公钥 + OP_3 + OP_CHECKMULTISIG)
# 示例 2: 花费多重签名 P2SH
# 参考: code/chapter03/02_spend_multisig_p2sh.py
alice_sk = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')
bob_sk = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
redeem_script = Script(['OP_2', alice_pk, bob_pk, carol_pk, 'OP_3', 'OP_CHECKMULTISIG'])
txin = TxInput('4b869865bc4a156d7e0ba14590b5c8971e57b8198af64d88872558ca88a8ba5f', 0)
txout = TxOutput(to_satoshis(0.00000888), P2pkhAddress('myYHJtG3cyoRseuTwvViGHgP2efAvZkYa4').to_script_pub_key())
tx = Transaction([txin], [txout])
alice_sig = alice_sk.sign_input(tx, 0, redeem_script)
bob_sig = bob_sk.sign_input(tx, 0, redeem_script)
txin.script_sig = Script(['OP_0', alice_sig, bob_sig, redeem_script.to_hex()])
signed_tx = tx.serialize()
print(f"Transaction size: {tx.get_size()} bytes")
Transaction size: 337 bytes
多重签名栈执行(简要)#
TXID: e68bef534c7536300c3ae5ccd0f79e031cab29d262380a37269151e8ba0fd4e0
阶段 1(哈希验证):ScriptSig → OP_0 + sig1 + sig2 + redeem_script → OP_HASH160 + 预期哈希 → OP_EQUAL
阶段 2(赎回脚本):OP_2 + 3公钥 + OP_3 + OP_CHECKMULTISIG → 验证 2-of-3 签名 → TRUE
P2SH 两阶段:哈希验证 → 栈重置 → 赎回脚本执行 → 1 (true)
3.3 时间锁定继承:CSV 增强的 P2SH#
检查序列验证(CheckSequenceVerify,CSV)支持相对时间锁,其中花费相对于 UTXO 创建时间延迟。让我们使用实际测试网数据检查真实实现。
真实实现:3 区块时间锁#
交易 ID:34f5bf0cf328d77059b5674e71442ded8cdcfc723d0136733e0dbf180861906f
此交易演示了一个将 CSV 时间锁与 P2PKH 签名验证结合的 P2SH 脚本——继承和托管应用的常见模式。
# 示例 3: 创建 CSV 时间锁脚本
# 参考: code/chapter03/03_create_csv_script.py
sk_csv = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
pk_csv = sk_csv.get_public_key()
seq = Sequence(TYPE_RELATIVE_TIMELOCK, 3)
redeem_csv = Script([seq.for_script(), 'OP_CHECKSEQUENCEVERIFY', 'OP_DROP',
'OP_DUP', 'OP_HASH160', pk_csv.get_address().to_hash160(), 'OP_EQUALVERIFY', 'OP_CHECKSIG'])
p2sh_csv = P2shAddress.from_script(redeem_csv)
print(f"P2SH Address: {p2sh_csv.to_string()}")
print(f"Time Lock: 3 blocks")
P2SH Address: 2N4Lnv72RRSJzz21PTnsBeECZHuMmVyS3iG
Time Lock: 3 blocks
关键函数#
Sequence(TYPE_RELATIVE_TIMELOCK, blocks):相对时间锁seq.for_script():推入赎回脚本seq.for_input_sequence():设置 input 的 sequence 字段
# 示例 4: 花费 CSV 时间锁脚本
# 参考: code/chapter03/04_spend_csv_script.py
txin_csv = TxInput('34f5bf0cf328d77059b5674e71442ded8cdcfc723d0136733e0dbf180861906f', 0, sequence=seq.for_input_sequence())
txout_csv = TxOutput(to_satoshis(0.00001), P2pkhAddress('myYHJtG3cyoRseuTwvViGHgP2efAvZkYa4').to_script_pub_key())
tx_csv = Transaction([txin_csv], [txout_csv])
sig_csv = sk_csv.sign_input(tx_csv, 0, redeem_csv)
txin_csv.script_sig = Script([sig_csv, pk_csv.to_hex(), redeem_csv.to_hex()])
print(f"Transaction size: {tx_csv.get_size()} bytes")
Transaction size: 220 bytes
CSV 栈执行(简要)#
赎回脚本:OP_3 OP_CHECKSEQUENCEVERIFY OP_DROP + P2PKH
流程:PUSH 3 → CSV 验证 → OP_DROP → OP_DUP → OP_HASH160 → OP_EQUALVERIFY → OP_CHECKSIG → 1 (true)
应用:数字继承、业务连续性、Lightning 通道。
3.4 P2SH vs Taproot#
P2SH 将脚本扩展为多方与时间条件,但花费时需暴露完整赎回脚本,无法像 Taproot 那样只揭示执行分支。
章节总结#
本章通过探索 P2SH 的两个基本模式——多重签名授权和时间锁定条件——连接了基础 P2PKH 交易和 Taproot 高级功能之间的桥梁。
掌握的关键概念:
两阶段验证:P2SH 的哈希然后执行模型为 Taproot 的承诺方案提供了概念基础,其中复杂脚本在花费之前保持私有。
多方授权:2-of-3 多重签名模式展示了 Bitcoin Script 如何处理条件逻辑和多个验证要求——理解 Taproot 脚本树执行所必需的技能。
时间约束:基于 CSV 的时间锁引入了相对时间概念,这些概念支撑了 Lightning Network 和其他建立在 Taproot 基础上的 Layer 2 协议。
基于栈的编程:多重签名和时间锁场景的详细栈执行追踪提供了调试和优化 Taproot 脚本路径所需的分析技能。
bitcoinutils 熟练度:Script 构建、P2shAddress 生成和签名创建的实践经验为开发者准备 Taproot 更复杂的原语。
真实交易分析:使用实际测试网交易和 mempool 数据构建生产 Taproot 开发所需的经验技能。
从 P2PKH 的简单签名验证到 P2SH 的复杂条件逻辑的进展,为 Taproot 的革命性方法奠定了基础:使复杂的智能合约与简单支付无法区分,同时提供前所未有的脚本灵活性和隐私性。
在下一章中,我们将研究 SegWit 的见证结构如何革命性地改变交易可延展性和费用计算——这些概念直接支持 Taproot 的效率改进,并构成理解 P2TR 基于见证的花费路径的基础。