33

文章目录

  1. 摘要
  2. 背景
    1. 挖矿简介
  3. 详细内容
    1. 系统建模
    2. 自私挖矿
    3. 分析
    4. 缓解方案
  4. 结束语
  5. 引用

摘要

比特币系统的安全性依赖于其底层的由矿工维护的分布式区块链系统。传统的观点认为挖矿协议这种激励机制对于矿工是公平的,并且在没有人拥有超过一半以上算力的情况下,比特币系统是安全的。

本文展示了一种攻击自私挖矿,可以让矿工获取相比于其算力比例更多的收益,从而造成挖矿的不公平。这种攻击方式有可能导致比特币系统产生严重的后果。以利益最大化作为目标的矿工,会选择加入自私挖矿的阵营。这就可能导致自私挖矿的阵营的算力有可能超过50%,从而破坏比特币区块链去中心化的性质。

一般情况下,自私挖矿策略对所有比例算力的挖矿者都有效。文中提出了一种针对自私挖矿的化解方案。该方案可以在矿工算力不超过25%的情况下,无法使用自私挖矿策略。

背景

比特币的安全性依赖于其底层解决密码学问题的难度。底层区块链由是一个松散的分布式系统,由被称作“矿工”的网络节点维护。每一个成功解决密码学难题的矿工可以获得固定币值的奖励。一个矿工拥有的计算能力(算力)越强,解决密码学难题的速度就越快,因此就能获得更多的奖励。这就是矿工参与维护这个系统的动力。

比特币的协议要求大多数矿工是诚实的,也就是遵守比特币的协议规定。如果一个个体拥有超过比特币全网50%的计算能力,那么他就可以随意操控比特币网络。但是如果这样,那么比特币的价值将大跌,这会严重影响矿工的收益。因此,在理性经济人模型下,大家都趋向于遵守比特币的协议。

经验证明,矿工们会通过一定的策略组成矿池,把大家的算力集中到一起。产生这种现象的原因是:大多数个人矿工的算力都较小,因此解决密码学难题的能力较小,这样就会导致收益十分不稳定(很难挖到,但一旦挖到收益很大)。矿池把大家的算力集中起来,可以以更快的速度挖到比特币。矿池挖到比特币后就可以按照矿工对于矿池的贡献来分发挖到的比特币。这样就极大的降低了收益的不稳定性。目前来看(论文发表于2014年),这些矿池都表现良好,没有什么不良行为。

传统观点认为,在没有超过50%算力的矿工存在的前提下,比特币是安全的。目前也没有发现那种攻击方式可以让矿工获取在算力不超过50%的情况下,获取超过其算力比例的奖励。因此,大家认为即使是组成矿池,大家的收益也是公平的,这种方式对于系统来说没有什么大危害。

本文针对目前的这种观点,提出了一种新的挖矿策略自私挖矿。在这种策略指导下,矿工挖到矿之后(解决一个密码学难题,生成一个新的区块),并不会立即将挖到的区块立刻广播到区块链网络,而是先放在本地。这样,只有自私矿工自己可以在本地这个较长的链上继续挖矿,而其他矿工都在之前较短的链上挖矿。这样就导致其他人的计算都浪费了,从而导致自己的收益增加,他人的收益减少。

挖矿简介

我们简单介绍下挖矿原理:

给定一个字节串 $x$,矿工需要找到一个对应的字节串 $y$,可以让$x \Vert y$ ($\Vert$表示拼接)对应的哈希值的前$N$比特位为0。比如我们有一个字符串Hello, Bitcoin!,我们要找到一个数字y,可以让字符串Hello, Bitcoin! y的哈希值的前3比特为0。我们计算如下:

可以看到,我们计算13次,也就是计算到字符串Hello, Bitcoin! 12时,才找到了一个开头有3个零比特的哈希值。所以前置0越多,计算次数就越多。若需要前$N$个比特位为零,则平均需要计算$2^N$次。这就是挖矿的基本原理。

详细内容

系统建模

