P2SH和P2WSH是比特币交易的高级脚本,能够构建复杂条件的智能合约交易。
首先,我们将看看多重签名脚本。接下来,我们介绍最常见的交易脚本P2SH,即Pay-to-Script-Hash支付给脚本哈希, 它打开了一个复杂脚本的整个世界。最后我们介绍P2WSH,即Pay-to-Witness-Script-Hash支付给见证脚本哈希, P2WSH的结构性调整对比特币交易产生了多方面的影响。
1 、多重签名
多重签名脚本设置了一个条件,其中N 个公钥被记录在脚本中,并且至少有M 个必须提供签名来解锁资金。这也称为M-N 方案,其中N 是密钥的总数,M 是验证所需的签名的数量。例如,2/3 的多重签名是三个公钥被列为潜在签名人,至少有2 个有效的签名才能花费资金。此时,标准多重签名脚本限制在最多15 个列出的公钥,这意味着您可以从1 到15 之间的多重签名或该范围内的任何组合执行任何操作。在本书发布之前,限制15 个已列出d 的密钥可能会被解除,因此请检查isStandard()函数以查看当前网络接受的内容。
设置M-N 多重签名条件的锁定脚本的一般形式是:
M <Public Key 1> <Public Key 2> … <Public Key N> N CHECKMULTISIG
M 是花费输出所需的签名的数量,N 是列出的公钥的总数。设置2 到3 多重签名条件的锁定脚本如下所示:
2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
上述锁定脚本可由含有签名和公钥的脚本予以解锁: 或者由3 个存档公钥中的任意2 个相一致的私钥签名组合予以解锁。两个脚本组合将形成一个验证脚本:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
当执行时,只有当未解锁版脚本与解锁脚本设置条件相匹配时,组合脚本才显示得到结果为真(Ture)。
上述例子中相应的设置条件即为:未解锁脚本是否含有3 个公钥中的任意2 个相对应的私钥的有效签名。
l CHECKMULTISIG 执行中的bug
CHECKMULTISIG 的执行中有一个bug,需要一些轻微的解决方法。当CHECKMULTISIG 执行时,它应该消耗堆栈(stack) 上的M + N + 2 个项目作为参数。然而,由于该错误,CHECKMULTISIG 将弹出(pop)超出预期的额外值或一个值。
我们来看看这个更详细的/使用以前的/验证示例:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
首先,CHECKMULTISIG 弹出最上面的项目,这是N(在这个例子中N 是“3”)。然后它弹出N 个项目,这是可以签名的公钥。在这个例子中,公钥A,B 和C.然后,它弹出一个项目,即M,仲裁(需要多少个签名)。这里M = 2。此时, CHECKMULTISIG应弹出最终的M 个项目,这些是签名,并查看它们是否有效。
然而,不幸的是,实施中的错误导致CHECKMULTISIG 再弹出一个项目(总共M +1 个)。检查签名时,不考虑额外的项目,因此它对CHECKMULTISIG 本身没有直接影响。但是,必须存在额外的值,因为如果不存在,则当CHECKMULTISIG 尝试弹出空堆栈时,会导致堆栈错误和脚本失败(将交易标记为无效)。因为额外的项目被忽略,它可以是任何东西,但通常使用0。因为这个bug 成为共识规则的一部分,所以现在它必须永远被复制。因此,正确的脚本验证将如下所示:
0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
这样解锁脚本就不是下面的:
<Signature B> <Signature C>
而是:
0 <Signature B> <Signature C>
从现在开始,如果你看到一个multisig 解锁脚本,你应该期望看到一个额外的0开始,其唯一的目的是解决一个bug,意外地成为一个共识规则的解决方法。
即保证例子中有3 个私钥签名对应3 个公钥用于检查多重签名,从而保证脚本不产生bug。
2 、 P2SH(Pay-to-Script-Hash)
P2SH在2012 年被作为一种新型、强大、且能大大简化复杂交易脚本的交易类型而引入。为进一步解释P2SH 的必要性,让我们先看一个实际的例子。
Mohammed,一个迪拜的电子产品进口商。Mohammed的公司采用比特币多重签名作为其公司会计账簿记账要求。多重签名脚本是比特币高级脚本最为常见的运用之一,是一种具有相当大影响力的脚本。针对所有的顾客支付(即应收账款),Mohammed 的公司要求采用多重签名交易。基于多重签名机制,顾客的任何支付都需要至少两个签名才能解锁,一个来自Mohammed,另一个来自其合伙人或拥有备份钥匙的代理人。这样的多重签名机制能为公司治理提供管控便利,同时也能有效防范盗窃、挪用和遗失。最终的脚本非常长:
2<Mohammed’s Public Key> <Partner1 Public Key> <Partner2 Public Key>
<Partner3 Public Key> <Attorney Public Key> 5 OP_C HECKMULTISIG 虽然多重签名十分强大,但其使用起来还是多有不便。基于之前的脚本,Mohammed 必须在客户付款前将该脚本发送给每一位客户,而每一位顾客也必须使用特制的能产生客户交易脚本的比特币钱包软件,每位顾客还得学会如何利用脚本来完成交易。
此外,由于脚本可能包含特别长的公钥,最终的交易脚本可能是最初交易脚本长度的5 倍之多。额外长度的脚本将给客户造成费用负担。最后,一个长的交易脚本将一直记录在所有节点的随机存储器的UTXO 集中,直到该笔资金被使用。采用这种复杂输出脚本使得在实际交易中变得困难重重。
P2SH正是为了解决这一实际难题而被引入的,它旨在使复杂脚本的运用能与直接向比特币地址支付一样简单。在P2SH 支付中,复杂的锁定脚本被电子指纹所取代,电子指纹是指密码学中的哈希值。
当一笔交易试图支付UTXO 时,要解锁支付脚本,它必须含有与哈希相匹配的脚本。P2SH 的含义是,向与该哈希匹配的脚本支付,当输出被支付时,该脚本将在后续呈现。
在P2SH 交易中,锁定脚本由哈希运算后的20 字节的散列值取代,被称为赎回脚本。因为它在系统中是在赎回时出现而不是以锁定脚本模式出现。
下表列示了P2SH 脚本。 ![P2SH 脚本](https://upload-images.jianshu.io/upload_images/17002326-953b3abf43331dde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
从表中可以看出,对于P2SH,详细描述了输出(赎回脚本)的条件的复杂脚本不会在锁定脚本中显示。
相反,只有它的散列值在锁定脚本中呈现,并且兑换脚本本身稍后呈现,作为解锁脚本在输出花费时的一部分。这使得给矿工的交易费用从发送方转移到收款方,复杂的计算工作也从从发送方转移到收款方。
一笔P2SH 交易运用锁定脚本将输出与哈希关联,而不是与前面特别长的脚本所关联。使用的锁定脚本为:
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
正如你所看到的,这个脚本比前面的长脚本简短多了。取代“向该5 个多重签名脚本支付”,这个P2SH 等同于“向含该哈希的脚本支付”。顾客在向Mohammed 公司支付时,只需在其支付指令中纳入这个非常简短的锁定脚本即可。当Mohammed想要花费这笔UTXO 时,附上原始赎回脚本(与UTXO 锁定的哈希)和必要的解锁签名即可,如:
<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG>
两个脚本经由两步实现组合。首先,将赎回脚本与锁定脚本比对以确认其与哈希是否匹配:
<2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG> HASH160 <redeem scriptHash> EQUAL
假如赎回脚本与哈希匹配,解锁脚本会被执行以释放赎回脚本:
<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG
###3、 P2WSH (Pay-to-Witness-Script-Hash)
P2WSH是支持隔离见证类型的P2SH, 我们先介绍隔离见证的概念。
隔离见证是比特币的一种结构性调整,旨在将见证数据部分从一笔交易的scriptSig(解锁脚本)字段移出至一个伴随交易的单独的见证数据结构。客户端请求交易数据时可以选择要或不要该部分伴随的见证数据。
隔离见证由以下BIPs 定义:
BIP-141 隔离见证的主要定义
BIP-143 版本0 见证程序的交易签名验证
BIP-144 对等服务——新的网络消息和序列化格式
BIP-145 隔离见证(对于矿工)的getblocktemplate 升级
l 为什么需要隔离见证?
隔离见证是一个将在多方面产生影响的结构性调整——可扩展性、安全性、经济刺激以及比特币整体性能:
l 交易延展性
将见证移出交易后,用作标识符的交易哈希不在包含见证数据。因为见证数据是交易中唯一可被第三方修改(参见交易识别符章节)的部分,移除它的同时也移除了交易延展性攻击的机会。通过隔离见证,交易变得对任何人(创建者本人除外)都不可变,这极大地提高了许多其它依赖于高级比特币交易架构的协议的
可执行性。比如支付通道、跨连交易和闪电网络。
l 脚本版本管理
在引入隔离见证脚本后,类似于交易和区块都有其版本号,每一个锁定脚本前也都有了一个脚本版本号。脚本版本号的条件允许脚本语言用一种向后兼容的方式(也就是软分叉升级)升级,以引入新的脚本操作数、语法或语义。非破坏性升级脚本语言的能力将极大地加快比特币的创新速度。
l 网络和存储扩展
见证数据通常是交易总体积的重要贡献者。更复杂的脚本通常非常大,比如那些用于多重签名或支付通道的脚本。有时候这些脚本占据了一笔交易的大部分(超过75%)空间。通过将见证数据移出交易,隔离见证提升了比特币的可扩展性。节点能够在验证签名后去除见证数据,或在作简单支付验证时整个忽略它。见证数据不需要被发送至所有节点,也不需要被所有节点存储在硬盘中。
l 签名验证优化
隔离见证升级签名函数(CHECKSIG, CHECKMULTISIG, 等)减少了算法的计算复杂性。引入隔离见证前,用于生成签名的算法需要大量的哈希操作,这些操作与交易的大小成正比。在O(n2)中关于签名操作数量方面,数据哈希计算增加,在所有节点验证签名上引入了大量计算负担。引入隔离见证后,算法更改减少了O(n2)的复杂性。
l 离线签名改进
隔离见证签名包含了在被签名的哈希散列中,每个输入所引用的值(数量)。在此之前,一个离线签名装置,比如硬件钱包,必须在签署交易前验证每一个输入的数量。这通常是通过大量的数据流来完成的,这些数据是关于以前的交易被引用作为输入的。由于该数量现在是已签名的承诺哈希散列的一部分,因此离线装置不需要以前的交易。如果数量不匹配(被一个折中的在线系统误报),则签名无效。
在P2PSH例子中,穆罕默德的公司使用了P2SH 来表达一个多重签名脚本。对穆罕默德的公司的支付被编码成一个这样的锁定脚本:
P2SH 输出脚本示例:
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
这个P2SH 脚本引用了一个赎回脚本( redeem script )的哈希值,该脚本定义了一个“2 of 3”的多重签名需求来使用资金。为了使用该输出,穆罕默德的公司将提供这个赎回脚本(其哈希值与P2SH 输出中的脚本哈希值匹配),以及满足该赎回脚本所需的签名,所有这些都在交易输出中:
显示一个P2SH 输出被使用的解码交易:
[…]“Vin” : [“txid”: “abcdef12345…”,”vout”: 0, “scriptSig”: “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”,]现在,让我们看看整个示例如何升级成为隔离见证。如果穆罕默德的客户使用的是一个隔离见证兼容的钱包,他们就会付款,创建一个“支付给脚本哈希”(P2WSH)输出,看起来就像这样:
P2WSH 输出脚本示例:
0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73
再一次,就像P2WPKH 的例子一样,你可以看到,隔离见证等同脚本要简单得多,省略了各种你在P2SH 脚本中见到的脚本操作符。相反,隔离见证程序仅包含两个推送到堆栈的值:一个见证版本(0)和一个32 字节的赎回脚本的哈希值。
提示当P2SH 使用20 字节的RIPEMD160(SHA256(script)) 哈希值时,P2WSH 见证程序使用了一个32 字节的SHA256(脚本)哈希值。在选择哈希算法时,这一差异是有意为之,被用于通过哈希值长度来区分两种类型的见证程序(P2WPKH andP2WSH),并为P2WSH(128 位而不是80 位P2SH)提供更强的安全性。
穆罕默德的公司可以通过提供正确的赎回脚本和足够的签名满足并花出P2WSH输出。作为见证数据的一部分,赎回脚本和签名被隔离在此支出交易之外。在交易输入内部,穆罕默德的钱包会放置一个空的scriptSig:
显示了一个P2WSH 输出被用隔离见证数据花出的解码交易:
[…]“Vin” : [“txid”: “abcdef12345…”,”vout”: 0, “scriptSig”:“”,][…]“witness”: “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”[…]我们可以看到见证数据被放置到Witness中,而scriptSig为空。
节点能够在验证签名后去除该见证数据,或在作简单支付验证时整个忽略它。见证数据不需要被发送至所有节点,也不需要被所有节点存储在硬盘中。
4、总结
**多重签名**十分强大,但是复杂的锁定脚本数据量很大,但其使用起来还是多有不便。
**P2SH**正是为了解决这一实际难题而被引入的,它旨在使复杂脚本的运用能与直接向比特币地址支付一样简单。在P2SH 支付中,复杂的锁定脚本被哈希值所取代。
**P2SH**的解锁脚本通常是交易总体积的重要贡献者,更复杂的脚本通常非常大,比如那些用于多重签名或支付通道的脚本。
**P2WSH**通过将解锁脚本(见证数据)移出交易提升了比特币的可扩展性,同时提高了区块容量并降低了交易手续费。
5、 参考文献 《精通比特币(第二版)》