比特币脚本的一个更强大的功能是流量控制,也称为条件条款。您可能熟悉使用构造IF … THEN … ELSE的各种编程语言中的流控制。比特币条件条款看起来有点不同,但是基本上是相同的结构。
在基本层面上,比特币条件操作码允许我们构建一个具有两种解锁方式的赎回脚本,这取决于评估逻辑条件的TRUE / FALSE结果。例如,如果x为TRUE,则赎回脚本为A,ELSE赎回脚本为B. 此外,比特币条件表达式可以无限期地“嵌套”,这意味着这个条件语句可以包含其中的另外一个条件,另外一个条件其中包含别的条件等等 。Bitcoin脚本流控制可用于构造非常复杂的脚本,具有数百甚至数千个可能的执行路径。嵌套没有限制,但协商一致的规则对脚本的最大大小(以字节为单位)施加限制。
比特币使用IF,ELSE,ENDIF和NOTIF操作码实现流量控制。此外,条件表达式可以包含布尔运算符,如BOOLAND,BOOLOR和NOT。
乍看之下,您可能会发现比特币的流量控制脚本令人困惑。那是因为比特币脚本是一种堆栈语言。同样的方式,当1+1看起来“向后”当表示为1 1 ADD时,比特币中的流控制条款也看起来“向后”(backward)。 在大多数传统(程序)编程语言中,流控制如下所示: 大多数编程语言中的流控制伪代码
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.6.1带有VERIFY操作码的条件子句
比特币脚本中的另一种条件是任何以VERIFY结尾的操作码。 VERIFY后缀表示如果评估的条件不为TRUE,脚本的执行将立即终止,并且该交易被视为无效。 与提供替代执行路径的IF子句不同,VERIFY后缀充当保护子句,只有在满足前提条件的情况下才会继续。
例如,以下脚本需要Bob的签名和产生特定哈希的前图像(秘密地)。
解锁时必须满足这两个条件:
1)具有EQUALVERIFY保护子句的赎回脚本。
HASH160 <expected hash> EQUALVERIFY <Bob's Pubkey> CHECKSIG
为了兑现这一点,Bob必须构建一个解锁脚本,提供有效的前图像和签名:
2)一个解锁脚本以满足上述赎回脚本。
<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? 如果我们想要做的是附加一个前提条件(保护条款),那么验证是更好的。 然而,如果我们想要有多个执行路径(流控制),那么我们需要一个IF … ELSE流控制子句。
提示 诸如EQUAL之类的操作码会将结果(TRUE / FALSE)推送到堆栈上,留下它用于后续操作码的评估。 相比之下,操作码EQUALVERIFY后缀不会在堆栈上留下任何东西。 在VERIFY中结束的操作码不会将结果留在堆栈上。
7.6.2在脚本中使用流控制
比特币脚本中流量控制的一个非常常见的用途是构建一个提供多个执行路径的赎回脚本,每个脚本都有一种不同的赎回UTXO的方式。
我们来看一个简单的例子,我们有两个签名人,Alice和Bob,两人中任何一个都可以兑换。 使用多重签名,这将被表示为1-of-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值来导航执行路径。