# 第7章 高级交易和脚本

# 7.1 介绍

本章将进行大量更新。如需参考BTC原有介绍,包括P2SH,隔离验证等内容请参考MasterBitcoin2CN (opens new window) 相关章节

在上一章中,我们介绍了比特币交易的基本要素,并且了解了最常见的交易脚本类型,即P2PKH脚本。在本章中,我们将介绍更高级的脚本,以及如何使用它来构建复杂条件的交易。

首先,我们将了解多重签名multisignature脚本。接下来,我们将开启了复杂脚本的整个世界。然后,查看新的脚本操作符。

# 7.2 多重签名

多重签名脚本设置了一个条件,脚本中记录了N个公钥,必须至少提供其中的M个签名才能解锁资金。这也称为M/N方案,其中N是密钥的总数,M是验证必须的签名数。例如,2/3的多重签名是三个公钥被列为潜在签名人,其中至少两个必须用于签名才能创建有效的使用资金的交易。

设置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

上述锁定脚本可被含有一对签名和公钥的解锁脚本满足:

<Signature B> <Signature C>

或者由3个公钥中任意2个对应的私钥产生的签名组合。

这两个脚本一起形成下面的组合验证脚本:

<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

执行时,仅当解锁脚本与锁定脚本设置的条件匹配时,此组合脚本的评估结果才为TRUE。上述例子中设置条件就是:解锁脚本是否含有3个公钥中的任意2个相对应的私钥的有效签名。

CHECKMULTISIG执行中的bug

CHECKMULTISIG的执行中出现了一个bug,需要做一些轻微的变通。 就是当CHECKMULTISIG执行时,它应该消耗堆栈上的M + N + 2个项目作为参数。 然而,由于该bug,CHECKMULTISIG会弹出一个额外的值或超出预期一个值。

我们使用前面的验证示例更详细地看一下:

<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>

从现在开始,如果你看到一个多签解锁脚本,你应该期望开头就看到有一个额外的0,其目的是解决一个bug,却意外地成为共识规则。

# 7.3 AccumulatorMultiSig

【第1章 比特币介绍】 (opens new window)中,我们曾介绍过迪拜的电子产品进口商Mohammed。他的公司账目广泛采用比特币的多重签名功能。多重签名脚本是比特币高级脚本最为常见的一种用途之一,是一种非常强大的功能。Mohammed的公司对所有客户付款,会计术语称为“应收账款”,即AR,都使用多重签名脚本。基于多重签名方案,客户支付的任何款项都会被锁定,必须至少两个签名才能解锁,一个来自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集中,直到该笔资金被使用。所有这些都使得这种复杂锁定脚本在实践中变得困难重重。

# 7.4 数据记录输出(RETURN操作符)

比特币的分布式和时间戳账本,即区块链技术,其潜在用途将大大超越支付领域。许多开发者试图充分发挥交易脚本语言的安全性和弹性优势,将其运用于数字公证服务、股票证书和智能合约等领域。使用比特币的脚本语言来实现这些目的的早期尝试,包括创建交易输出,把数据记录在区块链上,例如,以这样的方式记录文件的数字指纹,任何人可以通过引用该交易来建立该文件特定日期的存在证明。

运用比特币的区块链技术存储与比特币支付不相关数据的做法是一个有争议的话题。许多开发者认为其有滥用的嫌疑,因而试图予以阻止。另一些开发者则将之视为区块链技术强大功能的有力证明,认为应该给予大力支持。那些反对包含非支付数据的人辩称这将导致“区块链膨胀”,增加运行的全节点的磁盘存储成本,承担了区块链不应该携带的数据。而且,此类交易创建了不能花费的UTXO,使用目标比特币地址作为20字节的自由格式字段。因为比特币地址只是被当作数据使用,并不对应于私钥,所以会导致UTXO永远不能用于交易,因而是伪支付。这些交易永远不会被花费,所以永远不会从UTXO集中删除,会导致UTXO数据库的大小永远增加“膨胀”。

