一笔钱买两次东西?——双花安全问题分析
一、什么是双花攻击
双花攻击(Double Spend Attack)是一笔钱花了两次的简称,可以是双重支付,也可以是双重转账,最终导致“一笔钱”被攻击者使用了两次或者多次。
数字货币的本质也是货币,所以我们可以类比钞票流通的场景:
双花就像拿到冠字号重复(对于数字货币来说就是token)的钞票,他们拥有同样的唯一标识,但我们知道,只有一张是真的。一张钞票表面上一张纸,但内容上是一系列防伪技术+冠字号的集合,一张“数字货币”表面上是一串token,内容上是一系列安全措施的集合,但很明显,在“落袋为安”之前,token很容易被复制、容易被攻击者二次拿到。
所以双花攻击中没有产生新 Token,但攻击者能把自己花出去的钱,通过回滚、分叉等技术手段重新拿回来。
去中心化网络难以快速收敛、分布式节点难以快速同步,这是数字货币面临包括双花攻击在内的很多问题的本质原因。
二、常见攻击手法
1、算力攻击(51%攻击)
攻击者首先把一定数量的数字货币发给自己在交易所的钱包,这条分支我们命名为分支A,交易完成后立刻套现,此时状态下的分支A是主链。然后,攻击者到另一个分支上进行挖矿,命名为分支B,由于攻击者控制了51%以上的算力,其获得记账权的概率很大,于是当分支B的记账权超过分支A时,即便分支A上可能已经有了新的区块,B仍将取代A成为新的主链,分支A上的所有交易就会被回滚。此时分支A上攻击者已经套现,但又因为分支A被取消,从而又拿到了花掉的token。
在B分支落后的情况下要强行让它超过A分支,其实是挺难的,假设攻击者掌握了全网1%的计算能力,那么他争取到记账权的概率就是1%,两次争取到记账权的概率就是1%的平方。如果攻击者算力占据绝对优势,也就是超过全网一半的算力,拥有51%的算力,那么,即使落后很多,攻击者追上也只是时间问题。
2、竞争攻击(Race Attack)
这种攻击主要通过控制矿工费来实现双花。攻击者同时向网络中发送两笔交易,一笔交易发给自己(为了提高攻击成功的概率,他给这笔交易增加了足够的矿工费),一笔交易发给商家。由于发送给自己的交易中含有较高的手续费,会被矿工优先打包进区块的概率比较高。这时候这笔交易就会先于发给商家的那笔交易,那么发给商家的交易就会被回滚,如果回滚发生在商家交易确认之后,比如确认收到货款、确认发货,那么攻击者就实现了双花。对于攻击者来说,通过控制矿工费,最终实现了同一笔 Token 的“双花”。
你要是百度的话,race attack都会被翻译成“种族攻击”,让人摸不着头脑,明明race应该翻译成竞速、竞争,币圈水平真是一言难尽。
上述攻击基于这样一个事实:
手续费的目的主要是为了激励矿工不辍挖矿,早期矿工的挖矿底薪比较高,每个区块50枚BTC,但是创世块之后每出21万个块(每四年),底薪减半。等到所有2100万枚比特币都被挖出后,由交易费充当挖矿奖励。 每笔交易都有优先级,由“输入”的年龄、金额和交易输入数量决定。若发送的比特币金额过小,或币龄过低,则很有可能被收取费用,也可以人为的增加手续费,以减少矿工打包成区块的时间。
3、芬尼攻击( Finney Attack)
Hal Finney是第一个与中本聪进行区块链交易的人,第二个承认比特币的人,是得了渐冻症的优秀的密码学家,所以在他发现未确认交易双花攻击的问题之后,为了纪念他,这类攻击以他的名字命名。
在芬尼攻击中,攻击者通过控制区块的广播时间来实现双花,攻击对象针对的是接受 0 确认的商家。假设攻击者挖到区块,该区块中包含着一个交易,即 A 向 B 转了一定数量的 Token,其中 A 和 B 都是攻击者自己的地址。但是攻击者并不广播这个区块,而是立即找到一个愿意接受 0 确认交易的商家向他购买一个物品,向商家发一笔交易,用 A 向商家的地址 C 支付,发给商家的交易广播出去后,攻击者再把自己之前挖到的区块广播出去,由于发给自己的交易先于发给商家的交易,A向C支付的这笔交易就会被回滚,商家遭受损失。对于攻击者来说,通过控制区块的广播时间,就实现了同一笔 Token 的“双花”。
如上图所示,A->B的交易隐约可见,只在适当的时候才广播,本质上讲这也是一种竞速,由于区块链会校验A地址转出的这笔Token的真实性、有效性,所以一旦发现Token已经转给过B,A->C的交易就不会被认可,通过广播打了个时间差。
除了上述几类典型的双花攻击之外,还有Vector76 attack(76号向量攻击),这个攻击结合了竞争攻击中的高手续费、芬尼攻击中的广播时间差,使得商家即使在一次交易确认之后,也能够回滚,实现双花。至于为什么叫Vector76,根据我检索下来的情况来看,所谓攻击向量,就是指某一种针对性的攻击或攻击组合,针对某一个特定网站的XSS攻击也可以是向量攻击,XSS脚本就是攻击向量。所以我猜测,发现上述双花攻击类型的同志,出于简单且不重复的考虑,给它安了个Vector76的名字,还不如叫“一次性确认攻击”。
三、攻击实例
上面都是讲的一些理论,我们从一个实际的例子中,看一下双花攻击是怎么发生的。
区块链项目中,一个交易一般是由未签名的部分(UnsignedTx,交易要执行的内容)和签名的部分(交易的 witness,见证,就是签名)构成的。在比特币之类的区块链项目中,交易的 hash 计算实际上是包含了该交易的签名部分的,即 hash=SHA256(UnsignedTx+witness)。但在 NEO、ONT 等多种区块链平台中,交易的计算公式为 hash=SHA256(UnsignedTx),即交易的哈希是由未签名的部分计算的来的,与签名无关。NEO 智能合约在执行的时候,能够通过 Transaction_GetWitnesses 方法,从一个交易中获得该交易的签名,能够通过Witness_GetVerificationScript 方法,获得该签名的验签脚本。注意这两个方法,也就是说,如果攻击者针对同一个未签名交易 UnsignedTx1,构造两个不同的验签脚本,就可以造成该合约执行的不一致性。有关智能合约是什么,可以阅读历史文章《看得懂的区块链及智能合约概念》,地址是https://xie.infoq.cn/article/7088786f5bf4e5bd2463dac54。
正常情况下,合约的 VerificationScript 是由合约的输入等信息决定的,攻击者无法构造不同的验签脚本并通过验证。但是我们发现在 VerifyWitnesses方法中,当VerificationScript.Length==0 的时候,系统会调用 EmitAppCall 来执行目标脚本 hash。智能合约的哈希值就是智能合约的地址。
通过查看NEO源码,EmitAppCall(byte[] scriptHash, bool useTailCall = false) 方法是用来构建调用智能合约的二进制字节流的。
也就是说,当 VerificationScript长度等于0,或者 VerificationScript 等于目标脚本的时候,下一步都会执行签名验证。攻击者利用这个性质,就可以对 NEO 智能合约上的所有代币资产进行双花攻击,具体步骤如下:
攻击者构造智能合约交易 Tx_1(未签名内容 UnsignedTx_1, 验证脚本为 VerficationScript_1)。在 UnsignedTx_1 合约的执行中,合约判断自己的 VerficationScript 是否为 VerficationScript_1。如果为 VerficationScript_1,就发送代币给 A 用户。如果 VerficationScript 为空,则发送代币给 B 用户。
Tx_1 被打包到区块 Block_1 中。
攻击者收到 Block_1 后,将 Tx_1 替换成 Tx_2(Tx_1 具有与 Tx_1 相同的未签名内容 UnsignedTx_1,但验证脚本为空) 从而形成 Block_2。攻击者将 Block_1 发送给 A 用户,将 Block_2 发送给 B 用户。
当 A 用户收到 Block_1 时,发现自己收到攻击者发送的代币。当 B 用户收到 Block_2 时,也会发现自己收到了攻击者发送的代币,双花攻击完成。
四、总结
从上述内容可以看出,双花攻击的出现与分布式网络无中心的特征有直接关系,既然不能依托于统一的中心化状态这种方式去解决问题,那么就要依靠算法的设计、交互的机制和具体实现,多握几次手在所难免。
版权声明: 本文为 InfoQ 作者【石君】的原创文章。
原文链接:【http://xie.infoq.cn/article/fe14ddbd6327c141cc75d35c1】。文章转载请联系作者。
评论