P2SH-为比特币赋能的脚本

P2SH即Pay to Script Key Hash,最常见的应用是多重签名,N把公钥,M人签名时才能花费这笔交易(M<=N, N<=16),地址通常是以3开头的格式。当然,p2sh除了用来做多重签名之外,能做的事情简直无数。

P2PK

比特币早期中本聪时期是P2PK(pay to public key)这种输出形式的,最早是直接放入一把公钥进去了,还是未压缩的,感受一下:

1
04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG
  • 0x04开头,表示是未压缩的
  • 0x04后面紧接这的是64字节是公钥内容
  • OP_CHECKSIG:操作码,用于花费的时执行验证签名

那么验证时(即花费这笔输出)堆栈内容为:

1
2
3
4
5
花费脚本为:
<signature>

连接前向的输出,完整的堆栈:
<signature> <pubkey> OP_CHECKSIG

OP_CHECKSIG执行签名检查,并返回检查结果,true则通过签名检查,可以花费。

P2PKH

后来发现公钥其实用哈希就可以了,没必要放出公钥内容,被称为P2PKH(pay to public key hash)。于是输出脚本演化为:

1
OP_DUP OP_HASH160 <public_key_hash> OP_EQUALVERIFY OP_CHECKSIG
  • OP_DUP: 操作码,复制栈顶元素
  • OP_HASH160:操作码,计算栈顶元素Hash,即计算公钥的哈希
  • public_key_hash:公钥的哈希值,20字节
  • OP_EQUALVERIFY:操作码,判断是否相等
  • OP_CHECKSIG:操作码,用于花费的时执行验证签名

此类型输出,就是最常见的1开头的地址输出。那么验证时的堆栈内容为:

1
2
3
4
5
花费脚本为:
<signature> <pubkey> 

连接前向的输出,完整的堆栈:
<signature> <pubkey> OP_DUP OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG

验证运算过程:

  1. 从前往后找OP操作码,首先遇到OP_DUP
    • 执行后堆栈为:<signature> <pubkey> <pubkey> OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
  2. 执行OP_HASH160
    • 执行后堆栈为:<signature> <pubkey> <pubkey_hash> <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
  3. 执行OP_EQUALVERIFY,即验证该哈希是否与公钥匹配
    • 执行后堆栈为:<signature> <pubkey> OP_CHECKSIG
    • 到这里就与P2PKH一致了
  4. 执行OP_CHECKSIG,验证签名。

这个过程汪海波写过文章详细的阐述过:理解比特币脚本。p2pk改进为p2pkh后,输出长度缩小了一半多,同时隐私方面迈出了一小步:别人转给你的币在你未花费之前,别人是不知道你的公钥具体内容的。是不是很赞?

原始多重签名

随着社区快速发展,人们发现需要多重签名,很快就出现了多重签名的输出格式,Gavin在BIP11里描述了这种输出:

1
m {pubkey}...{pubkey} n OP_CHECKMULTISIG

2/2的一个原始多重签名示例:

1
2
3
4
2
04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4
0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af
2 OP_CHECKMULTISIG

验证时的堆栈内容为:

1
2
3
4
5
花费脚本为:
0 <sig_1> <...> <sig_M>

连接前向的输出,完整的堆栈:
0 <sig_1> <...> <sig_M> M <pubkey_1> <...> <pubkey_N> N OP_CHECKMULTISIG

OP_CHECKMULTISIG的运行过程稍微复杂一些:

  1. 弹出最后一个数,就是N,公钥总数。
    • 执行后堆栈为:0 <sig_1> <...> <sig_M> M <pubkey_1> <...> <pubkey_N>
  2. 弹出N个堆栈元素,就是这N把公钥。
    • 执行后堆栈为:0 <sig_1> <...> <sig_M> M
  3. 弹出签名数量M,即需要M个签名数量。
    • 执行后堆栈为:0 <sig_1> <...> <sig_M>
  4. 弹出M个堆栈元素,即需要M个签名。同时对M个签名进行验证。
    • 执行后堆栈为:0
  5. 弹出最后一个元素0
    • 0OP_0,为啥多这么一个奇怪的元素呢,因为早期实现OP_CHECKMULTISIG时,存在一个BUG,导致必须多放入一个元素到堆栈里。为了保持兼容性,则不得不放入OP_0,否则就是造成硬分叉。