在0.9版的Bitcoin Core客户端上,通过采用RETURN 操作符最终实现了妥协。RETURN 允许开发者在交易输出上增加80字节的非支付数据。然后,与伪UTXO不同,RETURN 创造了一种明确的可验证不可消费型输出,此类数据无需存储于UTXO集。RETURN输出被记录在区块链上,它们会消耗磁盘空间,也会导致区块链规模的增加,但它们不存储在UTXO集中,因此也不会使得UTXO内存池膨胀,更不会增加全节点昂贵的内存代价。

RETURN 脚本的样式:

  RETURN <data>

“data”部分被限制为80字节,且多表示为哈希值,如同SHA256算法输出一样(不过值是32字节)。许多应用都在其前面加上前缀以方便识别。例如, 这家网站Proof of Existence (opens new window) 的数字公证服务使用8字节前缀DOCTIOND,16进制ASCII编码为44×4F 43 50 50 4F 4F 46。

请记住,并不存在对应于RETURN 的解锁脚本,也就不能花费RETURN的输出。RETURN的关键点就是锁定的输出不能花费,因此它不需要被保存在UTXO集中供未来消费,RETURN是可验证但是不可花费的。 RETURN 常为一个金额为0比特币的输出, 因为分配到该输出的比特币都会永久消失。假如一笔 RETURN 被作为一笔交易的输入,脚本验证引擎将会阻止验证脚本的执行,将标记交易为无效。执行RETURN本质上导致脚本“返回”FALSE并停止执行。如果你不小心将 RETURN 的输出作为另一笔交易的输入,则该交易是无效的。

一笔标准交易(通过了 isStandard() 函数检验的)只能有一个 RETURN 输出。但是单个RETURN 输出能与任意类型的输出交易进行组合。

Bitcoin Core 0.10版本添加了两个新的命令行选项。 选项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字段实现。

时间锁对于延期交易和将资金锁定到将来某个日期很有用。更重要的是,时间锁将比特币脚本扩展到时间的维度,为复杂的多步骤智能合约打开了大门。

# 7.5.1 交易锁定时间(nLocktime)

比特币从一开始就有一个交易级的时间锁功能。交易锁定时间是交易级设置(交易数据结构中的一个字段),它定义了交易有效,可以在网络上传播或添加到区块链的最早时间。锁定时间也称为nLocktime,是来自于Bitcoin Core代码库中使用的变量名称。在大多数交易中将其设置为零,表示立即传播和执行。如果nLocktime不为零,低于5亿,则将其解释为区块高度,这意味着交易在指定的区块高度之前无效,并且不被传播,也不被包含在区块链中。如果大于或等于5亿,它被解释为Unix时间戳,并且交易在指定时间之前无效。指定未来区块或时间的nLocktime交易必须由发起系统持有,并且只有在有效后才被发送到比特币网络。如果交易在指定的nLocktime之前传输到网络,那么第一个节点就会拒绝该交易,并且不会传播到其他节点。使用nLocktime等同于一张延期支票。

Unix 时间戳是自 Unix 时代以来经过的秒数,从1970年1月1日00:00:00 UTC 开始,减去闰秒。闰秒被忽略,闰秒的 Unix 时间与之前的闰秒相同。每一天都被当作86400秒来处理。由于这种处理,Unix 时间并不是 UTC 的真实表示。

# 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上,成为锁定脚本的一部分,而不是交易的一部分。 这是通过另一种形式的时间锁来实现的,称为检查锁定时间验证(CLTV)。

# 7.5.2 检查锁定时间验证Check Lock Time Verify (CLTV)脚本

提示 nLocktime是交易级别时间锁,而CLTV是基于输出的时间锁。

CLTV不替换nLocktime,而是限制特定的UTXO,使它们只能在大于或等于nLocktime设置的值的将来交易中使用。

