探讨 比特币 P2SH 的设计理念、地址生成机制,以及交易验证
通过 [[剖析比特币交易生命周期.md]],我们深入了解了 P2PKH(Pay-to-PubKey-Hash)交易的完整流程,从地址生成、交易构造到脚本验证的每一个细节。P2PKH 作为比特币最基础的交易类型,虽然简单高效,但只能实现"单一公钥控制"的支付方式。
当我们想要实现多重签名钱包、时间锁或其他复杂的支付条件时,P2PKH 就显得力不从心了。P2SH(Pay-to-Script-Hash)应运而生,它让比特币的支付方式变得更加灵活和强大。
这篇文章将继续探讨 P2SH 的设计理念、地址生成机制,以及交易验证的完整过程。
在 [[剖析比特币交易生命周期.md]] 中,我们详细分析了 P2PKH 交易。P2PKH 的锁定脚本非常简单:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
这种方式只能实现"单一公钥控制"的支付,但在实际应用中,我们有时需要更复杂的支付条件,例如在 多重签名的场景下,公司账户需要 3 个高管中的 2 个签名才能转账,这个时候锁定脚本会变成:OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
如果 Alice 想给 Bob 公司的多签钱包转账,Alice 知道 Bob 公司 的 3 个公钥(每个 33 字节),构造这个复杂的锁定脚本(约 100+ 字节)交易体积大,我们知道手续费是按交易的字节大小收取的,这就使得支付给多签的交易手续费很高。
另外: 由于锁定脚本直接暴露在区块链上,任何人都能看到这些公钥,在花费前就已公开,隐私性比较差,如果 Bob 想升级多签方案,假设从 2/3 升级到 3/5, 所有付款方都需要更新地址,带来不便。
P2SH (Pay to Script Hash)通过一个巧妙的设计解决了上述问题:将复杂的锁定脚本哈希化,付款方只需要支付到这个哈希值。
P2SH 核心思想:
传统 P2PKH:
付款方 → 锁定到公钥哈希 → 收款方提供公钥和签名解锁
P2SH:
付款方 → 锁定到脚本哈希 → 收款方提供完整脚本和解锁数据
如果使用 P2SH 发起交易 Alice 只需要知道一个脚本哈希(20 字节),构建的锁定脚本为OP_HASH160 <scriptHash> OP_EQUAL(23 字节), 只有 Bob 在花费时才提供完整的多签脚本。
我们用一个表格 对比一下 P2PKH 与P2SH 的主要区别:
| 特性 | P2PKH | P2SH |
|---|---|---|
| 锁定脚本长度 | 至少 25 字节,多公钥显著增大 | 23 字节(固定) |
| 付款方需要知道 | 公钥哈希 | 脚本哈希 |
| 复杂度由谁负担 | 付款方 | 收款方 |
| 隐私性 | 公钥提前暴露 | 脚本花费时才公开 |
| 灵活性 | 只支持公钥 | 任意脚本 |
P2SH 于 2012 年通过 BIP 16 引入比特币,为实现复杂支付条件提供了更好的方案。接下来我们看看 P2SH 地址是如何生成的。
P2SH 地址的生成过程与 P2PKH 类似,但关键区别在于:P2PKH 对公钥哈希编码,P2SH 对脚本哈希编码。
我们以 2-of-3 多签钱包为例,完整流程如下:
步骤 1:构造赎回脚本(Redeem Script)
赎回脚本定义了资金的解锁条件,对于 2-of-3 多签:
redeemScript = OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
具体字节表示:
0x52 # OP_2 (需要 2 个签名)
0x21 <pubkey1_33_bytes> # 第一个公钥
0x21 <pubkey2_33_bytes> # 第二个公钥
0x21 <pubkey3_33_bytes> # 第三个公钥
0x53 # OP_3 (总共 3 个公钥)
0xae # OP_CHECKMULTISIG
步骤 2:计算脚本哈希
对赎回脚本执行 HASH160(SHA256 + RIPEMD160):
scriptHash = RIPEMD160(SHA256(redeemScript))
结果是 20 字节的哈希值,例如:
scriptHash = 89abcdefabbaabbaabbaabbaabbaabbaabbaabba
步骤 3:添加版本前缀
根据网络类型添加版本字节:
| 网络 | 版本字节 | 地址前缀 |
|---|---|---|
| 主网 | 0x05 | 3 |
| 测试网 | 0xc4 | 2 |
version = 0x05 # 主网 P2SH
data = version || scriptHash # 拼接:0x05 + 20 字节
步骤 4:计算校验和
对 version + scriptHash 执行双重 SHA256,取前 4 字节:
checksum = SHA256(SHA256(version + scriptHash))[:4]
步骤 5:Base58Check 编码
将三部分拼接后进行 Base58 编码:
address = Base58(version + scriptHash + checksum)
最终得到的地址格式:
主网:3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (以 3 开头)
测试网:2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (以 2 开头)
继续以2-of-3 多签地址为例:
公钥:
pubkey1 = 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
pubkey2 = 03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb
pubkey3 = 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
赎回脚本:
52 21 02c6047f... 21 03774ae7... 21 0279be66... 53 ae
脚本哈希:
scriptHash = HASH160(redeemScript) = 89abcdef...
P2SH 地址(主网):
3E8ociqZa9mZUSwGdSmAEMAoAxBK3FNDcd
P2SH 更具有灵活性,还可以构建例如时间锁的地址:
赎回脚本(2026年1月1日后可用):
04 69554880 # <timestamp> (1767196800)
b1 # OP_CHECKLOCKTIMEVERIFY
75 # OP_DROP
76 a9 14 <pubKeyHash> 88 ac # 标准 P2PKH 部分
P2SH 地址:
3FxYq8i7Ym5qLqXqKqLqXqKqLqXqKqLqXq
和之前一样,交易包含两部分:作为输入的 UTXO 解锁(构造解锁脚本)和 输出的 UTXO 的锁定(构造锁定脚本)。
先看第 2 部分:
了解 P2SH 地址是如何生成的,只需要对地址进行 Base58 解码,然后获取到赎回脚本的 Hash。 解析过程如下:
// 1. Base58 解码
const decoded = base58.decode(address);
// 2. 提取各部分
const version = decoded[0]; // 第 1 字节
const scriptHash = decoded.slice(1, 21); // 第 2-21 字节
const checksum = decoded.slice(21, 25); // 第 22-25 字节
// 3. 验证校验和...
从地址解析出 scriptHash 后,就可以构造锁定脚本了:
scriptPubKey = OP_HASH160 <scriptHash> OP_EQUAL
字节表示:
a9 # OP_HASH160
14 # 推送 20 字节
<scriptHash_20_bytes> # 脚本哈希
87 # OP_EQUAL
总长度固定为 23 字节,无论赎回脚本多复杂。P2SH 甚至比最简单的 P2PKH 还短 2 字节!
假设 Alice 给 Bob 的多签 P2SH 地址支付了 0.01 BTC (下文称这笔交易为 A),这笔支付的交易的输出,是这样的:
{
"value": 1000000, // 0.01 BTC
"scriptPubKey": "a914 abcdef... 87" // OP_HASH160 <scriptHash> OP_EQUAL
}
这笔 UTXO 被锁定到脚本哈希 abcdef...,只有 Bob 提供正确的赎回脚本才能解锁。
如果 Alice 给 Bob 的多签 P2SH 的交易输入来自于 P2PKH 的 UTXO ,交易的输入的构造就和上篇文章一样,因此这里,以 Bob 要花费这笔 UTXO 介绍如何构造解锁 P2SH 的脚本。
和上篇文章一样,解锁脚本scriptSig 包含对完整交易的签名,因此需要先将交易结构构造出来:
构造交易结构
假设 Bob 要给 Tom 支付 0.001 BTC, Bob 先选择交易 A 作为 Input 输入,输入中的 scriptSig 先留空,输出是用 Tom 的地址和找零地址【可选】构造来输出 UTXO 。
将输入和输出按如下方式拼接一起:

