第八课:比特币脚本——《区块链技术与应用》一课学习笔记

论坛 期权论坛 期权     
鹿鸣于野UMU   2020-3-28 03:36   1633   0


一、交易实例


上图中下面两行是输入和输出操作,分别把两个很长的语言压入栈里,比特币使用的语言非常简单,唯一能访问的空间就是堆栈,不像通用的语言C++等有全局变量、局部变量,还有什么动态分配的内存空间。
它是基于栈的语言,stack-basedlanguage,这里的输出脚本有两行,分别对应上面的两个输出,每个输出有自己单独的一段脚本。


Locktime是用来生效的交易时间,这里是0,表示立即生效,绝大多数情况下Locktime都是0,如果非0值,那这个交易就要过一段时间才生效,比如十个区块后再写入区块链里。有些比较特殊的transaction会调整locktime的设置。
Vin和vout对应的是输入输出部分,后面详细讲。
Blockhash是这个交易所在区块的哈希值,time是交易产生的时间,blocktime是区块产生的时间,time和blocktime都是表示从很早的时间点到现在过了多少秒。


一个交易中可以有多个输入,以上例子中只有一个输入。每个输入都要说明这个交易中的币是来自之前哪个交易的输出。所以前两行就是表明这个输出,即币的来源,txid就是之前这个交易的哈希值,vout表明是这个交易里的第几个输出。
Scriptsig是输入脚本,因为输入脚本最简单的形式就是给出一个signature就行了,说明你有权力花这个钱。
如果一个交易有多个输入的话,每个输入都要说明币的来源,并且要给出签名。也就是说比特币中的一个交易可能需要多个签名。


Value是输出的金额,就是给对方转出去多少钱,这里的单位是比特币。也有地方表示成satoshi是比特币里的最小单位,非常小。这里的satoshi是22684000satoshi。
N是需要,表示这是这个交易的第几个输出。
Script pubkey是输出脚本,输出脚本里最简单的形式就是给出一个public key.
Asm里是输出脚本里的内容,里面包含了一系列的操作,后面会详细解释操作什么意思。
ReqSigs表示这个输出需要多少个签名才能兑现,这里两个例子中都是只需要一个签名就够了。我们后面会讲到多重签名。有的输出要多个签名才行。
Type是类型,这两个type都是PubkeyHash,是公钥的哈希。
Addresses是输出的地址。


验证B转C交易的合法性,是要把B转C的输入脚本与A转B的输出脚本拼接在一起来执行,这两个脚本现在是分别执行的,首先执行输入脚本,如果没有出错,再执行输出脚本,如果能顺利执行,最后栈顶的结果为非零值,也就是true,验证通过,这个交易就是合法的。如果执行过程中出现任何错误,这个交易就是非法的。如果一个交易有多个输入的话,每个输入脚本都要和所对应的输出脚本匹配之后来进行验证,全都验证通过,这个交易才是合法的。

二、输入、输出脚本形式
以下是输入、输出脚本的几种形式:
第一种形式,最简单的形式,输出脚本里直接给出收款人的公钥,第二行的checksig是检查签名的操作。在输入脚本里直接给出签名就行了,这个签名是用私钥对输入脚本里整个交易的签名,这个签名是最简单的,因为public key是直接在输出脚本里给出的。



实际执行情况:


这三行是输入、输出脚本拼接起来的结果,实际中出于安全考虑,这两段脚本实际是分别执行的。
第一条把输入里提供的签名压入栈,第二条是把输出里提供的公钥压入栈,第三条checksig是把栈顶里的这两个元素弹出来,用公钥检查一下这个签名是否正确,如果验证通过,返回true,证明验证通过,否则的话执行错误,这个交易就是非法的。

以下是pay to public key的实例


上面这个交易是把签名压入栈,就一行。
下面这个交易是上面这个币的来源,它的输出有两行,第一行是把公钥压入栈,第二行是检查签名。

第二种形式:pay to public key hash


这种形式里与上种形式的区别是,这个脚本里没有给出收款人的公钥,而是公钥的哈希,公钥是在输入脚本里给出的。输入脚本既要给出签名,也要给出公钥。输出脚本里也有一些其他的操作,这些操作都是为了验证签名的正确,这种形式实际上是最常用的。


前两条语句来自输入脚本,后面的语句来自输出脚本,还是从上往下执行。

