6.4 比特币交易脚本和脚本语言

比特币交易脚本语言,称为脚本Scripts,是一种类似Forth的逆波兰表示法的基于堆栈的执行语言。 如果听起来不知所云,可能是你没有学习过20世纪60年代的编程语言,但是没关系,我们将在本章中解释这一切。 放置在UTXO上的锁定脚本和解锁脚本都以此脚本语言编写。 当一笔比特币交易被验证时,每一笔输入中的解锁脚本与其相应的锁定脚本一起执行,以确定这笔交易是否满足支付条件。

脚本是一种非常简单的语言,设计范围有限,可在一些硬件上执行,与嵌入式设备一样简单。 它仅需要做最少的处理,许多现代编程语言可以做的事情它都不能做。 但将它用于验证可编程货币,这就是一个深思熟虑的安全特性。

如今,大多数比特币网络处理的交易都是“Alice付给Bob”的形式,基于一种称为“P2PKH”(Pay-toPublic-Key-Hash)脚本。但是,比特币交易不局限于“支付给Bob的比特币地址”的脚本。事实上,锁定脚本可以被编写成表达各种复杂的情况。为了理解这些更为复杂的脚本,我们必须首先了解交易脚本和脚本语言的基础知识。

本节将会展示比特币交易脚本语言的各个内容;同时,也会演示如何使用它去表达简单的花费条件以及这些条件如何通过解锁脚本来满足。

小贴士: 比特币交易验证并不基于静态模式,而是通过执行脚本语言来实现的。这种语言允许表达几乎无限的各种条件。这也就是比特币拥有“可编程的货币”的力影响力的方式。

6.4.1 图灵非完备性

比特币脚本语言包含许多操作码,但在一个重要方面受到故意的限制——除了条件流程控制之外,没有循环或复杂的流程控制功能。这样就确保了脚本语言是非图灵完备的Turing Complete,这意味着脚本的复杂性有限,执行时间也是可预见的。脚本并不是一种通用语言,这些限制确保该语言不被用于创造无限循环或其它类型的逻辑炸弹,这样的炸弹可以植入在一笔交易中,引起针对比特币网络的“拒绝服务”攻击。记住,每一笔交易都会被网络中的全节点验证,受限制的语言能防止交易验证机制被作为漏洞。

6.4.2 无状态验证

比特币交易脚本语言是无状态的的,在执行脚本之前没有状态,或者在执行脚本之后也没有保存状态。所以执行脚本所需信息都已包含在脚本中。可以预见的是,一个脚本能在任何系统上以相同的方式执行。如果您的系统验证了一个脚本,可以确信的是比特币网络中的任何其他系统也将能验证这个脚本,这意味着一个有效的交易对每个人而言都是有效的,而且每一个人都知道这一点。这种结果的可预见性是比特币系统的一项至关重要的优势。

6.4.3 脚本构建(锁定与解锁)

比特币的交易验证引擎依赖于两类脚本来验证比特币交易:锁定脚本和解锁脚本。

锁定脚本是一个放置在输出上面的花费条件:它指定了今后花费这笔输出必须要满足的条件。 由于锁定脚本往往含有一个公钥或比特币地址(公钥哈希值),在历史上它曾被称为脚本公钥scriptPubKey。在这本书中,我们称之为“锁定脚本”,以承认这种脚本技术的广泛可能性。在大多数比特币应用程序中,我们所称的“锁定脚本”将以scriptPubKey的形式出现在源代码中。您还将看到被称为见证脚本(witness script)的锁定脚本(参见[隔离见证]章节),或者更普遍称为加密难题cryptographic puzzle。 这些术语在不同的抽象层次都代表同样的东西。

解锁脚本是这样一个脚本,它“解决”或满足由锁定脚本放置在输出上的条件,并允许使用输出。解锁脚本是每一笔比特币交易输入的一部分,而且往往含有一个由用户的比特币钱包(通过用户的私钥)生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作脚本签名ScriptSig。在大多数比特币应用的源代码中,ScriptSig便是我们所说的解锁脚本。你也会看到解锁脚本被称作“见证”(witness 参见[隔离见证]章节)。在本书中,我们将它称为“解锁脚本”,用以承认更广泛的锁定脚本需求,因为并非所有解锁脚本都一定包含签名。

