132010年8月15日

1840亿枚比特币诞生的那一天

一笔交易绕过金额检查,凭空产生了远超计划供应量的比特币。修复在几小时内完成,但事件留下了一个教训:货币规则能否守住,取决于执行它们的代码是否可靠。

2010年8月15日,星期日,Jeff Garzik 报告了刚刚挖出的区块74 638,以及一笔本不该存在的金额。一笔交易创建了两个输出,每个都超过920亿 BTC。两者之和接近 Bitcoin 计划供应量的九千倍。18:08:49 UTC,Garzik 在论坛上开了一个题为“Strange block 74638”的讨论帖,贴出了区块数据。几分钟内,大家就发现这既不是区块浏览器出错,也不是小数点放错了位置。网络确实接受了一个违反自身货币规则的区块。如果用户还要相信比特币不能被任意制造,就必须先找到漏洞、修复软件,再说服足够多的参与者沿另一条历史分支继续运行。

01

奇怪的区块74 638

Garzik 的帖子简短而直接,列出了交易哈希和两个巨额输出。同一讨论中随后贴出的 printblock 记录给出了精确数值:每个输出包含92 233 720 368.54275808 BTC。区块里还有一笔独立的 coinbase 交易,向矿工支付50.51 BTC。与通常只有50枚比特币的基础奖励相比,那两个输出简直荒谬:有人刚刚创造了超过1840亿个单位。

该交易背后没有知名人物或公开解释。保存下来的区块链记录了发生了什么,但没有记录谁利用了这个漏洞以及他们的目的。将作者指认为特定的黑客意味着要在故事中添加一个原始资料中没有的信息。唯一可以确定的是,这笔交易是故意设计来利用验证中的弱点。

网络接受了它,因为旧代码检查了单个输出,然后检查了它们的总和。每个巨大的输出都适合使用的数字类型,但当它们一起被添加时,超过了其上限。数字溢出到负数区域,后续的检查错误地得出结论,没有创建非法的正值。

因此,这并不是伪造签名、修改较旧的交易或占据大部分哈希率的问题。攻击者利用了特定实现处理整数的方式。数学规则“不发出超过输入的金额”本身是正确的;但是由于整数溢出,程序没有按照人们的意图执行这一规则。

该区块已经成为最长链的一部分。每个使用错误版本的节点都认为它是有效的,其他矿工也可以在其基础上继续挖矿。仅仅公开披露这个问题是不够的。社区必须比有缺陷的历史记录传播开来、巨大的交易输出开始广泛流通的速度更快地做出反应。

02

规则与机器之间的错误

2100万枚的上限常被说成 Bitcoin 不可改变的法则。这次事件却呈现了更准确的图景:上限是一组共识规则,各个程序和每一个Full Node都必须正确执行。如果所有广泛使用的节点软件都采用同一套有缺陷的实现,就可能暂时接受一个违背原本意图的状态。

整数溢出是编程中一个由来已久的问题。计算机寄存器能表示的数值范围有限;一旦越界,计算结果就可能落到数轴的另一端。对记账系统来说,这个技术细节至关重要。不仅要检查最终的总和,还要在计算过程中逐步检查,确保每一笔金额都在允许范围内。

因此,今天的 Bitcoin Core 会分别执行多项检查:输出不能为负,不能超过货币供应上限,每次相加后还要重新检查累计总额。这些代码并非凭空设想风险。它们背后有过真实的教训:少了一道保护,网络就曾记录下一笔在经济上不可能成立的交易。

这次错误也让技术规范与人们的共同预期之间的区别变得清楚。论坛上没有人认真主张,既然旧软件接受了这些巨额比特币,它们就理应有效。参与者认同的是:这违背了原先的规则。不过,共识还需要技术手段才能落实,也就是一个会拒绝错误区块的新版本。

代码与人的作用在这里交汇。代码按照确定的规则判断节点接受哪个区块;当人们发现代码与原本的规则相矛盾时,则要决定安装哪套代码。这次事件不是一次是否增发的表决,而是一场与时间的赛跑,目的是恢复社区早已视为系统基础的规则。

整数溢出的危险之处,恰恰在于机器看不出人一眼就能识别的荒谬。处理器操作的是存储在有限空间里的数字;计算一旦越界,结果就可能跳到取值范围的另一端。旧代码检查了各个输出,却没有在相加时把累计总额保持在安全范围内。因此,这笔交易通过了几项单独看来合理的检查,却仍然违背了这些条件的整体目的。修复必须落实到代码:仅在注释里写下比特币不会超过2100万枚还不够。每个节点都需要一条能在每一次计算中真正执行这一上限的规则。

