本节书摘来自华章计算机《区块链开发指南》一书中的第1章,第1.4节,作者:申屠青春 主编 宋 波 张 鹏 汪晓明 季宙栋 左川民 编著更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1.4 脚本系统
比特币在交易中使用脚本系统,与FORTH(一种编译语言)一样,脚本是简单的、基于堆栈的,并且是从左向右处理的,它特意设计成非图灵完整的形式,没有LOOP语句。
一个脚本本质上是众多指令的列表,这些指令记录在每个交易中,若交易的接收者想花掉发送给他的比特币,那么这些指令就是描述接收者是如何获得这些比特币的。一个典型的发送比特币到目标地址D的脚本,要求接收者提供以下两个条件,才能花掉发给他的比特币:
1)一个公钥,当进行散列生成比特币地址时,生成的地址是嵌入在脚本中的目标地址D。
2)一个签名,用于证明接收者保存了与上述公钥相对应的私钥。
脚本可以灵活地改变花掉比特币的条件,举个例子,脚本系统可能会同时要求两个私钥或几个私钥,或者无需任何私钥等。
如果联合脚本中未导致失败并且堆栈顶元素为真(非零),则表明交易有效。原先发送币的一方,控制脚本运行,以便比特币在下一个交易中使用。想花掉币的另一方必须把以前记录的运行为真的脚本,放到输入区。
堆栈保存着字节向量,当用作数字时,字节向量被解释成小尾序的变长整数,最重要的位用于决定整数的正负号。比如,0x81代表-1,0x80则是0的另外一种表示方式(称之为负0)。正0用一个NULL长度向量表示。字节向量可以解析为布尔值,这里False表示为0,True表示为非0。
1.4.1 脚本特点
表1-4至表1-12是脚本的所有关键字列表(命令/函数),一些更复杂的操作码已被禁用,不再考虑,因为钱包客户在这些操作码的程序实现上可能有Bug,如果某个交易使用了这些操作码,那么其将会使比特币块链产生分叉。我们提到脚本的时候,通常省略了这些把数字压入堆栈的关键字。
算术逻辑的输入仅限于有符号32位长整数,但输出有可能会溢出。如果任何命令的输入值其长度超过4字节,那么脚本必须中止和失败返回。如果在交易中出现了标记为已禁用的操作码,也必须终止和失败返回。
1.4.2 脚本运行过程
在这里,我们先讨论单输入单输出的比特币交易,因为这样描述起来更为方便,而且不会影响对脚本的理解,以下面的一个交易Hash值为例:
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
这是一个单输入单输出交易,下面来看下我们要关注的数据。
Hash:
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
对于输入交易,需要关注如下值。
前导输入的Hash:
437b95ae15f87c7a8ab4f51db5d3c877b972ef92f26fbc6d3c4663d1bc750149
输```
入脚本scriptSig:
3045022100efe12e2584bbd346bccfe67fd50a54191e4f45f945e3853658284358d9c062ad02200121e00b6297c0874650d00b786971f5b4601e32b3f81afa9f9f8108e93c752201038b29d4fbbd12619d45c84c83cb4330337ab1b1a3737250f29cec679d7551148a
对于输出交易,需要关注如下值。
转账金额:0.05010000 btc
输出脚本scriptPubKey:
OP_DUP OP_HASH160 be10f0a78f5ac63e8746f7f2e62a5663eed05788 OP_EQUALVERIFY OP_CHECKSIG
假设Alice是转账发送者,Bob是接受者。那么输入交易表明了Alice要动用的比特币的来源,交易输出表明了Alice要转账的数额和转账对象——Bob。那么,有读者可能会问,数据中的输入脚本和输出脚本是不是就是题和解?答对了一半!
在Bitcoin Wiki中提到:原先发送币的一方,控制脚本运行,以便比特币在下一个交易中使用。想花掉币的另一方必须把以前记录的运行为真的脚本,放到输入区。
换句话说,在一个交易中,输出脚本是数学题,输入脚本是题解,但不是这道数学题的题解。我开始看Wiki的时候,在这里遇到了一些障碍,没法理解输入脚本和输出脚本的联系。但是在考虑到交易间的关系之后,就明白了。
假设有这么一系列交易,如图1-7所示。
图![image](https://yqfile.alicdn.com/0b71bb6f7f88c33b4bdc1f475be8ce7b5e11243b.png)
1-7 三对交易示范图
那么,这一系列交易具有如下特征。
三个交易都是单输入单输出交易。
每个输入交易输出交易中,都包含对应的脚本。
交易a为Alice转账给Bob;交易b为Bob转账给Carol;交易c为Carol转账给Dave。
当前交易的输入都引用前一个交易的输出,如交易b的输入就是引用交易a的输出。
按照之前的说法,交易a中的输出脚本就是Alice为Bob出的数学题。那么,Bob想要引用交易a输出交易的比特币,就要解开这道数学题。题解是在交易b的输入脚本里给出的!Bob解开了这道题,获得了奖金,然后在交易b中为Carol出一道数学题,等待Carol来解……
所以说,在图1-8中相同颜色的输出和输入才是一对题和解。
![image](https://yqfile.alicdn.com/3c9c39e7970b8c365ba92e83651c0448c6e55485.png)
图1-8 输入输出一一对应图
**1.4.3 脚本操作码解读**
要理解比特币脚本,先要了解堆栈,这是一个后进先出(Last In First Out)的容器,脚本系统对数据的操作都是通过它完成的。比特币脚本系统中有两个堆栈:主堆栈和副堆栈,一般来说主要使用主堆栈。下面就来列举几个简单的例子,看下指令是如何对堆栈进行操作的。
常数入栈:指把一段常数压入到堆栈中,这个常数成为栈顶元素,如图1-9所示。
OP_DUP:复制栈顶元素,如图1-10所示。
![image](https://yqfile.alicdn.com/f8cd6edb4af042c98ec4c262f9576eb92ea8de74.png)
图1-9 常数入栈 图1-10 复制栈顶元素
OP_EQUALVERIFY:用于检查栈顶两个元素是否相等,如图1-11所示。
**1.4.4 脚本执行过程**
Alice在转账给Bob的时候,输出交易中给出了Bob的钱包地址(等价于公钥Hash),当Bob想要转账给Carol的时候,他要证明自己拥有与这个钱包地址对应的私钥,所以在输入交易中给出了自己的公钥及使用私钥对交易的签名。下面来看个实例。
交易a:?
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
交易b:?
62fadb313b74854a818de4b4c0dc2e2049282b28ec88091a9497321203fb016e
交易b中有一个输入交易引用了交易a的输出交易,它们的脚本是一对题与解。
题:交易a的输出脚本,若干个脚本指令和转账接收方的公钥Hash。
OP_DUPOP_HASH160be10f0a78f5ac63e8746f7f2e62a5663eed05788OP_EQUALVERIFY OP_CHECKSIG
解:交易b的输入脚本,这么一长串只是两个元素,签名和公钥(sig & pubkey)。
3046022100ba1427639c9f67f2ca1088d0140318a98cb1e84f604dc90ae00ed7a5f9c61cab02210094233d018f2f014a5864c9e0795f13735780cafd51b950f503534a6af246aca301 03a63ab88e75116b313c6de384496328df2656156b8ac48c75505cd20a4890f5ab
下面来看下这两段脚本是如何执行完成解题过程的。
首先执行的是输入脚本。因为脚本是从左向右执行的,那么先入栈的是签名,随后是公钥。接着,执行的是输出脚本。从左向右执行,第一个指令是OP_DUP——复制栈顶元素(如图1-12所示)。
OP_HASH160用于计算栈顶元素Hash,得到pubkeyhash,如图1-13所示。
然后将输出脚本中的公钥Hash入栈,为了与前面计算中所得到的Hash区别开来,这里称它为pubkeyhash',如图1-14所示。
![image](https://yqfile.alicdn.com/17ff1a4e5781efa8a9cc3f8c87c42a3d913d3af8.png)
OP_EQUALVERIFY则会检查栈顶前两个元素是否相等,如果相等则继续执行,否则中断执行,返回失败,如图1-15所示。
OP_CHECKSIG使用栈顶前两个元素执行签名校验操作,如果相等,则返回成功,否则返回失败,如图1-16所示。
![image](https://yqfile.alicdn.com/b552582ed5a1a721dff201d2cf22fb0b8fbb5874.png)
图1-15 检查Hash值是否相等 图1-16 返回结果
这样一串指令执行下来,就可以验证这道数学题是否做对了,也就是说,验明了想要花费钱包地址中比特币的人是否拥有对应的私钥。上面的执行过程是可以在脚本模拟器中进行的,并且能够看到每一步执行的状态。