基于 Java 语言构建区块链(七)—— 交易脚本(智能合约)
上一篇 文章我们引入 UTXOset 和 Merkle Tree 对交易流程做了些许优化,本篇文章我们将介绍比特币另一个更加重要的机制 —— 交易脚本。
在介绍 UTXO 的文章 中,我们已经了解到比特币的交易输出由锁定脚本锁定,它只能被交易输出所被指向的交易输入中的解锁脚本所解锁,今天让我们来详细讨论一下它们的实现机制。
交易详情
如今,大多数比特币网络处理的交易是以 “Alice 付给 Bob” 的形式存在的。同时,它们是以一种称为 “P2PKH”(Pay-to-Public-Key-Hash)脚本为基础的。然而,通过使用脚本来锁定输出和解锁输入意味着通过使用编程语言,比特币交易可以包含无限数量的条件。当然,比特币交易并不限于 “Alice 付给 Bob” 的形式和模式。”
这只是这个脚本语言可以表达的可能性的冰山一角。稍后, 我们将会全面展示比特币交易脚本语言的各个组成部分;同时,我们也会演示如何使用它去表达复杂的使用条件以及解锁脚本如何去满足这些花费条件。
比特币交易验证并不基于一个不变的模式,而是通过运行脚本语言来实现。这种语言可以表达出多到数不尽的条件变种。这也是比特币作为一种 “可编程的货币” 所拥有的权力。
我们以《精通比特币(第二版)》第二章节 中 Alice 向 Bob 购买咖啡为例,点击查看该笔 交易详情
交易输入:0.1000 BTC
手续费用:0.0005 BTC
支付费用:0.0150 BTC
找 零: 0.0845 BTC
该笔交易的数据如下:
1 | { |
交易输入
1 | "vin": [ |
它所包含的信息:
- 交易 ID。包含了它所指向的 UTXO 的交易的 Hash 值。
- UTXO 下标。定义了它所指向的 UTXO 在上一笔交易中交易输出数组的位置 (下标值)。
- 签名。用于满足它所指向的 UTXO 上所设定的花费条件。
交易输出
1 | "vout": [ |
它所包含的信息:
比特币的数量。单位:satoshis(聪)
比特币地址。交易输出所绑定的地址。
锁定脚本。定义了花费这笔交易输出所需要满足的限制条件。其中包含了一些字符串,例如:
OP_DUP
、OP_HASH160
、OP_EQUALVERIFY
、OP_CHECKSIG
,这些叫操作码,后面会做介绍。
那么,交易输入中的script
是如何满足交易输出中 script_string
的限制条件的呢?接下来,我们来一起看下比特币的交易脚本是如何工作的。首先,我们来了解一下比特币所用到脚本语言的特性以及它的工作原理。
比特币脚本语言
特性
脚本是一种类似 Forth 的基于堆栈的逆波兰表示法的图灵非完备语言。接下来,让我们逐个解释一下:
图灵非完备(Turing Incomplete)
什么是图灵完备
在可计算性理论里,如果一系列操作数据的规则(如指令集、编程语言、细胞自动机)可以用来模拟单带图灵机,那么它是图灵完备的。这个词源于引入图灵机概念的数学家艾倫・图灵。
虽然图灵机会受到储存能力的物理限制,图灵完全性通常指「具有无限存储能力的通用物理机器或编程语言」。
来源:维基百科
图灵非完备语言将会有有限的功能,不能进行跳转或 / 和循环。因此它们不能进入无线循环。图灵完备就意味着,在给定的计算资源和内存下,图灵完备程序,能够解决任何问题。Solidity 就是其中一种图灵完备语言。
为什么比特币脚本是图灵非完备的
因为没有必要。比特币脚本没有必要做到像以太坊智能合约那样复杂。事实上,如果一个脚本是图灵完备的,它会给恶意的人以机会去随意创造复杂的交易,这将会吃掉比特币网络的哈希率并降低整个系统的性能。
哈希率是比特币网络的处理能力的衡量单位。为了安全,比特币网络必须进行高强度的数学运算。网络的哈希率达到 10TH/s,意味着这个网络每秒能处理 10 亿次计算。
逆波兰表示法 (Reverse Polish notation)
逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬・武卡谢维奇 1920 年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。
来源:维基百科
例如:
解释 | 常规表示 | 逆波兰表示法 |
---|---|---|
三加四 | 3 + 4 | 34+ |
先 3 减去 4,再加上 5 | 3 - 4 + 5 | 3 4 - 5 + |
先 3 减去 4,再乘以 5 | (3 - 4)*5 | 3 4 - 5 * |
基于堆栈
这是一种具有 LIFO(Last In First Out)特性的数据结构,熟悉数据结构的应该非常清楚,这里不多做介绍。
想深入了解的朋友,可以查看我的另一篇文章
https://wangwei.one/posts/java-data-structures-and-algorithms-stack.html
类 Forth 脚本语言
比特币脚本恰好类似于编程语言 “Forth”,它也恰好是基于堆栈的一种编程语言。
查看:Forth 编程语言
工作原理
比特币的脚本语言非常简单,这种语言的代码无非就是一系列数据和操作符。脚本语言通过从左至右地处理每个项目的方式执行脚本。数字(常数)被推送至堆栈,操作符向堆栈推送(或移除)一个或多个参数,对它们进行处理,甚至可能会向堆栈推送一个结果。例如,OP_ADD 将从堆栈移除两个项目,将二者相加,然后再将二者相加之和推送到堆栈。
条件操作符评估一项条件,产生一个真或假的结果。例如,OP_EQUAL 从堆栈移除两个项目,假如二者相等则推送真(表示为 1),假如二者不等则推送为假(表示为 0)。比特币交易脚本常含条件操作符,当一笔交易有效时,就会产生 True 的结果。
我们以一个简单的脚本来进行演示:
1 | 2 3 OP_ADD 5 OP_EQUAL |
从左至右,依次执行,过程如下:
弄明白这个过程之后,你会发现其中所蕴含的堆栈特性以及逆波兰表示法特性。接下来,我们来看下比特币脚本的锁定与解锁逻辑。
锁定与解锁逻辑
比特币的交易验证引擎依赖于两类脚本来验证比特币交易:一个锁定脚本和一个解锁脚本。
锁定脚本是放置在输出上的消费条件:它指定将来要花费输出必须满足的条件。锁定脚本常被称为 scriptPubKey
,因为它通常包含公钥或比特币地址(公钥哈希)。
解锁脚本是通过 “解决” 或满足锁定脚本上的交易输出条件并允许交易输出花费的脚本。 解锁脚本是每个交易输入的一部分。大多数情况下,它包含了由用户私钥所产生的数字签名。解锁脚本常被称为 scriptSig
,因为它通常包含数字签名。
每当要验证一笔交易的有效性时,解锁脚本和锁定脚本会随着堆栈的传递被分别执行。首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本在执行过程中未报错 (例如:没有 “悬挂” 操作码),则复制 主堆栈 (而不是备用堆栈),并执行锁定脚本。如果从解锁脚本中复制而来的堆栈数据执行锁定脚本的结果 为 “TRUE”,那么解锁脚本就成功地满足了锁定脚本所设置的条件,因此,该输入是一个能使用该 UTXO 的有效授 权。如果在合并脚本后的结果不是”TRUE“以外的任何结果,输入都是无效的,因为它不能满足 UTXO 中所设置的使 用该笔资金的条件。
下图所示是最为常见类型的比特币交易(向公钥哈希进行一笔支付)的解锁和锁定脚本样本,该样本展示了在脚本验证之前将解锁脚本和锁定脚本串联而成的组合脚本。
这是比特币脚本中使用最为常见的一种形式,名叫 Pay to Public Key Hash (P2PKH)。基于前面 2 + 3 = 5
的验证过程,我们可以得到 P2PKH 脚本在堆栈引擎中的验证过程如下所示:
好了, 到此为止,你已经对比特币的交易脚本以及它的工作原理已经有了一个非常清楚的理解与认识。