在签名时,scriptSig 会临时替换为引用交易(即交易 A)UTXO 的赎回脚本(redeemScript),再加上 sighash flag 来构建签名原像,原像两次 hash 之后得到 tx_hash,最后私钥对 tx_hash 进行签名。
准备签名
由于 Bob 是多签地址,需要两个私钥 prikey1 和 prikey2 进行签名(2-of-3 中的 2 个):
sig1 = sign(privkey1, tx_hash)
sig2 = sign(privkey2, tx_hash)
拿到全部签名之后,就可以构造 scriptSig 了。
构造 scriptSig
P2SH 的 scriptSig 格式:
scriptSig = <sig1> <sig2> <redeemScript>
redeemScript = OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
scriptSig 需要完整提供赎回脚本,并且提供的签名顺序必须与赎回脚本中的公钥顺序对应。
最终完整的交易是以下交易格式的序列化:
{
"version": 2,
"inputs": [
{
"previous_output": {
"txid": "abc123...",
"vout": 0
},
"scriptSig": "00 <sig1> <sig2> <redeemScript>", // OP_0 开头是为了修复历史 bug
"sequence": 0xffffffff
}
],
"outputs": [
{
"value": 100000, // 0.001 BTC
"scriptPubKey": " tom 锁定脚本"
},
{
"value": 990000, // 0.0099 BTC 找零
"scriptPubKey": " bob 找零 "
}
],
"locktime": 0
}
矿工在收到交易后,从交易中提取 scriptSig 和 scriptPubKey

