在上一篇文章中,我们实现了比特币的基础组件:密码学工具、钱包系统和 UTXO 模型。这些是比特币的基石,但要让比特币真正运转起来,我们还需要一个核心组件:交易系统。
今天我们将深入探讨如何实现一个完整的交易系统,包括交易的构建、签名、验证,以及最重要的 UTXO 选择和找零机制。
一、为什么需要交易系统
在传统的银行系统中,转账很简单:从 A 账户扣除一定金额,向 B 账户增加相同金额。但比特币采用的 UTXO 模型完全不同,它更像是现金交易。
想象一下现实生活中的场景:你钱包里有一张 100 元、一张 50 元和两张 20 元的纸币。现在你要买 60 元的东西,你会怎么做?你可能会:
- 拿出 100 元纸币
- 商家收取 60 元
- 商家找零 40 元给你
这个过程中,你原来的 100 元纸币被”花掉”了,取而代之的是商家收到的 60 元和你收到的 40 元找零。
比特币的交易系统就是模拟这个过程。每笔交易都需要:
- 选择合适的 UTXO(纸币)
- 计算找零金额
- 对交易进行签名证明所有权
- 让网络验证交易的合法性
二、交易的基本结构
在开始实现之前,我们先理解交易的结构。一笔比特币交易由三个核心部分组成:
1 | 交易 (Transaction) |
每个输入都指向一个之前存在的 UTXO,并提供签名来证明你有权花费它。每个输出创建新的 UTXO,可以在未来的交易中被花费。
让我们看一个具体例子。假设 Alice 有两个 UTXO:
UTXO 集合(交易前):
| UTXO | 金额 | 所有者 | 状态 |
|---|---|---|---|
| tx1:0 | 100 BTC | Alice | 有效 |
| tx2:0 | 50 BTC | Alice | 有效 |
Alice 想给 Bob 转账 60 BTC,创建新交易:
交易输入:
| 引用 UTXO | 金额 | 签名 | 公钥 |
|---|---|---|---|
| tx1:0 | 100 BTC | Alice 的签名 | Alice 的公钥 |
交易输出:
| 输出索引 | 金额 | 接收地址 | 说明 |
|---|---|---|---|
| 0 | 60 BTC | Bob 的地址 | 转账 |
| 1 | 40 BTC | Alice 的地址 | 找零 |
交易完成后,UTXO 集合变成:
| UTXO | 金额 | 所有者 | 状态 | 说明 |
|---|---|---|---|---|
| tx1:0 | 100 BTC | Alice | 已花费 | 被新交易消耗 |
| tx2:0 | 50 BTC | Alice | 有效 | 未使用 |
| tx3:0 | 60 BTC | Bob | 有效 | 新创建 |
| tx3:1 | 40 BTC | Alice | 有效 | 找零 |
注意:Alice 原来的 tx1:0 被标记为”已花费”,不再存在于 UTXO 集合中。
三、Transaction 类:交易的核心
现在让我们实现 Transaction 类。这个类需要管理交易的输入、输出,计算交易 ID,并提供验证功能。
3.1 交易的创建
1 | export class Transaction { |
这里的关键点是:交易必须至少有一个输入和一个输出。没有输入意味着没有资金来源,没有输出意味着没有接收者。
3.2 交易 ID 的计算
交易 ID 是交易内容的哈希值,它唯一标识一笔交易。重要的是,交易 ID 的计算不应该包含签名,因为签名本身是对交易内容的 hash。如果把签名包含在内,就会形成循环依赖。
1 | private calculateId(): string { |
getContentForSigning() 方法返回的内容:
- 包含输入的引用信息(txId 和 outputIndex)
- 不包含签名和公钥
- 包含所有输出和时间戳
这样,无论签名如何变化,交易的内容始终是确定的,交易 ID 也保持不变。
3.3 金额验证
交易系统有一个基本的经济规则:输入总额必须大于或等于输出总额。差额就是矿工费。
1 | getInputAmount(utxoSet: Map<string, TxOutput>): number { |
让我们通过一个例子理解矿工费:
1 | 输入: |
这 1 BTC 的差额就是矿工费,奖励给将这笔交易打包进区块的矿工。
3.4 Coinbase 交易:特殊的第一笔交易
每个区块的第一笔交易是特殊的,它叫做 Coinbase 交易,是矿工的奖励。这笔交易没有真正的输入,因为它凭空创造了新的比特币。
1 | static createCoinbase( |
Coinbase 交易使用一个特殊的全零 txId 作为输入,表示这是新创造的比特币。在真实的比特币网络中,大约每 10 分钟就会产生一个新区块,矿工通过 Coinbase 交易获得区块奖励。
四、TransactionSigner:签名与验证
有了交易结构,我们需要一套机制来证明交易的合法性。这就是签名和验证的作用。
4.1 交易签名
签名交易就是用私钥对交易内容进行签名,证明你有权花费输入中引用的 UTXO。
1 | export class TransactionSigner { |
签名过程很直接:
- 获取交易的原始内容(不包含签名)
- 对每个未签名的输入进行签名
- 将签名和公钥存储在输入中
让我们看一个实际的签名例子:
原始交易(未签名):
1 | 输入: |
签名过程
↓
签名后的交易:
1 | 输入: |
4.2 交易验证:两层防护
验证交易是确保网络安全的关键。验证过程包含两个层次:
第一层:验证签名本身是否有效
1 | const isSignatureValid = Signature.verify(txData, signature, publicKey) |
这一步验证签名确实是由拥有对应私钥的人创建的。
第二层:验证公钥是否拥有被引用的 UTXO
1 | const sha256Hash = Hash.sha256(input.publicKey) |
只有两层验证都通过,交易才有效。即使 Bob 能创建有效的签名(第一层通过),如果他的公钥对应的地址不是 UTXO 的所有者,第二层验证就会失败。
让我们通过一个攻击场景来理解这两层防护的重要性:
场景:Bob 试图盗取 Alice 的 UTXO
1 | Alice 的 UTXO: tx1:0 (100 BTC) |
这两层防护确保了:
- 交易确实是由持有私钥的人签名的(防止伪造签名)
- 签名的人确实拥有被引用的 UTXO(防止盗用他人的 UTXO)
完整的验证代码:
1 | static verifyTransaction( |
五、TransactionBuilder:智能的交易构建器
手动构建交易很繁琐,需要选择 UTXO、计算找零、处理签名。TransactionBuilder 类封装了这些复杂性,提供了简洁的 API。
5.1 使用方式
让我们先看看如何使用 TransactionBuilder:
1 | // 简单转账 |
这种链式调用的 API 设计让代码既简洁又易读。
5.2 UTXO 选择策略:贪心算法
UTXO 选择是交易构建的核心问题。给定一个目标金额,如何从多个 UTXO 中选择最优的组合?
我们采用贪心算法:优先选择金额最大的 UTXO,直到满足需求。
1 | private selectUTXOs( |
让我们通过例子理解这个算法:
1 | 场景:Alice 需要支付 60 BTC |
如果需要支付 120 BTC:
1 | 步骤 1:排序 |
这个贪心算法的优点:
- 简单高效
- 通常能选择最少数量的 UTXO
- 减少交易大小(更少的输入意味着更小的交易体积)
5.3 完整流程
让我们通过一个完整的例子理解整个流程:
初始状态:
| UTXO | 金额 | 所有者 |
|---|---|---|
| tx1:0 | 100 BTC | Alice |
| tx2:0 | 50 BTC | Alice |
| tx3:0 | 25 BTC | Alice |
目标: Alice 给 Bob 转账 60 BTC
步骤 1:选择 UTXO
1 | 选择算法:贪心(从大到小) |
步骤 2:构建输入
1 | inputs = [ |
步骤 3:构建输出
1 | recipients = [{address: 'bob_address', amount: 60}] |
步骤 4:计算找零
1 | totalInput = 100 |
步骤 5:添加找零输出
1 | outputs.push(TxOutput(40, 'alice_address')) |
步骤 6:创建交易
1 | tx = new Transaction(inputs, outputs) |
步骤 7:签名交易
1 | 对每个输入签名 |
最终交易 (tx4):
| 项目 | 内容 |
|---|---|
| Transaction ID | tx4 |
| 输入 | |
| - tx1:0 | signature: 已签名 publicKey: Alice’s PubKey |
| 输出 | |
| - 输出 0 | 60 BTC → bob_address |
| - 输出 1 | 40 BTC → alice_address |
执行这笔交易后,UTXO 集合的变化:
| UTXO | 金额 | 所有者 | 状态 | 说明 |
|---|---|---|---|---|
| tx1:0 | 100 BTC | Alice | 已花费 | 被 tx4 消耗 |
| tx2:0 | 50 BTC | Alice | 有效 | 未使用 |
| tx3:0 | 25 BTC | Alice | 有效 | 未使用 |
| tx4:0 | 60 BTC | Bob | 有效 | 新创建 |
| tx4:1 | 40 BTC | Alice | 有效 | 找零 |
最终余额:
- Alice: tx2:0 (50) + tx3:0 (25) + tx4:1 (40) = 115 BTC
- Bob: tx4:0 (60) = 60 BTC
六、总结
在这篇文章中,我们实现了比特币的交易系统。我们学习了如何构建交易,包括选择 UTXO、计算找零和生成交易 ID。我们探讨了交易签名的两层验证机制:验证签名本身的有效性,以及验证签名者是否真正拥有被引用的 UTXO。我们还实现了 TransactionBuilder,它封装了 UTXO 选择和找零计算的复杂性,提供了简洁的链式 API。
这些组件构成了比特币价值转移的核心。有了它们,我们可以创建交易、签名交易、验证交易。Transaction 类管理交易的数据结构,TransactionSigner 负责安全性,TransactionBuilder 提供易用性。三个类各司其职,共同构建了一个健壮的交易系统。