这种类型的输出存在时间很短,大部分人几乎不知道它的的存在,如果你哪天看见某个交易的一个输出里冒出好几个地址,那就是这种古老原始的多重签名。早期主要应用于2/3的担保交易中。

P2SH

因为实在是又丑又笨,Gavin很快捣鼓出一个改进版本BIP16:

1
OP_HASH160 <redeem_script_hash> OP_EQUAL

其实不用把公钥放在输出里了,放入其HASH值即可,与早期P2PK进化为P2PKH一样,将这些公钥连接在一起并计算出其HASH160的值:

1
2
3
RedeemScript = OP_nRequired | PUBKEY_1 | ... | PUBKEY_N | N | OP_CHECKMULTISIG

20-byte-hash-value = RIPEMD-160(RedeemScript)

RedeemScript就是把参与的公钥以及m/n的设置值等连接在一起的内容,RedeemScript其实就是前面提到的“原始多重签名”的输出,哈希后产生的这20个字节刚好可以转为普通地址显示,就是现在最常见的3开头的p2sh多重签名地址。N最大为16把公钥。

验证时的堆栈内容为:

1
2
3
4
5
花费脚本为:
OP_0 <sig_1> <...> <sig_M> <redeemScript>

连接前向的输出,完整的堆栈:
OP_0 <sig_1> <...> <sig_M> <redeemScript> OP_HASH160 <redeemScriptHash> OP_EQUAL

验证过程分为两大步骤,第一步骤:

  1. 执行OP_HASH160,计算HASH值:<redeemScript> OP_HASH160
    • 执行后堆栈为:OP_0 <sig_1> <...> <sig_M> <redeemScriptHash><redeemScriptHash> OP_EQUAL
  2. 执行OP_EQUAL,验证两个哈希值是否相等
    • 执行后堆栈为:OP_0 <sig_1> <...> <sig_M>

第二步骤,将展开花费脚本中的RedeemScript展开得到子脚本,就得到与“原始多重签名”一致的堆栈数据格式,并执行类似的验证过程:

1
OP_0 <sig_1> <...> <sig_M> M <pubkey_1> <...> <pubkey_N> N OP_CHECKMULTISIG

当然,RedeemScript除了放多重签名脚本外,还可以放其他任何脚本,多重签名仅仅是一种应用而已,p2sh提供了无限可能性。

软分叉的关键点

在第一步骤中,redeemScript是作为一个整体数据进栈的,而在第二步里,redeemScript会按照脚本进行解析得到N个栈元素,然后再依次进栈进行验证。这个过程是非常巧妙的,在第一步里,仅验证了脚本的哈希值是否一致,并没有验证签名。真正的签名信息是在第二步骤里进行验证的。因为这点,所以可以软分叉实施P2SH,老节点仅执行第一步骤,新节点执行两个步骤。

当花费过一次后,redeemScript其实就公开了,那么对于老节点来说,任何人都可以用这个公开的redeemScript花掉相同地址的币(验证哈希值)。这就是被称为任何人可以花费(Anyone can spend)的原因。不过,特性激活后,新版全节点(出块节点必然是新版)会强制执行第二步验证,永远都不会被其他人偷花。

激活

P2SH的激活,其实算是UASF或者是GASF(Gavin Actived Soft Fork),那时也没有规范的软分叉升级方案,如BIP9。升级代码就直接发布了,并设立了激活信号日2012年04月01日(测试网是2月15日)。支持的用户直接升级代码,不支持的用户不升级代码。

为了防止潜在网络分叉,矿工在coinbase交易里打标识/P2SH/来标明支持P2SH。但这个仅供人为观察,并不是代码层面的。

影响深远

P2SH是一个高度灵活的脚本方案,意义重大,影响深远,简直像打开了宝藏一样。其为后面的SegWit,MAST都铺平了道路。

发展状况

数据来源:https://p2sh.info

截止2019年2月,全网大约32%的币存储在P2SH的输出里。

p2sh-valuesp2sh-values

P2SH的输出类型统计:

p2sh-typesp2sh-types

免责声明:本文转载自https://panzhibiao.com/,不代表呐喊推文立场,且不构成投资建议,请谨慎对待。

版权声明:作者保留权利,不代表呐喊推文立场。

(0)
上一篇 2022年6月21日 上午10:14
下一篇 2022年6月28日 下午9:02

相关推荐

发表回复

登录后才能评论
QQ交流群

① 热锅蚂蚁群:672485346

分享本页
返回顶部