P2SH
- Pay to Script Hash, 意为支付到脚本哈希。
这是一种可自定义脚本的标准模板, 自定义脚本被称为赎回脚本(Redeem Script)
脚本哈希
脚本哈希是对自定义脚本(赎回脚本)进行 Hash160
运算的结果, 大小为 20 字节。
假设赎回脚本是 6f93598893578893588851
Hash160计算器
Hash160
结果为 da5a92e670a66538be1c550af352646000b2367d
, 该值即为脚本哈希, 被存储在 UTXO
的 ScriptPubKey
中。
锁定脚本
P2SH
锁定脚本的操作码模板:
OP_HASH160 <脚本哈希> OP_EQUAL
以下是一个 P2SH
锁定脚本的示例:
其中 da5a92e670a66538be1c550af352646000b2367d
为脚本哈希。
计算脚本哈希是一个不可逆的操作, 因此无法从脚本哈希中计算出赎回脚本。这在一定程度提升了自定义脚本的安全性。
解锁脚本
P2SH
解锁脚本由两部分组成, 前半部分是自定义的解锁脚本, 后半部分是赎回脚本。
6f93598893578893588851
是赎回脚本, 转换成 ASM
格式为:
脚本执行
P2SH
脚本执行分为两部分:
- 标准脚本执行 - 依次拼接
ScriptSig
和ScriptPubKey
后执行, 主要用来验证脚本哈希的正确性。 - 赎回脚本执行 - 检查栈顶元素是否为
OP_1
, 若是则移除栈顶元素, 并执行赎回脚本。
脚本执行模拟
-
赎回脚本可以是任何有效的脚本,包括非标准脚本。只要在执行结束时栈上有且只有一个
OP_1
, 则解锁脚本有效。 -
赎回脚本最大为 520 个字节。
P2SH-P2MS
P2SH
最常见的应用场景是多签, 即将 P2MS
作为赎回脚本。
-
P2MS
输出的锁定脚本包含多个原始公钥信息, 无疑会增加交易的大小, 从而增加交易费用。而使用P2SH-P2MS
可以用多个公钥信息构建赎回脚本, 交易输出中只需存储赎回脚本哈希, 从而减小交易大小, 降低交易费用。 -
P2MS
最多支持 3 个公钥, 而由于单个压缩公钥大小为 33 字节字节,P2SH-P2MS
赎回脚本最多支持 15 个公钥。
花费时, P2MS
解锁脚本只需要提供需要的多个签名即可。而 P2SH-P2MS
除了必须的多个签名外, 还需要提供完整的赎回脚本。 因此在花费时, P2SH-P2MS
会比 P2MS
需要更多的交易费用。
实践
构建自定义赎回脚本
import { payments, script } from 'bitcoinjs-lib'
import { OPS } from 'bitcoinjs-lib/src/ops'
import { toHex } from 'uint8array-tools'
const redeem = script.compile([
OPS.OP_3DUP,
OPS.OP_ADD,
OPS.OP_9,
OPS.OP_EQUALVERIFY,
OPS.OP_ADD,
OPS.OP_7,
OPS.OP_EQUALVERIFY,
OPS.OP_ADD,
OPS.OP_8,
OPS.OP_EQUALVERIFY,
OPS.OP_1
])
const p2sh = payments.p2sh({ redeem: { output: redeem } })
console.log(toHex(p2sh.output!)) // a914da5a92e670a66538be1c550af352646000b2367d87
console.log(toHex(p2sh.redeem?.output!)) // 6f93598893578893588851
p2sh.output
为锁定脚本, p2sh.redeem?.output
为赎回脚本
以标准脚本作为赎回脚本, 如 P2MS
:
const p2sh = payments.p2sh({
redeem: payments.p2ms({
m: 2,
pubkeys: [
Buffer.from(
'020727931f73a3557750012196a95c2c711eedea3b981311bdaae0d77830147f5e',
'hex'
),
Buffer.from(
'03c843204c50447f6981eb9eb703d74f888dce775cc853ceba2d24b17758ac949c',
'hex'
),
Buffer.from(
'03cf0f461d623c3a7c73d3d540ae07e6308d56f1630551be0b7aa7b86632ce4f60',
'hex'
)
]
})
})
console.log(toHex(p2sh.output!)) // a914f71179c135963f43de2d173dbf8a0faa72def6ba87
console.log(toHex(p2sh.redeem?.output!)) // 5221020727931f73a3557750012196a95c2c711eedea3b981311bdaae0d77830147f5e2103c843204c50447f6981eb9eb703d74f888dce775cc853ceba2d24b17758ac949c2103cf0f461d623c3a7c73d3d540ae07e6308d56f1630551be0b7aa7b86632ce4f6053ae