《精通比特币第二版》中文版第七章高级交易和脚本1/2
精力有限,后期修订以github为主,建议大家移步github链接阅读更新版本,感谢理解:
https://github.com/tianmingyun/MasterBitcoin2CN
本文5900字。
7.1介绍
在上一章中,我们介绍了比特币交易的基本元素,并且查看了最常见的交易脚本类型,即P2PKH脚本。在本章中,我们将介绍更高级的脚本,以及如何使用它来构建具有复杂条件的交易。
首先,我们将看看多重签名脚本。接下来,我们将检查第二个最常见的交易脚本Pay-to-Script-Hash,它打开了一个复杂脚本的整个世界。然后,我们将检查新的脚本操作符,通过时间锁定将比特币添加时间维度。
7.2多重签名
多重签名脚本设置了一个条件,其中N个公钥被记录在脚本中,并且至少有M个必须提供签名来解锁资金。这也称为M-N方案,其中N是密钥的总数,M是验证所需的签名的数量。例如,2/3的多重签名是三个公钥被列为潜在签名人,其中至少有两个必须用于为有效的交易创建签名才能花费资金。此时,标准多重签名脚本限制在最多15个列出的公钥,这意味着您可以从1到1到15之间的多重签名或该范围内的任何组合执行任何操作。在本书发布之前,限制15个已列出的键可能会被解除,因此请检查isStandard()函数以查看当前网络接受的内容。
设置M-N多重签名条件的锁定脚本的一般形式是:
M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG
其中N是列出的公钥的总数,M是花费输出所需的签名的数量。
设置2到3多重签名条件的锁定脚本如下所示:
2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
上述锁定脚本可由含有签名和公钥的脚本予以解锁:
<Signature B> <Signature C>
或者由3个存档公钥中的任意2个相一致的私钥签名组合予以解锁。
两个脚本组合将形成一个验证脚本:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
当执行时,只有当未解锁版脚本与解锁脚本设置条件相匹配时,组合脚本才显示得到结果为真(Ture)。上述例子中相 应的设置条件即为未解锁脚本是否含有与3个公钥中的任意2个相一致的私钥的有效签名。
CHECKMULTISIG执行中的bug
CHECKMULTISIG的执行中有一个bug,需要一些轻微的解决方法。 当CHECKMULTISIG执行时,它应该消耗堆栈上的M + N + 2个项目作为参数。 然而,由于该错误,CHECKMULTISIG将弹出(pop)超出预期的额外值或一个值。
我们来看看这个更详细的使用以前的验证示例:
<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
首先,CHECKMULTISIG弹出最上面的项目,这是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,意外地成为一个共识规则的解决方法。
7.3 P2SH(Pay-to-Script-Hash)
P2SH在2012年被作为一种新型、强大、且能大大简化复杂交易脚本的交易类型而引入。为进一步解释P2SH的必要 性,让我们先看一个实际的例子。
在第1章中,我们曾介绍过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交易中,锁定脚本由哈希取代,哈希指代的是赎回脚本。因为它在系统中是在赎回时出现而不是以锁定脚本模 式出现。表7-1列示了非P2SH脚本,表7-2列示了P2SH脚本。
表7-1不含P2SH的复杂脚本
表7-1 不含P2SH的复杂脚本 表7-2 P2SH复杂脚本
从表中可以看出,对于P2SH,详细描述了输出(赎回脚本)的条件的复杂脚本不会在锁定脚本中显示。 相反,只有它的散列在锁定脚本中,并且兑换脚本本身稍后呈现,作为解锁脚本在输出花费时的一部分。 这会将收费和复杂性的负担从发送方转移到交易的收款人(花费)。
【译者注:本段原文描述如下:As you can see from the tables, with P2SH the complex script that details the conditions for spending the output (redeem script) is not presented in the locking script. Instead, only a hash of it is in the locking script and the redeem script itself is presented later, as part of the unlocking script when the output is spent. This shifts the burden in fees and complexity from the sender to the recipient (spender) of the transaction.】
让我们再看下Mohammed公司的例子,复杂的多重签名脚本和相应的P2SH脚本。
首先,Mohammed公司对所有顾客订单采用多重签名脚本:
2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 CHECKMULTISIG
如果占位符由实际的公钥(以04开头的520字节)替代,你将会看到的脚本会非常地长:
2
04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C58704A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D99779650421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800 5 CHECKMULTISIG
整个脚本都可由仅为20个字节的密码哈希所取代,首先采用SH256哈希算法,随后对其运用RIPEMD160算法。20字节 的脚本为:
54c557e07dde5bb6cb791c7a540e0a4796f5e97
一笔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
本章中描述的几乎所有脚本只能以P2SH脚本来实现。 它们不能直接用在UTXO的锁定脚本中。
7.3.1 P2SH地址
P2SH的另一重要特征是它能将脚本哈希编译为一个地址(其定义请见BIP0013)。P2SH地址是基于Base58编码的一 个含有20个字节哈希的脚本,就像比特币地址是基于Base58编码的一个含有20个字节的公钥。由于P2SH地址采用5作为前缀,这导致基于Base58编码的地址以“3”开头。例如,Mohammed的脚本,基于Base58编码下的P2SH地址变 为“39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw”。此时,Mohammed可以将该地址发送给他的客户,这些客户可以 采用任何的比特币钱包实现简单支付,就像这是一个比特币地址一样。以“3”为前缀给予客户这是一种特殊类型的地址的 暗示,该地址与一个脚本相对应而非与一个公钥相对应,但是它的效果与比特币地址支付别无二致。
P2SH地址隐藏了所有的复杂性,因此,运用其进行支付的人将不会看到脚本。
7.3.2 P2SH的优点
与直接使用复杂脚本以锁定输出的方式相比,P2SH具有以下特点:
在交易输出中,复杂脚本由简短电子指纹取代,使得交易代码变短。
脚本能被编译为地址,支付指令的发出者和支付者的比特币钱包不需要复杂工序就可以执行P2SH。
P2SH将构建脚本的重担转移至接收方,而非发送方。 P2SH将长脚本数据存储的负担从输出方(存储于UTXO集,影响内存)转移至输入方(仅存储于区块链)。
P2SH将长脚本数据存储的重担从当前(支付时)转移至未来(花费时)。
P2SH将长脚本的交易费成本从发送方转移至接收方,接收方在使用该笔资金时必须含有赎回脚本。
7.3.3赎回脚本和标准确认
在0.9.2版比特币核心客户端之前,P2SH仅限于标准比特币交易脚本类型(即通过标准函数检验的脚本)。这也意味着 使用该笔资金的交易中的赎回脚本只能是标准化的P2PK、P2PKH或者多重签名,而非 RETURN 和P2SH。
作为0.9.2版的比特币核心客户端,P2SH交易能包含任意有效的脚本,这使得P2SH标准更为灵活,也可以用于多种新 的或复杂类型的交易。
请记住不能将P2SH植入P2SH赎回脚本,因为P2SH不能自循环。虽然在技术上可以将RETURN包含在赎回脚本中,但由于规则中没有策略阻止您执行此操作,因此在验证期间执行RETURN将导致交易被标记为无效,因此这是不实际的。
需要注意的是,因为赎回脚本只有在你试图发送一个P2SH输出时才会在比特币网络中出现,假如你将输出与一个无效 的交易哈希锁定,则它将会被忽略。该UTXO将会被成功锁定,但是你将不能使用该笔资金,因为交易中含有赎回脚本,该脚本因是一个无效的脚本而不能被接受。这样的处理机制也衍生出一个风险,你能将比特币锁定在一个未来不能被花费的P2SH中。因为比特币网络本身会接受这一P2SH,即便它与无效的赎回脚本所对应(因为该赎回脚本哈希没有对其所表征的脚本给出指令)。
注意 P2SH锁定脚本包含一个赎回脚本哈希,该脚本对于赎回脚本本身未提供任何描述。P2SH交易即便在赎回脚本无效的情 况下也会被认为有效。你可能会偶然地将比特币以这样一种未来不能被花费的方式予以锁定。
7.4 数据记录输出(RETURN操作符)
比特币的分发和时间戳账户机制(也即区块链),其潜在运用将大大超越支付领域。许多开发者试图充分发挥交易脚本 语言的安全性和可恢复性优势,将其运用于电子公证服务、证券认证和智能协议等领域。比特币脚本语言的早期运用主 要包括在区块链上创造出交易输出。例如,为文件记录电子指纹,则任何人都可以通过该机制在特定的日期建立关于文 档存在性的证明。
运用比特币区块链存储与比特币支付不相关数据的做法是一个有争议的话题。许多开发者认为其有滥用的嫌疑,因而试 图予以阻止。另一些开发者则将之视为区块链技术强大功能的有力证明,从而试图给予大力支持。那些反对非支付相关 应用的开发者认为这样做将引致“区块链膨胀”,因为所有的区块链节点都将以消耗磁盘存储空间为成本,负担存储此类 数据的任务。更为严重的是,此类交易仅将比特币地址当作自由组合的20个字节而使用,进而会产生不能用于交易的 UTXO。因为比特币地址只是被当作数据使用,并不与私钥相匹配,所以会导致UTXO不能被用于交易,因而是一种伪支付行为。因此,这些交易永远不会被花费,所以永远不会从UTXO集中删除,并导致UTXO数据库的大小永远增加或“膨胀”。
在0.9版的比特币核心客户端上,通过采用Return操作符最终实现了妥协。Return允许开发者在交易输出上增加80字节的非交易数据。然后,与伪交易型的UTXO不同,Return创造了一种明确的可复查的非交易型输出,此类数据无需存储于UTXO集。Return输出被记录在区块链上,它们会消耗磁盘空间,也会导致区块链规模的增加,但 它们不存储在UTXO集中,因此也不会使得UTXO内存膨胀,更不会以消耗代价高昂的内存为代价使全节点都不堪重负。
RETURN 脚本的样式:
RETURN <data>
“data”部分被限制为80字节,且多以哈希方式呈现,如32字节的SHA256算法输出。许多应用都在其前面加上前缀以辅 助认定。例如,电子公正服务的证明材料采用8个字节的前缀“DOCPROOF”,在十六进制算法中,相应的ASCII码为 44 4f 43 50 52 4f 4f 46。
请记住 RETURN 不涉及可用于支付的解锁脚本的特点, RETURN 不能使用其输出中所锁定的资金,因此它也就没有必要记录在蕴含潜在成本的UTXO集中,所以 RETURN 实际是没有成本的。 RETURN 常为一个金额为0的比特币输出, 因为任何与该输出相对应的比特币都会永久消失。假如一笔 RETURN 被作为一笔交易的输入,脚本验证引擎将会阻止验证脚本的执行,将标记交易为无效。如果你碰巧将 RETURN 的输出作为另一笔交易的输入,则该交易是无效的。
一笔标准交易(通过了 isStandard() 函数检验的)只能有一个 RETURN 输出。但是单个RETURN 输出能与任意类型的 输出交易进行组合。
Bitcoin Core中添加了两个新版本的命令行选项。 选项datacarrier控制RETURN交易的中继和挖掘,默认设置为“1”以允许它们。 选项datacarriersize采用一个数字参数,指定RETURN脚本的最大大小(以字节为单位),默认为83字节,允许最多80个字节的RETURN数据加上一个字节的RETURN操作码和两个字节的PUSHDATA操作码。
注意最初提出了RETURN,限制为80字节,但是当功能被释放时,限制被减少到40字节。 2015年2月,在Bitcoin Core的0.10版本中,限制提高到80字节。 节点可以选择不中继或重新启动RETURN,或者只能中继和挖掘包含少于80字节数据的RETURN。
7.5时间锁(Timelocks)
时间锁是只允许在一段时间后才允许支出的交易。比特币从一开始就有一个交易级的时间锁定功能。它由交易中的nLocktime字段实现。在2015年底和2016年中期推出了两个新的时间锁定功能,提供UTXO级别的时间锁定功能。这些是CHECKLOCKTIMEVERIFY和CHECKSEQUENCEVERIFY。
时间锁对于后期交易和将资金锁定到将来的日期很有用。更重要的是,时间锁将比特币脚本扩展到时间的维度,为复杂的多步智能合同打开了大门。
7.5.1交易锁定时间(nLocktime)
比特币从一开始就有一个交易级的时间锁功能。交易锁定时间是交易级设置(交易数据结构中的一个字段),它定义交易有效的最早时间,并且可以在网络上中继或添加到块链中。锁定时间也称为nLocktime,来自于Bitcoin Core代码库中使用的变量名称。在大多数交易中将其设置为零,以指示即时传播和执行。如果nLocktime不为零,低于5亿,则将其解释为块高度,这意味着交易无效,并且在指定的块高度之前未被中继或包含在块链中。如果超过5亿,它被解释为Unix纪元时间戳(自Jan-1-1970之后的秒数),并且交易在指定时间之前无效。指定未来块或时间的nLocktime的交易必须由始发系统持有,并且只有在有效后才被发送到比特币网络。如果交易在指定的nLocktime之前传输到网络,那么第一个节点就会拒绝该交易,并且不会被中继到其他节点。使用nLocktime等同于一张延期支票。
7.5.1.1交易锁定时间限制
nLocktime就是一个限制,虽然它可以在将来花费,但是到现在为止,它并不能使用它们。我们来解释一下,下面的例子。
Alice签署了一笔交易,支付给Bob的地址,并将交易nLocktime设定为3个月。Alice把这笔交易发送给Bob。有了这个交易,Alice和Bob知道:
在3个月过去之前,Bob不能完成交易进行变现。
Bob可以在3个月后接受交易。
然而:
Alice可以创建另一个交易,双重花费相同的输入,而不需要锁定时间。 因此,Alice可以在3个月过去之前花费相同的UTXO。
Bob不能保证Alice不会这样做。
了解交易nLocktime的限制很重要。 唯一的保证是Bob在3个月过去之前无法兑换它。 不能保证Bob得到资金。 为了实现这样的保证,时间限制必须放在UTXO本身上,并成为锁定脚本的一部分,而不是交易。 这是通过下一种形式的时间锁定来实现的,称为检查锁定时间验证。
本章未完。待续。
参考内容:
1、本文部分内容摘自《精通比特币》第一版中译本,特此说明并致谢。
我正在发起“和我一起阅读《精通比特币第二版》”活动。
我把这作为一次认知学习法的实践。
我希望将认知学习法与《精通比特币第二版》的研读结合起来,尝试总结出一套可行的区块链知识技能快速入门的方法。
这也将成为我们大家一起合作编写的一本书《认知学习比特币》的雏形(所有有价值的讨论都将成为这本书的素材)。
而且我更希望通过本次认知学习方法论的实践以及迭代,将这种经过实践的学习方法迁移到更多领域的学习中。
所以我们会有第二季,第三季......
欢迎扫描二维码加入。
欲知详情请扫描二维码