Dup是指把栈顶的元素复制一遍,所以栈顶又多了一个公钥。
Hash160是把栈顶元素弹出来,取哈希,然后把得到的哈希值再压入栈,所以栈顶就变成了公钥的哈希值。
下面一条是把输出脚本里提供的公钥的哈希值压入栈,这个时候栈顶便有两个哈希值,注意搞清两者来源,上面的哈希值是输出脚本里提供的收款人的公钥的哈希,即发布这个交易时,我这个转账的钱要转给谁,在输出脚本里提供一个收款人公钥的哈希。
下面这个哈希是你要在花这个钱的时候,在输入脚本里给出的公钥,然后前面那个hash160的操作,取哈希后得到的。
Equalverify操作的作用是弹出栈顶的两个元素,比较它们是否相等,即比较栈顶的两个哈希值是否相等。这样做的目的是什么?如前课所讲,防止有人冒名顶替,用他自己的公钥冒充收款人的公钥。
假设这两个哈希值是相等的,那就从栈顶消失了。
Checksig,如前所述,弹出栈顶的这两个元素,用公钥检查一下这个签名是否正确。
以下是实例:


最后一种,也是最复杂的一种,pay to script hash,这种脚本的输出脚本,给出的收款人公钥的哈希,是收款人提供的一个脚本的哈希,这个脚本叫redeemscript,赎回脚本,将来花这个钱时,输入脚本里要给出这个redeemscipt的具体内容,同时还要给出让这个赎回脚本正确运行所需要的签名。







赎回脚本的功能,就是给出公钥,用checksig检查签名。
输出脚本是用来验证输入脚本里给出的赎回脚本是否正确。
P2SH的执行过程:


以上是输入、输出脚本的拼接,前两行来自输入脚本,后面三行来自输出脚本。首先把输入脚本的signature压入栈,然后把赎回脚本压入栈,然后取哈希的操作,得到赎回脚本的哈希,RSH是赎回脚本的哈希值,然后输出脚本里给出的哈希值压入栈,这个时候栈里就有两个哈希值,最后用EQUAL比较一下这两个哈希值是否相等。
然后作第二阶段的验证:
首先把输入脚本里序列化的赎回脚本作反序列化,反序列化的操作在PPT上没有展现,这是每个节点自己要完成的,然后执行这个赎回脚本,首先把这个pubkey压入栈,然后用checksig验证输入脚本里给出的signature的正确性,验证通过后整个pay to scripthash才算执行完成。


搞这么复杂的原因是什么?为什么要把这部分功能嵌入到赎回脚本里。
Paytoscripthash在最初的脚本里是没有的,后来通过软分叉的形式加进去了,它常见的应用场景就是对多重签名的支持,比特币应用可能要求多个签名才能把钱取出来,比如某个公司的账户,可能要求五个合伙人中任意三个人签名才能把公司账户上的钱取走,这样就为私钥的泄露提供了安全的保护,比如说有某个合伙人私钥泄露出去了,问题也不大。同时也为私钥的丢失提供了冗余,五个合伙人中即使有两个人把私钥忘掉了,剩下三个人也能把钱取出来,然后转到某个安全的账户。


原来的多重签名过于复杂、对用户要求过高,于是就出现了更新版,本质是把复杂从输出脚本转移到输入脚本,原来的复杂度被转到redeemscript里面,输出脚本只要给出赎回脚本的哈希值就行了,这个赎回脚本里要给出N的公钥,还有N和M的值,这个赎回脚本是在输入脚本里提供的,也就是说是由收款人提供的。

以网上购物为例,收款人是电商,它只要在网上公布了赎回脚本的哈希值,然后用户生成转账交易的时候,把这个哈希值包含在输出脚本就行了。
至于电商用什么样的多重签名规则,是5个选3个,还是3个里选2个,对用户来说是不可见的,用户没必要知道。对于用户而言,新的支付方式和原来的方式并无大的区别,只是把公钥的哈希值换成了赎回脚本的哈希值,当然输出脚本的写法上也有一些区别,但不是本质性的。
这个输入脚本是电商在花掉这笔输出时提供的,其中包含赎回脚本的序列化版本,同时还包含让这个赎回脚本验证通过所需的M个签名。将来如果电商改变了所采用的多重签名规则,比如原来5个里选3个,现在变成3个里选2个,只要改变输入脚本和赎回脚本的内容,然后把新的哈希值公布出去就行了。对用户而言,只是付款时要包含的哈希值发生了变化,其他的变化没有必要知道。


