Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

P2SH-为比特币赋能的脚本

哈哈笑417
227 0 0
P2SH即Pay to Script Key Hash,最常见的应用是多重签名,N把公钥,M人签名时才能花费这笔交易(M, O2 l+ C+ H) n# v( u8 ]$ @
P2PK6 i  P- \5 V6 \4 _
比特币早期中本聪时期是P2PK(pay to public key)这种输出形式的,最早是直接放入一把公钥进去了,还是未压缩的,感受一下:
- l' ~6 e2 B# G  r# X04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG% |) l* ]5 p1 @9 Y
0x04开头,表示是未压缩的0x04后面紧接这的是64字节是公钥内容OP_CHECKSIG:操作码,用于花费的时执行验证签名
- m& a  ^. V0 \1 H1 X! H: u那么验证时(即花费这笔输出)堆栈内容为:
( {8 ?' ~' _8 }/ w# h; C( n' \  a9 f: v; q; U( W/ A
花费脚本为:: h  A- a6 j* @5 |( N$ K# B
连接前向的输出,完整的堆栈:3 X$ K/ m' A" ^, Z) o* Q4 A" W
  OP_CHECKSIG
' l+ t* }' P% m- I8 K& nOP_CHECKSIG执行签名检查,并返回检查结果,true则通过签名检查,可以花费。6 Z, Q' a1 ?1 K  L/ k) P$ |
P2PKH
  p2 j4 c/ n2 e后来发现公钥其实用哈希就可以了,没必要放出公钥内容,被称为P2PKH(pay to public key hash)。于是输出脚本演化为:9 ?4 ]4 A# U  a$ @
OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
! X$ A& x' Z- ?1 j" l: ~OP_DUP: 操作码,复制栈顶元素OP_HASH160:操作码,计算栈顶元素Hash,即计算公钥的哈希public_key_hash:公钥的哈希值,20字节OP_EQUALVERIFY:操作码,判断是否相等OP_CHECKSIG:操作码,用于花费的时执行验证签名4 }' I1 w7 {( A* m* e
此类型输出,就是最常见的1开头的地址输出。那么验证时的堆栈内容为:! G6 G; J8 D' Y8 [

/ Z* f% }' P* J  g, u# D花费脚本为:6 E# {* P, e- k$ E& I* q
  ; y. I% Q/ |7 O3 }6 d' p# f! P
连接前向的输出,完整的堆栈:
  U" E* \# ~+ \+ v7 A' t  OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG4 P8 k: n7 e4 ^6 k3 a+ ]
验证运算过程:7 J! C" k- X: M% R, u
从前往后找OP操作码,首先遇到OP_DUP
7 D5 h" b' W% m执行后堆栈为:   OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
0 N7 O$ V" @1 N, K* y执行OP_HASH160
0 {6 }' N: t8 h0 F执行后堆栈为:    OP_EQUALVERIFY OP_CHECKSIG8 p6 W+ P8 R) Z; O  I8 a6 n8 m
执行OP_EQUALVERIFY,即验证该哈希是否与公钥匹配8 A& X7 |, L7 v7 k
执行后堆栈为:  OP_CHECKSIG
# {5 f4 E& e1 @. k9 T2 B5 ~到这里就与P2PKH一致了
( C, {$ I. d. O1 L  E. ~执行OP_CHECKSIG,验证签名。0 g3 K! D5 Y$ r! G/ h' u& x
这个过程汪海波写过文章详细的阐述过:理解比特币脚本。p2pk改进为p2pkh后,输出长度缩小了一半多,同时隐私方面迈出了一小步:别人转给你的币在你未花费之前,别人是不知道你的公钥具体内容的。是不是很赞?8 Q' n( @2 Q7 ~* s5 u* |7 O- r
原始多重签名; _; L! `5 Q1 F7 u
随着社区快速发展,人们发现需要多重签名,很快就出现了多重签名的输出格式,Gavin在BIP11里描述了这种输出:9 y! q$ C) C; b& l2 ^0 t
m {pubkey}...{pubkey} n OP_CHECKMULTISIG
& n3 a) _; U6 V& e4 f2/2的一个原始多重签名示例:
) U1 T0 ^' V2 C+ @- V04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4& u1 s2 o7 q1 j1 u8 H
0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af
* N6 d- j3 Q, F5 k& P; B2 h2 OP_CHECKMULTISIG+ C- w" V8 ~0 i; {
验证时的堆栈内容为:7 }& Z& u' u& o* m) P; W( n
花费脚本为:2 ?$ q; a5 a% |: ~) S2 e
0   # g+ _4 F4 |" N, {* u
连接前向的输出,完整的堆栈:
. q, H7 u! o: W0    M    N
  A- z9 j2 i1 Q5 h7 OOP_CHECKMULTISIG
7 \8 n2 m8 c0 P# n* _2 Q; Z' YOP_CHECKMULTISIG的运行过程稍微复杂一些:
0 B& p# P; a/ g4 \' r" J弹出最后一个数,就是N,公钥总数。
- Z- U$ Z- z8 G# M1 X6 t0 m执行后堆栈为:0    M   弹出N个堆栈元素,就是这N把公钥。6 L  ^) N( B0 v
执行后堆栈为:0    M弹出签名数量M,即需要M个签名数量。
: m1 e+ q9 Y! Z3 g- T3 q' {$ s- P执行后堆栈为:0   弹出M个堆栈元素,即需要M个签名。同时对M个签名进行验证。1 _- p. }/ z/ Z5 A
执行后堆栈为:0弹出最后一个元素0。
$ R5 b7 D/ Q. S- R+ x0即OP_0,为啥多这么一个奇怪的元素呢,因为早期实现OP_CHECKMULTISIG时,存在一个BUG,导致必须多放入一个元素到堆栈里。为了保持兼容性,则不得不放入OP_0,否则就是造成硬分叉。
3 H. u. N1 D! u9 M1 {这种类型的输出存在时间很短,大部分人几乎不知道它的的存在,如果你哪天看见某个交易的一个输出里冒出好几个地址,那就是这种古老原始的多重签名。早期主要应用于2/3的担保交易中。2 M/ k4 p+ [- V1 b8 @

) _! T0 s1 L, j$ s. Y% d) @" P! ^P2SH
" T" u2 i5 O9 `" l( v+ e* d. K5 R/ d因为实在是又丑又笨,Gavin很快捣鼓出一个改进版本BIP16:
' _  }8 s" O/ t, S6 m, cOP_HASH160  OP_EQUAL
$ f- \' q8 i) |) b+ T- z" z& l其实不用把公钥放在输出里了,放入其HASH值即可,与早期P2PK进化为P2PKH一样,将这些公钥连接在一起并计算出其HASH160的值:
4 M) w; k! G- K8 V" g$ S  m# qRedeemScript = OP_nRequired | PUBKEY_1 | ... | PUBKEY_N | N | OP_CHECKMULTISIG! N2 R/ o& Z% K4 P' a
20-byte-hash-value = RIPEMD-160(RedeemScript)
6 O/ r/ t) c' k4 E. ]& ^- B/ l, }RedeemScript就是把参与的公钥以及m/n的设置值等连接在一起的内容,RedeemScript其实就是前面提到的“原始多重签名”的输出,哈希后产生的这20个字节刚好可以转为普通地址显示,就是现在最常见的3开头的p2sh多重签名地址。N最大为16把公钥。. d& v6 ^1 g- H1 }; K
验证时的堆栈内容为:' ^  p) v% x$ [# R4 y5 i
花费脚本为:. n/ B  R! |) ^8 i# @5 L5 x
OP_0    6 `! s% g, |. n9 K
连接前向的输出,完整的堆栈:
) ?1 c: W! C. b8 Y2 _8 \OP_0     OP_HASH160  OP_EQUAL4 o- q- i* o" Y' }( p5 T7 I
验证过程分为两大步骤,第一步骤:- h- H! t6 G, C# W: L$ o3 A
1.执行OP_HASH160,计算HASH值: OP_HASH160
: N" ^+ y+ T: u$ i5 w7 J执行后堆栈为:OP_0     OP_EQUAL
* _1 s8 N- c/ }! J2.执行OP_EQUAL,验证两个哈希值是否相等
+ t& e: K; s' H执行后堆栈为:OP_0   
  G, N: Y2 e9 F; H/ }& X8 t* g第二步骤,将展开花费脚本中的RedeemScript展开得到子脚本,就得到与“原始多重签名”一致的堆栈数据格式,并执行类似的验证过程:& k4 _# @4 L0 P: R# N( C; k) h
OP_0    M    N OP_CHECKMULTISIG- o3 I; k0 u# p4 Y9 g: T
当然,RedeemScript除了放多重签名脚本外,还可以放其他任何脚本,多重签名仅仅是一种应用而已,p2sh提供了无限可能性。
7 k  h3 s: m6 s% ]. F. a- b) u. _软分叉的关键点
* I# }9 k$ G1 s; I3 K在第一步骤中,redeemScript是作为一个整体数据进栈的,而在第二步里,redeemScript会按照脚本进行解析得到N个栈元素,然后再依次进栈进行验证。这个过程是非常巧妙的,在第一步里,仅验证了脚本的哈希值是否一致,并没有验证签名。真正的签名信息是在第二步骤里进行验证的。因为这点,所以可以软分叉实施P2SH,老节点仅执行第一步骤,新节点执行两个步骤。
  `6 q. J% m6 _( \) K+ d5 m当花费过一次后,redeemScript其实就公开了,那么对于老节点来说,任何人都可以用这个公开的redeemScript花掉相同地址的币(验证哈希值)。这就是被称为任何人可以花费(Anyone can spend)的原因。不过,特性激活后,新版全节点(出块节点必然是新版)会强制执行第二步验证,永远都不会被其他人偷花。
: P$ X* n' U( B5 e/ r激活! G" |" f0 Y5 \  D4 \  a
P2SH的激活,其实算是UASF或者是GASF(Gavin Actived Soft Fork),那时也没有规范的软分叉升级方案,如BIP9。升级代码就直接发布了,并设立了激活信号日2012年04月01日(测试网是2月15日)。支持的用户直接升级代码,不支持的用户不升级代码。, A2 z7 Q: P8 D. R
为了防止潜在网络分叉,矿工在coinbase交易里打标识/P2SH/来标明支持P2SH。但这个仅供人为观察,并不是代码层面的。
- r+ N  L  \' f* \. V* e影响深远
9 Z5 ~6 A" f) q/ u% D: y( P5 \+ ^, xP2SH是一个高度灵活的脚本方案,意义重大,影响深远,简直像打开了宝藏一样。其为后面的SegWit,MAST都铺平了道路。
  O' c8 }+ W9 ^$ a' o发展状况
标签: P2SH 比特币
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

哈哈笑417 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    11