CLTV操作码采用一个参数作为输入,为与nLocktime相同格式的数字(区块高度或Unix纪元时间)。如VERIFY后缀所示,CLTV是在结果为false时停止脚本执行的操作码类型。如果结果为TRUE,则继续执行。

为了使用CLTV来锁定输出,必须将其插入到创建输出交易的输出兑换脚本中。例如,如果Alice支付Bob的地址,输出通常会包含如下P2PKH脚本:

DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG

要锁定一段时间,比如说3个月以后,交易将是一个包含兑换脚本的P2SH交易:

<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG

其中<now + months>是从交易开始被挖矿时间起估计3个月的区块高度或时间值:当前块高度+12,960(块)或当前Unix纪元时间+7,760,000(秒)。现在,不要担心CHECKLOCKTIMEVERIFY之后的DROP操作码,下面很快就会解释。

当Bob尝试花费这个UTXO时,他构建了一个引用UTXO作为输入的交易。他在该输入的解锁脚本中使用了他的签名和公钥,并将交易nLocktime设置为等于或大于Alice设置的CHECKLOCKTIMEVERIFY 时间锁。然后,Bob把这笔交易广播到比特币网络上。

Bob的交易评估如下。如果Alice设置的CHECKLOCKTIMEVERIFY参数小于或等于支出交易的nLocktime,脚本执行将继续(就好像执行“无操作”或NOP操作码一样)。否则,脚本执行停止,并且该交易被视为无效。

更确切地说,CHECKLOCKTIMEVERIFY失败并停止执行,标记交易无效(来自:BIP-65):

  1. 堆栈是空的要么
  2. 堆栈中的顶部项小于0;要么
  3. 顶层堆栈项和nLocktime字段的锁定时间类型(高度或者时间戳)不相同;要么
  4. 顶层堆栈项大于交易的nLocktime字段;要么
  5. 输入的nSequence字段为0xffffffff。

注释 CLTV和nLocktime描述时间锁必须使用相同的格式,无论是区块高度还是自Unix纪元以来经过的秒数。 最重要的是,在一起使用时,nLocktime的格式必须与输出中的CLTV格式相匹配,它们必须是区块高度或秒数时间。

执行后,如果满足CLTV的要求,则它前面的时间参数将作为堆栈上的顶部项保留,并且可能需要随DROP一起删除,以便正确执行后续脚本操作码。 经常在脚本中看到CHECKLOCKTIMEVERIFY后面跟着DROP就是这个原因。

通过将nLocktime与CLTV结合使用,【7.5.1.1交易时间锁限制】 (opens new window)中描述的情况就发生了变化。 Alice就不能再花这笔钱了(因为它被Bob的密钥锁定了),Bob也不能在3个月的锁定期终止前花掉它。

通过将时间锁功能直接引入到脚本语言中,CLTV允许我们开发一些非常有趣的复杂脚本。

该标准在BIP-65(CHECKLOCKTIMEVERIFY)中定义(附录BIP-65 (opens new window))。

# 7.5.3 相对时间锁

TODO

# 7.6 nSequence支付通道

相对时间锁可以设置在每个交易输入中,方法是设置每个输入中的nSequence字段。

# 7.6.1 nSequence

nSequence字段的最初设计是想在内存中修改交易(但是从未运用过)。这种情况下,一笔交易的输入包含的nSequence值低于232-1(0xffffffff),就表示该交易尚未“完全完成”。这样的交易将一直保留在内存池中,直到被花费相同输入,具有更高nSequence值的的另一笔交易代替。一旦收到一笔交易,其nSequence值为0xFFFFFFFF,那么它就被视为“完成”并交给矿工挖矿。

nSequence的最初设计从未被正确实现,在不使用时间锁的交易中,nSequence的值通常设置为0xFFFFFFFF。对于具有nLocktime或CHECKLOCKTIMEVERIFY的交易,nSequence值必须设置为小于231,以使时间锁保护有效,如下面所述。

# 7.6.2 使用nSequence和nLockTime的延时支付

TODO

