Skip to content

交易构造

目录

  1. 交易构造概述
  2. 选择输入
  3. UTXO 管理
  4. 构造输出
  5. 计算费用
  6. 处理找零
  7. 实际例子
  8. 常见问题

交易构造概述

交易构造流程

目标:创建一笔发送 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:

  1. 为每次交易使用新地址(接收和找零)
  2. 使用 CoinJoin 或混合服务
  3. 使用隐私币(但比特币本身是透明的)

Q3: 最小费用是多少?

A:

  1. 网络不强制最小费用
  2. 但矿工通常忽视非常低费用的交易
  3. 标准最小费率:约 1 sat/B(可能导致长时间等待)

Q4: 费用过高了怎么办?

A:

  1. 使用 RBF(Replace-by-Fee)提高费用
  2. CPFP(Child-Pays-For-Parent)加速子交易
  3. 下次发送时降低费率

Q5: 是否支持部分找零?

A: 比特币交易本身不支持。但可以使用多个输出来处理多个接收人的费用分摊。