错误出在验证规则的实现中,而不是密码学或发行计划。每个输出都是正数,但相加后溢出成了负数。输入金额与这个负数总额之间的差,随后被当成了手续费。版本 0.3.10 为每个输出和累计总额增加了上限;节点重组到修复后的分支后,错误区块便被排除在活跃历史之外。
2010年8月15日,开发者 Jeff Garzik 报告了异常区块 74 638。交易 1d5e512a9723cbef373b970eb52f1e9598ad67e7408077a82fdac194b65333c9 花费了一个价值 0.50 BTC 的输入,却产生了两个巨额输出。当时的 printblock 记录显示,每个输出为 92 233 720 368.54275808 BTC。这笔漏洞利用交易的作者一直未被可靠确认:公开记录能够证明交易数据,却不能证明创建者的身份或动机。 [1]
当时,Bitcoin 以聪为单位,用64位有符号整数表示金额。每个输出的整数值都是正数,略低于 int64 上限;记录中的十六进制数 0x7ffffffffff85ee0 也证实了这一数值。但两者之和 18 446 744 073 708 551 616 聪超出了取值范围,溢出后变成了 −1 000 000 聪。旧版验证程序将这个负数与输入金额比较,竟然接受了它。这是算术错误,而非数字签名被破解。讨论中最初的 JSON 记录在小数末位上有所不同;这里的精确计算采用 printblock 与十六进制记录共同确认的整数值。 [1]
输出总额变成 −0.01 BTC,而输入为 0.50 BTC,两者相减便产生了 0.51 BTC 的虚假手续费。因此,矿工在 coinbase 中领取了 50.51 BTC,而不是50 BTC的基础奖励。扣除已花费的输入,并计入 coinbase 超额领取的部分后,这两个巨额输出使错误分支的供应量恰好增加了 18 446 744 073 709 551 616 聪,即 2^64 聪,也就是 184 467 440 737.09551616 BTC。因此,这个数字并不只是两个输出金额的简单相加。 [2]
社区在时间压力下公开响应。Lfm 称漏洞严重,Gavin Andresen 提议快速检查每个输出是否超过 2100万 BTC,Satoshi Nakamoto 则发布更完整方案:拒绝负数或过高输出,并在每次相加后拒绝过高累计总额。Satoshi 还请矿工暂时停止,以便新的有效分支更快地追上并替换错误分支。 [2]
Satoshi 于 2010年8月15日论坛时间 23:48 宣布版本 0.3.10,距 Garzik 报告约五小时四十分钟。更新节点把漏洞交易与区块 74 638 视为无效,并从最后一个共同有效区块继续构建。同期公告后来称正确分支已超过错误分支,升级客户端可自动重组,无须手动删除 blockchain 文件。 [3]
并没有人在中央数据库中修改旧区块。当时存在两条竞争历史:旧节点按原规则接受该区块,修复节点拒绝它,只接受有效祖先之上的工作。修复分支累积更多 Proof of Work 后,网络跟随该分支,通胀区块退出活跃历史;起决定作用的是用户与矿工安装新规则及后续工作,而非某个管理员的命令。 [3]
当年加入的保护今天仍可在 Bitcoin Core 中看到。CheckTransaction 拒绝负输出、超过 MAX_MONEY 的输出,以及超出 MoneyRange 的累计总额;每次相加后检查,可防止危险总和先回绕再比较。源码注释明确引用 CVE-2010-5139,另一个重复输入检查则对应不同的 CVE-2018-17144 事件。 [4]
该事件说明,2100万枚的上限不是仅靠哈希保护的魔法常数:它由精确验证规则及其正确实现强制执行。审计必须覆盖单值范围、安全加法、coinbase 与手续费、检查顺序、边界测试向量,以及独立实现之间的一致性。历史也同时展示了早期社区快速修复的能力,以及当时对少数开发者、矿工和人工协调的高度依赖。 [5]
要获得更完整的理解,请将本词条与以下词条结合阅读: Bitcoin Core, 共识规则, 交易, UTXO, Reorg, Soft Fork.