Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

P2SH-为比特币赋能的脚本

哈哈笑417
262 0 0
P2SH即Pay to Script Key Hash,最常见的应用是多重签名,N把公钥,M人签名时才能花费这笔交易(M" V1 v1 K) C6 h" E( t% e
P2PK  L3 d5 b& h8 N. C- \
比特币早期中本聪时期是P2PK(pay to public key)这种输出形式的,最早是直接放入一把公钥进去了,还是未压缩的,感受一下:
) k  r: l3 Y3 P. `# `* \  ]04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG
% \/ L4 n* h( K6 u7 |* u0x04开头,表示是未压缩的0x04后面紧接这的是64字节是公钥内容OP_CHECKSIG:操作码,用于花费的时执行验证签名
* y3 n, P! A( M8 |: t/ }" Z4 G那么验证时(即花费这笔输出)堆栈内容为:! s  ~% w/ H' v& w& A7 j# A

/ [" B! U& P/ c& C' I) i花费脚本为:
2 z: f" ]. C% F/ S$ Z# n连接前向的输出,完整的堆栈:
' l8 U' v" c+ A1 l  OP_CHECKSIG
, B) k6 V' m! v: x, ]OP_CHECKSIG执行签名检查,并返回检查结果,true则通过签名检查,可以花费。
. E7 _- `. a3 SP2PKH
- L* m6 U9 {7 |6 t/ I, I后来发现公钥其实用哈希就可以了,没必要放出公钥内容,被称为P2PKH(pay to public key hash)。于是输出脚本演化为:* x" w; N0 C4 Z! |- `8 _4 [, r
OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG5 G3 P* E; }& W1 g" O
OP_DUP: 操作码,复制栈顶元素OP_HASH160:操作码,计算栈顶元素Hash,即计算公钥的哈希public_key_hash:公钥的哈希值,20字节OP_EQUALVERIFY:操作码,判断是否相等OP_CHECKSIG:操作码,用于花费的时执行验证签名
- k( q  F& T+ R( R5 Q4 G% x. t" m此类型输出,就是最常见的1开头的地址输出。那么验证时的堆栈内容为:
7 Q5 b+ V- J0 G; V! [
6 k% v) ?* W* c! \6 N8 y+ n花费脚本为:
1 K+ q: C9 W" t; L% }/ S  $ `8 T  F) A8 S
连接前向的输出,完整的堆栈:
1 O% S; L8 v+ K% @+ {; V  OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
+ Q8 P  l3 W1 s# {& Y验证运算过程:
3 [# L( B  e) W9 c( U5 O从前往后找OP操作码,首先遇到OP_DUP
' j! H! f) F: v执行后堆栈为:   OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
9 m- q# D" F0 N4 b; l) t! \执行OP_HASH160' x6 p# y5 m2 y
执行后堆栈为:    OP_EQUALVERIFY OP_CHECKSIG% j, s3 Z& \' S: E- e. u
执行OP_EQUALVERIFY,即验证该哈希是否与公钥匹配* y; ]% @! W- F# T
执行后堆栈为:  OP_CHECKSIG
) }0 F+ v' x& g. R, k: A到这里就与P2PKH一致了
. G' q1 _7 H2 _* o' g执行OP_CHECKSIG,验证签名。
+ u' M8 A0 k- L, z& v  `% w, J这个过程汪海波写过文章详细的阐述过:理解比特币脚本。p2pk改进为p2pkh后,输出长度缩小了一半多,同时隐私方面迈出了一小步:别人转给你的币在你未花费之前,别人是不知道你的公钥具体内容的。是不是很赞?
0 W' Q8 t: _% i原始多重签名
, o4 z8 w. y" ?+ Z8 V随着社区快速发展,人们发现需要多重签名,很快就出现了多重签名的输出格式,Gavin在BIP11里描述了这种输出:
& G4 E9 [/ v& J: }5 {6 L/ Nm {pubkey}...{pubkey} n OP_CHECKMULTISIG
' ?* G$ P: q5 r0 p" D4 [( J) L2/2的一个原始多重签名示例:
: D- J0 J/ P( G5 g9 I04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4& N  f4 p  L$ ^' X1 o- U
0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af& C% e% b1 Y. Z, N1 H7 m
2 OP_CHECKMULTISIG  Q# M7 e: Z$ A, w. ~" Y" X5 T
验证时的堆栈内容为:9 k9 f* Y( L* ?% P2 k' n" k* ^
花费脚本为:
' T# ?" f, K. m* I) D- J) a1 f0   / f/ T) k2 [2 k- H- i' B7 D
连接前向的输出,完整的堆栈:
9 y1 E) ^/ e/ c0    M    N
: y% W. ?2 z* Y8 O* k/ \OP_CHECKMULTISIG. S7 C' }( |- ^
OP_CHECKMULTISIG的运行过程稍微复杂一些:
- N$ u; e% v/ S" n7 J弹出最后一个数,就是N,公钥总数。
- c9 ?( |5 B0 Z, [5 |执行后堆栈为:0    M   弹出N个堆栈元素,就是这N把公钥。9 e. e8 r/ M- _. o) |- E7 E2 K2 }
执行后堆栈为:0    M弹出签名数量M,即需要M个签名数量。: D7 J+ W! R& ~7 V" k
执行后堆栈为:0   弹出M个堆栈元素,即需要M个签名。同时对M个签名进行验证。6 G2 L/ V# ^- _2 |$ Z/ Z
执行后堆栈为:0弹出最后一个元素0。* B6 i6 q# j  m" q' s& W4 r
0即OP_0,为啥多这么一个奇怪的元素呢,因为早期实现OP_CHECKMULTISIG时,存在一个BUG,导致必须多放入一个元素到堆栈里。为了保持兼容性,则不得不放入OP_0,否则就是造成硬分叉。& m' X4 l# g& h( V3 R: j7 Y2 x3 r
这种类型的输出存在时间很短,大部分人几乎不知道它的的存在,如果你哪天看见某个交易的一个输出里冒出好几个地址,那就是这种古老原始的多重签名。早期主要应用于2/3的担保交易中。
  i7 p; ~$ h1 I$ s9 Z( H6 e. A7 d/ ]  W
+ s5 ~' o: o* e+ Z" X/ GP2SH, S! e3 q4 X" V0 t, d, S) H
因为实在是又丑又笨,Gavin很快捣鼓出一个改进版本BIP16:
" Q- o1 U0 {: R- J5 rOP_HASH160  OP_EQUAL3 A  A7 w) X) D/ j3 x4 i$ L
其实不用把公钥放在输出里了,放入其HASH值即可,与早期P2PK进化为P2PKH一样,将这些公钥连接在一起并计算出其HASH160的值:9 _0 s( H" C5 T/ O+ [# N
RedeemScript = OP_nRequired | PUBKEY_1 | ... | PUBKEY_N | N | OP_CHECKMULTISIG
2 F* r# [# W1 T) L8 C20-byte-hash-value = RIPEMD-160(RedeemScript)
% u: r2 f( z; w/ g8 X/ Z1 CRedeemScript就是把参与的公钥以及m/n的设置值等连接在一起的内容,RedeemScript其实就是前面提到的“原始多重签名”的输出,哈希后产生的这20个字节刚好可以转为普通地址显示,就是现在最常见的3开头的p2sh多重签名地址。N最大为16把公钥。! b) W# f$ v% y+ x; c
验证时的堆栈内容为:% `2 M5 ?0 u( C: d8 B
花费脚本为:
$ {- p! Q8 S, m+ w# F. \OP_0   
4 r1 V8 V( v: U3 ?. g连接前向的输出,完整的堆栈:
5 c( q. ?: l/ E) jOP_0     OP_HASH160  OP_EQUAL
& M( C. r/ f  K* z$ E' X验证过程分为两大步骤,第一步骤:
3 q! ^  E+ C+ N/ H1.执行OP_HASH160,计算HASH值: OP_HASH160
8 q+ k* C9 Z, q3 m6 U执行后堆栈为:OP_0     OP_EQUAL3 j' T' H) B* K
2.执行OP_EQUAL,验证两个哈希值是否相等
( y* V8 c9 f4 k! }; G6 y执行后堆栈为:OP_0   : f3 d; |; u5 G& ~$ @+ E/ m
第二步骤,将展开花费脚本中的RedeemScript展开得到子脚本,就得到与“原始多重签名”一致的堆栈数据格式,并执行类似的验证过程:
5 a9 L& k8 ]: l: TOP_0    M    N OP_CHECKMULTISIG
; f" A) g: g$ ]" G+ z当然,RedeemScript除了放多重签名脚本外,还可以放其他任何脚本,多重签名仅仅是一种应用而已,p2sh提供了无限可能性。  z& d0 r5 H1 U( \) J0 o) U
软分叉的关键点5 l; B# C7 Q! {: U
在第一步骤中,redeemScript是作为一个整体数据进栈的,而在第二步里,redeemScript会按照脚本进行解析得到N个栈元素,然后再依次进栈进行验证。这个过程是非常巧妙的,在第一步里,仅验证了脚本的哈希值是否一致,并没有验证签名。真正的签名信息是在第二步骤里进行验证的。因为这点,所以可以软分叉实施P2SH,老节点仅执行第一步骤,新节点执行两个步骤。
3 D+ g% x0 ]9 v6 @) X4 a当花费过一次后,redeemScript其实就公开了,那么对于老节点来说,任何人都可以用这个公开的redeemScript花掉相同地址的币(验证哈希值)。这就是被称为任何人可以花费(Anyone can spend)的原因。不过,特性激活后,新版全节点(出块节点必然是新版)会强制执行第二步验证,永远都不会被其他人偷花。
# n% g0 a9 v* k; j激活
5 ^* u- p& r/ u& o; |6 [9 a# U# S4 EP2SH的激活,其实算是UASF或者是GASF(Gavin Actived Soft Fork),那时也没有规范的软分叉升级方案,如BIP9。升级代码就直接发布了,并设立了激活信号日2012年04月01日(测试网是2月15日)。支持的用户直接升级代码,不支持的用户不升级代码。. `4 z# y' P; G5 i4 o& a5 ?' c
为了防止潜在网络分叉,矿工在coinbase交易里打标识/P2SH/来标明支持P2SH。但这个仅供人为观察,并不是代码层面的。
- d$ k- t( Q  i% x( ]3 i% T影响深远2 a6 A: ]. q. f9 x$ Z4 m5 B
P2SH是一个高度灵活的脚本方案,意义重大,影响深远,简直像打开了宝藏一样。其为后面的SegWit,MAST都铺平了道路。
$ O4 y, r$ w" r1 ^* R. |发展状况
标签: P2SH 比特币
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

哈哈笑417 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    11