比特币脚本是基于栈的语言,且不是图灵完备的。不支持循环语句,防止无限循环导致停机。但是与密码学相关的功能比较强大,比如说哈希计算、签名、验签等。
在比特币没有账户的概念,谁拥有这笔交易的输出谁就可以花费这笔交易中的比特币,为了证明拥有这笔交易的输出就需要提供密钥接受验证,验证通过就可以花费这笔交易的输出。
一组交易包含交易ID、版本号、交易输入、交易输出等信息,vin部分为交易输入,里面包含输入脚本,vout部分为交易输出,里面包含输出脚本,如下所示:
"result": {
"txid": "921a…dd24",
"hash": "921a…dd24",
"version": 1,
"size": 226,
"locktime": 0,
"vin": [{ // 交易的输入
"txid": "c0cb…c57b",
"vout": 0,
"scriptSig": {
"asm": "3045...0018",
"hex": "4830...0018"
},
}],
"vout": [ // 交易的输出
{
"value": 0.22684000,
"n": 0,
"scriptPubKey": {
"asm": "DUP HASH160 628e…d743 EQUALVERIFY CHECKSIG",
"hex": "76a9…88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [ "19z8LJkNXLrTv2QK5jgTncJCGUEEfpQvSr"]
}
},{
"value": 0.53756644,
"n": 1,
"scriptPubKey": {
"asm": "DUP HASH160 da7d…2cd2 EQUALVERIFY CHECKSIG",
"hex": "76a9…88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": ["1LvGTpdyeVLcLCDK2m9f7Pbh7zwhs7NYhX"]
}
}],
"blockhash": "0000000000000000002c510d…5c0b",
"confirmations": 23,
"time": 1530846727,
"blocktime": 1530846727
}
如下图所示,当前交易的输入脚本与来源交易的输出脚本拼接在一起,在栈上运行:
下面将介绍几种不同比特币脚本使用方法:
输出脚本直接给出了收款人的公钥,输入脚本提供了用私钥对整个交易的签名,最后通过OP_CHECKSIG验证。如果通过验证,则证明这个行为确实是私钥拥有者所为,在下面的例子中,花费交易用私钥对这笔交易进行签名,而上一笔交易的输出脚本用公钥对这笔花费交易的进行验签,验签通过后,就可以证明这个交易确实是比特币拥有者签发的。
输入脚本(input script)如下所示:
PUSHDATA(Sig)
输出脚本(output script)如下所示:
PUSHDATA(PubKey)
CHECKSIG
输入脚本与输出脚本拼接后,组成:
PUSHDATA(Sig)
PUSHDATA(PubKey)
CHECKSIG
先将签名(Sig)压入栈中,然后将公钥(PubKey)压入栈中,然后执行CHECKSIG指令,弹出栈顶的Sig和PubKey,验证签名是否正确,然后将TRUE或FALSE压入栈中,表示签名验证结果,TRUE表示交易是合法的。
交易ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e的第一个输入:
交易f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16的第一个输出:
输出脚本只给出收款人公钥的哈希,输入脚本提供了签名和用于验签的公钥,比P2PK方式多了一步验证公钥哈希是否吻合,后续过程就是验签了。
输入脚本(input script)如下所示:
PUSHDATA(Sig)
PUSHDATA(PubKey)
输出脚本(output script)如下所示:
DUP
HASH160
PUSHDATA(PubKeyHash)
EQUALVERIFY
CHECKSIG
输入脚本与输出脚本拼接后,组成:
PUSHDATA(Sig)
PUSHDATA(PubKey)
DUP
HASH160
PUSHDATA(PubKeyHash)
EQUALVERIFY
CHECKSIG
先将签名(Sig)压入栈中,然后将公钥(PubKey)压入栈中,执行DUP指令复制栈顶数据(PubKey),HASH160指令弹出栈顶的PubKey,取哈希后将公钥哈希(PubKeyHash’)压入栈中,然后将真实的公钥哈希(PubKeyHash)压入栈中,执行EQUALVERIFY指令弹出栈顶的两个数据,比较两个哈希是否相同,如果相同则执行CHECKSIG指令,弹出栈顶的两个数据PubKey和Sig,使用公钥(PubKey)验证签名(Sig)是否正确,将TRUE或FALSE压入栈中,表示脚本执行结果,TRUE表示交易是合法的。
交易921af728159e3019c18bbe0de9c70aa563ad27f3f562294d993a208d4fcfdd24的第一个输入:
交易c0cb92ca8e41070233bf965d808b0fc4bac144dab05690b17823fac3e184c57b的第一个输出:
P2SH在比特币最初版本是没有的,后期软分叉加入,采用的BIP16的方案
这种形式的输出脚本是收款人提供的脚本(redeemScript)哈希,收款人要花费这笔交易的时候需要提供一些签名(数目不定)及一段序列化的redeemScript。
输入脚本(input script)如下所示:
…
PUSHDATA(Sig)
…
PUSHDATA(serialized redeemScript)
输出脚本(output script)如下所示:
HASH160
PUSHDATA(redeemScriptHash)
EQUAL
验证时分两步:
redeemScript可以设计成多种形式,比如前面介绍的P2PK或者P2PKH形式,以及后面章节介绍的多重签名形式。
redeemScript如下所示:
PUSHDATA(PubKey)
CHECKSIG
输入脚本(input script)如下所示:
PUSHDATA(Sig)
PUSHDATA(serialized redeemScript)
输出脚本(output script)如下所示:
HASH160
PUSHDATA(redeemScriptHash)
EQUAL
PUSHDATA(Sig)
PUSHDATA(serialized redeemScript)
HASH160
PUSHDATA(redeemScriptHash)
EQUAL
将签名(Sig)压入栈中,然后将序列化redeemScript压入栈中,执行HASH160指令将弹出栈顶的“serialized redeemScript”,计算哈希,然后将哈希值压入栈中,然后将脚本哈希值(redeemScriptHash)压入栈中,执行EQUAL比较两个哈希值是否相等,如果相等,则继续下面的步骤。这时栈中只有一个签名数据(Sig)。
2. 第二步反序列化redeemScript并执行该脚本 ,以验证签名:
PUSHDATA(PubKey)
CHECKSIG
将公钥(PubKey)压入栈中,这时栈中有签名和公钥两个数据,调用CHECKSIG即可验证签名,如果验证成功,将TRUE压入栈中,表示验签成功,说明交易是合法的。
最早的多重签名,需要付款人指明收款人公钥的个数、收款人花费时所需签名的个数,给用户转账带来很大的不便,目前已经不推荐使用旧版本的多重签名。脚本如下所示:
输入脚本(input script)如下所示:
x
PUSHDATA(Sig_1)
PUSHDATA(Sig_2)
...
PUSHDATA(Sig_M)
输出脚本(output script)如下所示:
M
PUSHDATA(pubkey_1)
PUSHDATA(pubkey_2)
...
PUSHDATA(pubkey_N)
N
CHECKMULTISIG
输出脚本中M表示需要的签名个数,N表示共有多少公钥,输入脚本中提供M个签名,执行CHECKMULTISIG会根据N从栈顶弹出N个公钥,根据数字M弹出M个签名,还会弹出一个多余的数据(由于历史bug,相应地输入脚本第一行需要多一个无用数据),然后验证M个签名,将验证结果压入栈中。
使用P2SH实现多重签名,减轻了付款人的工作,付款人只需转账给一个redeem脚本哈希就可以,收款人花费该交易时提供对应的redeem脚本,该脚本中指明需要的签名个数M、公钥总数N,反序列redeem脚本然后执行验证,这就是P2SH实现的多重签名。
redeemScript如下所示:
M
PUSHDATA(pubkey_1)
PUSHDATA(pubkey_2)
...
PUSHDATA(pubkey_N)
N
CHECKMULTISIG
输入脚本(input script)如下所示:
x
PUSHDATA(Sig_1)
PUSHDATA(Sig_2)
...
PUSHDATA(Sig_M)
PUSHDATA(serialized RedeemScript)
输出脚本(output script)如下所示:
HASH160
PUSHDATA(RedeemScriptHash)
EQUAL
交易bc26380619a36e0ecbb5bae4eebf78d8fdef24ba5ed5fd040e7bff37311e180d的第一个输入:
push的最后一个数据是序列化的脚本,反序列化后得到:
2 027c…74a3 022c…c94b 0357…ce3a 3 CHECKMULTISIG
交易0ac29fc675909eb565a0984fe13a47dae16ca53fb477b9e03446c898b925ab6b的第二个输出,为redeem脚本哈希:
输出脚本(output script)如下:
RETURN
…[zero or more ops or text]
包含了这样的output script的output被称为Provably Unspendable/Prunable Outputs。
假如有一个交易的input指向这个output,不论input里的input script如何设计,执行到RETURN这个命令之后都会直接返回false,RETURN后面的其他指令也就不会执行了,所以这个output无法再被花出去,对应的UTXO也就可以被剪枝了,无需保存。
Proof of Burn可用于以下场景: