比特币脚本研究
原理
比特币脚本是一个类FORTH的语言,它具有以下特点:
脚本简单,由opcode和data组成
图灵不完备,没有循环
执行顺序是从左到右
基于栈执行,遵循后进先出
应用
简单应用
我们先通过几个的例子来描述一下脚本的简单执行:
加法操作
上面的输出的结果为true,然后我们来描述一下具体每一步做了什么
Empty
5 4 OP_ADD 9 OP_EQUAL
5
4 OP_ADD 9 OP_EQUAL
把5压入栈中,栈顶为5
5 4
OP_ADD 9 OP_EQUAL
把4压入栈中,栈顶为4
9
9 OP_EQUAL
执行OP_ADD,取出栈顶的4和5作为输入,并且把结果9作为输出压回栈中
9 9
OP_EQUAL
把9压入栈中
1
执行OP_EQUA,取出栈顶的两个9,并且比较,如果相等则把输出的true(1)压入栈中
减法操作
上面的输出结果为true,下面是执行过程的描述:
Empty
5 4 OP_SUB 1 OP_EQUAL
5
4 OP_SUB 1 OP_EQUAL
把5压入栈中,栈顶为5
5 4
OP_SUB 1 OP_EQUAL
把4压入栈中,栈顶为4
1
1 OP_EQUAL
执行SUB,取出栈顶的4和5作为输入,4是v0,5是v1,OP_SUB执行的结果是(v1 - v0) = 1。把结果1压入栈中
1 1
OP_EQUAL
把1压入栈中
1
执行OP_EQUA,取出栈顶的两个1,并且比较,如果相等则把输出的true(1)压入栈中
这里需要注意,一些opcode有前后执行顺序,必须严格按照顺序把数据压入栈中,例如算术运算OP_SUB,或者签名验证OP_CHECKSIG等,如果顺序错误会导致脚本执行失败。 通过上述例子我们可以简单的理解脚本的执行流程,后面会通过锁定脚本,解锁脚本及Witness等描述脚本更多用法
锁定脚本和解锁脚本
这里我们把类型分为隔离见证前(pre-segwit)和隔离见证后(segwit)的来做区分
P2PK(pay to public key)
sigScript: <sig>pkscript: <pubKey> OP_CHECKSIG完整脚本为
Empty
<sig> <pubKey> OP_CHECKSIG
<sig>
<pubKey> OP_CHECKSIG
把<sig>签名压入栈中
<sig> <pubKey>
OP_CHECKSIG
把<pubKey>公钥压入栈中
1
执行OP_CHECKSIG,取出<pubKey>和<sig>。验证签名是否通过,如果通过则把结果1压入栈中
OP_CHECKSIG如果通过,则栈中仅剩1,则表示当前sigScript可以解锁pkscript。
P2PKH(pay to public key hash)
sigScript: <sig> <pubKey>pkscript: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG完整脚本为
Empty
<sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
<sig>
<pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
把<sig>压入栈中
<sig> <pubKey>
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
把<pubKey>压入栈中
<sig> <pubKey> <pubKey>
OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
执行OP_DUP,复制栈顶数据并把结果压入栈中
<sig> <pubKey> <pubKeyHash>
<pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
执行OP_HASH160,取出栈顶元素<pubKey>并且执行RIPEMD160算法生成<pubKeyHash>并且压入栈
<sig> <pubKey> <pubKeyHash> <pubKeyHash>
OP_EQUALVERIFY OP_CHECKSIG
压入<pubKeyHash>
<sig> <pubKey>
OP_CHECKSIG
执行OP_EQUALVERIFY,取出栈顶两个元素并且比较,比较成功则继续执行后面的脚本,如果失败则直接终止
1
执行OP_CHECKSIG取出栈顶两个元素并且把结果放入
对比原始的P2PK多了一个对pubKey的校验:这里OP_EQUALVERIFY并不输出任何值,只是控制是否可以继续执行后面的脚本。所有的VERIFY结尾的opcode都不输出任何值,如果不通过,则直接返回错误。
P2MS(pay to multisig)
sigScript: 0 <sig1> <sig2>pkscript: 2 <pubKey1> <pubKey2> <pubKey3> 3 OP_CHECKMULTISIG完整的脚本为
上面是个2-3签名的例子,开头的0是因为OP_CHECKMULTISIG有一个bug,需要多输入一个额外的值,输入0是为了防止延展性攻击BIP147
P2SH(pay to script hash)
sigScript: <script params> <redeem script>pkscript: OP_HASH160 <scriptHash> OP_EQUAL我们使用常见的P2SH使用的redeem script来处理Redeem script: 2 <pubKey1> <pubKey2> <pubKey3> 3 OP_CHECKMULTISIG 则完整的脚本有两段
执行过程:
先执行OP_HASH160 <scriptHash> OP_EQUAL判断是否一致
再根据<script params> <redeem script>判断执行结果是否通过
P2SH-P2WPKH/P2SH-P2WSH
和P2SH的区别是使用了witness字段传入参数 pkscript: OP_HASH160 <scriptHash> OP_EQUALsigScript: 0 <pubKeyHash>|<redeemScriptHash>witness: <sig> <pubKey> | <scriptParam><redeemScript> 后面会介绍隔离见证后的应用
P2WPKH(pay to witness public key hash)
pkScript: 0 <pubKeyHash>sigScript: emptyWitness: <sig> <pubKey> 执行脚本过程等价于P2PKH,但是极大的压缩了发送方数据量(pkScript只保留版本和hash,sigScript保留为空),对于(未升级)旧的比特币节点可能认为任何人都可以解锁。对于升级后的脚本会判断segwit版本,例子中的0,然后再进行签名公钥校验
P2WSH(pay to witness script hash)
pkScript: 0 <scriptHash>sigScript: emptyWitness: <script params> <redeemScript> 执行脚本过程等价于P2SH,同样也是压缩了发送方数据量。这里注意,P2WPKH和P2WSH很容易就可以分辨出来,因为publicKeyHash使用的是RIPEMD160,所以只有20字节,对应pkScript是22字节,而scriptHash使用的是SHA256输出为32字节,对应pkScript是34字节。所以为了统一地址做了taproot升级
P2TR(Public Key Path Spending)
pkScript: 1 <tweaked public key>sigScript: emptyWitness: <schnorr-sig>注意,这里直接使用了tweaked public key,是一个转换后的public key,是32字节,然后witness里面必须是Schnorr的签名。
P2TR(Script Path Spending)
pkScript: 1 <tweaked public key>sigScript: emptyWitness: <script> <control-block> P2TR类型和P2WPKH/P2WSH类型的witness版本号分别为1和0,这样可以很容易区分。同时又改善了P2WPKH/P2WSH里面的Hash长度不一致的问题,统一改成了32字节的tweaked public key。结合MAST的使用,通过使用witness字段让脚本有更多的衍生用法。当witness元素大于等于2的时候,则使用的是Script Path Spending,最后一个元素则为control-block,倒数第二个元素为script,前面的元素为输入参数。
铭文(inscriptions)
Ordinals协议支持的一种将任意内容(图片、视频等文件)附加到单个sat的协议,可以将它们变成比特币原生的数字艺术品。简单的说Inscriptions是一种NFT。铭刻(inscribe)是通过将要铭刻的聪发送到交易中来完成的,该交易会在链上显示铭文内容。然后,此内容与那个聪建立联系,将其变成一个不可改变的数字艺术品,可以被追踪、转移、储存、购买、出售。 以一个铭文铸造的例子来描述一个图片类型的NFT如何生成整个铭文铸造过程分为两部分
Commit承诺
Commit阶段需要提交基于reveal脚本作为tapleaf生成(见BIP340)的tweaked public key
Reveal 揭露
Reveal阶段需要传入参数,脚本的内容及control-blockpkScript: 1 <tweaked public key>sigScript: emptywitness: <script params> <script> <control-block> pkScript中的tweaked public key在前置的commit交易中已经提交。我们重点关注witness提交的内容以一个铭文铸造交易62e6629dcc1d451e2a9f2ba407c306d88c3128a14703be4e3616988cf44d108e。 通过解析witness第一个字段可知witness一共有三个部分组成:第一个部分长度为64字节(参数,实际为接受账户的Schnorr签名)第二个部分是script部分,解析出来后
脚本第一个数据为用户的公钥,传入签名参数和公钥后执行OP_CHECKSIG,如果通过则把true放到栈中,此时注意到后面推入了0(false),则后面的两段IF...ENDIF语句并不会执行(语句中间则为镌刻的NFT数据)。此时栈中仅有1,则脚本验证通过。 第三个部分为control-block,也可以分解为三个部分
到这相当于通过commit和reveal的方式完成了对铭文的镌刻,并且发送给对应的用户。
最后更新于