随着BIP-68的激活,新的共识规则适用于输入中的nSequence值小于231的任何交易( 1<<31位未设置为1)。从编程角度,如果没有设置最高位(1<<31位 )为1,意味着它是一个表示“相对锁定时间”的标志。否则( 设置了1<<31位为1),nSequence值就被保留用于其他用途,例如启用CHECKLOCKTIMEVERIFY,nLocktime,Opt-In-Replace-By-Fee以及其他未来的新开发功能。

交易的输入中的nSequence值小于231,就表示具有相对时间锁。这种交易中的输入只有相对锁定时间到期后才能有效。例如,一笔交易的输入的nSequence相对时间锁是30个区块,那么只有当输入引用的UTXO被挖出后再经过30个区块之后,该交易才有效。由于nSequence是每个输入中的字段,因此交易可能包含任何数量的时间锁定输入,这其中的每个输入都必须满足时间限制交易才能有效。交易中的输入可以是时间锁定输入(nSequence <231),也可以是没有相对时间锁定(nSequence> = 231)的输入。

nSequence值以块或秒为单位,但与nLocktime中使用的格式略有不同。类型(type)标志用于区分计数块和计数时间(以秒为单位)。类型标志设置在第23个最低有效位(即值1 << 22)。如果设置了类型标志,则nSequence值将被解释为512秒的倍数。如果未设置类型标志,则nSequence值被解释为区块数。

当将nSequence解释为相对时间锁时,只考虑16个最低有效位。一旦对标志(位32和23)求值,nSequence值通常用16位掩码(例如nSequence或者0x0000FFFF)进行“屏蔽”。

下图显示由BIP-68定义的nSequence值的二进制结构。 BIP-68 definition of nSequence encoding

图7-1 BIP-68 中nSequence编码的定义(出处: BIP-68)

BIP-68规定了基于nSequence值的共识执行的相对时间锁。该标准参见BIP-68, Relative lock-time using consensus-enforced sequence numbers。 (opens new window).

#

#

# 7.7 复杂脚本

# 7.7.1## 流程控制脚本(条件语句 )

比特币脚本的一个更强大的功能是流程控制,也称为条件语句。您可能熟悉各种编程语言中的类似IF…THEN…ELSE的流程控制。比特币条件语句看起来有点不同,但本质上是相同的构造。

基本上,比特币条件操作码允许我们构造一个具有两种解锁方式的兑换脚本,具体取决于对逻辑条件求值的真/假结果。例如,如果x为真,则兑换脚本为A,否则ELSE兑换脚本为B。

此外,比特币条件表达式可以无限期地“嵌套”,这意味着一个条件语句可以包含另外一个条件语句,其中又会包含别的条件语句等等 。比特币脚本流程控制可用于构造非常复杂的脚本,可以有数百甚至数千个可能的执行路径。嵌套没有限制,但共识规则对脚本的最大字节数有限制。

比特币使用IF,ELSE,ENDIF和NOTIF操作码实现流程控制。此外,条件表达式可以包含布尔运算符,如BOOLAND,BOOLOR和NOT。

乍看之下,您可能会发现比特币的流程控制脚本令人困惑。那是因为比特币脚本是一种堆栈语言。正如当1+1表示为1 1 ADD时看起来是“逆向”的,比特币中的流程控制语句也看起来是“逆向”的。

在大多数传统(过程)编程语言中,流程控制如下所示:

大多数编程语言中的流控制伪代码

if (condition):
  code to run when condition is true
else:
  code to run when condition is false
code to run in either case

在基于堆栈的语言中,比如比特币脚本,逻辑条件出现在IF之前,看起来像是“逆向”的,如下所示:

Bitcoin脚本流程控制

 condition
IF
  code to run when condition is true
ELSE
  code to run when condition is false
ENDIF
code to run in either case 

阅读Bitcoin脚本时,请记住,条件语句在IF操作码的前面

# 7.7.2 VERIFY操作码条件语句

