你打开钱包,看到一笔转账"已确认 1 个区块"。十分钟后再看,它不见了。
没有人攻击你,也没有出错。只是网络上另一条链后来居上,而你那笔交易所在的那个块被丢掉了。
两个矿工在同一秒挖出了块。两个块都合法,都有有效的工作量证明。
网络分成了两半,各自认了一个。
现在没有裁判,没有投票,也没有人有权宣布哪个算数。这一讲讲的就是:在这种情况下,一群互不信任的人靠一条谁都能独立算出的规则,最终收敛到同一条链上。
上一讲解决了"谁能出块"。这一讲解决"出现两个块时听谁的"。
一、分叉选择规则
⚠️ 一个几乎人人都说错的细节:
规则不是"最长链",是"累计工作量最大的链"(chainwork)。
每个区块贡献的工作量 = 2²⁵⁶ / (目标值 + 1) ≈ 该块对应的难度
链的累计工作量 = 所有区块之和
⭐ 选累计工作量最大的那条链。
为什么这个区别重要:
⚠️ 攻击者可以把难度调低,快速造出一条【区块数更多】但工作量更少的链。
如果规则是"最长",这条垃圾链会赢。
⟹ 按累计工作量,它一文不值。
打个比方
“最长链"和"最重的链"的差别,像比较两摞纸。
“最长"是比页数——而页数是可以灌水的:把难度调低,就能快速印出一大摞薄纸。
“最重"是比总重量——每一页有多重,由它对应的难度决定。攻击者印再多张,那摞纸也轻。
⭐ 记法:“最长链"是在难度恒定时的简化说法。真正的规则永远是"最重的链”。
⚠️ 这个区别在正常运行时看不出来(难度稳定时,长 = 重),所以它长期被当成一个可以忽略的措辞问题。它不是:整个防御建立在"攻击者没法便宜地造出更重的链"上,而如果规则真的是比页数,这条防御就是免费可破的。
二、临时分叉与孤块
两个矿工几乎同时挖到块,网络会短暂看到两条链:
┌── 区块 101a (矿工 A)
区块 100 ──┤
└── 区块 101b (矿工 B)
⭐ 每个节点跟随自己先收到的那个,网络暂时分裂。
下一个块出现在哪一边,哪一边就赢。
输掉的那个块叫孤块(orphan/stale block),它的奖励作废——矿工的电费白花了。
孤块率取决于什么
设:区块在全网传播需要 t 秒,平均出块间隔 T 秒
在 A 的块传播的这 t 秒里,别人可能也挖出块:
⭐ 孤块率 ≈ t / T
比特币:t ≈ 几秒,T = 600 秒 ⟹ 孤块率约 0.5% ✅ 可忽略
若 T = 12 秒,t ≈ 2 秒 ⟹ 孤块率约 17% 灾难性
⭐ 这就是比特币选择 10 分钟出块间隔的核心原因——不是保守,是为了把孤块率压到可忽略。
⚠️ 而孤块率高会直接导致中心化(第 9 讲第八节):
大矿池的块在池内无需传播 ⟹ 它的孤块率更低
⟹ 同等算力下大矿池收益更高
⟹ 算力向大矿池集中
以太坊的应对:叔块奖励
以太坊 PoW 时期出块 12–15 秒,孤块率必然很高。它引入了 GHOST 的简化版:
⭐ 孤块(称为"叔块")也能被后续区块引用,并获得部分奖励。
效果:
① 小矿工的收益方差降低,中心化压力减轻
② 被引用的叔块也贡献链的"重量",让攻击更难
三、确认数:“6 个确认"从哪来
一笔交易被打包不等于安全——那条链可能被更重的链取代。每多一个后续区块,回滚的成本就翻倍增长。
中本聪在白皮书第 11 节做了这个计算:
攻击者算力占比
q,诚实方p = 1−q。攻击者落后z个块,问他最终追上的概率。
这是一个泊松随机游走问题,结果:
P(追上) = 1 − Σ(k=0..z) [ λᵏe⁻ᵛ/k! ] × [ 1 − (q/p)^(z−k) ] 其中 λ = z·q/p
代入具体数字:
攻击者算力 q = 10%
z=1 → 20.46%
z=2 → 5.10%
z=3 → 1.32%
z=4 → 0.35%
z=5 → 0.09%
z=6 → 0.024% ⭐ 这就是"6 个确认"的来源
z=10 → 0.0001%
攻击者算力 q = 20%
z=6 → 1.43% 6 个确认已经不够了
z=10 → 0.11%
攻击者算力 q = 30%
z=6 → 13.21%
z=10 → 4.17%
z=30 → 0.02% ⟹ 要达到同等安全需要 30 个确认
⭐ 两个重要推论:
① "6 个确认"不是一个绝对安全的数字,
⚠️ 它对应的是"攻击者算力 ≤ 10%"这个假设下 0.024% 的风险。
② 确认数应该按【交易金额】来定:
买一杯咖啡:0 确认可以接受
转一亿美元:应该等更久,因为攻击者愿意投入的算力更多
这个模型的三个假设
① 攻击者算力固定不变
② 诚实矿工不会察觉并做出反应(现实中交易所会暂停充值)
③ ⭐ 攻击者的目的是双花,而不是别的
四、51% 攻击:能做什么,不能做什么
⭐ 用那两摞纸来说:51% 攻击的意思是攻击者有能力持续造出更重的那一摞。注意它买到的只是"重量”——下面这一节说明,重量能改写顺序,但改不了那些需要签名才能做的事。
这是被误解最多的话题之一。
✅ 能做
① 双花:先付款收货,再重组链把付款抹掉
② 审查:拒绝把某些交易打包(但别人的块仍会包含它,除非算力接近 100%)
③ 独占区块奖励:让其他矿工的块全部变成孤块
❌ 不能做
① ⚠️ 偷别人的币 —— 没有私钥就签不出交易,
而无效签名会被【每一个全节点】拒绝
② 凭空增发 —— 违反 coinbase 上限,同样被全节点拒绝
③ 改变很久以前的区块 —— 需要重做那之后的全部工作
⭐ 这个区分回到第 3 讲的核心论点:
矿工控制的是"顺序”,不是"规则”。规则由运行全节点的人共同执行。
⚠️ 但如果没有人跑全节点,这个防线就不存在了。
攻击成本的现实
对大链,租用 51% 算力的成本极高,且 ASIC 无法大量租到(第 15 讲)。但对小链,这个攻击是真实且频繁发生的:
⭐ 一条与比特币共用 SHA-256 算法的小链,
攻击者只需从比特币算力中临时租一小部分即可。
历史上多条中小市值链遭受过成功的 51% 双花攻击。
⚠️ 所以"这条链用了 PoW 所以安全"是错的。安全性等于它的算力占该算法总算力的比例。
五、自私挖矿:真正的阈值不是 50%
2013 年,Eyal 和 Sirer 证明了一件让人不安的事:
一个算力远低于 50% 的矿工,可以通过偏离协议获得超出其算力比例的收益。
策略
① 挖到一个块 ⟹ ⚠️ 不广播,藏起来,在私链上继续挖
② 观察公链:
若公链追平(诚实方也挖到 1 个):
立刻发布自己的块,制造竞争
部分诚实算力会转到攻击者的块上
⟹ 若攻击者赢,诚实方那个块作废
若攻击者领先 ≥ 2 个块,而公链出了 1 个:
发布一个块盖过它 ⟹ 诚实方白干
若领先太多可能被追上:
全部发布,锁定收益
⭐ 核心逻辑:攻击者在私链上的工作从不浪费,而每次"抢发"都让诚实矿工的工作作废。
注意攻击者的绝对收益可能下降,但相对份额上升——而难度会随之下调,最终他的绝对收益也会回升。
阈值
设 γ 为"平局时选择跟随攻击者区块的诚实算力比例”,则攻击有利可图的门槛是:
α > (1 − γ) / (3 − 2γ)
| γ(攻击者的网络优势) | 阈值 |
|---|---|
| 0(攻击者传播最慢) | 33.3% |
| 0.25 | 30% |
| 0.5(平局时各占一半) | 25% |
| 1(攻击者传播压倒性快) | 趋近 0 |
⭐ 结论:真正的安全阈值不是 50%,而是 25% 左右——且网络连接越好的攻击者,门槛越低。
更糟的是它的动态后果:
自私挖矿有利可图 ⟹ 理性矿工加入攻击者的池子(收益更高)
⟹ 攻击者算力上升 ⟹ 收益更高 ⟹ ⭐ 正反馈,走向集中
为什么现实中没有大规模发生
① 一旦被发现,币价下跌,⚠️ 而矿工的 ASIC 资产被绑定在这条链上(第 15 讲)
② 声誉成本:矿池会流失矿工
③ 需要极好的网络连接(拉高 γ)
④ 检测手段在改善(异常的孤块模式可被观察)
但这些全部是链外的经济和社会约束,不是协议保证。 这是理解 PoW 安全性的关键:它的安全边界一部分在协议里,一部分在协议外。
六、其他几种攻击
| 攻击 | 机制 | 防御 |
|---|---|---|
| 日蚀攻击(Eclipse) | 占满受害节点的全部对等连接,让它只能看到攻击者构造的世界 | 增加连接数、锚定连接、多样化对等选择 |
| 时间戳操纵 | 微调时间戳影响难度调整 | 第 9 讲的中位数规则 + 限幅 |
| 费用狙击(fee sniping) | 链尾费用高时,矿工不去挖新块而是重挖上一个块抢手续费 | 区块奖励减半后会加剧(第 17 讲) |
| 贿赂攻击 | 用智能合约悬赏其他矿工挖某条分叉 | 无技术性防御,只能靠成本 |
七、重组的现实
1–2 个块的重组:⭐ 每天都在发生,完全正常
3+ 个块的重组: 罕见,通常意味着有问题
比特币历史上最严重的一次:2013 年 v0.8 升级引发的意外共识分歧,
造成约 24 个区块的分叉,靠社区协调回滚解决
⚠️ 注意最后这个例子的性质:它不是攻击,是两个版本的客户端对同一个区块的合法性判断不一致。共识系统里,实现的差异本身就是共识风险——这也是"多客户端实现"在以太坊被强调的原因。
八、Go 实现
package fork
import (
"math"
"math/big"
)
// ChainWork 计算一条链的累计工作量。
// ⭐ 分叉选择比较的是这个值,不是区块数量。
func ChainWork(targets []*big.Int) *big.Int {
total := new(big.Int)
two256 := new(big.Int).Lsh(big.NewInt(1), 256)
for _, t := range targets {
// 单块工作量 = 2²⁵⁶ / (target + 1)
w := new(big.Int).Div(two256, new(big.Int).Add(t, big.NewInt(1)))
total.Add(total, w)
}
return total
}
// AttackerSuccess 实现中本聪白皮书第 11 节的双花成功概率。
// q: 攻击者算力占比;z: 攻击者落后的区块数(即确认数)。
func AttackerSuccess(q float64, z int) float64 {
if q >= 0.5 {
return 1.0 // 算力过半时必然成功,只是时间问题
}
p := 1 - q
lambda := float64(z) * (q / p)
sum := 1.0
for k := 0; k <= z; k++ {
poisson := math.Exp(-lambda) * math.Pow(lambda, float64(k)) / factorial(k)
sum -= poisson * (1 - math.Pow(q/p, float64(z-k)))
}
return sum
}
// ConfirmationsFor 返回把双花风险压到 maxRisk 以下所需的确认数。
// 实际使用中应按交易金额来选 maxRisk。
func ConfirmationsFor(q, maxRisk float64) int {
for z := 0; z < 1000; z++ {
if AttackerSuccess(q, z) < maxRisk {
return z
}
}
return -1 // 该算力比例下无法达到目标风险
}
// SelfishMiningThreshold 返回自私挖矿有利可图的算力门槛。
// gamma: 平局时跟随攻击者区块的诚实算力比例。
// gamma = 0.5 时门槛只有 25%,远低于直觉中的 50%。
func SelfishMiningThreshold(gamma float64) float64 {
return (1 - gamma) / (3 - 2*gamma)
}
func factorial(n int) float64 {
f := 1.0
for i := 2; i <= n; i++ {
f *= float64(i)
}
return f
}
九、本讲小结
- ⚠️ 规则是"累计工作量最大",不是"最长"。 否则攻击者可以调低难度快速造出一条区块数更多、工作量更少的链。
- 临时分叉每天都在发生,输的块叫孤块,奖励作废。⭐ 孤块率 ≈ 传播时间 / 出块间隔。
- 这就是比特币选 10 分钟的核心原因:把孤块率压到 0.5%。若出块间隔 12 秒,孤块率会到 17%。
- 孤块率高直接导致中心化:大矿池内部无需传播,孤块率更低 ⟹ 同等算力收益更高。以太坊用叔块奖励缓解。
- “6 个确认"对应的是"攻击者算力 ≤ 10%“下 0.024% 的风险。攻击者有 30% 算力时,6 个确认的风险高达 13%,需要 30 个确认才能达到同等安全。
- 确认数应该按金额定:买咖啡 0 确认可以,转一亿应该等更久——因为攻击者愿意投入的算力随金额上升。
- 51% 能做:双花、审查、独占奖励。不能做:偷币、增发、改久远历史。矿工控制顺序,不控制规则——但前提是有人在跑全节点。
- “用了 PoW 所以安全"是错的:安全性等于算力占该算法总算力的比例。共用 SHA-256 的小链可以被租算力攻击。
- 自私挖矿把真正的阈值从 50% 拉到
(1−γ)/(3−2γ):网络条件中等(γ=0.5)时只要 25%,网络优势压倒性时趋近 0。 - 它还有正反馈:有利可图 ⟹ 理性矿工加入 ⟹ 算力更集中。现实中未大规模发生,靠的是 ASIC 资产绑定、声誉、网络条件——全是协议之外的约束。
- 2013 年的 24 块重组不是攻击,是两个客户端版本的判断不一致。 实现差异本身就是共识风险——这是多客户端实现被强调的原因。
思考题
- 构造一个例子:链 A 有 100 个块但难度很低,链 B 有 50 个块但难度很高。说明按两种规则各会选哪条,以及为什么"最长"是危险的。
- 一条链把出块间隔从 600 秒降到 60 秒,传播时间不变。孤块率变成多少?这会怎样影响小矿工?
- 以太坊的叔块奖励为什么能缓解中心化?它有没有引入新的攻击面?
- 用第三节的公式,计算攻击者算力 25% 时,把风险压到 0.1% 以下需要多少确认。
- 一笔价值 1000 万美元的交易,你会要求多少个确认?请给出你的推理过程(提示:考虑攻击者能投入多少)。
- 白皮书模型假设"诚实矿工不响应”。现实中交易所会暂停充值。这会怎样改变攻击的经济学?
- 51% 攻击者想把某个地址的余额改成 100 万 BTC。他能做到吗?请说明会在哪一步被拦下。
- 完整推演自私挖矿:攻击者领先 2 个块时公链出了 1 个块,他该怎么做?领先 1 个块时呢?
- 为什么 γ 越大攻击门槛越低?攻击者能用什么手段提高自己的 γ?
- 自私挖矿在现实中很少发生,靠的是哪些链外约束?如果这些约束消失(比如一条没有 ASIC、矿工匿名的链),会怎样?