之前文中有提及,比特币交易验证其合法性依赖于脚本进行,本篇便专门写比特币系统中的脚本语言。

交易实例

我们来看一个交易实例

image-20230107101211872

可以看到收到了23个确认,所以回滚概率很小。

比特币系统中使用的脚本语言非常简单,唯一可以访问的内存空间只有栈,所以也被称为“基于栈的语言”

宏观结构

交易结构

image-20230107102100512

vin结构

image-20230107103120163

Vout的内容

image-20230107120140925

验证交易合法性的实际过程

image-20230107120332935

B转给C的交易的比特币来源是A转给B,所以C要拿到B->C的输入脚本(input script)和A->B的输出脚本(output script),来验证交易的合法性,在早期直接将两个脚本按照如图顺序(input script在前,output script在后) 拼接后执行,后来考虑到安全性问题,两个脚本改为分别执行:先执行input script,若无出错,再执行output script。

如果脚本可以顺利执行,最终栈顶结果为true,则验证通过,交易合法;如果执行过程中出现任何错误,则交易非法。

如果一个交易有多个输入脚本,则每个输入脚本都要和对应的输出脚本匹配执行,全部验证通过才能说明该交易合法。

输入输出脚本的几种形式

P2PK形式(Pay to public key)

特点:输出脚本直接给出收款人公钥。(CHECKSIG为检查签名操作)

image-20230107120925318

image-20230107121314641

注意:实际执行是分开执行,为了方便说明才将两个脚本合并

实例:

image-20230107121428478

P2PKH形式(Pay to public key hash)

最常用的形式

特点:输出脚本不直接给出收款人公钥,而是收款人公钥的哈希。

image-20230107122530705

image-20230107122938045

EQUALVERIFY该操作的目的是为了防止冒名顶替(公钥)。假设比较正确,则两个元素消失(不往栈中压入TRUE或FALSE)。

image-20230107123004043

P2SH形式(Pay to script hash)

image-20230107123338984

image-20230107123424237

验证

第一阶段执行拼接后的输入和输出脚本。

image-20230107123832605

第二阶段,其实要先将输入脚本中序列化的赎回脚本反序列化,然后执行它,看看是否有错误

image-20230107123900244

为什么要弄这么复杂?用之前介绍的P2PK不就可以了吗?为什么要将这部分功能嵌入到赎回脚本?
毫无疑问,针对这个例子,这样做确实复杂了。实际上P2SH在BTC系统中起初并没有,后来通过软分叉(后续会有一篇文章专门介绍硬分叉和软分叉)加入了这个功能。实际上,该功能的常见应用场景是对多重签名的支持。
在BTC系统中,一个输出可能需要多个签名才能取出钱来。例如,对于公司账户,可能会要求5个合伙人中任意3个的签名才能取走钱。要求5个合伙人中任意3个的签名这种做法可以使一个私钥泄露和丢失时系统能正常使用

多重签名

该方法通过CHECKMULTISIG来实现,其中输入脚本提供M个签名,输出脚本给出N个公钥和阈值M,表示N个人至少有M个签名即可实现转账(N>=M)。输入脚本只需要提供N个公钥中M个合法签名即可。【给出的M个签名顺序要和N个公钥中相对顺序一致】

image-20230107130640909

脚本执行

image-20230107130813992

这里压入完成之后,CHECKMULTISIG判断是否三个公钥对应的有两个签名符合要求(签名顺序要跟公钥顺序一致)

早期的实际应用中,多重签名就是这样写的。但是,在应用中体现出了一些问题。例如,在网上购物时候,某个电商使用多重签名,要求5个合伙人中任意3个人才能将钱取出。这就要求用户在生成,转账交易时候,要给出五个合伙人的转账公钥以及N个M的值。而对于用户来说,需要购物网站公布出来才能知道这些信息。不同电商对于数量要求不一致,会为用户转账交易带来不便之处(因为这些复杂性全暴露给了用户)。
为了解决这一问题,就需要用到P2SH

使用P2SH实现多重签名

image-20230107131055447

本质上是将复杂度从输出脚本转移到输入脚本,可见此时输出脚本只有三行,原本复杂度被转入到赎回脚本redeemScript中。输出脚本只需要给出赎回脚本的哈希值即可。该赎回脚本在输入脚本提供,即收款人提供。
这样做,类似之前提到的电商,电商平台只需要公布赎回脚本哈希值即可,用户只要在输出脚本中包含该哈希值,用户无需知道收款人的相关规则(对用户更加友好,对用户隐藏细节)。

脚本执行:

image-20230107131709606

image-20230107131724777

实例:

image-20230107131953855

现在的多重签名,大多都采用P2SH的形式

一个特殊的脚本

以RETURN开始,后面可以跟任何内容。
RETURN操作,无条件返回错误,所以该脚本永远不可能通过验证。执行到RETURN,后续操作不会再执行。
该方法是销毁比特币的一种方法。

image-20230107133823935

为什么要销毁比特币?现在比特币价值极高,销毁是不是很可惜?

  • 部分小币种(AlternativeCoin)要求销毁部分比特币才能得到该种小币种。例如,销毁一个BTC可以得到1000个小币。即,使用这种方法证明付出了一定代价,才能得到小币种。

  • 往区块链中写入内容。我们经常说,区块链是不可篡改的账本,有人便利用该特性往其中添加想要永久保存的内容。例如:知识产权保护——知识产权的哈希值。

有没有觉得第二个应用场景有些熟悉?实际上,之前谈到BTC发行的唯一方法,便是通过铸币交易凭空产生(数据结构篇中)。在铸币交易中,有一个CoinBase域,其中便可以写入任何内容。那么为什么不使用这种方法呢,而且这种方法不需要销毁BTC,可以直接写入。

因为这种方法只有获得记账权的节点才可以写入内容。而上面的方法,可以保证任何一个BTC系统中节点乃至于单纯的用户,都可以向区块链上写入想写入的内容。【发布交易不需要有记账权,发布区块需要有记账权
任何用户都可以使用这种方法,通过销毁很小一部分比特币,换取向区块链中写入数据的机会。实际上,很多交易并未销毁BTC,而是支付了交易费。

例如下图为一个铸币交易,其中包含两个交易,第二个交易便是仅仅想要往其中写入内容。

image-20230107135000087

下图为一个普通的转账交易,其就是仅仅为了向区块链写入内容。该交易并未销毁BTC,只是将输入的费用作为交易费给了挖到矿的矿工。
这种交易永远不会兑现,所以矿工不会将其保存在UTXO中,对全节点比较友好。

image-20230107135223592

实际中的脚本,都需要加上OP前缀,如:CHECKSIG应该为OP_CHECKSIG,这里仅仅为了学习友好,就删去了该前缀

总结

BTC系统中使用的脚本语言非常简单,简单到没有一个专门的名称,我们就称其为”比特币脚本语言“。而在后文的以太坊的智能合约中,则比此复杂得多。实际上,该脚本语言甚至连一般语言中的循环都不支持,但设计简单却也有其用意。
如果不支持循环,也就永远不会出现死循环,也就不用担心停机问题。而在以太坊中,由于其语言图灵完备,所以要依赖于汽油费机制来防止其陷入死循环。此外,该脚本语言虽然在某些方面功能很有限,但另外一些方面功能却很强大
例如,前文提到的CHECKMULTISIG用一条语句便实现了检查多重签名的功能。这一点与很多通用编程语言相比,是很强大的。