比特币脚本中的另一种条件形式是操作码以VERIFY结尾。 VERIFY后缀表示如果评估的条件不为TRUE,脚本的执行将立即终止,并且该交易被视为无效。

与提供可选执行路径的IF子句不同,VERIFY后缀充当保护子句,只有在满足前提条件时才会继续执行。

例如,以下脚本需要Bob的签名和产生特定哈希的原像(密钥)。 这两个条件必须都满足才能解锁:

有EQUALVERIFY保护子句的兑换脚本。

HASH160 <expected hash> EQUALVERIFY <Bob's Pubkey> CHECKSIG

为了兑换成功,Bob必须构建一个解锁脚本,提供有效的原像和签名:

满足上述兑换脚本的解锁脚本

<Bob's Sig> <hash pre-image>

没有原像,Bob无法继续执行到检查其签名的脚本部分。

该脚本可以用IF编写:

具有IF保护语句的兑换脚本

HASH160 <expected hash> EQUAL
IF
   <Bob's Pubkey> CHECKSIG
ENDIF

Bob的解锁脚本是一样的:

满足上述兑换脚本的解锁脚本以

<Bob's Sig> <hash pre-image>

使用IF的脚本与使用VERIFY后缀的操作码作用相同;,它们都可以作为保护语句。 但是,VERIFY的构造更有效率,少用了两个操作码。

那么,我们什么时候使用VERIFY,什么时候使用IF? 如果我们想要做的是附加一个前提条件(保护语句),那么VERIFY后缀更好。 然而,如果有不止一个执行路径(流程控制),那么IF ... ELSE流程控制语句更合适。

提示 诸如EQUAL之类的操作码会将结果(TRUE / FALSE)推送到堆栈上,留下它用于后续操作码的执行。 相比之下,操作码EQUALVERIFY后缀不会在堆栈上留下任何东西。 以VERIFY结尾的操作码都不会将结果留在堆栈上。

# 7.7.3 在脚本中使用流程控制

比特币脚本中流程控制的常见的用途是构建一个提供多个执行路径的兑换脚本,每个执行路径都是兑换UTXO的不同方式。

我们来看一个简单的例子,两个签名人,Alice和Bob,两人中任何一个都可以兑换。 使用多重签名,表示为1/2多重签名脚本。 为了演示,我们先使用IF语句执行相同的操作:

IF
 <Alice's Pubkey> CHECKSIG
ELSE
 <Bob's Pubkey> CHECKSIG
ENDIF

看到这个兑换脚本,你可能会想:“条件在哪里?IF语句前面什么也没有啊!”

条件并不是兑换脚本的一部分。 相反,条件是提供给解锁脚本,允许Alice和Bob“选择”他们想要的执行路径。

Alice用解锁脚本兑换:

<Alice's Sig> 1

最后的1作为条件(TRUE),使IF语句可以执行有Alice签名的第一个兑换路径。

如果是Bob兑换,他必须通过给IF语句赋一个FALSE值才能选择第二个执行路径:

<Bob's Sig> 0

Bob的解锁脚本将0放置在堆栈上,导致IF语句执行第二个(ELSE)脚本,该脚本需要Bob的签名。

由于可以嵌套IF语句,就可以创建一个执行路径的“迷宫”。 解锁脚本可以提供一个“映射”,选择实际执行的路径:

IF
    script A
ELSE
   IF
script B
  ELSE
script C
  ENDIF
ENDIF

在这种情况下,有三个执行路径(脚本A,脚本B和脚本C)。 解锁脚本以一系列TRUE或FALSE值的形式提供路径。 例如要选择路径脚本B,解锁脚本必须以1 0(TRUE,FALSE)结尾。 这些值将被推送到堆栈,第二个值(FALSE)首先停留在堆栈的顶部。 外部IF语句弹出FALSE值并执行第一个ELSE语句。 然后,TRUE值移动到堆栈的顶部,再通过内部(嵌套)的IF来执行,选择B执行路径。

