2010 年比特币溢出事件:漏洞修复与链重组 图 1
2010 年比特币溢出事件:漏洞修复与链重组 · 图 1

一笔交易怎么造出 1844 亿枚

2010 年 8 月 15 日,区块 74638 打包了一笔在今天看来根本不可能通过验证的交易:它向两个地址各输出约 922 亿枚比特币,合计约 1844 亿——是设计总量 2100 万枚的近九千倍。漏洞不在签名而在加法:当年的节点验证”输出总和不得超过输入总和”时,用一个 64 位整数累加各输出面值,两个 922 亿级别的数字相加在二进制里绕回(整数溢出)成了一个极小的值,检查被从算术层面绕过。这正是今天每份合约审计教材都盯着溢出检查的原因,比特币为此交的学费见下方处置过程。

修复与链分歧需要分开理解

修复的核心是补足金额范围检查。Bitcoin v0.3.10 的 CTransaction::CheckTransaction 源码明确检查单个输出不能为负、不能超过 MAX_MONEY,累加后的输出总额也不能超过该上限。不能只检查相加后的某个值与输入是否相等,而忽略中间值和单项边界。这是从实际修复版代码可以复核的教训,不需要给未经确认的提交哈希冠以中本聪修复提交之名。

规则修正与网络收敛是两个步骤:升级节点不再接受包含异常交易的分支,矿工在排除该交易的历史上继续出块;旧节点和新节点能否回到同一条链,还取决于各自认可的规则与累计工作量。这里的回滚指链重组,不是管理员在数据库里直接删除用户余额。本文不把某个后来追平的高度写成“回滚到的高度”,也不承诺全体参与者在同一时刻完成升级。一般重组机制见 比特币重组与交易状态

此次事故不能称为比特币唯一一次协调链重组。BIP50 记录了 2013 年 3 月另一场链分叉:部分旧节点因 Berkeley DB 锁限制拒绝区块,0.8 节点可以处理;BTCGuild 与 Slush 将节点降级到 0.7,使算力转向旧版兼容链,最终促使 0.8 节点重组。两次事件成因不同,但后者足以否定笼统的唯一性。谈历史必须说明比较的是漏洞类型、协调行动还是链重组,而不能拿模糊口径制造纪录。

那 1844 亿枚后来怎么样了

异常输出没有留在修复后接受的主链历史中,但不能说它们从未被任何节点接受:事故之所以需要处置,正是旧实现曾经接受了这笔交易。重组也不必让原分支中的所有普通交易永久失效;没有冲突且仍满足规则的交易,可以在新分支重新确认。业务损失与确认状态变化应逐笔核对,不能仅凭异常增发总额或含糊的区块数量代替统计。

三条工程教训

第一,金额验证不能只靠编程语言的整数类型;单项范围和累加范围都应有明确检查,修复版源码提供了具体例子。第二,代码发布不等于全网同时采用。Core 的 2016 年官方声明明确解释避免内置自动更新的立场;这是一项可引用的历史设计说明,不应断言它完全由此次事故促成,见 比特币的自愿升级机制:钱包为什么不自动更新。第三,复盘应区分代码缺陷、接纳规则和运营协调。BIP50 对数据库依赖与网络分歧的记录说明,非密码学组件的行为也可能影响节点对区块的判断。

对今天的用户意味着什么

普通持有者应理解,开源协议并非永远不会出现实现缺陷,修复也需要参与者协调。但历史上一次成功收敛,不能保证下一次事故仍能在相同时限内解决。节点运营者应关注所用版本的安全公告,保留变更记录与恢复准备;支付服务应在异常分歧时评估暂停入账的必要性,不能只看一台浏览器显示的确认数字。

复盘时怎样保留可核对的边界

阅读事故材料,先问证据回答的是什么:源码可以证明某个版本采用了何种检查;官方复盘可以解释当事人的协调行动;区块记录可以展示某条分支的交易,却未必完整保留所有被放弃的历史。不同证据不能互相替代,尤其不能拿一份较晚的源码反推所有节点在事故当天的状态。

对于传播广泛却缺少直接材料的细节,例如精确修复时长、每个矿池切换时刻或某笔普通交易的最终损失,应保留未知或省略。修复版中的金额检查与 BIP50 记载的后续事故,已足以说明本文的主要结论:密码学验证、软件实现和参与者协调共同影响网络安全。承认这些边界,比用“唯一一次”或“绝不会再发生”作总结更有解释力。本文不构成投资建议。