Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

P2SH-为比特币赋能的脚本

哈哈笑417
117 0 0
P2SH即Pay to Script Key Hash,最常见的应用是多重签名,N把公钥,M人签名时才能花费这笔交易(M
6 F& g" Z2 Q/ U- ]7 P9 n* RP2PK+ c. q4 {5 d5 i7 I8 P: n, p9 m
比特币早期中本聪时期是P2PK(pay to public key)这种输出形式的,最早是直接放入一把公钥进去了,还是未压缩的,感受一下:
% n" P9 T! {: n2 p/ Q; I$ _04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG3 B- r% @' [- [6 a# D, D* }
0x04开头,表示是未压缩的0x04后面紧接这的是64字节是公钥内容OP_CHECKSIG:操作码,用于花费的时执行验证签名
' K# a! y& Z- d; Q那么验证时(即花费这笔输出)堆栈内容为:
6 a( _" w4 t: z
0 g3 ]  I+ N4 D' j花费脚本为:
! N5 |  N) A2 A) v8 E( v" v连接前向的输出,完整的堆栈:
7 O$ T) ~2 C- u- W' }- m* R  OP_CHECKSIG2 A; G+ `6 Q9 m# |; q
OP_CHECKSIG执行签名检查,并返回检查结果,true则通过签名检查,可以花费。" P8 @6 z' W. x8 g9 y
P2PKH4 N0 f- t% ?3 e5 M1 l1 h( a
后来发现公钥其实用哈希就可以了,没必要放出公钥内容,被称为P2PKH(pay to public key hash)。于是输出脚本演化为:
- z% L4 h& o; V, ?8 sOP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
/ f7 w" }. ^0 ?0 t8 V: IOP_DUP: 操作码,复制栈顶元素OP_HASH160:操作码,计算栈顶元素Hash,即计算公钥的哈希public_key_hash:公钥的哈希值,20字节OP_EQUALVERIFY:操作码,判断是否相等OP_CHECKSIG:操作码,用于花费的时执行验证签名- o' A0 a. w! Y  }4 O
此类型输出,就是最常见的1开头的地址输出。那么验证时的堆栈内容为:2 G" ?5 W. t, Y9 a3 ~
- p" _5 A4 f" h3 q2 ^
花费脚本为:
; d* K! q+ Q% n  
( K6 v2 a8 }( {! H连接前向的输出,完整的堆栈:  O+ }% W. [; \& c8 k
  OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
0 C  V2 A1 [$ v/ j! i验证运算过程:, B# H- K' ~" n  V+ P
从前往后找OP操作码,首先遇到OP_DUP
) s  ^5 k/ b* y2 L# R- G9 q执行后堆栈为:   OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
" W* Y% y6 }3 e7 M. W/ t2 m执行OP_HASH1609 ?; D- [" x( T3 _* X. ^9 l
执行后堆栈为:    OP_EQUALVERIFY OP_CHECKSIG
8 o9 y9 f7 a* F# A' C执行OP_EQUALVERIFY,即验证该哈希是否与公钥匹配9 ~1 p4 d# U' X1 n. T* D( ~
执行后堆栈为:  OP_CHECKSIG% B* C: d5 t9 i0 o
到这里就与P2PKH一致了6 H* y: {3 }# A2 G
执行OP_CHECKSIG,验证签名。
6 Z& V- T! ]5 x) m! {1 Q这个过程汪海波写过文章详细的阐述过:理解比特币脚本。p2pk改进为p2pkh后,输出长度缩小了一半多,同时隐私方面迈出了一小步:别人转给你的币在你未花费之前,别人是不知道你的公钥具体内容的。是不是很赞?
) N/ z' n2 Z7 S1 E. ~8 y! S原始多重签名& U; c# S& b, X+ K, s2 i6 j
随着社区快速发展,人们发现需要多重签名,很快就出现了多重签名的输出格式,Gavin在BIP11里描述了这种输出:8 I/ N8 c" P! l' @; J
m {pubkey}...{pubkey} n OP_CHECKMULTISIG
& b9 t8 W# ^% X$ B+ J2/2的一个原始多重签名示例:/ N+ Z# F. u3 V
04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4
! A8 K2 u+ [' Q9 h6 f0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af
( t2 L) }' ?. c" J2 j2 OP_CHECKMULTISIG
9 T, C# I3 R/ s8 K3 u验证时的堆栈内容为:/ ]+ L0 C6 C" E1 o9 |4 Y
花费脚本为:
6 D' P& m! x# _9 |* o5 J0   0 r- p( v6 A2 N0 V% M6 t. c1 t
连接前向的输出,完整的堆栈:
( ^4 u+ z8 b9 L0 g' j3 e# ^3 y! `0    M    N
9 e0 |  f" M8 z7 a7 f) a+ x2 C5 c" OOP_CHECKMULTISIG
% t3 {$ @+ n+ z) A+ bOP_CHECKMULTISIG的运行过程稍微复杂一些:
) A. X7 |5 h) j/ v弹出最后一个数,就是N,公钥总数。6 L, p1 |8 d0 _& V
执行后堆栈为:0    M   弹出N个堆栈元素,就是这N把公钥。
$ r- a8 t6 k: ^) V1 a! W) S执行后堆栈为:0    M弹出签名数量M,即需要M个签名数量。, X3 d# U. D* D" Y6 ^
执行后堆栈为:0   弹出M个堆栈元素,即需要M个签名。同时对M个签名进行验证。
/ F! o3 V2 I( d' H7 r执行后堆栈为:0弹出最后一个元素0。; V4 @5 r( V7 f+ l6 \5 X$ A
0即OP_0,为啥多这么一个奇怪的元素呢,因为早期实现OP_CHECKMULTISIG时,存在一个BUG,导致必须多放入一个元素到堆栈里。为了保持兼容性,则不得不放入OP_0,否则就是造成硬分叉。6 E9 M: b, e5 c/ C
这种类型的输出存在时间很短,大部分人几乎不知道它的的存在,如果你哪天看见某个交易的一个输出里冒出好几个地址,那就是这种古老原始的多重签名。早期主要应用于2/3的担保交易中。
  c- i5 V! L5 ^* n. d  D% q$ Z4 i7 D" M8 X( W* a/ [5 S
P2SH
( @: j' G, ]+ j, h* n# v因为实在是又丑又笨,Gavin很快捣鼓出一个改进版本BIP16:7 S' \1 I& q( R8 ?1 X
OP_HASH160  OP_EQUAL9 b$ `) g0 c$ t$ @. F- j
其实不用把公钥放在输出里了,放入其HASH值即可,与早期P2PK进化为P2PKH一样,将这些公钥连接在一起并计算出其HASH160的值:2 T$ C7 @7 j# i' d1 j% J
RedeemScript = OP_nRequired | PUBKEY_1 | ... | PUBKEY_N | N | OP_CHECKMULTISIG! J4 J; U9 G5 S# K
20-byte-hash-value = RIPEMD-160(RedeemScript)
8 C. U4 ~. n, ?RedeemScript就是把参与的公钥以及m/n的设置值等连接在一起的内容,RedeemScript其实就是前面提到的“原始多重签名”的输出,哈希后产生的这20个字节刚好可以转为普通地址显示,就是现在最常见的3开头的p2sh多重签名地址。N最大为16把公钥。
2 I) y' w0 d" l( _8 H验证时的堆栈内容为:3 R' R7 ?5 U; S& \6 Z
花费脚本为:% S: p* Q! ~0 C9 G) S5 \9 g
OP_0    % P! N/ m. j( ]& H# Q  @# n
连接前向的输出,完整的堆栈:
8 B6 I7 [6 z/ z, V+ BOP_0     OP_HASH160  OP_EQUAL
  D7 \8 S5 `+ u! F% l- c验证过程分为两大步骤,第一步骤:
  v  _4 f  {$ `8 i1.执行OP_HASH160,计算HASH值: OP_HASH160
7 F! O6 G) T1 V执行后堆栈为:OP_0     OP_EQUAL
* ~  q, [0 x2 B2 Z7 x2.执行OP_EQUAL,验证两个哈希值是否相等
: s% n5 B- M$ R0 K6 _执行后堆栈为:OP_0   
- q3 Z2 f+ [, K0 B; F) d. V( o1 ^$ N第二步骤,将展开花费脚本中的RedeemScript展开得到子脚本,就得到与“原始多重签名”一致的堆栈数据格式,并执行类似的验证过程:/ P9 H8 P( t0 v/ R' d
OP_0    M    N OP_CHECKMULTISIG
6 y# s1 w$ F1 x7 D  \' @当然,RedeemScript除了放多重签名脚本外,还可以放其他任何脚本,多重签名仅仅是一种应用而已,p2sh提供了无限可能性。) y' i8 i9 U- \1 P  _+ R) S6 r9 H
软分叉的关键点, u5 j, Z; r; L5 f1 [8 R# g
在第一步骤中,redeemScript是作为一个整体数据进栈的,而在第二步里,redeemScript会按照脚本进行解析得到N个栈元素,然后再依次进栈进行验证。这个过程是非常巧妙的,在第一步里,仅验证了脚本的哈希值是否一致,并没有验证签名。真正的签名信息是在第二步骤里进行验证的。因为这点,所以可以软分叉实施P2SH,老节点仅执行第一步骤,新节点执行两个步骤。
1 q0 ~) b/ k! R2 {, ^/ D( t当花费过一次后,redeemScript其实就公开了,那么对于老节点来说,任何人都可以用这个公开的redeemScript花掉相同地址的币(验证哈希值)。这就是被称为任何人可以花费(Anyone can spend)的原因。不过,特性激活后,新版全节点(出块节点必然是新版)会强制执行第二步验证,永远都不会被其他人偷花。: V1 ~3 Z$ a2 V' r
激活
# P2 B; _3 P: p8 S+ f, _; R) PP2SH的激活,其实算是UASF或者是GASF(Gavin Actived Soft Fork),那时也没有规范的软分叉升级方案,如BIP9。升级代码就直接发布了,并设立了激活信号日2012年04月01日(测试网是2月15日)。支持的用户直接升级代码,不支持的用户不升级代码。
! O5 L* B2 \" I( t+ Y为了防止潜在网络分叉,矿工在coinbase交易里打标识/P2SH/来标明支持P2SH。但这个仅供人为观察,并不是代码层面的。  X, A1 w* H; W- k
影响深远
; ]9 j) N9 b' gP2SH是一个高度灵活的脚本方案,意义重大,影响深远,简直像打开了宝藏一样。其为后面的SegWit,MAST都铺平了道路。7 w3 J* M6 P4 b: V, I& I1 V
发展状况
标签: P2SH 比特币
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

哈哈笑417 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    11