比特币中的交易结构

要想认识比特币系统中的脚本,首先需要熟悉其交易结构。交易结构如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"result": {
"txid": "921a…dd24", //交易的id
"hash": "921a…dd24", //交易的hash
"version": 1, //使用的比特币协议的版本号
"size": 226, //交易的大小
"locktime": 0, //多长时间后交易生效,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", //交易所在区块的hash
"confirmations": 23, //已经有多少个确认
"time": 1530846727, //交易产生的时间
"blocktime": 1530846727 //所在区块产生的时间
}

验证交易合法性的方法

        首先需要指出,比特币系统中,通过堆栈来检查交易的合法性。
        具体验证过程:将交易中每个输入脚本与该比特币来源所在交易的输出脚本拼接在一起(输入脚本在前,输出脚本在后),然后执行,若执行无误,栈顶值就为非零值(即true),那么该交易合法;否则,交易非法。
        常用的验证交易合法性的脚本拼接方法主要有P2PK (Pay to Public Key)、P2PKH (Pay to Public Key Hash)、P2SH (Pay to Script Hash)。

P2PKH

P2PKH是最常用的一个脚本拼接方法。

下面是输入脚本和输出脚本所包含的内容:

1
2
3
4
5
6
7
8
9
input script:
PUSHDATA(Sig)
PUSHDATA(PubKey)
output script:
DUP
HASH160
PUSHDATA(PubKeyHash)
EQUALVERIFY
CHECKSIG

执行时将脚本拼接,如下

1
2
3
4
5
6
7
PUSHDATA(Sig)			//将输入脚本中的签名压入栈
PUSHDATA(PubKey) //将输入脚本中的公钥压入栈
DUP //将栈顶元素(此时为公钥)复制后压入栈
HASH160 //弹出栈顶元素(公钥),经HASH160生成其hash后压入栈
PUSHDATA(PubKeyHash) //将输出脚本中的收款人公钥的hash压入栈
EQUALVERIFY //弹出栈顶两个元素,比较是否相同(防止其他人冒名顶替)
CHECKSIG //验证数字签名

P2SH

P2SH常用来实现多重签名。

下面是输入脚本和输出脚本所包含的内容:

1
2
3
4
5
6
7
8
9
10
11
input script:
x
PUSHDATA(Sig_1)
PUSHDATA(Sig_2)
...
PUSHDATA(Sig_M)
PUSHDATA(serialized RedeemScript)
output script:
HASH160
PUSHDATA(RedeemScriptHash)
EQUAL

其中,RedeemScript为赎回脚本,serialized RedeemScript为序列化的赎回脚本,RedeemScript如下所示:

1
2
3
4
5
6
7
M
PUSHDATA(pubkey_1)
PUSHDATA(pubkey_2)
...
PUSHDATA(pubkey_N)
N
CHECKMULTISIG

将脚本拼接后如下(假设收款人共有3个公钥,要实现转账需要至少提交2个有效签名)

第一阶段:

1
2
3
4
5
6
7
FALSE					//无意义的值,用来弥补比特币系统之前的Bug
PUSHDATA(Sig_1) //将第一个签名入栈
PUSHDATA(Sig_2) //将第二个签名入栈
PUSHDATA(serialized RedeemScript) //将序列化后的赎回脚本入栈
HASH160 //将栈顶元素弹出后计算hash值(此时栈顶为序列化的赎回脚本)
PUSHDATA(RedeemScriptHash) //将赎回脚本的hash值入栈
EQUAL //将栈顶两个元素弹出,比较是否相同

第二阶段:

1
2
3
4
5
6
2			//将2入栈
PUSHDATA(pubkey_1) //将第一个公钥入栈
PUSHDATA(pubkey_2)
PUSHDATA(pubkey_3)
3
CHECKMULTISIG //检查多签名是否合法

Proof of Burn脚本

        该脚本是一种很特殊的脚本,其交易的输出通常为0,而输入则作为支付给矿工的交易费。其输出脚本如下所示:

1
2
3
output script:
RETURN
…[zero or more options or text]

        假如有一个交易的输入指向这个输出,不论输入里的脚本如何设计,执行到RETURN这个命令之后都会直接返回false,RETURN后面的其他指令也就不会执行了,所以这个输出无法再被花出去,对应的UTXO也就不需要保存了。

        注:虽然该output会直接返回一个false,但是,交易依然能够被写入区块,因为当执行脚本验证交易的合法性时,该output并不会被执行(执行的是“交易中的输入脚本与该比特币来源所在交易的输出脚本拼接在一起所形成的脚本”)。

        这样可以实现销毁比特币,或者向区块链中保存一些特殊信息,例如保存发明专利。

最后更新: 2019年03月20日 20:23

原始链接: http://yoursite.com/2019/03/19/比特币系统的脚本/