每一个比特币验证节点会通过同时执行锁定和解锁脚本来验证一笔交易。每个输入都包含一个解锁脚本,并引用了之前存在的UTXO。 验证软件将复制解锁脚本,检索输入所引用的UTXO,并从该UTXO复制锁定脚本。 然后依次执行解锁脚本和锁定脚本。 如果解锁脚本满足锁定脚本的条件,则输入有效(请参阅【6.4.3.3 解锁脚本和锁定脚本的单独执行】)。 所有输入都是作为交易总体验证的一部分独立验证的。

请注意,UTXO被永久地记录在区块链中,因此是不变的,并且不受在新交易中引用失败的尝试的影响。 只有正确满足输出条件的有效交易才会导致输出被视为“已花费”,然后从未花费交易输出集(UTXO set)中移除。

下图是最常见类型的比特币交易(P2PKH:对公钥哈希的付款)的解锁和锁定脚本的示例,显示在脚本验证之前解除锁定和锁定脚本的连接所产生的组合脚本:

图6-3结合scriptSig和scriptPubKey来评估交易脚本

图6-3 结合scriptSig和scriptPubKey评估交易脚本

6.4.3.1脚本执行堆栈

比特币的脚本语言被称为基于堆栈的语言,因为它使用一种被称为堆栈stack的数据结构。堆栈是一个非常简单的数据结构,可以被视为一叠卡片。堆栈允许两个操作:推送push和弹出pop。 推送就是在堆栈顶部添加一个项目。 弹出从堆栈中删除最顶端的项。堆栈上的操作只作用于堆栈最顶端项目。堆栈数据结构也被称为“后进先出”( Last-In-First-Out)或 “LIFO” 队列。

脚本语言执行脚本时,从左到右处理每个项目。数字(数据常量)被推送入堆栈。操作码(Operators)从堆栈中推送或弹出一个或多个参数,对其进行操作,并可能将结果推送到堆栈上。例如,操作码 OP_ADD 将从堆栈中弹出两个项目,对它们求和,并将求和结果值推送到堆栈上。

条件操作码(Conditional operators)对一个条件进行评估,产生一个 TRUE 或 FALSE 的布尔结果(boolean result)。例如, OP_EQUAL 从堆栈中弹出两个项目,如果它们相等,则推送为 TRUE(由数字1表示),否则推送为 FALSE(由数字0表示)。比特币交易脚本通常包含条件操作码,以便它们可以产生用来表示有效交易的 TRUE 结果。

6.4.3.2一个简单的脚本

现在让我们将学到的关于脚本和堆栈的知识应用到一些简单的例子中。

如图6-4,脚本“ 2 3 OPADD 5 OP_EQUAL ”演示了算术加法操作码 OP_ADD ,该操作码将两个数字相加,然后把结果推送到堆栈, 后面的条件操作符 OP_EQUAL 是验算之前的两数之和是否等于 5 。为了简化起见,前缀OP在演示步骤过程中被省略了。有关可用脚本操作码和函数的更多详细信息,请参见【附录:交易脚本】

图6-4比特币的脚本验证中,执行简单的数学运算

图6-4比特币的脚本验证中,执行简单的数学运算

尽管绝大多数解锁脚本都指向一个公钥哈希值(本质上就是比特币地址),因此需要使用资金的所有权证明,但脚本不必那么复杂。任何解锁和锁定脚本的任何组合如果结果为真(TRUE),则为有效。前面用于脚本语言示例的简单算术计算同样也是一个有效的锁定脚本,该脚本能用于锁定交易输出。

使用部分算术脚本作为锁定脚本的示例:

  1. 3 OP_ADD 5 OP_EQUAL