使用这个结构,构造的兑换脚本就可以有数十或数百个执行路径,每个脚本提供了一种不同的方式来兑换UTXO。 花费时,构建一个解锁脚本,通过在每个流程控制点的堆栈上放置相应的TRUE和FALSE值来指引执行路径。

# 7.8 复杂的脚本示例

在本节中,我们将本章中的许多概念合并成一个例子。

我们的例子使用了迪拜一家公司所有者Mohammed的故事,他们主营进出口业务。

在这个例子中,Mohammed希望用灵活的规则建立公司资本账户。他创建的方案需要使用时间锁设置不同级别的授权。 多重签名计划的参与者是Mohammed,和他的两个合伙人Saeed和Zaira,以及他们的公司律师Abdul。三个合伙人根据多数规则作出决定,也就是三人中的两人必须同意才可以。然而,如果他们的密钥出现问题,他们希望他们的律师能够用三个合伙人中任何一人的签名收回资金。最后,如果所有的合伙人一段时间临时都联系不上或不能工作,他们希望律师能够直接接管该帐户。

这是Mohammed设计的实现上述目的脚本(每一行前面的数字是行号):

具有时间锁的可变多重签名

01  IF
02    IF
03      2
04    ELSE
05      <30 days> CHECKSEQUENCEVERIFY DROP
06      <Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
07      1
08    ENDIF
09    <Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
10  ELSE
11    <90 days> CHECKSEQUENCEVERIFY DROP
12    <Abdul the Lawyer's Pubkey> CHECKSIG
13  ENDIF

Mohammed的脚本使用嵌套的IF ... ELSE流程控制语句实现三个执行路径。

在第一个执行路径中,该脚本是三个合伙人的简单的2/3多重签名。该执行路径由第3行和第9行组成。第3行将多重签名的法定人数设置为2(2/3)。 通过在解锁脚本的末尾设置TRUE TRUE来选择该脚本:

第一个执行路径的解锁脚本(2/3 多签)

0 <Mohammed's Sig> <Zaira's Sig> TRUE TRUE

提示 此解锁脚本开头的0是因为CHECKMULTISIG中的一个错误,会从堆栈中多弹出一个额外的值。 CHECKMULTISIG会忽略这个额外的值,但它必须存在,否则脚本执行将失败。 推送0(通常)是解决bug的方法,如【7.2 多重签名CHECKMULTISIG执行中的bug】 (opens new window)所述。

第二个执行路径只能在UTXO创建30天后才能使用。 此时,它需要Abdul(律师)和三个合伙人之一(1/3)的签名。 这是通过第7行实现的,该行将多签的法定人数设置为1。要选择此执行路径,解锁脚本将以FALSE TRUE结束:

第二个执行路径的解锁脚本(律师 + 1/3)

0 <Saeed's Sig> <Abdul's Sig> FALSE TRUE

提示 为什么先FALSE后TRUE? 反了吗?是这两个值被推到堆栈的顺序,先推FALSE,后推 TRUE。 因此,第一个IF操作码首先弹出的是TRUE。

最后,第三个执行路径允许律师单独花费资金,但只能在90天之后。 要选择此执行路径,解锁脚本必须以FALSE结束:

第三个执行路径的解锁脚本(仅适用于律师)

<Abdul's Sig> FALSE

在纸上运行脚本来查看它在堆栈上的行为。

阅读这个例子还需要考虑几件事情。 看看你能不能找到答案?

  • 为什么律师不能通过在解锁脚本上选择FALSE,随时兑换第三条执行路径?
  • 在UTXO挖出后,5天、35天和105天分别可以使用多少条执行路径?
  • 如果律师失去密钥,资金是否丢失? 如果91天过去了,你的答案是否会改变?
  • 合伙人如何每隔29天或89天“重置”时钟,以防止律师获取资金?
  • 为什么这个脚本中的一些CHECKSIG操作码有VERIFY后缀,而其他的没有?

#