比特币脚本

交易实例

图 1

这个交易的下面一个输出已经被花掉了,并且这个交易得到了23个confirmation,回滚的可能性很小。
输入脚本包含两个操作,分别把两个很长的数压入栈中,比特币使用的脚本语言非常简单,唯一能访问的内存空间只有堆栈,不像通用编程语言C++之类,有全局变量、局部变量、动态分配的内存空间等,它这里只有一个栈,所以叫基于栈的语言。输出脚本有两行,分别对应上面的两个输出,每个输出有自己单独的一段脚本。

交易具体内容

首先看一下交易的宏观信息
图 2

txid:transaction id,交易id
hash:交易的hash
version:比特币协议版本号
size:交易大小
locktime:用来设定交易的生效时间,0表示立即生效,非0值表示过一段时间生效,比如说等10个区块之后才能被写入区块链中
vin:输入部分
vout:输出部分
blockhash:这个交易所在区块的hash值
confirmations:确认数
time:交易产生时间
blocktime:区块产生时间
图 3

一个交易可能有多个输入,所以vin是个数组结构,上图只包含一个输入,每个输入都要说明输入花的币来自之前哪个交易的输出,所以前两行表示来源
txid:之前交易的hash
vout:之前这个交易的第几个输出
scriptSig:输入脚本
图 4

一个交易可能也有多个输出,所以vout也是一个数组结构,上图只包含两个输出
value:输出金额,单位是比特币
n:这个交易的第几个输出
scriptPubKey:输出脚本
asm:输出脚本的内容
reqSigs:需要多少个签名
type:输出类型,上图都是公钥的hash
address:输出地址
图 5

使用脚本验证交易合法性

验证这个交易的合法性就要将B→C的输入脚本和A→B的输出脚本拼接在一起执行。注意后一个交易的输入脚本在前,前一个交易的输出脚本在后,在早期的比特币系统中,这两个脚本是拼接在一起,从头到尾执行一遍,后来出于安全因素考虑,这两个脚本改为分别执行,首先执行输入脚本,如果没有出错,就执行输出脚本,如果能顺利执行,最后栈顶的结果为非0值,也就是true,这个交易就是合法的。如果执行过程中有任何错误,这个交易就是非法的。如果交易有多个输入,那么每个输入脚本都要和所对应的输出脚本进行配对验证,全都验证通过,这个交易才是合法的。
图 6

输入和输出脚本的最简单形式就是P2PK,输出脚本直接给出收款人的公钥(PUSHDATA),CHECKSIG是检查签名的操作,输入脚本直接给出用私钥对输入脚本所在交易的签名。
图 7

此处为了方便演示将输入脚本和输出脚本拼接在了一起,实际上是分开的。该脚本执行首先将输入脚本提供的签名压入栈中,然后将输出脚本提供的公钥压入栈,然后将栈顶两个元素弹出,用公钥检查签名是否正确,如果正确返回true
图 8

上图是P2PK实例,上面这个交易的输入脚本就是把签名压入栈,下面的交易是上面交易的币的来源,输出有两行,第一行将公钥压入栈,第二行是验证。
图 9

输入和输出脚本的第二种形式是P2PKH,这种形式与上一种形式的区别在于输出脚本没有直接给出收款人的公钥,给出的是公钥的hash,公钥在输入脚本给出,输入脚本既要给出签名,也要给出公钥,输出脚本其它操作是为了验证签名的正确性。这种形式是最常用的。
图 10

上面两行来自输入脚本,后面来自输出脚本,还是从上往下执行,前两条语句将签名和公钥压入栈,DUP 表示将栈顶元素复制一遍,HASH160表示将栈顶元素弹出,取hash之后再压入栈,此时栈顶变成公钥的hash值,此时栈的情况如下图
图 11

EQUALVERIFY是弹出两个栈顶元素,比较是否相等,相等就会消失
图 12

最后 CHECKSIG 和上一种形式一致,弹出两个元素,用公钥检查签名的正确性,正确最后栈中只会留下一个true
图 13

P2PKH是最常用的形式
图 14

