2021-02-23

比特币脚本


这是比特币的一个交易实例:
 
这个交易有一个输入两个输出,左边这个地方写的是Output其实是这个交易的输入这个意思是说这个输入的币是来自前面那个交易的输出。右边那个Output中上面的还没花出去下面的那个已经花出去了。这个交易已经受到了23个确认所以回滚的可能性很小了下面是这个交易的输入和输出脚本输入脚本包含两个工作分别把两个很长的数压入栈里.比特币所使用的脚本语言较为简单唯一能访问的内存空间就是一个堆栈不同于通用的编程语言像C,C++这种有全局变量有局部变量还有什么动态分配的内存空间,它这里就是有一个栈所以叫做基于栈的语言,这里的输出脚本有两行分别对应上面的两个输出,每个输出有自己单独的一段脚本。
看一下这个交易的具体内容:
 
首先看一下这个交易的宏观信息包括第一行的txid,第二行的hash,这个交易的哈希值,接下来是version,使用的比特币协议的版本,size是这个交易的大小,locktime是指用来设定交易的生效时间,这里的0表示立即生效,绝大多数情况下,都是0,如果是非零值的话,这个交易要过段时间才能生效,比如说,要等十个区块之后,才能被写入区块链里,有一些比较特殊的tx,会用到这些locktime,下面这个vin,vout是输入输出部分,blockhash是这个区块所在的哈希值,这个哈希值也是由一长串的0开头,这是挖矿要求的难度要求,都是这样的,confirmations是这个交易已经有多少个确认信息,这里是有23个确认信息,time是这个交易产生的时间,blocktime是这个区块产生的时间。这两个时间都是表示成从以前很早的时间点到现在过了多少秒。
这个是交易的输入结构:
 
是个数组一个交易可以有多个输入这个图里只有一个输入在每个输入都要说明这个输入所花的币来自哪里,所以前两行就是给出输出的币的来源。第一个txid就是之前第一个交易的哈希值,vout表示是这个交易里的第几个输出下面的这个就是输入脚本ScriptSig因为输入脚本最简单的就是给出一个signature(输出方私钥的签名)就可以了,证明你有权利花这个钱。如果一个交易有多个输入的话每个输入都要说明币得来源并且要给出签名,也就是说比特币的一个交易中可能要多个签名。
这个是交易的输出
 
也是一个数组结构,这个例子中有两个数组,这个value是输出的金额这里的单位是比特币也有表示的是Satoshi一聪,比特币里的最小单位Satoshi这个单位是很小的所以如果表示成Satoshi的话转账的金额看上去都是很大的这里表示成Satoshi的话,就是22684000聪,n是序号表示这是这个交易里的第几个输出这个scriptPubKey是表示输出脚本输出脚本就简单的形式就是给出一个PubKey(付款人的公钥),asm显示的是输出脚本的内容,里面包含一系列的操作,reqSigs是说这个输出需要多少个签名才能兑现,type是输出的类型,这两个类型都是pubkeyhash是公钥的哈希,addresses是输出的地址。
输入与输出脚本的执行
 
这里是一个小型的区块链,在前面这个区块里有一个A转B的交易B收到后又隔了两个区块转个了C,也就是说B转给C的币的来源是A转给B的这个交易,所以下面这个内容里我能就可以看到B转C的交易里txid和vout指向的是A转B的交易的输出,那么要验证这个交易的合法性就是把A转B的输出脚本和B转C的输入脚本拼接在一起来执行的
 
注意这里有个交叉前面这个交易的输出脚本放在后面而后面的输入脚本放在前面,在早期的比特币实现中这两个脚本是拼接到一起从头到尾执行一遍,后来出于安全因素的考虑这两个脚本改为分别执行首先执行输入脚本如果没有出错再执行输出脚本如果能顺利执行最后占领的结果为非0值也就是true验证通过如果执行过程中出现任何错误那么这个交易就是非法的。如果一个交易有多个输入的话那么每个输入脚本都要与之对应的输出脚本相匹配后来进行验证全都验证通过这个交易才是合法的.
输入输出脚本的几种形式
 