首先是应对BUG而写入的FALSE,然后依次把两个签名压入栈,然后是序列化的赎回脚本,目前只是把它作为数据压入栈,到这里输入脚本就执行完了。
下面就是输出脚本,取哈希,然后把输出脚本里提供的哈希值压入栈顶,最后判断一下这两个哈希值是否相等。到这里,第一阶段的验证就完成。
然后开始第二阶段的验证,把输出脚本展开后执行。


先把M压入栈,然后把3个公钥压入栈,再把N压入栈,最后检查一下多重签名的正确性,3个里面是否有2个是正确的。

第二阶段里与前面直接使用checkmultisig的情况是类似的。
实例见下:


输入脚本中的最后一个就是序列化的赎回脚本。反序列化后得到的就是3个里取2个的多重签名脚本。
最后一个脚本,非常特殊的脚本:


Return的作用是无条件地返回错误,所以包含这个操作的脚本永远都不可能通过验证,执行这个return语句就会出错,然后执行就终止了,后面跟的内容根本就没有机会执行。
为什么要设计这样的输出?永远都无法花出去的钱。这个脚本是证明销毁比特币的一种方法。
为什么要销毁比特币呢?两种应用场景,一种是一些小币种要求销毁一定数量的比特币才能获得这个币种,这种小币种叫AltCoin,就是alternative coin,除了比特币之外其他小的币种都可以认为它是AltCoin,比如有些小的币种要求你销毁比特币,可以得到一千个这样的小币,也就是说你要用这种方法证明了你付出了一定的代价,才能够得到这个小币种。
另外一种场景,就是往区块链里写内容,我们说区块链是不可篡改的账本,有人就利用这种特性往里面添加一些需要永久保存的内容,比如说我们第一节课讲的场景digital commitment,要证明在某个时间知道某个事情,比如知识产权保护的,把某些知识产权的内容取哈希值后,把哈希值放到return语句的后面,这后面的内容永远不会被知晓,你往里面写什么都没有关系,而且哈希值不会占太大地方,也不会泄露知识产权的内容,将来出现纠纷,像知识产权发生纠纷,你把具体内容公布出去,证明你在某个时间点已经知道某个知识了。
类似场景,CoinBase域,在这个域里写内容也没人管,还不用销毁比特币就可以往里面写。为什么不用CoinBase域呢?CoinBase域只有获得记账权的节点才能用,如全节点在发布区块时可以在CoinBase里写。而第一个方法任何节点都能写,甚至一个普通用户都能写,发布交易不需要拥有记账权,发布区块才需要有记账权。任何用户都可以用这种方法,销毁很少一点比特币,换取往这个区块链写入一些内容的机会。
有些交易根本没销毁比特币,只支付了交易费。

以上脚本中,输入是0.05个比特币,输出是0,说明0.05个比特币全都作为交易费支付给挖出区块的矿工了。
矿工看到这个脚本,知道它的输出永远都不可能兑现,就不需要把它保存到UTXO里面,这样对全节点是比较友好的。
我们这个PPT中涉及比特币的脚本操作,为了简单起见,都没有加入op前缀,如checksig,实际中应该是OP-checksig。
比特币系统用的脚本语言非常交易,甚至连名字都没有,不支持循环,因此很多功能这个脚本都不支持。用意是,不支持循环就不会出现死循环,就不用担心停机问题,hoting problem。像以太坊中它智能合约的语言是图灵完备的,表达能力很强,所以就需要汽油费的机制来防止这个程序进入死循环。
虽然这个语言在某些方面功能有限,但在另外一些方面它的功能却很强大,就是和密码学相关的功能很强大,如checkmultisig检查多重签名,用一条语句就能完成了。这个比很多通用的编程语言就方便很多。比特币语言已针对其应用场景作了优化。

课程学习笔记往期索引:
第七课:比特币挖矿——《区块链技术与应用》一课学习笔记

第六课:挖矿难度调整——《区块链技术与应用》一课学习笔记

第五课:比特币网络——《区块链技术与应用》一课学习笔记

第四课:比特币系统的实现——《区块链技术与应用》一课学习笔记
第三课:共识协议——《区块链技术与应用》一课学习笔记
第二课:数据结构——《区块链技术与应用》一课学习笔记
第一课:密码学原理——《区块链技术与应用》一课学习笔记




分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:20
帖子:2
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP