作者:Kalle Rosenbaum & Linnéa Rosenbaum

比特币是由人开发出来的。人们编写软件,然后运行这个软件。当安全漏洞或者严重的 bug 被发现的时候 —— 这两者真的有区别吗?—— 也总是由人来发现的;自始至终,是我们这样的血肉之躯。本章思考当漏洞被发现的时候,人们做了什么,该做什么、不该做什么。第一节会解释 “尽责披露(responsible disclosure )” 这个术语的含义,它指的是在发现漏洞之后,如何负责任地行动,以尽可能减少它所造成的伤害。剩余的章节会带你游览历史上在比特币软件中发现的一部分最为严重的漏洞,以及开发者、矿工和用户们是如何处理它们的。在比特币的童年期,许多事情并不像今天一样严密。
9.1 尽责披露
设想你在 Bitcoin Core 软件中发现了一个 bug,这个 bug 让他人可以远程关闭一个 Bitcoin Core 节点,只要通过网络发送一些特殊构造的消息就行。再设想,你并无恶意,也不希望这个漏洞被利用。那你要怎么做?你要是保持沉默,没准有人同样会发现这个漏洞,可那个人并不一定像你这么善良。
在披露一项安全问题的时候,披露它的人应采取 尽责披露 流程。比特币开发者们也常常使用这个词,Wikipedia 解释它的含义是:
硬件和软件的开发者常常需要时间和资源来修复他们的错误。通常,发现这些漏洞的会是有道德操守的黑客【1】。黑客和计算机安全科学家可以选择将引起公众对这些漏洞的注意作为自己的社会责任。隐藏问题可能会带来虚假的安全感。为避免此事,相关的各方会协调和协商一个合理的漏洞修复时间窗口。基于漏洞的潜在影响、开发和引用紧急修复或变通措施所需的预期时间,以及其它因素,这个时间窗口会在几天都几个月不等。
—— Wikipedia,“尽责披露” 词条
这意味着,如果你找出了一个安全漏洞,你应该报告给负责开发这个系统的团队。但在比特币世界里,这要怎么做呢?如章节 7.1(中文译本)所述,没有人控制着比特币,只有一个比特币开发的焦点,也就是 Bitcoin Core 的 Github 代码库。这个代码库的维护者负责其中的代码,但他们不对整个比特币系统负责 —— 也没人付这个责任。此外,通用的最佳习惯是给 [email protected] 发送邮件。
在一封 2017 年的名为 “尽责披露 bug” 的邮件中,Anthony Towns 尝试了总结他认为是最佳操作的做法。他收集了来自多个信息源和不同人的输入,以呈现他对这个话题的看法。
- 漏洞应该通过 bitcoincore.org 网站【0】上的安全页面来报告
- 致命漏洞(马上可以被利用的,或者已经被利用且造成了重大伤害的)将这样处理:
- 尽快发布补丁
- 广泛通知需要升级(或者禁用受到影响的系统)
- 尽可能少披露实际问题,以推迟攻击【1】【2】
- 不致命的漏洞(因为利用起来困难或者昂贵)将这样处理:
- 在常规的开发流程中打上补丁并审核
- 从 master 分支向后移植修复或变通措施到当前的发行版本中【2】
- 开发者将尝试发布修复措施也不揭晓漏洞的性质,办法是向有经验但还不知情的开发者提供拟议的修复措施,告诉他们这修复了一项漏洞,请求他们定位漏洞【2】
- 在修复措施被发行和得到广泛采用之前,开发者可以建议其它比特币实现采用漏洞修复措施,如果可以做到不暴露漏洞的话;比如,如果修复措施具有显著的性能优势,可以作为合并这些代码的理由【3】
- 在漏洞揭晓之前,开发者将一般地建议友好的山寨币开发者跟进这项修复。但这只能在修复措施已在比特币网络中广泛部署之后【4】
- 开发者一般不会通知曾经表现出敌意的山寨币开发者(比如,使用漏洞来攻击其他人,或者违反禁令)【5】
- 比特币开发者不会在超过 80% 的比特币节点部署修复之前公开漏洞。漏洞披露者将被鼓励和要求遵循相同的策略【1】【6】
—— Anthony Towns,“bug 的尽责披露” 邮件楼,Bitcoin-dev 邮件组(2017)
上述清单表明了,在给比特币发布补丁时应该多么谨慎,因为补丁可能会泄露漏洞的位置。第四点尤其有趣,因为它解释了如何测试补丁是否被隐藏得足够好。实际上,如果少数经验丰富的开发者都无法在明知它是修复措施时定位漏洞,那其他人想要找出来可能就很难了。
在这封邮件之前,邮件楼里的邮件讨论的是要不要、什么时候、怎么向山寨币开发者和其它比特币实现的开发者披露漏洞。其实没有干脆地答案。“帮助好人” 看起来是合理地,但谁能确定他们是不是好人呢,那条界线在哪里?Bryan Bishop 主张,帮助山寨币甚至是诈骗币保护自己免于安全爆破是一种道德责任。
保护比特币及其用户免于眼前的威胁是不够的,还有一种更广泛的责任,是保护所有类型的用户和不同的软件免于无论什么形式的多种多样的威胁,哪怕这些人正在使用愚蠢和不安全的软件,而你个人不维护、不开发也不推荐使用它。处理对一项漏洞的知识是一件棘手的事,而你可能接收到带有比其初始描述具有更严重直接或间接影响的知识。
—— Bryan Bishop,“bug 的尽责披露” 邮件楼,Bitcoin-dev 邮件组(2017)
在 Towns 的邮件前面还有 Gregory Maxwell 的邮件,他在其中主张安全漏洞可能比看起来的更加严重。
我曾多次看到,一个被认为难以利用的问题,事后被证明是很容易利用的,只要你找到正确的机关;或者一个微小的 DoS 问题事后证明是严重很多的问题。
简单的性能 bug,如果精心部署,也可能用来割裂网络 —— 矿工 A 和交易所 B 在一个网络里,其他人在另一个网络里,然后就有人能重复花费了。
等等等等。所以,虽然我绝对同意,不同的事情应该(并且也可以)用不同方式处理,但并不总是有那么清晰的界限。主要还是得谨慎:认为问题会比你已知的更加严重。
—— Gregory Maxwell,“bug 的尽责披露” 邮件楼,Bitcoin-dev 邮件组(2017)
所以,即使一个漏洞看起来是难以利用的,也最好假设它是容易利用的 —— 只是你还没搞清楚怎么利用它。
Gregory 还提到,“认为我们这个邮件楼是在讨论 ‘披露’,是不正确的。披露指的是你告诉供应商。这个邮件楼说的是 ‘公开’,它们的影响是不一样的。公开是指你确定你也告诉了潜在的攻击者。” 这最后一个观察,关于披露和公开的区别,是重要的。尽责披露还是简单的部分,合理公开才是难的部分。
9.2 童年阴影
比特币最初只是一个个人项目(至少,从它的创建者的假名来看,是一个人),那时候比特币几乎没有价值。因此,漏洞披露和 bug 修复的动作都不像现在这么严格。
Bitcoin Wiki 有一个比特币经历过的公开漏洞(CVE)清单。本章会介绍其中一部分的安全漏洞,以及比特币早年发生的意外。我们不会介绍所有漏洞,只选取其中我们觉得特别有趣的。
9.2.1 2010-07-28:花费任何人的钱币(CVE-2010-5141)
在 2010 年 7 月 28 日,用名 “ArtForz” 的匿名人披露了 bitcoin 软件版本 0.3.4 中包含的一个漏洞,它让任何人都能花费其他人的钱币。ArtForz 尽责地 报告这个漏洞给了中本聪和另一位名为 “Gavin Andresen” 的开发者。
问题是,当时的脚本操作码 OP_RETURN 会直接退出程序的执行,所以,如果被花费的钱币的脚本公钥是 <pubkey> OP_CHECKSIG ,而脚本签名是 OP_1 OP_RETURN,那么脚本公钥中的程序就完全不会执行。唯一会发生的事就是 1 被推入堆栈,然后 OP_RETURN 导致脚本公钥中的程序被跳过。而程序执行完之后,堆栈顶部的任何非零的值都意味着花费条件得到满足。因为栈顶的元素是 1,不是 0,所以花费能够成功。
这是当时处理 OP_RETURN 的代码:
case OP_RETURN:
{
pc = pend;
}
break;
pc = pend; 的效果就是让程序的剩余部分被跳过,意味着脚本公钥中的任何锁定脚本都会被无视。修复措施之一是改变 OP_RETURN 的含义,使之立即传出(脚本执行)失败。
case OP_RETURN:
{
return false;
}
break;
中本聪在自己的代码库中完成了这项变更,然后编译出了一个可执行的二进制问题,使用 0.3.5 的版本号。然后,他在 Bitcointalk 论坛中发帖 “警告!请尽快升级到 0.3.5(***** ALERT *** Upgrade to 0.3.5 ASAP)”,催促用户安装这个二进制文件,但并不提供它的源代码。
请立即升级到 0.3.5 版本!我们修复了一个实现上的 bug,它让虚假交易可能被网络接受。在升级到 0.3.5 版本以前,不要接受任何比特币支付!
—— 中本聪,Bitcointalk 论坛(2010)
最初的消息后面经过了编辑,其完整的形貌已经不可知了。上面的片段来自一个带有引用的回复。一些用户试用了中本聪的二进制文件,但在运行时遇到了问题。很快,中本聪就说:
还没有来得及更新 SVN 。请等待 0.3.6 版本,我正在开发。这期间你可以关闭自己的节点。
—— 中本聪,Bitcointalk 论坛(2010)
(译者注:此处的 “SVN” 应指 “Apache Subversion”,一种开源的版本控制系统。)
35 分钟之后,他说:
SVN 已经在 0.3.6 版本中更新。
正在上传 0.3.6 的 Windows 系统下执行文件到 Sourceforge ,然后我会重新编译 Linux 系统下的执行文件。
—— 中本聪,Bitcointalk 论坛(2010)
(译者注:“Sourceforge” 是一个上传代码供其他人下载的网站。)
这时候,他似乎也更新了最初那个帖子,将 “0.3.5” 改成了 “0.3.6”:
请立即升级到 0.3.5 版本!我们修复了一个实现上的 bug,它让虚假交易可能被显示为已得到接受。在升级到 0.3.5 版本以前,不要接受任何比特币支付!
如果你现在还无法升级到 0.3.6,最好先关闭你的比特币节点,升级后再重启。
0.3.6 还实现了更快的哈希运算:
- 感谢 tcatm,实现了中间状态缓存的优化
- 感谢 BlackEye,加入了 Crypto++ ASM SHA-256
最终加速效果是 2.4 倍。
下载页:
http://sourceforge.net/projects/bitcoin/files/Bitcoin/bitcoin-0.3.6/
Windows 和 Linux 用户:即使你拿到了 0.3.5 版本,也依然要升级到 0.3.6 版本。
—— 中本聪,Bitcointalk 论坛(2010)
请注意两条消息对问题的措辞不同:最开始那条说的是 “(虚假交易)被网络接受”,后面这条说的是 “显示为被接受”。也许中本聪为了让人们不要过多关注真正的问题而淡化了问题的严重性。不管怎么说,升级到 0.3.6 版本的人就能如常使用比特币了。这个问题被解决了,而且很神奇的是,没有人丢失自己的比特币。
中本聪的消息也提到了挖矿上的性能优化。尚不清楚为什么要在一个关键的安全修复中包含这个,但有可能也是为了混淆真正的问题。不过,看起来更有可能的是,他只是发布了在 Subversion 代码库的开发分支上的东西(而不管这些东西是什么),只是在其中添加了安全修复。
那时候,比特币的用户远远没有今天那么多,比特币也几乎没有价值。如果今天还这样应对 bug,那一定会被当成一场大型狗血剧:
- 中本聪推出了一个仅包含二进制文件的 0.3.5 版本,在其中包含修复。不发行补丁,也不提供源代码,可能会被当成混淆问题的措施。
- 0.3.5 甚至无法运行。
- 0.3.6 中的修复措施实际上是一次硬分叉,如章节 5.2(中文译本)所解释的。
另一个可以争议的事情是,让用户关掉自己的节点,究竟是好事还是坏事?今天不可能再这样干了,但那时候,许多用户都主动关注论坛上的更新,而且通常也很专业。因此,那时候是可能做到的,可能也是个合理的措施。
9.2.2 2010-08-15 合并的输出数值溢出(CVE-2010-5139)
在 2010 年 8 月中旬,Bitcointalk 论坛的用户 “jgarzik”,也就是 Jeff Garzik,发现区块高度 74638 中的一笔交易的两个输出具有罕见的高价值:
区块 #74638 中的这个 “输出” 非常奇怪:
"out" : [ { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0xB7A73EB128D7EA3D388DB12418302A1CBAD5E890 OP_EQUALVERIFY OP_CHECKSIG" }, { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0x151275508C66F89DEC2C5F43B6F9CBE0B5C4722C OP_EQUALVERIFY OP_CHECKSIG" } ]92233720368.54277039 BTC?那不就是 UINT_64(无符号 64 比特整数)的最大值吗?
—— Jeff Garzik,Bitcointalk 论坛(2010)
很有可能这是一个 bug,导致两个输出的价值 —— 是 int64(有符号 64 位整数),而不是 Garzik 认为的是 uint_64 —— 的和溢出成了一个负值 -0.00997538 BTC 。从而,无论输入的价值是多少,输出的 “和” 都更小,从而根据当时的代码,这是一笔有效的交易。
在这种情况下,这个 bug 已经因为一次真实的爆破而公开了。这一爆破的不幸好过是,大约 2 x 920 亿 BTC 被创造了出来,严重稀释了当时大约只有 370 万 BTC 的货币供给量。
在一个相关的帖子中,中本聪表示如果有人能停止挖矿(当时叫做 “生产(generating)”),他会很感激。
如果人们能停止生产,会有所帮助。我们可能需要另外挖出一条区块链分支,人们在当前这条分支上生产得越慢,我们就能越快建立另一条分支。
第一个补丁将放在 SVN rev 132 中。还没上传。我还在推入一些杂项的变更,需要先完成,然后我就会上传补丁。
—— 中本聪,Bitcointalk 论坛(2010)
他的计划是,制作一个软分叉,让上面这样的交易变成无效的,从而作废掉包含这样的交易的区块(尤其是上述位于 74638 高度的区块)。不到一个消失之后,他就在 Subversion 代码库的 revision 132 中提交了一个补丁,并在论坛中发帖,讲了他认为用户应该做的事:
补丁已上传到 SVN rev 132!
就目前而言,推荐的步骤:
1)关停节点。
2)下载 “knightmb” 的 blk 文件(替代掉你的 blk001.dat 和 blkindex.dat 文件)。
3)升级软件。
4)软件会从少于 74000 个区块开始同步。让它重新下载剩余的区块。
如果你不想使用 knightmb 的文件,你可以直接删掉你的 blk*.dat 文件(以 “blk” 开头的 “,dat” 文件),但如果每个人都同时下载全部的区块,会给网络造成很大的负担。
我很快会编译出发行版。
—— 中本聪,Bitcointalk 论坛(2010)
他希望人们从某一个用户(“kinghtmb”)那里下载区块数据:这人发布了在自己的硬盘上出现的区块链,也即 blkXXXX.dat 文件和 blkindex.dat 文件。用这样的办法下载区块数据(而不是从头重新同步)的理由是减少网络的带宽瓶颈。
这里有一个很大的问题:用户从 knightmb 那里下载到的区块,在 Bitcoin 软件启动时不会经过验证;blkindex.dat 文件已经包含了 UTXO 集,软件会直接接受其中的任何数据,当成是已经验证过了。knightmb 可以篡改数据,给他自己或其他人凭空增加一些比特币。
(译者注:这种可能性确实存在,然而,一旦用户以重新编制索引的启动选项运行软件,就会曝光;说到底,尽管用户替换掉这两个文件之后,软件启动时不会自动重新验证,但用户是可以让软件重新验证的。)
再一次,人们似乎听从了这些建议,回滚无效区块及其后代的行动成功了。矿工开始在原来的 74637 高度的区块后挖矿,第一个后续区块出现在 UTC 时间 23:53,也就是发现问题的大概 6 个消失后。到了第二天(8 月 16 日)的 08:10 ,在区块高度 74689,新的链取代了旧的链,因此所有未升级软件的节点也重组为跟随新的链。这是比特币历史上最深的区块重组 —— 达到了 52 个区块。
相比 OP_RETURN 引起的问题,这个问题的处理方式可以说更聪明了:
- 发布了不仅只有二进制文件的补丁
- 发行的新软件的工作符合预期
- 没有硬分叉
在问题处理期间,用户被请求停止挖矿。我们可以讨论这是不是一个好主意,但假设你是一个矿工,并且你也被说服了在坏区块之后挖出的区块最终都会在一次很深的区块链重组中被消灭:那么为什么你要浪费资源来挖掘这些区块呢?
你可能也认为,中本聪的建议 —— 从某个陌生人的硬盘下载区块链和 UTXO 集 —— 有点可以。如果真是这样,你是对的:确实是可疑的。但是,在那种情形下,这种紧急反应也是合理的。
这一次的事故处理与 OP_RETURN 那一次还有一个重大区别:这个漏洞真的被利用了,因此修复也可以更直接一些。而在 OP_RETURN 的事故中,开发者必须模糊化修复措施,而且公开的声明也不能直接揭晓问题是什么。
9.2.3. 2013-03-11 DB 锁问题 0.7.2 - 0.8.0(CVE-2013-3220)
一个非常有趣,同时也有教育意义的问题出现在 2013 年 3 月。当时区块链似乎在高度 225429 之后出现了分裂(这也是下面这段引文中的 “分叉(fork)” 的意思)。这次事故的细节在 BIP50 中披露了。该文档的总结说:
在被挖出和广播的一个区块中,交易输入的总数量超过了以往观测到的最大值。Bitcoin 0.8 节点可以处理这个区块,但一些 0.8 版本以前(pre-0.8)的 Bitcoin 节点就拒绝了它,导致了意料之外的区块链分叉。与 0.8 及以往版本不兼容的链(下文称为 “0.8 链”)这时候聚集了 60% 的挖矿算力,使得这次分裂无法自动解决(如果 pre-0.8 链的算力能打败 0.8 链,那就会强迫 0.8 节点重组到 pre-0.8 链上,从而自动解决分裂)。
为了尽快恢复一条公认的区块链,BTCGuild 和 Slush 将他们的节点软件从 Bitcoin 0.8 降级到了 0.7 ,从而他们的矿池也会拒绝那个更大的区块。这让占据多数的算力回到了没有那个更大区块的链上,最终让 0.8 节点重组到 pre-0.8 链上。
—— 多位 Bitcoin Core 开发者,BIP50(2013)
在这次危及中,BTCGuild 和 Sluch 两个矿池所采取的迅速行动令人印象深刻。他们能将占据多数的算力转移到分裂的 pre-0.8 分支上,从而帮助重建共识。这给了开发者时间来开发可持续的修复措施。
这个事件中的另一个同样有趣的问题是,版本 0.7.2 与自身不兼容,以往的版本也一样。这在 BIP50 的 “根本原因” 章节中有解释:
这个不够高的 BDK 锁配置,隐式地成了一个决定区块有效性的网络共识规则(尽管这是一个不一致也不安全的规则,因为在不同节点上锁的使用量可能会有区别。
—— 多位 Bitcoin Core 开发者,BIP50(2013)
简而言之,问题在于,Bitcoin Core 软件用来验证区块的数据库锁的数量不是确定性的。一个节点可能需要 X 个锁,而另一个节点可能需要 X+1 个锁。节点对一个区块可以占用的锁的数量还有限制。如果需要的锁的数量超过了这个限制,这个区块将被认为是无效的。所以,如果 X+1 超过了这个限制,而 X 没有超过,那么这两个节点就会分裂成两个分支,互不同意对方那条是有效的。
最终选出的解决方案,除了上述两大矿池所采取的紧急行动,还有:
- 在 0.8.1 版本限制区块,不仅限制其体积,还限制其锁的数量。
- 给旧版本(0.7.2 及部分旧版本)打上补丁,使用同样的新规则,同时提升全局的锁限制。
除了在第二点提到的提高全局锁数量限制,这些规则都被实现为临时规则,在一段预先确定的时间窗口内使用。计划是当绝大多数节点都升级了,就移除这些限制。
这项软分叉显著地降低了共识故障的风险,而几个月后,在 5 月 15 日,临时规则在整个网络中一致停用。请注意,这一反激活本质上也是一次硬分叉,但并没有争议。而且,它是跟以前的软分叉一起发布的,所以运行软分叉后软件的人都知道这个硬分叉会随后到来。因此,在这个硬分叉激活的时候,绝大部分节点都保持了同步。但是,也依然有少量没有升级的节点在这个过程中被挤出了网络。
你可能会好奇:如果发生在今天,还能这么应对吗?今天的挖矿领域变得更加复杂了,可能分裂的两边都会有哈希算力,就难以像 BIP50 那样足够快地发布补丁。说服在 “错误” 分支上的矿工放弃自己的区块奖励可能也有难度。
9.2.4 BIP66
BIP66 是有趣的,因为它显示出了以下事物的重要性:
- 好的择优密码学
- 尽责披露
- 部署修复而不揭晓漏洞
- 在经过验证的区块上挖矿
BIP66 是一项提案,提议收紧放在比特币脚本中的签名的编码规则。提案的动机是能够用 OpenSSL 软件以外的软件和代码库来解析签名(原来你甚至只能用最新版本的 OpenSSL)。OpenSSL 是一个通用密码学的代码库;那时候 Bitcoin Core 还使用它。
这个 BIP 在 2015 年 7 月 4 日激活。不过,除了上述完全真实的内容以外,BIP66 还修复了一项在 BIP 中没有提到的严重得多的问题。
漏洞
这个漏洞到 2015 年 7 月 28 日,才由 Pieter Wuille 在一封发往 Bitcoin-dev 邮件组的邮件中完整公开:
各位好,
我希望披露一项我在 2014 年 9 月发现的漏洞;在 BIP66 的部署率在本月初达到 95% 的阈值之后,该漏洞已经成为无法利用的了。
简要说明
一笔专门制作的交易,可能让区块链在以下三类节点间分裂:
- 在 32 位系统和 64 位 Windows 系统上使用 OpenSSL 的节点
- 在非 Windows 的 64 位系统(Linux、OSX,等等)上使用 OpenSSL 的节点
- 使用一些非 OpenSSL 代码库来解析签名的节点
—— Pieter Wuille,《披露:由 BIP66 直接解决的共识 bug》,Bitcoin-dev 邮件组(2015)
这封邮件进一步列出了发现这项漏洞的细节,以及导致这个漏洞的更具体原因。最后,Pieter 给出了事件的时间线;我们会回顾其中最重要的几个事件。如图 13 所示,其中一些已经讲过了。

- 图 13. 围绕 BIP66 的事件的时间顺序。加粗的部分在上文中讲过了 -
披露之前
在还没有任何人知道这个问题的时候,它可以被现在已经撤回的 BIP62 解决:该 BIP 可以减少出现交易熔融性(transaction malleability)的可能性。BIP62 所提出的变更之一是收紧关于签名编码的共识规则,也称为 “严格 DER 编码”。Pieter Wuille 在 2014 年 7 月为该 BIP 提出了一些调整,就可以解决这个问题:
- 2014 年 7 月 18 日:为了让比特币的签名编码规则不依赖于 OpenSSL 的具体解析器,我修改了 BIP32 提议,使其严格 DER 签名要求也适用于版本号为 1 的交易。那时候还没有非 DER 签名将进入区块,所以可以假设它不会直接影响任何人。详见 https://github.com/bitcoin/bips/pull/90 和 http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-July/006299.html 。那时候还不知道,但如果 BIP66 得到了部署,就能解决这个漏洞。
—— Pieter Wuille,《披露:由 BIP66 直接解决的共识 bug》,Bitcoin-dev 邮件组(2015)
因为这个 BIP 的覆盖面太广,远远超过 “严格 DER 编码” 这一项,所以它一直在改变,迟迟得不到部署。后来,这个 BIP 被呈网状了,因为 “隔离见证” BIP141 以另一种更加完整的方式解决了交易熔融性问题。
披露之后
OpenSSL 发布了带有补丁的新版本软件;如果在一开始就在 Bitcoin 软件中使用这些补丁(新软件),也将解决问题。然而,仅仅在新版本的 Bitcoin Core 使用新版本的 OpenSSL,会让问题变得更糟糕。Gregory Maxwell 在 2015 年 1 月的另一个邮件楼中解释了这一点:
虽然对于绝大多数应用来说,急切地拒绝某些签名通常是可以接受的,但比特币是一个共识系统,也就是所有的参与者都必须一般地同意输入数据的有效性或无效性。可以说,一致性比 “正确性” 还要重要性。
……
然而,上面这个补丁,只能修复这个广泛问题的一个症状:在共识规范的行为中依赖于不是为共识用途而设计和分发的软件(尤其是 OpenSSL)。因此,作为一项增量提升,我提议尽快使用一次专门软分叉来强制执行更严格的 DER 兼容性,就使用 BIP62 的一个子集。
—— Gregory Maxwell 论 OpenSSL 升级,Bitcoin-dev 邮件组
他指出,使用无意在共识系统中服役的代码会带来严重的风险,并提议比特币实现严格的 DER 编码。这是择优密码学 —— 我们在章节 7.4(中文译本)中讨论过 —— 的重要性的一个极好的例子。
这些事件可能会给你一种印象 —— Gregory Maxwell 知道 Pieter Wuille 日后公开的那个漏洞,但想以 “预防” 的名义暗度陈仓,而不引起对真正问题的太多关注。可能是这样,但这都只是猜测。
然后,如 Maxwell 所提议的,BIP66 作为 BIP62 的一个子集而被提出,它仅仅指定严格的 DER 编码。这个 BIP 显然被广泛接受了,在 7 月就部署了,然而讽刺的是,还是因为 “无验证挖矿(validationless mining )” 而出现了两次区块链分裂。这些分裂会在下一节中讨论。

此中还可以得到一个关键结论:一个 BIP 或多或少应该是 原子化 的,意思是它应该足够完整,提供了有用的东西或者解决了一个具体的问题,但同时要足够小,能够获得用户的广泛指出。你在一个 BIP 中放置的东西越多,它被接受的概率就越小。
因为无验证挖矿而出现的区块链分裂
不幸的是,BIP66 的故事到这里还没做结束。在 BIP66 激活的时候,事后证明是非常混乱的,因为一些矿工在收到区块后并不验证,而是直接在后面开始挖矿。这就叫 “无验证挖矿”,或者叫
“SPV 挖矿”(“SPV” 的意思是 “简易支付验证”,也只验证区块头)。一封警告消息被发送给了比特币节点,其中附有一个描述这个问题的网页:
在 2015 年 7 月 4 日凌晨,(BIP66)已抵达 950/1000(95%)的阈值。但不久之后,一个小矿工(未升级的 5% 的其中之一)就挖出了一个无效的区块 —— 正如人们所料。不幸的是,事情显露,网络中大概一半的挖矿算力都在不完全验证区块的设置下挖矿(叫做 “SPV 挖矿”),因此在这个无效区块之后挖出了新的区块。
—— Bitcoin Core 开发者,bitcoin.org 上的警告消息(2015)
这个警告页面指示人们,如果使用的是旧版本的 Bitcoin Core,应在平时自愿的区块确认要求之上额外等待 30 个区块的确认。
上面提到的分裂发生在 2015 年 7 月 4 日,UTC 时间 02:10,在区块高度 363730 之后。这个问题在当日 03:50 得到解决 —— 在挖出 6 个无效区块之后。不幸的是,同样的问题在第二天再次发生:在 7 月 5 日的 21:50 ,但这一次,无效的分支仅持续了 3 个区块。

导致 BIP66 创建、部署的事件,还有后续发生的事件,是非常好的案例研究,揭示了比特币开发者不得不谨慎的缘由。来自 BIP66 事件的一些关键结论:
- 在开放性和不公开漏洞之间取得平衡,是一个微妙的问题。
- 为尚未公开的漏洞部署修复措施一个棘手的游戏。
- 保持共识不容易。
- 无意在共识系统中服役的软件,常常会带来风险。
- BIP 应该在一定程度上原子化的。
9.3 结论
比特币有 bug 。发现 bug 的人被鼓励负责任地披露 bug 给比特币开发者,使他们能在 bug 公开之前修复这个 bug 。理想情况下,修复措施可以伪装成性能提升,或使用其它烟雾弹。
我们浏览了这些年间浮出水面的一些较为严重的漏洞,以及处理他们的动作。一些漏洞因为被利用而暴露出来,而另一些得到了尽责披露,能够在恶意人有机会利用它们之前得到修复。