一种最简单的形式就是Pay to Public Key ,输出脚本里直接给出收款人的公钥第二行的checksig是检查签名的操作,在输入脚本里直接给出签名就行了,这个签名就用私钥对整个交易的签名。这种形式是最简单的因为Public Key是直接在输出脚本里给出的。
 
这三行是把输入输出拼接起来后的结果,第一行来自输入脚本后两行来自输出脚本注意出于安全考虑这两段脚本是分别执行的。图里第一条语句把输入脚本里提供的签名压入栈第二条是把输出里提供的公钥压入栈第三条是把占领的元素弹出来用公钥检查这个签名是否正确如果正确的话返回true否则的话执行出错。
这是Pay to Public Key的一个实例:
 
上面这个输入脚本就是把签名压入栈下面这个交易是上面输入币的来源它的输出有两行,第一行是把公钥压入栈第二行就是checksig
另一种形式
 
这种新式与刚才的区别在于输出脚本里没有给出收款人的公钥,给出的是公钥的哈希。公钥是在输入脚本里给出的,输入脚本既要给出签名也要给出公钥。输出脚本里其它还有一些操作一开始的DUP HASH160这些操作都是为了验证签名的正确性,这种的形式实际上是就常用的
脚本执行起来的结果:
 
这个是吧上一页的输入脚本和输出脚本拼接之后得到的,强两条语句来自输入脚本后面的语句来自输出脚本,还是从上往下执行,第一条语句先把签名压入栈第二条语句把公钥压入栈第三条语句是把栈顶的语句全复制一遍,所以栈顶又多了一个公钥下一条语句是把栈的元素弹出来然后取哈希然后再把得到的哈希再弹入栈所以栈就变成了公钥的哈希值。下面这条语句是把输出脚本里的提供的公钥的哈希值压入栈这个时候栈里就有两个哈希值,上面这个哈希值是输出脚本里面提供的收款人公钥的哈希,下面这个哈希值是你要花这个钱的时候在输入脚本里给出的公钥然后前面这个操作HASH160操作取哈希后得到的。再下面这个是弹出栈顶的两个元素,比较栈顶的两个哈希值是否相等这样做的目的是防止有人冒充最后这条弹出栈顶的两个元素用公钥检查是否正确,假设这个签名是正确的这个脚本顺利运行结束栈点留下的是true,如果执行过程中任何一个环节发生错误比如输入里给出的公钥和输出里给出的的哈希值对不上或者输入给出的签名跟输出里对不上那么这个交易就是非法的。
 
Pay to Public Key hash是最长用的脚本形式最上面的实列用的就是这个脚本。
最后一种也是最复杂的一种就是 Pay to Script Hash
 
这种形式的脚本给出的不是收款人的公钥哈希而且收款人提供的一个脚本的哈希redeemScript(赎回脚本)将来花这个钱的时候输入脚本里要给出redeemScript(赎回脚本)的具体内容同时给出这个redeemScript(赎回脚本)能正常运行签名
验证的时候分为两步:
 
第一步验证输入脚本里给出的redeemScript(赎回脚本)是不是跟输出脚本里给出的哈希值匹配,如果不匹配的话就说明给出的redeemScript(赎回脚本)是不对的,如果输入里给出的redeemScript(赎回脚本)是正确的那么第二步还要不redeemScript(赎回脚本)的内容当作操作指令来执行一遍看看最后能不能顺利执行,如果两部验证都通过了那么这个交易才是合法的
 
这里的输入脚本就是给出签名再给出序列化的赎回脚本,赎回脚本的内容就是给出公钥然后用这个CHECKSIG检查签名,下面这个输出脚本是用来验证你给出的赎回脚本是否正确。
看一下这个Pay to Script Hash的执行过程
 
