上周比特币核心版0.16.3客户端的突然发布,以及开发者催促大家尽快升级,让比特币界的人大吃一惊。表面原因是0.14-0.16.2版客户端存在拒绝服务(DoS)媒介,需要打补丁。后来我们发现0.15-0.16.2版本核心客户端的另一个漏洞可能会造成比特币的过度交割问题。
在这篇文章中,作者试图解释:发生了什么?潜在的危险是什么?如果有人利用这个漏洞会发生什么?
(图片来自:Mowie Jak Jest)
在我们触及实际漏洞之前,我们需要说明一些事情。首先,我们需要对重复支付进行界定,因为这个漏洞可以用于重复支付。
所谓双付款的情况,比如说,爱丽丝付给鲍勃一笔钱,然后她又付给查理同样的钱。Alice基本上尝试了两次付款,其中一次她知道会被拒绝。当然,当我们考虑付款的时候,通过写这两笔付款,爱丽丝的账户有些透支了。这非常接近比特币的工作原理,但不是很准确。
比特币不是基于账户模型,而是基于未使用的交易输出(UTXO)。一个事务的输出基本上包含一个地址和数量。产出一旦使用,就不能再花掉。想象一个UTXO(作为一枚硬币送给你)。它可以是任何数字,比如0.413 BTC。
比特币的双支付是指一个币(UTXO)被花了两次。通常,这意味着爱丽丝将她的0.413 BTC发送给鲍勃,然后她再次将相同的比特币发送给查理。
比特币的解决方案是,其中一笔交易将被包含在一个区块中,这个区块将决定谁实际获得报酬。如果这两个事务以某种方式传递给多个块,那么后面的块将被软件拒绝。如果两个事务都在同一个块中,那么该块也将被软件拒绝。
基本上比特币软件会检测到重复支付行为。如果发生重复支付行为,应拒绝相应的区块。
然而,在两个不同的事务中发送相同的UTXO并不是唯一的双花方法。实际上存在一种病态的情况,就是同一个UTXO在同一个交易中进行双重支付。在这种情况下,爱丽丝把同一个硬币寄给鲍勃两次。因此,爱丽丝实际支付了0.413 BTC,但鲍勃收到了0.826 BTC。显然,这不是一个有效的交易,因为只发送了一个价值0.413 BTC的UTXO。这相当于爱丽丝给鲍勃两次发送了同样的10美元,而鲍勃收到了20美元。
因此,总结一下我们已经定义的两种类型的双重支付尝试:
使用两个或两个以上的交易,花费相同的UTXO;同一UTXO需要多次使用一个事务;结果表明比特币核心软件正确处理了第一个问题,而第二个问题正是我们应该关心的。任何人都可以像这样构造一个双花事务,但是让节点接受这个事务就是另一回事了。
目前有两种方式可以让交易被包含在一个区块内:a .支付足够的费用,将交易广播到网络,然后矿工会负责将交易包含在区块内;
b .作为矿工,将交易带入区块;
(A)除了创建事务并将其广播到网络上的节点,您不需要做太多工作。(二)你需要找到足够的工作量证明。这也是这个漏洞的关键。
(A)不是可能的攻击媒介,因为这些交易会立即被标记为无效,并且网络上的节点会拒绝它们。没有矿工的配合,这样的交易是无法进入矿工的记忆银行的,因为不会被传播。
(B)是漏洞出现的唯一情况。 换句话说,要利用这个漏洞,你需要工作量的证明,或者足够的矿机设备和电力。
明确一下,双花交易需要处理的情况有四种:
1A?—多个mempool事务消耗相同的UTXO
1B?—多个块事务消耗相同的UTXO
2A?—单个mempool事务多次使用相同的UTXO
2B?—单个块事务多次使用同一个UTXO
该漏洞有两种表现形式。0.14.x版本的客户端存在拒绝服务(DoS)漏洞,而0.15.x-0.16.2版本的客户端存在过度传递漏洞。接下来我们就分别分析一下。
故事从2009年比特币0.1版本的客户端开始。这个版本的代码通过拒绝案例1B和案例2B(检查块中没有重复支付)来强制达成共识。
为了清楚起见,作者删除了很多代码
你可以看到“检查冲突”的注释,它的代码负责检查每一个输入都没有花掉。“将输出标记为已用”注释了接下来的代码,标记了UTXO的使用。如果任何UTXO被使用了一次以上,它将导致一个错误。
2011年,PR 443并入比特币代码库。这种变化是为了处理通过mempool传输单笔交易的双重支付的情况(上面的2A案例)。这个关于合并请求的评论的目的非常明确:
“而且,任何有重复输入的交易都不会被包含在块中……几周前,有人尝试过,但是这些交易没有被包含在块中。我假设在某个地方有一个检查门,可以防止这些重复的事务进入块中,尽管我没有在这个问题上做任何挖掘。这实际上是为了防止这种明显无效的交易被中继。”
实际的代码更改或多或少执行了与上面ConnectInputs中“检查冲突”注释下的代码相同的操作,但是在不同的位置。代码在CheckTransaction中运行,它负责以上所有四种情况(1A、1B、2A、2B)。因此,我们在块双重支付的共识代码中有一些冗余,就像1B和2B的案例都被检查了两次,其中一次在CheckTransaction中,另一次发生在ConnectInputs中。
到2013年,PR 2224被纳入比特币软件。这一变化的目的是区分一致错误(如重复支付)和系统错误(如磁盘空间耗尽)之间的区别,如PR说明中所述:
"它引入了CValidationState,将存储上述文章内容就是块的元数据,或正在执行的事务的验证数据。它用于区分验证错误(例如,不符合网络规则)和运行时错误(例如,磁盘空间不足)。在过去,这些可能会导致混乱,因为用完磁盘空间会导致块被标记为无效。此外,CValidationState还承担了跟踪DoS级别的角色(所以不需要存储在事务或块中…)“
实际相关代码修改如下:
当时ConnectInputs已经模块化为多个方法,这个函数变成了检查双重支付的函数。这里的关键变化是,前面的错误改成了assert
assert在C中做什么?它会完全停止程序。程序员为什么在这里停程序?这就是拉请求的目的。这是当时的代码片段:
它将像以前一样处理1B和2B的案例。函数名从ConnectInputs更改为ConnectBlock,但检查用例1B和2B的冗余仍保留在PR 443中。正如我们已经看到的,UpdateCoins进行了第二次双重支付检查。其中,CheckBlock通过调用CheckTransaction进行了第一次双花检查:
由于这是第二次检查相同的内容,所以使UpdateCoins的双花检查失败的唯一方法就是存在尚力财经小编2022某种UTXO数据库或事务存储损坏。事实上,这似乎是改变断言的原因。 因为CheckBlock在通过CheckTransaction更新币之前已经检查过了,所以我们已经知道一个交易不是双花交易。所以PR 2224正确推测UpdateCoins的这个状态一定是系统错误,而不是共识错误。在这种情况下,为了防止进一步的数据损坏,正确的做法是停止该程序。
到2017年,PR 9049作为比特币0.14的一部分被引入比特币网络。随着Segwit (SegWit)的推出,它是众多加快块验证时间的变化之一,它的代码变化其实很少:
你能看到布尔函数?添加了FCheckDuplicateInputs以加速块检查。正如我们将在接下来看到的,这是一个被认为是多余的检查。不幸的是,在PR 2224中,UpdateCoins中的代码被更改为系统损坏检查,而不是一致检查。在0.14.0版本的客户端中,它的代码进行了更多模块化的改动,assert也进行了一些改动:以前是作为冗余校验使用的,现在负责块中单个事务的双重支付校验(case 2B)和程序的停止。从技术上讲,这仍然是执行协商一致规则。只是在中止程序的问题上,它表现得非常糟糕。
尚力财经小编2022pr 9049是怎么通过的?Greg Maxwell给了我IRC上的聊天记录。
长话短说,在讨论PR 9049时,开发者倾向于认为区块级单笔交易双支付(案例2B)会在PR 443检查,而没有考虑PR224。这使得开发者对PR 9049的关注度不高;总结一下:
1。2011年,用于防止重复支付交易中继(2A案例)的PR 443实际上产生了一个副作用,即它为区块的重复支付共识规则检查创建了冗余检查(1B和2B案例)。
2和PR 2224于2013年推出。作为副作用,在(1)中用于块验证的代码从冗余升级到一致级别;
3,PR 9049是2017年推出的,它跳过了(1)中使用的代码,用于单个块中单笔交易的重复支付(案例1B)检查。开发人员错误地认为代码是多余的,因为他们没有考虑(2)。事实上,这一变化跳过了共识的关键部分。
平心而论,这些东西的合流导致了这个漏洞。
DOS漏洞的严重程度
这意味着0.14.x版本的核心软件可能会因为一个奇怪的阻塞而崩溃。要使软件崩溃,攻击者需要做:
创建一个两次花费相同UTXO的事务;通过充分的工作量证明,将(1)中的交易并入一个比特币区块;将此块广播到0.14尚力财经小编2022.x版本软件的节点;[x ]( 1)和(3)的成本不高,而步骤(2)的最低成本是12.5 BTC。如果你认为从博弈论的角度来看分裂网络并不那么好,那么利用这个漏洞的动机就相当低了。作为一个攻击者,充其量你花了12.5 BTC让一些完整的节点崩溃。因为不可能从分裂网络中获利,攻击者也不可能轻易补偿自己的攻击成本。如果这是唯一的漏洞,那么攻击者可能会给很多人带来一些不便,但不会持久,因为这些被攻击的节点可以简单地重启并连接到其他诚实的节点。一旦出现长链,那么恶意屏蔽攻击将完全失去威胁。除非攻击者继续以每块12.5 BTC的代价创建块,并传播到0.14.x版本软件的节点,否则攻击是不可持续的。换句话说,虽然这个漏洞确实存在,但是对DoS攻击的经济刺激是相当低的。Hyper-vulnerability从0.15.0版本软件开始,核心软件引入了一个新的特性,可以更快地找到并存储UTXO,这只是引入了另一个漏洞。当区块链中包含单笔交易双支付的区块时,软件会将其视为有效,不会出现崩溃现象。 这意味着一个病态的事务(同一个UTXO在同一个事务中被多次使用,例如2B)。0.14版本的节点会因此而崩溃,而使用0.15版本软件的节点会认为交易有效,这基本上是在凭空创造比特币。说说是怎么发生的。0.15出现的PR 10195,介绍了很多内容,但它的要点是改变了UTXO的存储方式,使它们的搜索更加有效。所以里面发生了很多变化,包括对早期UpdateCoins函数的改变:
注意一下assert(false)周围的代码是如何被完全拿出来的。注意,0.15.0中的PR 10537也更改了代码。
断言失败的条件现在取决于输入。SpendCoin,看起来是这样的:
本质上,SpendCoin返回“false”值的唯一方法是让硬币在UTXO集中不存在。但是你可以看到,这要求硬币是新鲜的,而不是脏的。这些并不是常见的术语,但谢天谢地,core的开发者周立铭给出了解释:
“现在的问题是,UTXO什么时候会被标记为FRESH?当它们被添加到UTXO数据库中时,它们被标记为新的。但是,UTXO数据库仍然只存在于存储中(作为缓存)。当它保存到磁盘时,存储中的条目将不再被标记为新的……”标记为新的货币是进入内存池的货币。攻击者可以通过UpdateCoins函数中的assert语句破坏节点。更糟糕的是,如果硬币属于脏的(基本上是从磁盘读取的),那么这将导致比特币的过度交付。因此,攻击者可以欺骗那些运行0.15.0-0.16.2版本软件的矿工接受一个奇怪的无效块,从而导致比特币的过量供应。
过度漏洞的严重程度
这种攻击的经济诱因似乎明显高于DoS攻击,因为攻击者可能凭空创造出比特币。但你仍然需要有采矿设备来实施攻击,但考虑到潜在的经济诱因,这可能是值得的,或者看起来是这样。
以下是利用该漏洞的简单攻击方式:
创建一个双支付交易的区块,它会两次支付自己,比如说50 BTC100 BTC;向0.15/0.16客户端中的所有矿工广播此块;版本0.14.x节点会崩溃;旧节点和其他替代客户端将拒绝该块;许多区块链浏览器运行在定制软件上,而不是基于核心。因此,至少一些浏览器会拒绝这个块,并且不会显示来自这个块的任何交易。根据矿工运行的软件,我们可能会有链分裂;
有可能所有矿工都在运行比特币核心0.15版软件。在这种情况下,它们不会受到攻击。户端可能会停滞不前。也有可能矿工会运行其它东西,在这种情况下,当他们发现一个区块时,链就会发生分叉。由于这些违规行为,网络上的人们很快就会追踪到这一点,可能已提醒一些开发人员,并且core开发者已经修复了它。如果存在分叉,那么在那个时候,关于哪条链是正确的共识链,将开始得到讨论,而出现意外超发的链,可能会遭到抛弃。如果真的发生了,那么社区可能会自愿进行一次回滚,以惩罚攻击者。所以对于攻击者来说,这不会带来50 BTC的收入,更可能的是失去12.5 BTC。如果攻击者加倍花费,比如说200 BTC,那么超发漏洞将持续存在的可能性会更小,因为攻击会更明显。因此,从攻击者的角度来看,这并不是一种好的获利方式。攻击者可以获利的另一种方式,就是事先做空比特币,然后再执行攻击。这也是具有风险的,因为攻击不能保证比特币价格会下跌,特别是当危机得到迅速和果断处理时。此外,考虑到大多数交易所提供的杠杠交易,都需要AML/KYC,这可能导致攻击者很快暴露。攻击者不仅面临着巨大的资金风险,而且还会有身体危险。从经济角度来看,这并不是一个容易获利的漏洞。当然,有一些别有用心的人,可能会用这种漏洞来吓唬那些比特币持有者。投资回报率将变得更抽象,因此从理论上来讲,这可能会达到这些别有用心者的目的。结论毫无疑问,这是一个相当严重的漏洞。尽管我和Awemany之间有着分歧,但我很感激他选择公开地和我辩论。也就是说,考虑到经济博弈理论,我不认为这个漏洞会像他所描述的那样严重。即使这个漏洞在被发现之前被坏人所利用,攻击者可能也不会选择利用它,因为从经济学上来讲,它是没有意义的。可以肯定的是,这一技术漏洞应该被修复,并且开发者应该做得更好,但真正能够利用这种漏洞的对象其实是非常少的,基本上,只有那些想要摧毁比特币的组织才会这么干。Bitcoin Core开发者的教训有很多:1、任何共识变化(即使是微小的变化,例如9049),也需要更多的人进行审查;2、需要对病理交易进行更多的检查;3、代码库中哪些检查是冗余的,哪些检查是不冗余的,以及实际代码将要做些什么,需要变得更加清晰;4、过去存在漏洞,将来也会存在漏洞。现在重要的是,学习并反思这一教训;