比特币脚本原理和使用方法

文章目录

  • 一、比特币脚本概述
  • 二、P2PK (Pay to Public Key)
    • 2.1 脚本语法
    • 2.2 脚本实例
  • 三、P2PKH (Pay to Public Key Hash)
    • 3.1 脚本语法
    • 3.2 脚本实例
  • 四、P2SH (Pay to Script Hash)
    • 4.1 脚本语法
    • 4.2 以P2SH实现P2PK为例介绍验证过程
  • 五、多重签名
    • 5.1 旧版本多重签名
    • 5.2 用P2SH实现多重签名
    • 5.3 P2SH多重签名实例
  • 六、Proof of Burn

一、比特币脚本概述

比特币脚本是基于栈的语言,且不是图灵完备的。不支持循环语句,防止无限循环导致停机。但是与密码学相关的功能比较强大,比如说哈希计算、签名、验签等。

在比特币没有账户的概念,谁拥有这笔交易的输出谁就可以花费这笔交易中的比特币,为了证明拥有这笔交易的输出就需要提供密钥接受验证,验证通过就可以花费这笔交易的输出。

一组交易包含交易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
}
  • 输出脚本一般情况下包含的信息主要是公钥哈希,用于验证花费该笔资金时给出的公钥是否正确,然后执行验签指令;
  • 输入脚本一般主要是包含签名、和对应的公钥,与输出脚本拼接后即可进行验签。

如下图所示,当前交易的输入脚本与来源交易的输出脚本拼接在一起,在栈上运行:
比特币脚本原理和使用方法_第1张图片

下面将介绍几种不同比特币脚本使用方法:

二、P2PK (Pay to Public Key)

输出脚本直接给出了收款人的公钥,输入脚本提供了用私钥对整个交易的签名,最后通过OP_CHECKSIG验证。如果通过验证,则证明这个行为确实是私钥拥有者所为,在下面的例子中,花费交易用私钥对这笔交易进行签名,而上一笔交易的输出脚本用公钥对这笔花费交易的进行验签,验签通过后,就可以证明这个交易确实是比特币拥有者签发的。

2.1 脚本语法

输入脚本(input script)如下所示:

PUSHDATA(Sig)

输出脚本(output script)如下所示:

PUSHDATA(PubKey)
CHECKSIG

输入脚本与输出脚本拼接后,组成:

PUSHDATA(Sig)
PUSHDATA(PubKey)
CHECKSIG

先将签名(Sig)压入栈中,然后将公钥(PubKey)压入栈中,然后执行CHECKSIG指令,弹出栈顶的Sig和PubKey,验证签名是否正确,然后将TRUE或FALSE压入栈中,表示签名验证结果,TRUE表示交易是合法的。

2.2 脚本实例

交易ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e的第一个输入:
在这里插入图片描述
交易f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16的第一个输出:
在这里插入图片描述

三、P2PKH (Pay to Public Key Hash)

输出脚本只给出收款人公钥的哈希,输入脚本提供了签名和用于验签的公钥,比P2PK方式多了一步验证公钥哈希是否吻合,后续过程就是验签了。

3.1 脚本语法

输入脚本(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表示交易是合法的。

3.2 脚本实例

交易921af728159e3019c18bbe0de9c70aa563ad27f3f562294d993a208d4fcfdd24的第一个输入:
在这里插入图片描述
交易c0cb92ca8e41070233bf965d808b0fc4bac144dab05690b17823fac3e184c57b的第一个输出:
在这里插入图片描述

四、P2SH (Pay to Script Hash)

P2SH在比特币最初版本是没有的,后期软分叉加入,采用的BIP16的方案
这种形式的输出脚本是收款人提供的脚本(redeemScript)哈希,收款人要花费这笔交易的时候需要提供一些签名(数目不定)及一段序列化的redeemScript。

4.1 脚本语法

输入脚本(input script)如下所示:

…
PUSHDATA(Sig)
…
PUSHDATA(serialized redeemScript)

输出脚本(output script)如下所示:

HASH160
PUSHDATA(redeemScriptHash)
EQUAL

验证时分两步:

  1. 先验证这段序列化的redeemScript是否与output script中的哈希值匹配;
  2. 然后反序列化并执行redeemScript ,配合前面的签名,检查是否可以执行通过。

redeemScript可以设计成多种形式,比如前面介绍的P2PK或者P2PKH形式,以及后面章节介绍的多重签名形式。

4.2 以P2SH实现P2PK为例介绍验证过程

redeemScript如下所示:

PUSHDATA(PubKey)
CHECKSIG

输入脚本(input script)如下所示:

PUSHDATA(Sig)
PUSHDATA(serialized redeemScript)

输出脚本(output script)如下所示:

HASH160
PUSHDATA(redeemScriptHash)
EQUAL
  1. 第一步先验证脚本哈希值是否匹配,输入脚本与输出拼接,组成:
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压入栈中,表示验签成功,说明交易是合法的。

五、多重签名

5.1 旧版本多重签名

最早的多重签名,需要付款人指明收款人公钥的个数、收款人花费时所需签名的个数,给用户转账带来很大的不便,目前已经不推荐使用旧版本的多重签名。脚本如下所示:

输入脚本(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个签名,将验证结果压入栈中。

5.2 用P2SH实现多重签名

使用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
  1. 第一阶段将M个签名、序列化redeem脚本、redeem脚本哈希压入栈中,执行EQUAL指令验证给出的序列化redeem脚本与交易来源中的redeem脚本哈希是对应的;
  2. 第二阶段反序列化redeem脚本,执行CHECKMULTISIG脚本验证多重签名。

5.3 P2SH多重签名实例

交易bc26380619a36e0ecbb5bae4eebf78d8fdef24ba5ed5fd040e7bff37311e180d的第一个输入:
在这里插入图片描述
push的最后一个数据是序列化的脚本,反序列化后得到:
2 027c…74a3 022c…c94b 0357…ce3a 3 CHECKMULTISIG

交易0ac29fc675909eb565a0984fe13a47dae16ca53fb477b9e03446c898b925ab6b的第二个输出,为redeem脚本哈希:
在这里插入图片描述

六、Proof of Burn

输出脚本(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可用于以下场景:

  • 永久存储一些信息,比如在某个时间证明存在某些事情,比如在2019年1月1日把知识产权的哈希放到链上,当以后产生纠纷的时候,你把知识产权公布出来,知识产权的哈希在特定时间已经上链,就可以证明你在特定时间已经知道了这个知识产权。
  • 代币转换,比如你把一些比特币转换成其他数字货币,你需要通过这种方式来证明你付出了一些代价。
  • 销毁比特币。

你可能感兴趣的:(比特币)