开始的时候也是把输入输出脚本拼接在一起,前两行来自输入脚本,剩下的来自输出脚本。首先把Signature压入栈然后把赎回脚本压入栈然后是取哈希的操作的到赎回脚本的哈希这个PSR是指赎回脚本的哈希值,接下来还要把输出脚本里给出的压入栈,这个时候这个栈里就有两个哈希值了。最后用EQUAL比较一下这两个哈希值是否相等,如果相等这两个哈希值就从栈点消失了,如果不相等就游戏结束了,到这里第一阶段的验证就结束了。
第二阶段的验证:
 
第二个验证首先要把输入脚本里提供的序列化的赎回脚本进行返序列化,这个反序列化是每个节点要自己完成的。然后执行这个赎回脚本首先把这个PubKey压入栈然后用CHECKSIG输入脚本里给出的Signature(签名)
的正确性,验证通过之后整个Pay to Script Hash才算完成。为什么要把这个功能嵌入到赎回脚本里面?对于简单的列子的确是复杂了,Pay to Script Hash这个功能在初版本的比特币里面是没有的后来通过软分叉的形式加进去了,它的一个常见的应用场景就是对多重签名的支持,比特币系统中一个输出可能要求多个签名,才能把钱取出来,比如某个公司的账户可能要求五个合伙人中任意三个人的签名。才能把公司账上的钱取走这样为私钥的泄露提出了一些安全的保护,比如说有某个合伙人的私钥泄露出去了问题也不大因为还需要另外两个人的签名才可以把钱取出来即使有两个人的私钥丢失了剩下三个人也可以把钱取出来然后转到某个安全的账户。这个功能是通过checkmultisig来实现的
 
就是这一页最后一行的checkmultisig操作。输出脚本里给出N个公钥同时指定一个预值M输入脚本只要提供这N个公钥对应的签名中任意M个合法的签名就能通过,比如刚举的例子中N=5M=3,5个合伙人中任意三个人的签名都可以,输入脚本的第一行有一个红色的×这个情况是比特币中checkmultisig的实现有一个bug执行的时候回从堆栈上多弹出一个元素这个就是它代码实现的一个bug,这个bug现在已经没有办法改了,因为这是一个去中心化的系统要想通过软件升级的方法去修复这个bug代价是很大的要改的话需要硬分叉,所以实际采用的解决方案是在输入脚本里往栈多压进去一个没有的元素。第一行这个红色的×就是没有的元素为了迁就checkmultisig实现上的bug往堆栈上多压进去的元素另外注意给出的这M个签名的相对顺序要跟它们在N个公钥中的相对顺序一致才行。
这个是checkmultisig的执行过程
 
这个例子中假设三个签名中给出两个就行那么大家可以看到这两个签名给出的顺序和在公钥中的顺序是是一样的,在公钥当中第一个公钥排在第二个前面那么要给出签名的时候也是这个顺序,这个第一行的false就是前面说的多余的元素首先把这个多余的元素压入栈里然后把这两个签名依次压入栈这个时候输入脚本就执行完了,接下来的输出脚本里就把这个预值M压入栈然后把三个公钥压入栈然后把N的值压入栈最后就执行checkmultisig看看这个堆栈里是不是包含这个三个中的两个如果是的话那么验证通过,注意这个过程中并没有用到Pay to Script Hash就是用比特币脚本中的checkmultisig来实现的,早期的多重签名就是这样实现的,在实际应用中有一些不是很方便的地方网上购物某个电商用多重签名那么要求五个合伙人中任意三个才能把钱取出来,这里要求网上购物的用户在支付的时候生成的转账交易里给出这五个合伙人的公钥同时还要给出N和M的值,像这个例子中要写明N=5、M=3这些都是用户在网上购物生产转账交易时输出脚本里要给出的信息给出这五个公钥给出N和M是多少,那么用户是怎么知道这些信息的呢?需要这个购物网站在他的网上公布出来那么这样一来就把复杂性都暴露给用户了,那么这个时候就用到了P2SH
 
