主页 > imtoken中国版 > BTC - 比特币脚本
BTC - 比特币脚本
2021-12-14
比特币系统中使用的脚本语言非常简单。 唯一可以访问的内存空间是栈,它是一种基于栈的语言,与通用脚本语言有很大的不同。
观察 blockchain.info 上的交易实例
以下面的交易为例:
这笔交易有一个输入和两个输出,其中一个已经花费,另一个还没有。
输入脚本
输入脚本包含两个操作,将两个长数字压入堆栈。
输出脚本
输出脚本有两行,对应上面两个输出,即每个输出都有自己独立的脚本。
交易结构
交易的总体结构
交易输入
交易的输入是一个列表。 在这个例子中,只有一个输入,所以这个列表只有一个列表项。
如果交易有多个输入,则每个输入都必须来源和签名。
交易输出
交易的输出也可以有多个,形成一个列表。
一个直观的例子
下图中,两笔交易属于两个区块,中间有两个区块。 B转C的交易中BTC的来源是之前A转B的交易。所以右边交易中对应输入的txid就是左边交易的id,而里面的vout右边的交易指向左边交易对应的输出。
在早期的比特币系统中,为了验证交易的合法性,必须将交易B->C的输入脚本和交易A->B的输出脚本一起执行,看能否执行。 .
后来为了安全起见,将这两个脚本改为分开执行。 首先,执行输入脚本。 如果没有错误,则执行输出脚本。 如果能够顺利执行,并且最后得到一个非零值(true),那么这个交易就是合法的。
如果一笔交易有多个输入,则每个输入脚本都必须在前一个特定区块中找到对应的输出脚本,匹配后进行验证。 全部验证通过后,交易才合法。
几种形式的输入/输出脚本
[1] P2PK(支付公钥)
简要描述;简介
输入脚本中直接给出付款人的签名(付款人使用自己的私钥对输入脚本所在的整个交易进行签名),输出脚本中直接给出公钥,最后的CHECKSIG为用于检查签名的命令。
这种形式的脚本是最简单的,因为在输出脚本中直接给出了公钥。
执行
一共三个语句,从上到下执行。
第一条语句将签名从输入脚本推入堆栈:
第二条语句将输出脚本中的公钥压入堆栈:
第三条语句弹出栈顶的两个元素,使用公钥PubKey校验签名Sig是否正确。 如果正确,则返回True,表示验证通过:
[2] P2PKH (Pay to Public Key Hash)
简要描述;简介
P2PKH的输出脚本中并没有给出收款人的公钥,而是给出了公钥的哈希值。 输入脚本中给出了此人的公钥(即必须同时给出公钥和签名)。 其他的是一些操作说明,后面我们会详细了解它们是如何工作的。 P2PKH 是最常用的形式。
执行
从上到下一共执行了7条语句。
第一条语句将签名从输入脚本推入堆栈:
第二条语句将输入脚本中的公钥压入堆栈:
第三条语句复制栈顶元素(因此再次按下公钥):
第四条语句弹出栈顶元素得到hash,然后将得到的hash值压入栈中(即栈顶的公钥成为它的hash值):
第五个语句将输出脚本中提供的公钥哈希值压入堆栈:
第六条语句弹出栈顶的两个元素,比较是否相等(防止有人用自己的公钥冒充币源交易收款人的公钥):
第七条语句弹出栈顶的两个元素,使用公钥PubKey校验签名Sig是否正确。 如果正确,则返回True,表示验证通过:
[3] P2SH(支付给脚本哈希)
简要描述;简介
这是最复杂的形式,输出脚本给出的不是收款人公钥的散列哈希值btc,而是收款人提供的兑换脚本(Redeem Script)的散列。 当这个输出脚本的BTC在未来要被花费时,相应交易的输入脚本必须给出赎回脚本的具体内容,同时给出赎回脚本正确运行所需的签名。
两步验证
验证时,一方面验证输入脚本给出的兑换脚本内容是否与对应输出脚本给出的兑换脚本的哈希值匹配; 另一方面,反序列化并执行赎回脚本,验证输入脚本给出的签名是否正确。
赎回脚本表格
使用P2SH实现P2PK的功能
赎回脚本中给出公钥,输入脚本中给出交易签名和赎回脚本的具体内容,输出脚本中给出赎回脚本的哈希值。
第一阶段验证,首先验证输入脚本和输出脚本的执行结果。
第一步,将输入脚本中的交易签名入栈:
第二步,将输入脚本中给出的赎回脚本压入栈中:
第三步,弹出栈顶元素得到哈希,然后入栈得到赎回脚本的哈希(Redeem Script Hash):
第四步,将输出脚本中给出的赎回脚本的哈希值入栈:
第五步,比较栈顶的两个元素是否相等,相当于用前面输出脚本给出的赎回脚本hash来验证输入脚本提供的赎回脚本是否正确:
第二阶段验证是对输入脚本提供的序列化兑换脚本的验证。 首先,必须对其进行反序列化以获得可执行的赎回脚本。 然后执行这个赎回脚本。
第一步是将脚本中硬编码的公钥压入堆栈:
第二步是验证输入脚本中给出的交易签名的正确性。 如果验证通过,则返回True:
P2SH在最初版本的比特币系统中是不存在的,是后来以软分叉的形式加入的。 其常用场景之一是多重签名。
多重签名
比特币系统中的交易输出可能需要来自其交易输入的多个签名才能提取 BTC。 例如,一家公司可能要求五名合伙人中的任何三名提供签名才能转移公司的资金。 这种设计既为私钥的泄露提供了一定的安全保护,又为私钥的丢失提供了一定的容错能力。
[1] 最早的多重签名
简要描述;简介
最早的多重签名是通过比特币脚本中的CEHCKMULTISIG操作实现的。 输出脚本中指定了N个公钥,同时指定了一个不超过N的阈值M。 只要在输入脚本中提供任何M个签名,就可以通过验证。
注意图中有一个红叉,这是因为在比特币系统中CEHCKMULTISIG操作的实现存在一个bug,这个bug会导致额外的元素从栈中弹出,因为这是一个去中心化的系统,这个bug到现在是修不了了,改的唯一办法就是去硬分叉,这个很贵。 这个红叉的意思是在输入脚本中添加一个无用的元素入栈,从而抵消这个bug的影响。
另外,给定的M个签名的相对顺序必须与对应输出脚本中N个公钥中对应公钥的相对顺序一致。
执行
第一步是将输入脚本中的冗余元素(前面提到的红色叉号)压入堆栈:
第二步,将输入脚本中的M个签名依次入栈(这里M=2):
在这里输入脚本并执行。
第三步是将输出脚本中给出的阈值 M 压入堆栈:
第四步是将输出脚本中给出的 N 个公钥压入堆栈:
第五步是将输出脚本中给出的 N 个公钥压入堆栈:
第六步,执行CEHCKMULTISIG,检查N个签名中的M个签名是否依次入栈。
注意这是最早的多重签名,并没有使用P2SH,是通过比特币脚本中原生的CEHCKMULTISIG实现的。
这在实际使用中有些不便。 例如,某电商网站开通了比特币支付通道,但需要5个合作伙伴中的3个签名才能转账BTC。 但是这样做之后,当用户用BTC支付时,生成的转账交易中必须给出5个合伙人的公钥,同时还要给出N和M的值 。
而这些公钥,以及N和M的值,必须由电商网站向用户公布,而且不同电商网站的规则不同,导致用户产生转账交易不便. 因为这些复杂性是暴露给用户的。
[2] 用P2SH实现多重签名
简要描述;简介
与之前的实现相比,这样做的本质是将输出脚本的复杂性转移到了赎回脚本上,输出脚本只需要给出赎回脚本的哈希值即可。 N个公钥和N、M的值在赎回脚本中给出,赎回脚本由输入脚本提供,这样就和支付的用户隔离了。
比如下面B是用户,A是电商平台,C是A把赚到的钱转入的账户:
当B要向电商平台A支付时,不需要A的赎回脚本,只要在输出脚本中写入A的赎回脚本的哈希值(RSH)即可。 在这种模式下,电商网站只需要在网站上公布兑换脚本的哈希值即可。 当用户产生转账交易时,哈希值可以包含在输出脚本中。 至于电商网站用的是什么脚本的签名规则,用户是看不到的。
从用户的角度来看,使用这种P2SH支付方式和上节课学习的P2PKH支付方式没有太大区别,只是输出的脚本是兑换脚本的哈希值,而不是公钥的哈希值而已一个值(输出脚本的编写方式有一些差异,请参阅各自的命令)。
输入脚本用于电商网站转BTC时使用。 这样,输入脚本必须包含M个签名和兑换脚本的序列化版本。
如果以后电商改变采用的多重签名规则,只需要改变兑换脚本的内容和输入脚本的内容,然后发布新的兑换脚本的哈希值即可。 对于用户来说,只有输出脚本中包含的散列值在支付时发生变化。
输入输出脚本执行
第一步是将红色十字占位符元素压入堆栈:
第二步,将输入脚本中的M个签名入栈:
第三步,将保存在输入脚本中的序列化赎回脚本压栈:
在这里输入脚本并执行。
第四步,弹出栈顶元素得到hash然后压栈,即获取栈顶赎回脚本的hash:
第五步是将输出脚本中给出的赎回脚本哈希值(RSH)压入堆栈:
第六步,判断栈顶的两个元素是否相等,即判断计算出的赎回脚本哈希是否等于给定的赎回脚本哈希:
此时执行输出脚本,即第一阶段验证完成。
赎回脚本执行
第一步是将阈值 M 压入堆栈:
第二步是将 N 个公钥压入堆栈:
第三步是将给定数量 N 的公钥压入堆栈:
第四步,使用CEHCKMULTISIG操作检查多重签名的正确性:
燃烧证明:燃烧比特币
这是一个特殊的输出脚本。 当RETURN语句执行时,会发生错误,然后终止校验,后面的语句就没有机会执行了。
为什么要设计这样的输出脚本? 这样输出的 BTC 永远不会被花费。 这是一种用来证明销毁比特币的方法。
为什么要烧掉比特币? 一般有两种应用场景:
一些小型加密货币(AltCoin:Alternative Coin)需要销毁一定数量的比特币才能获得一定数量的此类硬币。 这时候,Proof of Burn 就可以证明他销毁了这些比特币。 写一些东西到区块链。 因为区块链是一个不可变的账本,所以有人利用这个特性写一些需要永久保存的内容。 例如,在第一课学习的数字承诺中,你需要在某个时间证明你知道某件事。 例如,对于一些知识产权保护哈希值btc,将知识产权进行哈希处理后,可以将哈希值放在输出脚本的RETURN语句后面。 反正hash值很小,hash值也看不出原来的内容。 日后有争议时,以原文内容为准。 你可以在区块链上找到这笔交易的输出脚本中的哈希值,你可以证明你在某个时间点掌握了这些知识。
上面提到的第二个应用场景,回想一下之前学过铸币交易的时候,铸币交易的CoinBase域里面随便写什么,何乐而不为呢? 这种方法难度大,需要获取记账权,并且需要在设置CoinBase域的内容时获取记账权。 从根本上来说,是因为发布交易不需要记账权,而发布区块需要记账权。
任何用户都可以使用 Proof of Burn 方法销毁极少量的比特币,以换取将一些内容写入比特币系统区块链的机会。
例子
也可以在不销毁比特币且仅支付交易费用的情况下将内容写入区块链:
查看输出脚本,开头是RETURN,后面的内容就是要写入的内容:
因为输出永远不会被花费,所以不需要存储在UTXO中,对全节点非常友好。
总结
比特币系统使用的脚本语言非常简单,不是图灵完备的语言,甚至不支持循环。 这样的设计也是有它的用处的。 如果不支持循环,则不会有无限循环。 后来学习到的以太坊的脚本语言是图灵完备的,所以用了其他的机制来防止进入死循环。
比特币的脚本语言针对比特币的应用场景做了很好的优化,比如可以实现校验多重签名时的CHECKMULTISIG操作,这是它的强项。
分类:
技术要点:
相关文章: