如果用代码构建一个比特币交易
比特币区块链技术的核心业务无疑是构建 交易。我将向你展示编写你自己的 P2PKH 交易所需的步骤,这是一种你在区块链中最常见的标准交易。
为了构建交易,仅有一个 ECDSA 密钥对是不够的。我们需要密钥对的区块链历史,或者更确切地说,发送价值到与我们的密钥对关联的地址的交易。然后,我们将使用 ECDSA 私钥为这些交易输出生成签名,使它们成为我们交易的输入。
给定:
我们希望通过我们的密钥对 K 将 S 聪发送到地址 A 。以下是总体步骤:
让我们从 ex-tx-build.c 开始。
在 ECDSA 密钥对历史的最开始,派生的 P2PKH 地址没有与之关联的交易输出,因此在区块链上没有比特币价值。当有人后来发布一笔交易将比特币发送到我们的地址时,该密钥对突然变得有价值。考虑我们现在著名的测试网地址:
mqMi3XYqsPvBWtrJTk8euPWDVmFTZ5jHuK
及其区块链历史 。在所有交易中, 三个输出在撰写本文时仍未花费:
总计 0.951 BTC 输出值(= 95100000 聪)。具体来说,这些是发送资金到我们地址的输出:
如果你现在将这些词语转换为数据结构:
<f34e1c37e736...a2f3, 0>
<65216856608d...20c6, 0>
<6b580bada66e...3934, 1>
你就拥有了我们的密钥对/钱包的 UTXO outpoints (输出点) 。除了构成钱包的余额外,UTXO 集合的重要性在于它包含了我们可以重用来构建我们自己交易的输出点。在交易输入中花费一个 UTXO 后,它将从集合中移除,因为 UTXO 总是被完全花费。
值得一提的是,扫描 UTXO 需要大量的网络代码,因此从网络浏览器构建钱包历史是一个快速的替代方案。不过,这会让你付出一些隐私代价,因为你通常不希望与不受信任的服务共享你的地址。
你现在应该知道如何构建一个 P2PKH 交易输出。假设我们想发送 0.251 BTC(25100000 聪,小端序):
e0 fe 7e 01 00 00 00 00
到这个地址:
mhmhRnN58ki9zbRJ63mpNGQXoYvdMXZsXt
它解码为以下 hash160 摘要:
18 ba 14 b3 68 22 95 cb
05 23 0e 31 fe cb 00 08
92 40 66 08
这是我们交易的第一个输出:
/* value (0.251 BTC) */
e0 fe 7e 01 00 00 00 00
/* script length */
19
/* P2PKH script */
76 a9 14 18 ba 14 b3 68
22 95 cb 05 23 0e 31 fe
cb 00 08 92 40 66 08 88
ac
我在 tx.h 中编写了一个方便的 C 宏,用于从一个值和一个 hash160 构建 P2PKH 输出:
void bbp_txout_create_p2pkh(bbp_txout_t *txout,
const uint64_t value, const char *hash160);
基本上,hash160 字节被包含在OP_DUP OP_HASH160
和OP_EQUALVERIFY OP_CHECKSIG
操作码中。十六进制从左到右解释,因此编码为小端序。我们使用宏轻松创建我们的第一个输出:
bbp_txout_create_p2pkh(&outs[0], 25100000,
"18ba14b3682295cb05230e31fecb000892406608");
我们的 UTXO 集合分别值<0.87, 0.001, 0.08> BTC,所以第一个:
<f34e1c37e736...a2f3, 0>
对于 0.251 BTC 输出来说已经足够了。事实上,我们的输入值超过了输出值 0.619 BTC,这些将默默地作为费用返回给交易矿工。我们知道 UTXO 不能部分花费,并且由于费用对我们来说有些重要,我们添加了一个用于_找零_的第二个输出。
我们的最终交易将有一个输入和两个输出,第二个是找零输出,将多余的输入值返回到我们自己的地址。这是我们交易的第二个输出:
/* value (0.619 BTC) */
e0 84 b0 03 00 00 00 00
/* script length */
19
/* P2PKH script */
76 a9 14 6b f1 9e 55 f9
4d 98 6b 46 40 c1 54 d8
64 69 93 41 91 95 11 88
ac
另一方面,很明显,输出值不可能超过输入值。非 coinbase 交易从不创造比特币价值。这是我们的第二个输出代码:
bbp_txout_create_p2pkh(&outs[1], 61900000,
"6bf19e55f94d986b4640c154d864699341919511");
这很棘手,甚至有争议,因为交易签名的主题不是交易本身。在研究 ECDSA 签名时,你了解到签名过程分为三个步骤:
这里的问题是,比特币交易不能以通常的方式签署,因为签名实际上是交易的一部分,即输入脚本。方法必须有所不同。实际上,我们将为每个交易输入生成不同的签名,这些签名将嵌入其 P2PKH 脚本中。
实际上,对于每个输入 I
,要签署的消息是交易的一个稍微修改的版本,其中:
I
脚本设置为它引用的 UTXO 的输出脚本。I
之外的输入脚本被截断为零长度。SIGHASH
标志。对于单输入交易,我们不需要截断任何其他输入脚本:
bbp_outpoint_fill(&outpoint,
"f34e1c37e736727770fed85d1b129713ef7f300304498c31c833985f487fa2f3", 0);
bbp_txout_create_p2pkh(&prev_outs[0], 87000000,
"6bf19e55f94d986b4640c154d864699341919511");
bbp_txin_create_signable(&ins_sign[0], &outpoint, &prev_outs[0]);
我们有了输出和可签名的输入,所以我们继续构建消息:
tx.version = bbp_eint32(BBP_LITTLE, 1);
tx.outputs_len = 2;
tx.outputs = outs;
tx.inputs_len = 1;
tx.inputs = ins_sign;
tx.locktime = 0;
msg_len = bbp_tx_size(&tx, BBP_SIGHASH_ALL);
msg = malloc(msg_len);
bbp_tx_serialize(&tx, msg, BBP_SIGHASH_ALL);
外部函数都来自 tx.h:
bbp_tx_size
和 bbp_tx_serialize
函数负责调整交易结构的大小并将其序列化为原始字节。bbp_outpoint_fill
函数从一个 UTXO 填充一个 bbp_outpoint_t
结构。UTXO 引用由先前交易的 txid 和交易中的输出索引组成。bbp_txin_create_signable
函数通过复制相应的 UTXO 脚本为我们的消息创建一个伪输入。由于我们将签署所有交易输入,我们将 flag
参数设置为 SIGHASH_ALL
(01
)。请注意,这里将其填充为 32 位没有明显原因。
这是我们输入签名的主题:
/* 版本 (32-bit) */
01 00 00 00
/* 输入数量 (varint) */
01
/* 输入 UTXO txid (hash256, little-endian) */
f3 a2 7f 48 5f 98 33 c8
31 8c 49 04 03 30 7f ef
13 97 12 1b 5d d8 fe 70
77 72 36 e7 37 1c 4e f3
/* 输入 UTXO 索引 (32-bit) */
00 00 00 00
/* 输入 UTXO 脚本 (varint + data) */
19 76 a9 14 6b f1 9e 55
f9 4d 98 6b 46 40 c1 54
d8 64 69 93 41 91 95 11
88 ac
/* 输入序列 */
ff ff ff ff
/* 输出数量 (varint) */
02
/* 输出值 (64-bit) */
e0 fe 7e 01 00 00 00 00
/* 输出脚本 (varint + data) */
19 76 a9 14 18 ba 14 b3
68 22 95 cb 05 23 0e 31
fe cb 00 08 92 40 66 08
88 ac
/* 找零输出值 (64-bit) */
e0 84 b0 03 00 00 00 00
/* 找零输出脚本 (varint + data) */
19 76 a9 14 6b f1 9e 55
f9 4d 98 6b 46 40 c1 54
d8 64 69 93 41 91 95 11
88 ac
/* 锁定时间 (32-bit) */
00 00 00 00
/* SIGHASH (32-bit) */
01 00 00 00
将上述字节与 ex-tx-build.c 的 msg
输出进行比较。这也是 OP_CHECKSIG
操作码验证输入签名的消息。
完整源码在 GitHub。
你了解到构建交易的第一步是获取密钥对的 UTXO 集合。输出定义了我们需要收集的价值。交易输出值不得超过收集的 UTXO 值。找零输出解决了 UTXO 的不可分割问题。UTXO 脚本出现在其相应输入的可签名消息中。
在下一篇文章中,我们将签署并打包我们的原始交易。如果你喜欢这篇文章。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!