我最近一直在尝试写一个比特币挖矿软件,不是为了真的挖比特币,纯粹是为了做技术研究。
在看了BIP一些关于隔离见证的文档之后,我想应该马上拿一个实际的例子来分析一下,加深印象。我选择在btc主网上拿一个区块来分析,这里我选择区块高度是638316,这是最新生成的区块。
用区块浏览器查看这个高度的区块信息以及它的coinbase交易,连接如下:https://btc.com/00000000000000000011d1260195a49f92eb720e8f1c4c8d23ea592760dac663
用网上公开的API查看coinbase交易的原始二进制数据:
https://api.blockchair.com/bitcoin/raw/transaction/f395f865fc046764cb8e2140b696528e9aee02d51e1c9504a4a173b6e0b08686
得到原始数据如下:
010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff54036cbd091b4d696e656420627920416e74506f6f6c3234a80083002078d4031efabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000ffffffff03ab3fab27000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9edc16bd501e4b5d616ccf8d4ed95305ed8f7bf5bf0f9712a2c5af31a6e2f6e9b360000000000000000266a24b9e11b6d1a85afc306f75e4a446ddebd03bcffa5211696993ea106b804f9adc58d4de4e40120000000000000000000000000000000000000000000000000000000000000000000000000
计算原始数据的txHash:
func TestCoinbase(t *testing.T) {
txStr := "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff54036cbd091b4d696e656420627920416e74506f6f6c3234a80083002078d4031efabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000ffffffff03ab3fab27000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9edc16bd501e4b5d616ccf8d4ed95305ed8f7bf5bf0f9712a2c5af31a6e2f6e9b360000000000000000266a24b9e11b6d1a85afc306f75e4a446ddebd03bcffa5211696993ea106b804f9adc58d4de4e40120000000000000000000000000000000000000000000000000000000000000000000000000"
buf, err := hex.DecodeString(txStr)
if err != nil {
t.Error(err)
return
}
txHash := common.Sha256AfterSha256(buf)
t.Log(hex.EncodeToString(txHash[:]))
}
结果如下:
5af3bb3c88f26f5c2cd3e0850a302851d4e7983d780c2562f564e8280254275e
这个值和我用来查询这笔交易的时候用的txid相差很大。这是正常的,因为这个coinbase采用隔离见证的方式来打包交易数据。txid计算的时候不会包含witness data,但是txhash计算的时候会包含witness data。
下面要仔细考察一下这个用隔离见证方式打包的coinbase交易内容是怎样的(因为用csdn表格编辑发布文章有bug,后我发现表格不见了,所以才用下面这种丑陋的方式呈现)。
01000000 Version 版本号
0001 [marker][flag] 隔离见证标志
01 - input数量
0000000000000000000000000000000000000000000000000000000000000000 - input对应的前一个output所在的交易id
ffffffff -input对应的前一个output所在的交易中的位置
54 -input脚本大小
036cbd091b4d696e656420627920416e74506f6f6c3234a80083002078d4031efabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000 -input脚本
ffffffff -sequence序列号
03 -output数量
ab3fab2700000000 -[0]output 的比特币金额
19 -[0]output脚本大小
76a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac -[0]output脚本
0000000000000000 -[1]output 的比特币金额
26 -[1]output脚本大小
6a24aa21a9edc16bd501e4b5d616ccf8d4ed95305ed8f7bf5bf0f9712a2c5af31a6e2f6e9b36 -[1]output脚本
0000000000000000 -[2]output 的比特币金额
26 -[2]output脚本大小
6a24b9e11b6d1a85afc306f75e4a446ddebd03bcffa5211696993ea106b804f9adc58d4de4e4 -[2]output脚本
01 -隔离见证数量
20 -隔离见证数据大小
0000000000000000000000000000000000000000000000000000000000000000 -隔离见证数据
00000000 -锁定时间
从上面的解析数据可以看出,这个coinbase交易有一个输入,却有三个输出。其中第一个输出是旷工用来接收BTC的,第二个输出是旷工的commitment,更多详情请移步这里。这最后一个输出是做什么的?我一直不知道。另外,看来coinbase交易其实是不需要旷工进行签名的,因为witness字段全都是空值。
那这里的ScriptSig字段里填写的都是什么内容呢?试着用BTC脚本语言解码看看。
03 - 把后面3个字节数据压到栈顶
6cbd09 - 等于十进制638316,这正是区块高度
1b - 把后面27个字节数据压到栈顶
4d696e656420627920416e74506f6f6c3234a80083002078d4031e - Mined by AntPool24࠸ԃ,这是旷工的相关信息fabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000 - 后面这一大堆就解码不出来了
由于BTC脚本语言中没有0xfa这个字节对应的操作码,所以到这里我就看不懂最后那一大段数据是什么意思了,也许是旷工随便写的呢。
对于这里未知的内容,一方面需要更仔细去查看一下BIP相关内容,另一方面也需要去努力写代码测试。
最后来计算一下txid,把上面的[marker][flag]和最后跟witness相关的字段数据删除后重新组合数据计算txid,代码如下:
func TestCoinbase(t *testing.T) {
txStr := "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff54036cbd091b4d696e656420627920416e74506f6f6c3234a80083002078d4031efabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000ffffffff03ab3fab27000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9edc16bd501e4b5d616ccf8d4ed95305ed8f7bf5bf0f9712a2c5af31a6e2f6e9b360000000000000000266a24b9e11b6d1a85afc306f75e4a446ddebd03bcffa5211696993ea106b804f9adc58d4de4e40120000000000000000000000000000000000000000000000000000000000000000000000000"
tx := Transaction{}
if err := tx.Parse(txStr); err != nil {
t.Error("parse transaction wrong")
t.Error()
return
}
tx.Print()
buf, err := hex.DecodeString(txStr)
if err != nil {
t.Error(err)
return
}
txHash := common.Sha256AfterSha256(buf)
t.Log("txhash:",hex.EncodeToString(txHash[:]))
txWithoutWitness := "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff54036cbd091b4d696e656420627920416e74506f6f6c3234a80083002078d4031efabe6d6d2fe32793d84d2d60b446f55dedce3cb2792f60bb2dcb417d07b9283d88fe014b0200000000000000706b0100f24d0000ffffffff03ab3fab27000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9edc16bd501e4b5d616ccf8d4ed95305ed8f7bf5bf0f9712a2c5af31a6e2f6e9b360000000000000000266a24b9e11b6d1a85afc306f75e4a446ddebd03bcffa5211696993ea106b804f9adc58d4de4e400000000"
buf, err = hex.DecodeString(txWithoutWitness)
if err != nil {
t.Error(err)
return
}
txHash = common.Sha256AfterSha256(buf)
t.Log("txid:",hex.EncodeToString(txHash[:]))
}
计算结果如下:
transaction_test.go:97: txhash: 5af3bb3c88f26f5c2cd3e0850a302851d4e7983d780c2562f564e8280254275e
transaction_test.go:106: txid: 8686b0e0b673a1a404951c1ed502ee9a8e5296b640218ecb646704fc65f895f3
拿这两个值和api获取到的txid和hash值对比一下,发现是可以对得上的(但是注意字节序列刚好相反)。
https://api.blockchair.com/bitcoin/raw/transaction/f395f865fc046764cb8e2140b696528e9aee02d51e1c9504a4a173b6e0b08686
(全文完)
参考资料和帮助工具:
https://en.bitcoin.it/wiki/Script
https://blockchair.com/api/docs#link_M3
https://www.bejson.com/convert/ox2str/
https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_ID
https://learnmeabitcoin.com/guide/coinbase-transaction
https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki