交易是比特币系统中最重要的一部分,一笔交易通过花费UTXO来实现价值的链上转移,并同时产生新的UTXO,要想花费这些新产生的UTXO,必须解决UTXO对应的数学难题,比特币系统中称之为锁定脚本。让我们从一笔交易的构成开始,逐步揭开交易的面纱。
交易基本格式如下图所示:
交易基本格式可以参考https://bitcoin.org/en/developer-reference。
上图中的VarInt是一种网络传输数字编码格式,用于减少传输带宽,格式如下:
例如:0x21被编码为0x21;0x203被编码为0xfd0302,注意0xfd后面跟着的uint16和0xfe后面跟着的uint32都是小端序。
这里我通过解析两个现实交易来拨开交易的真实面目。
该交易ID为4c45186fa3ef11d414b94e2e74364e2800dd348582322909e6f96f9e25192981
区块高度548202
交易原始数据及字段解释如下
总结下coinbase交易:
- 只有一个输入和输出
- 输入的hash值为空,因为coinbase本身是块中第一个交易,用于生成比特币和收集块中其他交易的交易费
- coinbase的签名脚本中除了显示块高度字段外,可以填入任意内容
该交易ID为006afa95dec0305ef42d501d9b64daee5f5d8ab2dbf629687ea9748476c0ccc8
块高度541,779
交易原始数据及字段解释如下
一笔交易输入通过解锁对应的上一笔交易中的输出脚本来花费输出中指定的金额。
其中对应的上一笔交易输出指的是由该笔输入部分中的hash和n两个字段共同指定的一笔交易输出部分。
从上面的两个交易例子中可以看到,脚本是由操作码和操作数构成,而脚本验证指的是通过依次执行输入中的解锁脚本和对应输出脚本来验证交易合法性,其中脚本是基于堆栈执行的。下面是上述两个交易中交易脚本验证的基本流程,最终堆栈上返回true代表脚本验证通过。
- Pay-to-Pubkey
<pubkey> OP_CHECKSIG
<signature>
第一行为锁定脚本,第二行为解锁脚本,创世区块中的交易使用这种类型,如下为输出脚本:
41-将接下来的65字节push到栈上
04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f
-65字节的公钥
ac-OP_CHECKSIG操作码
- Pay-to-PubkeyHash
OP_DUP OP_HASH160 <pubkeyHash> OP_EQUALVERIFY OP_CHECKSIG
<signature> <pubkey>
上面签名基本交易格式章节中提到的两笔交易就是采用了这种类型。
- Pay-to-ScriptHash
OP_HASH160 <scriptHash> OP_EQUAL
<signatures> {redemption script}
这种类型分两部分进行验证:
第一部分:{redemption script} OP_HASH160 <scriptHash> OP_EQUAL
第二部分:<signatures> 反序列化后的{redemption script}
详细解析见下面的MultiSig。
- MultiSig
m <pubkey 1> ... <pubkey n> n OP_CHECKMULTISIG
OP_0 <signature 1> ...<signature m>
第一行输出脚本的含义是:在n个公钥中,至少要有m个签名与之对应。
第二行锁定脚本提供了m个签名。
让我们来看一笔真实交易:
交易ID为d8ded48f7874a6ef590655e18963ffdf0e20a160702bd671dcc2ca75d72e12d5
这笔交易有135个输入,4个输出,所有输入输出脚本均为P2SH格式。
我们来看第一个输入,其地址为pplthtxum75y27cqt3fa2qf3vhtl45h66v5q5lkda8
其解锁脚本为:
其中105字节长度的redemption script解析如下
52-OP_2操作码
21-长度:33字节
023c6e535a80f80d65dd8c2b54ad4741768f824c42cc46ad738f40e823f192b6b7
-第一个公钥
21
038f98b3ff700359433cf70ca700b30a235ac0236ea5c40a6dd760a42989b05939
-第二个公钥
21
02f93bb4288abf14e1c87f618f9dbd4d8847045021eb9586e2f99c79faec43ec97
-第三个公钥
53-OP_3操作码
这是一个典型的2-3多重签名,提供三个公钥,要求至少两个签名与之匹配。
而该解锁脚本的前部分恰好提供了两个签名,也就是说P2SH脚本验证的第二部分,即反序列化后的redemption script的解锁,可以在该输入的解锁脚本中完成。
我们接下来去找该输入对应的上一笔交易的输出。
其交易ID为:7f9d4c72c980d338467538cbbf308000be1b098792016035f723f6d5a8ef62d0
交易输出脚本数据如下:
a9147ebbacdcdfa8457b005c53d5013165d7fad2fad387
脚本解析:
a9-OP_HASH160
14-将接下来的20字节push到栈上
7ebbacdcdfa8457b005c53d5013165d7fad2fad3
-公钥哈希值
87-OP_EQUAL操作码
P2SH脚本验证的第一部分的锁定脚本,其中哈希值是redemption脚本的哈希值。
注意
签名顺序要与公钥顺序一致,因为OP_CHECKMULTISIG操作码在校验多重签名时会依次从后往前检查签名与交易是否匹配,如果不匹配则继续向前遍历公钥,而公钥只能被查找与使用一次,后续签名校验时将无法使用之前参与校验的公钥,这会导致本本可以校验通过的签名无法校验通过。
原因详见:https://bitcoin.org/en/developer-reference#opcodes
- Nulldata
OP_RETURN [SMALLDATA]
这是一类比较特殊的脚本类型,包含该字段的锁定脚本将永远无法解锁,也就是说输出中对应的金额将永久销毁。
该字段后面可以添加任意数据,可以使用这部分空间实现区块链的上层协议开发,wormhole项目使用这部分空间来实现BCH链上的ERC20 Token的生成与转移。
先直观的感受一下交易中的签名。 这是一个交易原始数据,其中加粗部分就是签名:
0100000001416e9b4555180aaa0c417067a46607bc58c96f0131b2f41f7d0fb665eab03a7e000000006a47304402201c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a02204f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad012103bf350d2821375158a608b51e3e898e507fe47f2d2e8c774de4a9a7edecf74edaffffffff01204e0000000000001976a914e81d742e2c3c7acd4c29de090fc2c4d4120b2bf888ac00000000
这段16进制表示的交易可以对应下表来解释,其中红色部分对应签名。
签名是对一笔新建交易序列化前的最后一个步骤,也就是说上表的ScriptSig和Script Length是在交易序列化前进行填充,而在此之前,其余字段均已填充完毕。
签名的计算公式:
其中,Secp256k1ECDSA是一种曲线为Secp256k1的椭圆曲线数字签名算法,Txhash是交易被签名部分的哈希值,Hashtype是计算哈希值时采用的哈希类型,Privatekey是生成比特币地址的使用的私钥。
Hashtype决定了交易的哪些字段被签名,截止今日,BitcoinCash支持三种Hashtype以及两种Hashtype描述符。
hashtype:SIGHASH_ALL,SIGHASH_SINGLE,SIGHASH_NONE
后缀ALL,SINGLE,NONE表示该种签名方式包含的交易输出个数,也就是说hashtype影响的是交易输出
hashtype descriptor:SIGHASH_ANYONECANPAY,SIGHASH_FORKID
SIGHASH_ANYONECANPAY影响了签名包含的交易输入个数,SIGHASH_FORKID作为一种防重放机制引入到bitcoinCash中。
它们的取值如下:
SIGHASH_ALL = 1,
SIGHASH_NONE = 2,
SIGHASH_SINGLE = 3,
SIGHASH_FORKID = 0x40,
SIGHASH_ANYONECANPAY = 0x80,
各Hashtype详细计算步骤说明如下
图片来源:https://github.com/minium/Bitcoin-Spec/blob/master/Bitcoin.pdf
计算步骤:上图TxNew中阴影部分为SIGHASH_ALL类型签名区域,包含版本号,输入,全部输出,nLockTime。清空其余输入的scripSigLen和scriptSig字段,即其余输入scripSigLen字段为0,scriptSig字段不占用空间。复制输入对应的TxPrev锁定脚本并去除其中的OP_CODESEPARATOR操作码,将改动后的脚本填入scriptSig字段,计算脚本长度并填入scriptSigLen。在修改后的TxNew后序列化并在末端添加四字节小端序HashType字段,此处为0x01000000。最后进行double-sha256哈希运算,生成上述签名公式中的Txhash。
计算步骤:上图TxNew中阴影部分为SIGHASH_SINGLE类型签名区域,包含版本号,输入,签名输入对应的输出,nLockTime。(输入对应的输出指的是二者在交易输入区与输出区的索引是相同的),清空其余输入的scripSigLen,scriptSig和nSequence字段。输出数量即图中#vout字段重置为Input Index+1,对于本例,此时#vout等于1。按照#out数量重新调整输出大小,待签名以外输出的nValue置为-1,scriptPubkeyLen置为0。复制输入对应的TxPrev锁定脚本并去除其中的OP_CODESEPARATOR操作码,将改动后的脚本填入scriptSig字段,计算脚本长度并填入scriptSigLen。在修改后的TxNew后序列化并在末端添加四字节小端序HashType字段,此处为0x11000000。最后进行double-sha256哈希运算,生成上述签名公式中的Txhash。
SIGHASH_SINGLE不对其他输入的sequence字段签名,意味着其他参与者可以通过更改sequence字段来更新交易。
计算步骤:清空其余输入的scripSigLen,scriptSig和nSequence字段,清空输出数量#vout。复制输入对应的TxPrev锁定脚本并去除其中的OP_CODESEPARATOR操作码,将改动后的脚本填入scriptSig字段,计算脚本长度并填入scriptSigLen。在修改后的TxNew后序列化并在末端添加四字节小端序HashType字段,此处为0x10000000。进行double-sha256哈希运算,生成上述签名公式中的Txhash。
SIGHASH_NONE意味着只保护交易输入而不关心交易输出,它可以用来实现类似空白支票的应用。
修饰符必须和上面三种Hashtype共同使用,SIGHASH_ANYONECANPAY表示计算Txhash时只包含该输入,其他输入被移除。
计算步骤:将输入数量#vin重置为0,移除该输入外的其他输入,最后按照上诉三种Hashtype对应的步骤执行。
SIGHASH_ANYONECANPAY与上述类型组合可以产生特定功能的交易,例如其与SIGHASH_ALL组合可以用来实现众筹,将输出设定为众筹目标,当全部输入总和超过目标时众筹成立,否则交易无效。再比如其与SIGHASH_NONE组合可以实现一种名叫“吸尘器”的应用,它用来将可能构成Dust Transaction的微小金额的UTXO捐赠给受赠者。
SIGHASH_FORKID哈希方式为BitcoinCash提供了重放保护,它将一笔交易按如下TxCopy中的顺序序列化。
对上述TxCopy中的字节序列进行double-sha256哈希运算,生成签名公式中的Txhash
SIGHASH_FORKID与上述三种Hashtype以及SIGHASH_ANYONECANPAY的组合影响到hashPrevouts等字段的计算方法。具体细节参考https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/doc/abc/replay-protected-sighash.md
这里提到防重放攻击,在硬分叉后,BitcoinCash中构建的合法交易可以直接拷贝到Bitcoin中使用并验证通过,这样会导致交易发起者BTC的丢失。因此引入SIGHASH_FORKID利用Bitcoin与BitcoinCash对交易脚本验证中签名字段的不同解释来防止这类重放攻击。
最终填充到SicriptSig中的签名由下图生成
图片来源:https://github.com/minium/Bitcoin-Spec/blob/master/Bitcoin.pdf
计算步骤:将按照上述Hashtype步骤生成的交易哈希值Txhash,通过私钥签名生成原始签名。然后将原始签名DER编码。最后在DER编码后的签名末端添加截断至一字节的Hashtype。
以文首交易签名为例
304402201c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a02204f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad01
其具体结构解析如下:
DER Header:0x30
Total Lenth:0x44
Integer Header:0x02
R-Length:0x20
The R coordinate:
01c3be71e1794621cbe3a7adec1af25f818f238f5796d47152137eba710f2174a
Integer Header:0x02
S-Length:0x20
The S coordinate:
4f8fe667b696e30012ef4e56ac96afb830bddffee3b15d2e474066ab3aa39bad01
Hashtype:0x01
签名的校验过程通常通过操作码OP_CHECKSIG执行,具体步骤如下
- 去除签名最后一个字节并获取Hashtype。
- 根据Hashtype计算待验证交易的哈希值HashNew。
- 使用公钥解析待验证签名得到被私钥签名的哈希值HashOld。
- 校验HashNew是否等于HashOld,相等则签名校验通过,否则校验失败。
详细过程请参考wiki中关于OP_CHECKSIG的描述:https://en.bitcoin.it/wiki/OP_CHECKSIG#Procedure_for_Hashtype_SIGHASH_SINGLE
综上,签名类型由生成签名的hashtype决定,签名类型的区别本质上是被签名字段的不同。通过对交易中不同字段签名,实现对特定对象的保护,而未签名字段则给其余交易参与者提供了更多灵活性,利用这种特性可以实现合约功能,例如捐赠。
判断一笔交易是否是标准交易,要检查如下几个方面:
- 交易大小不大于100KB
- 交易的版本号应当为1或2
- 交易的解锁脚本大小不能大于1650字节
- 解锁脚本中的操作码只允许为OP_16及以下的操作码
- 交易是final状态
- 输出脚本类型是标准类型
- 交易为Non-Dust Transaction
- 最多一个Nulldata类型输出
- 多重签名的公钥个数只能为1,2或3
交易其实是使用解锁脚本解锁输入与对应的上一笔交易的输出脚本,从而花费其UTXO,创造新的UTXO的过程。
作为交易的核心的脚本,目前共有五类:P2PK,P2PKH,P2SH,MultiSig,NullData。此外,交易本身有必须满足规定的标准才能在比特币网络中被传播与确认。