这是用P2SH实现的多重签名它的本质是把复杂都从输出脚本转移到了输入脚本,转移复杂度后输出脚本就变的简单了,原来的复杂都被转移到了redeemScript赎回脚本里面输出脚本只要有赎回脚本的哈希就行,这个赎回脚本里要给出这N个公钥还有N和M的值这个赎回脚本是在输入脚本里提供的也就是说是由收款人提供的
比如前面的这个网上购物的例子收款人是电商他只要在网站上公布这个赎回脚本的哈希值然后用户生成转账交易的时候把这个哈希值包含在输出脚本里就可以了,至于这个电商用什么样的多重签名规则是五个里选三个还是三个里选两个对于用户是不可见的用户没必要知道从用户的角度来看采用这种支付方式和采用Pay to public hash相比较没有多大的区别只不过是把公钥的哈希换成了赎回脚本的哈希当然输出脚本上也有一些区别但不是本质性的,这个输入脚本是电商在花掉输出的时候提供的其中包含赎回脚本的序列化版本,同时还包含让这个验证脚本所需的M个签名将来如果电商改变了多重签名规则比如原来是五个里选三个现在变成三个里选两个只要改变输入脚本和赎回脚本的内容然后把新的哈希值公布出去就行了对用户来说只不过是要付款的时候要包括的哈希值发生了变化其它的变化没有必要知道。
 
这个是把输入脚本和输出脚本拼接在一起的情况第一行的false就是为了应付checkmultisig所带来的bug而准备的一个没用的元素,执行的时候先把它压入栈然后依次把两个签名压入栈接下来是序列化的赎回脚本目前只是把它作为数据压入栈到这里输入脚本就执行完了下面是输出脚本取哈希然后把输出脚本里提供的哈希值压入栈顶最后判断一下这两个哈希值是否相等到这里第一阶段的验证就完成了。
开始第二个阶段的验证:
 
把赎回脚本展开后执行先把M压入栈然后把三个公钥压入栈把N压入栈最后检查一下多重签名的正确性三个里面有两个是正确的第二阶段的验证过程跟前面使用checkmultisig的情况是类似的
这是网上用play to script hash做的一个实列:
 
上面输入脚本的最后一个就是序列化的赎回脚本反序列化里得到的就是三个里面去两个的多重签名脚本下面这个输出脚本的内容跟上面的是一样的现在的多重签名都是采用的这种play to script hash的形式
最后这个脚本的格式是比较特殊的看下图:
 
这种格式的输出脚本开头是return这个操作后面可以跟任意的内容,return这个操作的作用是无条件的返回错误所以包含这个操作的脚本永远不可能通过验证,执行到return就会出错然后执行就终止了后面跟的内容根本就没有机会执行,这个脚本是证明销毁比特币的一种方法,那么为什么要销毁比特币呢?这个一般是有两种应用场景,一种场景是有些小的币种要求销毁一定数量的比特币才能够得到这个币种有时候管这种小的币种叫AltCoin除了比特币之外其它小的加密货币你都可以叫做AltCoin比如有的小币种要求你销户一个比特币可以得到一千个这样的小币,也就是说你要用这种方法证明你付出了一定的代价才能得到这个小币种。另外一个应用场景是往区块链里写入一些内容,我们说区块链是个不可篡改的账本有人就用这个特性往里面添加一些需要永久保存的一些内容,比如说你有什么知识产权要保存你可以把知识产权去哈希后放在return后面而且也没泄露出来知识产权的内容将来如果出现了纠纷你就把这个具体内容公布出去就这个哈希值的当时的输入公布出去,证明你在某个时间点已经知道某个知识,这个是任何人都可以去写入内容的一个方法,这种形式脚本的一个好处是矿工看到这种脚本的时候知道它里面的输出永远不可能兑现所以就没有必要把它保存在UTXO里这样对全节点是比较友好的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值