系统由$n$个矿工$1, 2, \ldots, n$组成,每个矿工占算力比例为$m_1, m_2, \ldots, m_n$,满足$\sum_{i=1}^n = 1$。每一个矿工都会选择一个链的头部进行挖矿操作,挖出下一个区块的时间间隔$t_i \backsim exp(m_i^{-1})$($exp$为指数分布)。我们假设矿工都是理性的,也就是说矿工以最大化自己的挖矿利益为目的。

矿工们可以根据特定的策略来组成矿池联合挖矿。矿池的算力比例为矿池中所有矿工算力比例之和,矿池中矿工所得奖励和他们的计算能力成正比。矿池得到的挖矿奖励从他们所挖到并且被包含在主链中的区块中得到。

自私挖矿

本小节我们描述自私挖矿算法。我们简单将矿工分为两个阵营:自私挖矿阵营和诚实挖矿阵营。

自私挖矿的主要思想是将诚实矿工的算力浪费在被孤立的公共最长链分支上,也就是让诚实矿工所挖到的区块最终不会成为最长链中的区块。

自私矿工通过在特定时间选择性的将他们所挖到的区块公开到区块链网上去,从而达到他们的目标。简单讲,自私矿工挖到新的区块之后,偷偷的保存在本地,这样就在本地产生了一个新的分岔。自私矿工在新的分岔上继续挖矿,而公网的诚实矿工还在短的分岔上挖矿,继而浪费诚实矿工算力。我们使用python代码作为伪代码详细说明该算法。

def selfish_mining():
    # 一开始矿工处于同一起跑线,大家知道的链一样
    public_chain = get_public_known_chain()
    private_chain = get_public_known_chain()

    # 私有链分支的长度现在为 0
    private_branch_len = 0

    while True:
        # 大家开始挖矿
        miner, block = mining()

        # 1. 如果挖到区块的是自私矿工
        if miner is selfish:
            # 1.2 计算超过公链的长度
            n_blocks_ahead_previous = len(private_chain) - len(public_chain)

            # 1.2 将区块添加到私链后面
            private_chain.append(block)
            private_branch_len += 1

            # 1.3 如果先前公司链长度都为1,现在私链的长度变为了2
            if n_blocks_ahead_previous == 0 and private_branch_len == 2:
                # 公开私链,同时将私链长度置 0
                publish_all_private_chain()
                private_branch_len = 0
        # 2. 如果挖到区块的是诚实矿工
        else:
          	# 2.1 计算超过公链的长度
	          n_blocks_ahead_previous = len(private_chain) - len(public_chain)

            # 2.2 将区块添加到公链后面
          	public_chain.append(block)

            # 2.3.0 如果先前都一样长
            if n_blocks_ahead_previous == 0:
              	# 抛弃私链,直接使用公链
	              private_chain = public_chain
                private_branch_len = 0

            # 2.3.1 如果先前私链比公链高一个区块
            elif n_blocks_ahead_previous == 1:
              	# 此时公开本地私链的最后一个区块
                # 这时候大家高度一样
                # 试试运气看看哪条链能成为主链
                publish_the_last_block_of_private_chain()

            # 2.3.2 如果先前私链的长度还比公链高两个区块
            elif n_blocks_ahead_previous == 2:
              	# 公开所有私链
              	publish_all_private_chain()
                private_chain_len = 0
            
            # 2.3.4 如果先前私链领先了超过2个区块
            else:
              	# 公开私链中最早的一个未公开的区块
              	publish_earliest_private_block()
  

从上面的算法我们可以看出:

  1. 如果公链的长度比私链长了(算法35~39行),由于私链的算力比公链小很多,因此私链可以追上公链的概率较小,此时私链采取直接用公链替换本地的私链;
  2. 如果自私矿工首先发现了一个区块,那么自私矿工就处于了领先地位,这时候,自私矿工不急于将这个新发现的区块公开,而是将其放在自己的矿池中。这时候可能产生两种结果:
  3. 诚实矿工发现了一个区块,这时候自私矿工的领先被追平了。
  4. 自私矿工又发现了一个区块,这时候自私矿工就领先了2个区块。