有一种最复杂的脚本形式P2SH,这种的形式的输出脚本给出的不是收款人的公钥hash,而是收款人提供的脚本的hash,叫redeemScriptHash(赎回脚本)。将来花这个钱的时候输入脚本要给出赎回脚本的具体内容,同时要给出让赎回脚本正常运行的签名
图 15
图 16
图 17
P2SH为什么这么复杂?P2SH在最初的比特币版本中并没有,后来通过软分叉的形式加进去的。常见的应用场景是对多重签名的支持。比特币系统中,一个输出可能要有多个签名才能把钱取出,比如某个公司的账户,可能要求5个合伙人中,任意3个合伙人的签名,才能把钱从公司账户中取走,这样为私钥的泄露提供了安全的保证,比如某些合伙人私钥泄露出去了,那么问题也不大,因为还需要另外两人的签名,才能取出钱来,同时也为私钥的丢失提供了冗余,5个合伙人中,即使有2人忘掉了私钥,剩下3人仍然可以把钱取出,然后转到某一个安全的账户。这个功能是通过CHECKMULTISIG实现的
图 18
输出脚本里给出N个公钥,同时给出一个域值M,输入脚本只要提供N个公钥中对应的签名中任意M个合法签名就可以通过验证。输入脚本的第一行红X表示压入一个多余的元素,CHECKMULTSIG有一个bug是会多弹出一个元素,因为去中心化的特性,现在不能通过软件升级改正。给出的M个签名的相对顺序要和N个公钥中的相对顺序一致才可以。
图 19
这个过程并没有用到P2SH,而是用的原生的CHECKMULTISIG。这样实现有许多不方便的地方,比如网上购物,某个电商平台用多重签名,要求5个合伙人中任意3个合伙人的签名才能把钱取出来,这就要求消费者在支付的时候要给出5个合伙人的公钥,同时还要给出N和M的值,这里N是5,M是3。消费者只能从平台获知这些信息,转账非常麻烦,这时候就可以采用P2SH实现多重签名
图 20
用P2SH实现多重签名可以将输出脚本的复杂度转移到赎回脚本里,输出脚本只要给出赎回脚本的hash,赎回脚本要给出N个公钥和N和M的值,赎回脚本在输入脚本提供,也就是说由收款人提供。
图 21
图 22
现在的多重签名一般都采用这种P2SH形式

图 23
最后一种脚本形式比较特殊,这种格式的输出脚本开头是RETURN操作,后面可以跟任意内容,RETURN操作的作用是无条件返回错误,所以包含这个操作的脚本永远不可能通过验证,执行到RETURN语句就出错终止。那么output的比特币岂不是永远花不出去了?确实花不出去,这种脚本的作用是证明销毁比特币,一种应用场景是有些小的币种要求销毁一定比特币才能换取这个币种,这种小币种叫AltCoin(Alternative Coin)。另外一个应用场景是往区块链里写一些内容,因为区块链是个不可篡改的账本,有人就利用这个特性,往里面添加一些永远需要保存的内容,比如前文提到的digital commitment,证明在某个时间知道某个事情,比如将知识产权的内容取hash写入RETURN后面,后面的内容反正永远不会执行,写什么都无所谓,而且放进去的是hash值,不会占太多地方也不会泄露内容,将来出现产权纠纷,可以将具体内容公布出去,证明你在某个时间已经知道某个知识了。
图 24

这个交易的输入是铸币交易,第一个输出是出块奖励和交易费,第二个输出的金额是0,输出脚本就是上面那种特殊的形式,开头是RETURN,后面是一些看起来乱七八糟的东西,这个输出的目的就是往区块链里面写一些东西
图 25

这是个普通的转账交易,输出脚本也是以RETURN开头。这个交易的输入是0.05个比特币,输出金额是0,说明输入金额全部用来支付交易费了,这个交易并没有销毁任何比特币,只不过把输入的比特币作为交易费转给挖到矿的矿工了,这种形式的好处是矿工看到这种脚本的时候知道它里面的输出永远不可能兑现,所以没有必要保存在UTXO里面,这样对全节点比较友好。