03

在压力下修复

在公告发布后,论坛上出现了停止接受交易和暂时停止挖矿的建议。每一个新的区块如果建立在有缺陷的交易之上,将会增加需要放弃的工作量。在没有中心关闭开关的网络中,中本聪无法停止挖矿。他只能发布修复并说服运营者启动它。

0.3.10 版本加入了正确的金额检查,拒绝区块74 638。修复后的节点退回最后一个共同认可的有效位置,矿工开始建立不含那笔增发交易的另一条分支。一段时间里,两套历史按照各自累积的工作量竞争。

随后,Satoshi 宣布正确的链已经超过错误的链。更新后的软件执行了链重组,错误区块从活跃历史中消失。被放弃分支上本身仍然有效的交易,可以重新进入等待队列,之后再次被打包;但那些巨额输出无法通过新检查。

这种修复是有代价的。废弃分支上的矿工们损失了奖励,用户们不得不接受几个小时的历史记录不会被继续记录。在2010年,网络规模很小,协调很快。在一个规模远大的生态系统中,同样的干预将意味着更多的经济参与者、软件变体以及对修复边界的争议。

人们常把这次迅速响应讲成 Satoshi 的一次神奇干预。实际上,它由一系列能够查证的行动组成:Garzik 公开问题,其他人分析溢出原因,开发者准备修改,节点运营者安装新版本的Bitcoin 客户端,矿工则为正确分支提供工作量。作者可以提出修复方案,但网络必须真正采纳它,修复才会生效。

04

最严格的规则需要最谦逊的发展

修复后,有效比特币的数量回到了原定范围,错误产生的巨额比特币无法在继续运行的链上花费。因此,这次事故没有造成永久增发。但它也不只是测试版软件的一件奇闻。它提醒人们:对货币规则的信心,来自反复验证过的程序,而不是一句口号。

比特币通常被视为与那些由中央权威通过决策改变供应的系统相对立的。然而,即使在这里,人们也没有消失。开发者编写实现,审查者寻找错误,运营者选择版本,矿工创建区块顺序。区别在于,这种干预是公开的,每个节点都可以自己拒绝那些他们不同意的规则。

这一事件也解释了为什么今天的共识代码会如此谨慎地进行修改。一个小小的算术优化可能会产生货币上的后果。测试、更多的实现检查和缓慢的审查并不是多余的官僚主义;它们是防止重蹈几位二进制数字就能将一个不合法的数字变成看似有效数字的那一刻的防御措施。

Jeff Garzik 并没有通过一个论坛帖子就使整个网络变得安全,但他展示了公开账本的优势。每个人都可以看到这个奇怪的区块,重新计算其输出并检查修复情况。这个错误并没有被隐藏在运营者的内部数据库中,而是被整个社区所看到,并且有证据证明旧节点已经接受了它。

供应上限经受住了这次事故,但方式并非“不可改变”一词容易让人想象的那样。软件确实可能出错;这次能够恢复,是因为错误看得见,社区有共同认可、可以回归的规则,也有足够多的人实际完成了修复。1840亿枚比特币的故事并不否定供应上限。它记录了维持这一上限究竟需要什么。

这一事件也提醒我们,代码的公开性本身并不能消除漏洞。开源让人们无需等待某一家供应商,就能发现、比对并修复问题,但起决定作用的是具体参与者的关注。必须有人注意到那些不可能的数字,有人找到有缺陷的条件,运营者必须采纳更新。透明性创造了审查的可能性;唯有行动才将其转化为对网络的防御。正因如此,这个故事多年后依然重要:比特币的安全性不在于一个完美原始程序的设想,而在于精确规则、独立验证以及在不隐瞒错误的情况下做出响应的能力。

Atlas

图鉴中的人物与概念

OVFLValue Overflow IncidentGARZIKJeff GarzikTX交易RULES共识规则COREBitcoin CoreNODEFull NodeREORGReorg
  1. 01
    BitcoinTalk — Strange block 74638

    杰夫·加兹克的原始警告,精确的输出,溢出分析以及反应时间轴。

  2. 02
    BitcoinTalk — overflow bug SERIOUS

    当时的开发者协调,修复验证错误,并转换到修复后的链。

  3. 03
    Bitcoin Core — transaction consensus checks

    当前的实现对个别输出的范围以及它们的总和进行了控制。

下一个故事 · 14Slush Pool:一个来自布拉格的反对挖矿彩票的想法