在被追平的情况下,自私矿工立即公开他们的私有区块。这时候出现了两个相同长度的分支,显然自私矿工会在自己的自私矿池的分支上挖矿;而诚实矿工在两个分支上进行挖矿都是有可能的。这时候我们将算力分为三部分:

  1. 在原先矿池的私链分支上挖矿的自私矿池的算力$m_a$
  2. 在原先矿池的私链分支上挖矿的诚实矿工的算力$m_b$
  3. 在原先的公链分支上挖矿的诚实矿工的算力$m_c$

如果$m_a$挖到了区块,那么自私矿工得到2个区块的奖励;如果$m_b$挖到了区块,那么自私矿工可以得到一个区块的奖励;如果$m_c$挖到了区块,那么自私矿工显然啥也得不到。

在领先两个区块的情况下,这时候,自私矿工的领先优势就很明显了。自私矿工显然继续在自己的分支上挖矿。此时一旦诚实矿工发现了一个新区块,那么自私矿工就公开一个最早未公开的区块。由于诚实矿工的算力大,总能追上自私矿工,所有一旦诚实矿工追到自私矿工只领先一个区块的时候,自私矿工就把他领先的区块公开了。这时候,自私矿工公开的链长度是最长的,这样自私矿工就获取所有区块奖励,浪费了诚实矿工的算力。

算法描述到此结束,下面我们给出详细的理论分析。

分析

为了便于分析,文中给出领先区块长度的状态转移图,可以帮助更直观地理解自私挖矿。图中$\alpha$表示自私矿工的算力,用$\lambda$表示诚实矿工中在自私矿工的分支链上挖矿的比例,状态概率转移图如下:

状态概率转移图

其中$0^{\prime}$是一个特殊状态,它表示原先的自私矿工的链被追平了。这时候转移概率$\alpha, \gamma(1-\alpha), (1-\gamma)(1-\alpha)$的三条路径分别对应了我们前文描述的在追平情况下的$m_a, m_b, m_c$三个路径。

对此概率转移图进行分析,我们可以得到如下的方程组:

$$ \begin{cases} \alpha p_0 &= (1-\alpha)p_1 + (1-\alpha)p_2 \\\ p_{0^{\prime}} &= (1-\alpha)p_1 \\\ \alpha p_1 &= (1-\alpha)p_2 \\\ \alpha p_k &= (1-\alpha)p_{k+1}, (\forall k \ge 2)\\\ 1 &= \sum_{k=0}^{\infty}p_k + p_{0^\prime} \end{cases} $$

具体求解过程及结果可参考原文。

自私挖矿仿真结果如下:

自私挖矿收益

从上图我们可以看出在极端的情况下($\gamma = 1$),任意算力的矿池使用自私挖矿策略都可以获取超出诚实挖矿的收益。由于比特币在实现中总是会在自己首先接受到的区块上挖矿,那么如果自私挖矿的矿池与整个区块链网络节点的连接非常充分,可以及时的将区块传播给这些节点,那么$\gamma$就可以变得很大。这显然在很大程度上可以降低攻击的门槛。

下图给出了在不同$\gamma$值的情况下,矿池需要多少算力进行自私挖矿时就能获得额外的收益。

自私挖矿阈值

我们可以看到,即使在$\gamma=0$的情况下,只要自私矿池的算力超过$1/3$就可以使用自私挖矿策略获得更高的收益。

缓解方案

从前面的图我们可以看出在$\gamma=0.5$时,自私挖矿的阈值为$1/4$。因此只需要将挖矿策略改为:矿工收到分岔区块时,从分岔中随机选择一个区块进行挖矿而不是在首先收到的区块上进行挖矿即可。

结束语

比特币是首个被广泛接受的加密货币,整个比特币系统由网络中的所有对等来维护。这些节点可以通过挖矿来获取相应的收益。本文提出了一种自私挖矿策略,可以使矿工获取超过其算力比例的收益,导致整个系统的激励机制变得不公平。文中还提出了一个简单的缓解方案,可以提高自私挖矿的门槛:原先任意算力的矿池都可以使用自私挖矿,采用缓解方案后,矿池至少需要全网1/4的算力才能进行私自挖矿攻击。

引用

[1] Eyal, Ittay, and Emin Gün Sirer. "Majority is not enough: Bitcoin mining is vulnerable." International conference on financial cryptography and data security. Springer, Berlin, Heidelberg, 2014.