该脚本能被包含解锁脚本输入的一笔交易所满足:

  1. 2

验证软件将锁定和解锁脚本组合起来,结果脚本是:

  1. 2 3 OP_ADD 5 OP_EQUAL

正如在上图6-4中所看到的一步步例子,当脚本被执行时,结果是OP_TRUE,交易有效。这不仅是一个有效的交易输出锁定脚本,同时产生的UTXO也能被任何具备算术技能知道数字2能够满足脚本的人所花费。

小贴士: 如果堆栈顶部的结果显示为TRUE(标记为{0x01}),即为任何非零值,或脚本执行后堆栈为空,则交易有效。如果堆栈顶部的结果显示为FALSE(0字节空值,标记为{})或脚本执行被操作码明确终止,如OP_VERIFY、 OP_RETURN,或条件终止符如OP_ENDIF,则交易无效。详见【附录:交易脚本】。

以下是一个稍微复杂一点的脚本,它用于计算 2+7-3+1 。注意,当脚本在同一行包含多个操作码时,堆栈允许一个操作码的结果由下一个操作码执行。

  1. 2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL

请试着用纸笔自行演算脚本,当脚本执行完毕时,堆栈中会留下一个TRUE值。

6.4.3.3 解锁脚本和锁定脚本的单独执行

在最初版本的比特币客户端中,解锁脚本和锁定脚本按顺序连起来执行。出于安全因素考虑,在2010年发生了改变,因为存在一个漏洞,允许格式错误的解锁脚本将数据推送到堆栈并损坏锁定脚本。而在当前的方案中,脚本是单独执行的,在两次执行之间传输堆栈,如下所述。

首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本在执行过程中未报错(例如:没有留下“dangling”操作码),则复制主堆栈,并执行锁定脚本。如果从解锁脚本中复制而来的堆栈数据执行锁定脚本的结果为“TRUE”,那么解锁脚本就成功地满足了锁定脚本所设置的条件,因此,该输入是一个能使用该UTXO的有效授权。如果合并脚本执行后的结果是”TRUE“以外的任何结果,输入都是无效的,因为它不能满足UTXO中所设置的使用该笔资金的条件。

6.4.4 P2PKH(Pay-to-Public-Key-Hash)

比特币网络处理的大多数交易都是由“付款至公钥哈希”或P2PKH脚本锁定的输出,这些输出都含有一个锁定脚本,将输入锁定为一个公钥哈希值,即我们常说的比特币地址。由P2PKH脚本锁定的输出可以通过提供一个公钥和由相应私钥创建的数字签名来解锁(花费)。参见【6.5数字签名(ECDSA)】

例如,我们再次回顾一下Alice向Bob咖啡馆支付的案例。Alice向Bob咖啡馆的比特币地址支付0.015比特币,该笔交易的输出内容为以下形式的锁定脚本:

  1. OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

脚本中的 Cafe Public Key Hash 即为咖啡馆的比特币地址,但该地址不是基于Base58Check编码。事实上,大多数比特币地址的公钥哈希public key hash都显示为十六进制码,而不是大家所熟知的以1开头的基于Bsase58Check编码的比特币地址。

上述锁定脚本相应的解锁脚本是:

  1. <Cafe Signature> <Cafe Public Key>

将两个脚本结合起来可以形成如下组合验证脚本:

  1. <Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160
  2. <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

只有当解锁脚本与锁定脚本的设定条件相匹配时,执行组合验证脚本时才会显示结果为真(TRUE)。换句话说,只有当解锁脚本得到了咖啡馆的有效签名,交易执行结果才会被通过(结果为真),该有效签名是从与公钥哈希相匹配的咖啡馆的私钥中所获取的。

图6-5和图6-6(分两部分)显示了组合脚本一步步检验交易有效性的过程。

图6-5评估P2PKH交易的脚本(1/2)

图6-5评估P2PKH交易的脚本(1/2)

图6-6评估P2PKH交易的脚本(2/2)

图6-6评估P2PKH交易的脚本(2/2)