scriptSig : <sig1> <sig2> <redeemScript>
redeemScript : <sig1> <sig2> OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
scriptPubKey : OP_HASH160 <scriptHash> OP_EQUAL
P2SH 的验证过程比 P2PKH 复杂,会分为两个阶段:先验证脚本哈希匹配,再验证赎回脚本的执行结果。
验证脚本哈希执行 scriptSig 与 scriptPubKey 的拼接脚本:
<sig1> <sig2> <redeemScript> OP_HASH160 <scriptHash> OP_EQUAL
此时 <redeemScript> 被视为一个完整的数据序列。栈执行过程:
| 步骤 | 操作 | 栈状态 |
|---|---|---|
| 1 | 推送 sig1 | [sig1] |
| 2 | 推送 sig2 | [sig1, sig2] |
| 3 | 推送 redeemScript | [sig1, sig2, redeemScript] |
| 4 | OP_HASH160 | [sig1, sig2, HASH160(redeemScript)] |
| 5 | 推送 scriptHash | [sig1, sig2, HASH160(redeemScript), scriptHash] |
| 6 | OP_EQUAL | [sig1, sig2, true] |
这一步主要是验证:
HASH160(redeemScript) == scriptHash
如果哈希不匹配,验证失败,直接终止执行,如果匹配,进入第二阶段执行。
比特币客户端在执行 P2SH 时,有一个特别的处理,在第一阶段执行完 scriptSig 时【第 3 步】,会保留一个栈的副本,栈中包含 [sig1, sig2, redeemScript]。在第一阶段匹配无误后,会继续基于这个栈的副本执行:
<sig1> <sig2> OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
栈执行过程:
| 步骤 | 操作 | 栈状态 |
|---|---|---|
| 初始 | scriptSig 残留 | [sig1, sig2] |
| 1 | OP_2 | [sig1, sig2, 2] |
| 2 | 推送 pubkey1 | [sig1, sig2, 2, pk1] |
| 3 | 推送 pubkey2 | [sig1, sig2, 2, pk1, pk2] |
| 4 | 推送 pubkey3 | [sig1, sig2, 2, pk1, pk2, pk3] |
| 5 | OP_3 | [sig1, sig2, 2, pk1, pk2, pk3, 3] |
| 6 | OP_CHECKMULTISIG | [true] |
OP_CHECKMULTISIG 详解:
从栈中取出:
n = 3(公钥数量)pubkeys = [pk1, pk2, pk3]m = 2(需要的签名数量)signatures = [sig1, sig2]验证逻辑:
for each signature in signatures:
for each pubkey in pubkeys:
if verify(pubkey, signature, tx_hash):
match_count++
break
return match_count >= m
如果至少 2 个签名有效,OP_CHECKMULTISIG 推入 1(true)。
最终验证:
以下是交易执行的动态图:

我们在这篇文章介绍了 P2SH 交易如何构建与验证,可以看出来 P2SH 是比特币协议中一个强大的设计,实现很多的优化,例如:
支持复杂的脚本:支持多重签名、时间锁等各种复杂支付条件
P2SH 的灵活性,让比特币在保持协议简洁性的同时,获得了强大的扩展能力,也为闪电网络等二层方案 、 SegWit(隔离见证)等实现提供了基础。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!