交易构造
目录
交易构造概述
交易构造流程
目标:创建一笔发送 X BTC 给 Bob 的交易
1. 选择输入
选择足够的 UTXO 覆盖金额 + 费用
2. 构造输出
输出 1:Bob 的地址,X BTC
输出 2:找零地址(如需要)
3. 估算费用
根据交易大小和网络情况
4. 调整找零
金额 = 输入总和 - 输出总和 - 费用
5. 签名
为每个输入签名
6. 广播
发送到 P2P 网络钱包的角色
钱包需要做:
1. 维护 UTXO 列表
追踪所有未花费的输出
2. 管理私钥
安全存储和使用
3. 构造交易
选择输入、输出、计算费用
4. 签名
使用私钥签署交易
5. 广播
将交易发送到网络选择输入
UTXO 选择策略
问题:
我有多个 UTXO,应该选择哪些?
常见策略:
1. 贪心算法(Greedy)
选择最大的 UTXO,直到足够
优点:快速、简单
缺点:可能剩余很多零散 UTXO
2. 最优策略(Optimal)
最小化费用和找零
复杂度:NP 完全问题
实际:使用贪心或启发式
3. 隐私策略(Privacy)
考虑 UTXO 来源和关联
避免合并具有链上关联的 UTXO
4. 最小化 UTXO(Consolidation)
定期合并小 UTXO
减少钱包复杂性UTXO 列表示例
钱包中的 UTXO:
UTXO 1: 5 BTC (来自交易 A,输出 0)
UTXO 2: 2 BTC (来自交易 B,输出 1)
UTXO 3: 0.5 BTC (来自交易 C,输出 0)
UTXO 4: 0.1 BTC (来自交易 D,输出 2)
需求:发送 3 BTC
贪心选择:
选 UTXO 1 (5 BTC) 足够了
可能产生 5 - 3 = 2 BTC 找零
可选方案:
选 UTXO 1 + UTXO 3 = 5.5 BTC
产生 2.5 BTC 找零
选择依赖:
- 费率(每字节费用)
- 隐私考虑
- 钱包策略UTXO 排序
选择前通常排序 UTXO:
按金额排序:
大到小:选择大 UTXO
小到大:优先用小 UTXO(避免浪费)
按时间排序:
最新 → 最旧:使用最近的 UTXO
最旧 → 最新:考虑隐私,混合 UTXO 年龄
按成熟度排序:
优先使用成熟的 UTXO(确认多)
避免使用未成熟的 UTXO(可能被篡改)UTXO 管理
UTXO 追踪
钱包需要维护:
1. 已收到的 UTXO
来自其他钱包的输入
标记为 "未花费"
2. 已花费的 UTXO
被当前钱包的交易使用
标记为 "已花费"
3. 待确认的 UTXO
钱包发送的交易还未确认
临时标记(待定)
4. 成熟度
跟踪每个 UTXO 的确认数UTXO 状态机
UTXO 生命周期:
1. 创建
在交易输出中创建
2. 未花费
被记录在区块链上
可被花费
3. 待消费
被某个交易的输入引用
但交易还未确认
4. 已花费
交易已确认
UTXO 不再可用
5. 冲突
如果交易被替换或被撤销
UTXO 回到未花费状态
状态示意图:
创建 → 未花费 ← ─ ─ ┐
↓ │
待消费 ─ → 已花费
↑ │
└ ─ ─ ─ 冲突 ┘UTXO 成熟度
概念:
交易包含在区块中的时间越久,越"成熟"
定义:
成熟度 = 当前区块高度 - UTXO 所在区块高度
例子:
UTXO 在区块 100 中被创建
当前在区块 106 中
成熟度 = 106 - 100 = 6 确认
限制:
Coinbase UTXO(矿工奖励)
必须成熟 100 个区块才能花费
普通 UTXO
可在创建后立即花费(但可能未确认)
钱包策略:
1 确认:接受但有风险
6 确认:标准安全
≥ 20 确认:非常安全构造输出
标准输出类型
1. P2PKH (Pay-to-Public-Key-Hash)
锁定脚本:OP_DUP OP_HASH160 <hash160> OP_EQUALVERIFY OP_CHECKSIG
地址前缀:1(比特币主网)
例:1A1z7agoat4eiX8nSBLw6KvLncesTiGQwP
大小:25 字节
2. P2SH (Pay-to-Script-Hash)
锁定脚本:OP_HASH160 <hash160> OP_EQUAL
地址前缀:3(比特币主网)
例:3J98t1WpEZ73CNmYviecrnyiWrnqRhWNLy
大小:23 字节
3. P2WPKH (Pay-to-Witness-Public-Key-Hash, SegWit v0)
锁定脚本:OP_0 <20 字节公钥哈希>
地址前缀:bc1(比特币主网,Bech32)
例:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
大小:22 字节
4. P2WSH (Pay-to-Witness-Script-Hash)
锁定脚本:OP_0 <32 字节脚本哈希>
大小:34 字节
5. P2TR (Pay-to-Taproot)
锁定脚本:OP_1 <32 字节 Taproot 公钥>
大小:34 字节创建输出的步骤
步骤 1:获取收款人地址
address = "1A1z7agoat4eiX8nSBLw6KvLncesTiGQwP"
步骤 2:解码地址获取公钥哈希
pubkey_hash = Base58Check_Decode(address)
pubkey_hash = 0x62e907b15cbf27d5425399fbeb6d815c69a4991968b79a88
步骤 3:创建锁定脚本
script = OP_DUP + OP_HASH160 + <pubkey_hash> + OP_EQUALVERIFY + OP_CHECKSIG
script = 76 a9 14 62e907b15cbf27d5425399fbeb6d815c69a499196 88 ac
(共 25 字节)
步骤 4:指定金额
amount = 1.5 BTC = 150000000 聪
amount_bytes = 0x00ca9a3b00000000 (8 字节小端序)
步骤 5:组合输出
output = amount_bytes + script_length + script
= 00ca9a3b00000000 + 19 + 76a91462e907b15cbf27d5425399fbeb6d815c69a499196 88ac计算费用
费用概念
概念:
交易费用 = 输入总和 - 输出总和
公式:
fee = input_total - output_total
费用的来源:
- 矿工奖励(区块奖励 + 费用)
- 鼓励矿工打包交易
例子:
输入:5 BTC
支付:3 BTC
找零:1.99 BTC
费用:5 - 3 - 1.99 = 0.01 BTC费率计算
费率(Fee Rate):
单位费用 = 总费用 / 交易大小
单位:
sat/B:聪每字节
sat/vB:聪每虚拟字节(考虑 SegWit)
例子:
交易大小:250 字节
总费用:0.001 BTC = 100000 聪
费率:100000 / 250 = 400 sat/B
市场费率:
低(可能慢):10-50 sat/B
正常:50-100 sat/B
快速:100-200 sat/B
急速:200+ sat/B
费率查询:
可以查询内存池中未确认交易的费率
监控平均费率变化动态费估计
策略:根据网络情况调整费用
监控指标:
1. 内存池大小
大 → 费用上升
小 → 费用下降
2. 待确认交易数
多 → 需要更高费用竞争
少 → 可以降低费用
3. 历史费率
查看最近 N 个区块的费率
取平均或中位数
工具:
Mempool.space:
显示实时内存池状态
预估不同确认目标的费用
Bitcoind estimatefee:
- estimatefee N:在 N 个区块内的费用估计
- 返回单位:BTC/kB
例子:
estimatefee 6 → 0.00050000 BTC/kB
= 50 sat/B(合理的正常费率)处理找零
找零的重要性
概念:
如果输入 > 输出 + 费用,
剩余部分通过找零输出返回
例子:
输入:10 BTC
支付:3 BTC
费用:0.001 BTC
找零:10 - 3 - 0.001 = 6.999 BTC
找零必须:
1. 足够覆盖交易手续费
2. 不能为负(否则交易无效)
3. 如果太小,可能被视为"粉尘"找零地址
找零地址的选择:
1. 同一地址
最简单但隐私性差
区块链分析容易追踪
2. 生成新地址
更好的隐私
钱包应该管理所有地址
推荐做法
3. 变更地址(Change Address)
HD 钱包的一个分支
用于接收找零
与接收地址分开管理
HD 钱包的地址派生:
m/44'/0'/0'/0/0 → 第一个接收地址
m/44'/0'/0'/0/1 → 第二个接收地址
...
m/44'/0'/0'/1/0 → 第一个变更地址(找零)
m/44'/0'/0'/1/1 → 第二个变更地址
...找零金额的最小值
"粉尘"(Dust)的概念:
定义:
交易输出如果太小,可能不值得花费
标准限制:
- 非 OP_RETURN 输出最小:546 聪
- OP_RETURN 输出最小:0 聪
计算原理:
最小输出 = 最小费率 × 典型消费大小
假设:
- 最小费率 = 1 sat/B
- 消费大小 ≈ 546 字节(多个输入/输出)
- 最小 = 1 × 546 = 546 聪
策略:
1. 如果找零 < 546,舍入到 0 或增加到 > 546
2. 舍入到 0 意味着额外费用
3. 通常选择舍入(保证交易有效)
例子:
输入:10 BTC
支付:9.999999454 BTC(精确到聪)
计算找零:10 - 9.999999454 - 0.000000546 = 0
或者增加找零:
支付调整为:9.999998000 BTC
找零:1000 聪(值得保留)实际例子
完整交易构造例子
场景:
钱包中有 UTXO:5 BTC (TXID=abc123..., vout=0)
要发送:2 BTC 给 Bob
网络费率:50 sat/B
步骤 1:估算交易大小
1 输入 P2PKH:约 146 字节
2 输出 P2PKH:约 68 字节
总计:约 214 字节
步骤 2:计算费用
费用 = 214 × 50 sat/B = 10700 聪 = 0.000107 BTC
步骤 3:构造输出
输出 1(支付):
地址:1A1z7agoat4eiX8nSBLw6KvLncesTiGQwP
金额:2 BTC = 200000000 聪
脚本:25 字节 P2PKH
输出 2(找零):
地址:1B2y8oW5E6rH7tK9nP2qS4dG6jE8vF0xZ1
金额:5 - 2 - 0.000107 = 2.999893 BTC = 299989300 聪
脚本:25 字节 P2PKH
步骤 4:构造交易
版本:1
输入计数:1
输入:
前一 TXID:abc123...
Vout:0
脚本:72 字节签名脚本
序列:0xFFFFFFFF
输出计数:2
输出 1:2 BTC
输出 2:2.999893 BTC
锁定时间:0
步骤 5:签名
为输入计算签名:
message = 去掉签名脚本的交易序列化
签名 = ECDSA_Sign(SHA256(SHA256(message)), private_key)
步骤 6:广播
将完整的签名交易发送到 P2P 网络常见问题
Q1: 为什么总是有找零?
A: 因为 UTXO 是原子的,必须整体花费。任何剩余都必须作为新 UTXO 返回。
Q2: 如何避免被追踪?
A:
- 为每次交易使用新地址(接收和找零)
- 使用 CoinJoin 或混合服务
- 使用隐私币(但比特币本身是透明的)
Q3: 最小费用是多少?
A:
- 网络不强制最小费用
- 但矿工通常忽视非常低费用的交易
- 标准最小费率:约 1 sat/B(可能导致长时间等待)
Q4: 费用过高了怎么办?
A:
- 使用 RBF(Replace-by-Fee)提高费用
- CPFP(Child-Pays-For-Parent)加速子交易
- 下次发送时降低费率
Q5: 是否支持部分找零?
A: 比特币交易本身不支持。但可以使用多个输出来处理多个接收人的费用分摊。
