交易验证
目录
验证概述
验证的目的
核心问题:
如何确保交易合法且不会造成伤害?
验证层级:
1. 语法验证(Syntactic)
交易格式是否正确
所有字段是否有效
2. 语义验证(Semantic)
签名是否有效
UTXO 是否存在且未被花费
3. 共识验证(Consensus)
是否符合网络的共识规则
区块是否有效
目的:
保护网络安全
防止双花
维护系统完整性谁来验证
全节点:
下载并验证所有交易
维护完整的 UTXO 集合
轻客户端:
只验证与自己相关的交易
使用 SPV(Merkle 路径验证)
矿工:
验证交易
打包到区块
收款人:
至少要求足够的确认
如果需要立即验证,运行全节点交易验证规则
基本验证
1. 大小检查
✓ 交易大小 > 0
✓ 交易大小 <= 最大大小(4MB SegWit)
✓ 交易大小 <= 区块剩余空间
2. 输入/输出检查
✓ 至少有 1 个输入
✓ 至少有 1 个输出
✓ 输入数 < 2^16
✓ 输出数 < 2^16
3. 脚本大小
✓ 解锁脚本 <= 10,000 字节
✓ 锁定脚本 <= 10,000 字节
4. 费用检查
✓ 输出总和 <= 输入总和
✓ 费用合理(> 最小中继费率)UTXO 验证
对每个输入进行验证:
1. 前一交易是否存在
✓ 前一 TXID 在区块链中
✓ 指定的输出索引有效
2. UTXO 是否未被花费
✓ 检查 UTXO 集合(utxo_set)
✓ 不在已花费列表中
3. 金额是否有效
✓ 输出金额 <= 2100 万 BTC
✓ 输出金额 > 0
4. Coinbase 特殊规则
✓ Coinbase 输出需要 100 个确认才能花费
✓ 否则拒绝(BIP 30)签名验证
关键验证步骤:
1. 签名格式
✓ DER 格式有效
✓ r 和 s 都在有效范围
2. 公钥格式
✓ 压缩格式(33 字节)或未压缩(65 字节)
✓ 公钥在椭圆曲线上
3. 签名有效性
✓ ECDSA_Verify(hash, sig, pubkey)
✗ 失败则交易无效
4. SIGHASH 类型
✓ 根据 SIGHASH 类型验证相应字段脚本验证
脚本执行
比特币脚本是栈基的语言
执行流程:
1. 初始化栈:[]
2. 按顺序读取操作码
3. 压入数据到栈
4. 执行操作码
5. 栈操作:PUSH、DUP、HASH、CHECKSIG 等
6. 最终栈顶必须为真
验证条件:
脚本执行无错误 AND 栈顶为真 → 有效
脚本执行错误 OR 栈顶为假 → 无效P2PKH 验证
锁定脚本:
OP_DUP OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
解锁脚本:
<signature> <pubkey>
完整执行:
1. 执行解锁脚本
栈:[]
push sig:[sig]
push pub:[sig, pub]
2. 执行锁定脚本
OP_DUP:[sig, pub, pub]
OP_HASH160:[sig, pub, hash(pub)]
push hash:[sig, pub, hash(pub), expected_hash]
OP_EQUALVERIFY:[sig, pub](如果相等)
OP_CHECKSIG:[] ✅(如果签名有效)
任何步骤失败 → 交易无效脚本验证的限制
比特币脚本的故意限制:
1. 不是图灵完整的
没有循环(只有 WHILE 被禁用)
没有递归
操作码有限
2. 有执行时间限制
操作码不能超过 201
栈大小有限
3. 有内存限制
避免 DoS 攻击
原因:
保证脚本快速执行
防止恶意脚本锁定网络双花防护
双花的本质
双花问题:
同一个 UTXO 被两笔交易同时花费
例子:
UTXO A(5 BTC)
交易 1:A → Bob (5 BTC)
交易 2:A → Charlie (5 BTC)
问题:A 被花费两次!
解决方案:
1. UTXO 集合(账本)
2. 共识机制
3. 区块链组织防双花的机制
内存池防护:
1. 交易进入内存池时
检查输入是否已在内存池中
如果已存在 → 拒绝(mempool conflict)
2. 内存池中的冲突
两笔交易不能使用相同输入
第二笔交易被拒绝
区块链防护:
1. 区块确认
一旦交易进入确认的区块
输入被标记为已花费(从 UTXO 中移除)
2. UTXO 集合维护
节点维护所有未花费输出的列表
任何新交易的输入必须在 UTXO 中
验证失败 → 拒绝
重组防护:
1. 长链规则(Longest Chain Rule)
节点接受最长的链
2. 确认数
6 个确认通常被认为"已最终确定"
极难逆转
3. 51% 攻击
需要控制多数算力才能重组交易替换(RBF)
概念:
使用更高费用的交易替换原交易
机制:
BIP 125 规则:
信号 RBF:序列号 < 0xFFFFFFFF
选择 RBF:替代交易支付更高费用
替换条件:
1. 新交易使用相同输入的至少一个
2. 新交易费用 >= 旧交易费用 + 新增费用
3. 替代交易不会被拒绝的其他原因
优势:
交易发送太低费用?可以提高
避免无限期等待
风险:
攻击者可能滥用 RBF
接收者应等待足够确认再视为最终共识验证
共识规则的必要性
不同节点需要达成一致:
同一笔交易的有效性
同一个区块的有效性
同一条链的有效性
共识规则定义了:
什么是有效交易
什么是有效区块
什么是有效区块链
所有节点遵循相同的规则
→ 全网达成一致
→ 比特币网络正常运行硬分叉 vs 软分叉
硬分叉(Hard Fork):
新规则比旧规则更严格
旧节点无法识别新交易/区块
导致永久网络分裂
例子:
增加区块大小限制
改变 PoW 算法
结果:
没有升级的旧节点被抛弃
形成两条独立的区块链
软分叉(Soft Fork):
新规则比旧规则更严格
但旧规则仍认为新交易/区块有效
向后兼容
例子:
添加新的操作码(旧节点忽略)
引入新的脚本类型
结果:
旧节点可继续运行
但无法完全验证新功能
通常需要升级获得完整功能
验证的关键区别:
硬分叉:必须升级或被分叉
软分叉:旧节点可兼容,但功能受限交易优先级
优先级计算
公式:
priority = (sum of input values * input age) / tx_size
优先级 = (输入总值 × 输入年龄) / 交易大小
例子:
输入 1:2 BTC,100 个确认 → 2 × 100 = 200
输入 2:3 BTC,50 个确认 → 3 × 50 = 150
总计:350
交易大小:250 字节
优先级 = 350 / 250 = 1.4
含义:
高优先级 = 旧的、高价值的 UTXO
低优先级 = 新的、低价值的 UTXO优先级对应的费用
费用规则:
1. 高优先级交易
priority > 57,600,000
可免费中继(不需要费用)
矿工优先打包
2. 中等优先级
57,600,000 > priority > 1,000,000
需要最小费用或较低费用
通常会被打包
3. 低优先级
priority < 1,000,000
需要更高的费用
可能等待较长时间
注:规则随时间调整,上述值仅参考常见错误
交易被拒绝的原因
1. 无效签名
ECDSA_Verify 失败
→ 拒绝
2. UTXO 不存在
输入指向不存在的交易
→ 拒绝
3. UTXO 已被花费
输入已在其他交易中
→ 拒绝(double spend attempt)
4. 费用不足
对于低优先级交易
→ 拒绝或进入低费内存池
5. 脚本错误
脚本语法错误
→ 拒绝
6. 大小过大
超过最大大小限制
→ 拒绝
7. Coinbase 规则
Coinbase 输出未满足 100 个确认
→ 拒绝常见问题
Q1: 交易被拒绝后可以重新发送吗?
A:
- 可以,使用 RBF(Replace-by-Fee)
- 或等待原交易超时(通常 72 小时)
- 然后使用新交易重新发送
Q2: 验证一笔交易需要多长时间?
A:
- 对于简单交易(1 输入 2 输出):几毫秒
- ECDSA 验证:约 1 毫秒/签名
- 复杂交易或多重签名:可能 10+ 毫秒
Q3: 交易可以验证但不被打包吗?
A:
- 可以,如果费用太低
- 交易有效但在内存池中等待
- 最终可能因超时被删除
Q4: 一个交易可以有多个签名吗?
A:
- 可以,使用多重签名或多输入交易
- 每个输入都需要对应的签名
- 所有签名都必须有效
Q5: 何时可以认为交易"最终"?
A:
- 进入一个确认的区块:基本安全
- 6 个确认:标准安全
- 100+ 确认:极其安全
