既然Jing的节点已经构建了一个候选区块,那么就轮到Jing的矿机对这个新区块进行“挖掘”,求解工作量证明算法以使这个区块有效。从本书中我们已经学习了比特币系统中不同地方用到的哈希加密函数。比特币挖矿过程使用的是 SHA256哈希函数。

​用最简单的术语来说,挖矿就是重复计算区块头的哈希值,不断修改该参数,直到与哈希值匹配的一个过程。哈希函数 的结果无法提前得知,也没有能得到一个特定哈希值的模式。哈希函数的这个特性意味着:得到哈希值的唯一方法是不断的尝试,每次随机修改输入,直到出现适当的哈希值。

10.7.1 工作量证明算法

​哈希函数输入一个任意长度的数据,输出一个长度固定且绝不雷同的值,可将其视为输入的数字指纹。对于特定输入,哈希的结果每次都一样,任何人都可以用相同的哈希函数,计算和验证哈希结果。一个加密哈希函数的主要特征就是不同的 输入几乎不可能出现相同的数字指纹。因此,有意的选择一个输入去生成一个想要的哈希值值是几乎不可能的,更别提用随机的方式生成想要的哈希值了。

​无论输入的大小是多少,SHA256函数的输出的长度总是256bit。在例10-8中,我们将使用Python解释器来计算语句 “I am Satoshi Nakamoto” 的SHA256的哈希值。

​例10-8 SHA256示例

$ python
Python 2.7.1
>
>
>
 import hashlib
>
>
>
 print hashlib.sha256("I am Satoshi Nakamoto").hexdigest()
5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e

在例10-8中, 5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e 是”I am Satoshi Nakamoto”的哈希值。改变原句中的任何一个字母、标点、或增加字母都会产生不同的哈希值。

如果我们改变原句,得到的应该是完全不同的哈希值。 例如,我们在句子末尾加上一个数字,运行例10-9中的Python脚本。例10-9 通过反复修改 nonce 来生成不同哈希值的脚本(SHA256)

code/hash\_example.py\[\]

执行这个脚本就能生成这些只是末尾数字不同的语句的哈希值。例10-10 中显示了我们只是增加了这个数字,却得到了非 常不同的哈希值。

例10-10 通过反复修改nonce 来生成不同哈希值的脚本的输出

$ python hash_example.py
I am Satoshi Nakamoto0 =
>
 a80a81401765c8eddee25df36728d732...
I am Satoshi Nakamoto1 =
>
 f7bc9a6304a4647bb41241a677b5345f...
I am Satoshi Nakamoto2 =
>
 ea758a8134b115298a1583ffb80ae629...
I am Satoshi Nakamoto3 =
>
 bfa9779618ff072c903d773de30c99bd...
I am Satoshi Nakamoto4 =
>
 bce8564de9a83c18c31944a66bde992f...
I am Satoshi Nakamoto5 =
>
 eb362c3cf3479be0a97a20163589038e...
I am Satoshi Nakamoto6 =
>
 4a2fd48e3be420d0d28e202360cfbaba...
I am Satoshi Nakamoto7 =
>
 790b5a1349a5f2b909bf74d0d166b17a...
I am Satoshi Nakamoto8 =
>
 702c45e5b15aa54b625d68dd947f1597...
I am Satoshi Nakamoto9 =
>
 7007cf7dd40f5e933cd89fff5b791ff0...
I am Satoshi Nakamoto10 =
>
 c2f38c81992f4614206a21537bd634a...
I am Satoshi Nakamoto11 =
>
 7045da6ed8a914690f087690e1e8d66...
I am Satoshi Nakamoto12 =
>
 60f01db30c1a0d4cbce2b4b22e88b9b...
I am Satoshi Nakamoto13 =
>
 0ebc56d59a34f5082aaef3d66b37a66...
I am Satoshi Nakamoto14 =
>
 27ead1ca85da66981fd9da01a8c6816...
I am Satoshi Nakamoto15 =
>
 394809fb809c5f83ce97ab554a2812c...
I am Satoshi Nakamoto16 =
>
 8fa4992219df33f50834465d3047429...
I am Satoshi Nakamoto17 =
>
 dca9b8b4f8d8e1521fa4eaa46f4f0cd...
I am Satoshi Nakamoto18 =
>
 9989a401b2a3a318b01e9ca9a22b0f3...
I am Satoshi Nakamoto19 =
>
 cda56022ecb5b67b2bc93a2d764e75f...

每个语句都生成了一个完全不同的哈希值。它们看起来是完全随机的,但你在任何计算机上用Python执行上面的脚本都 能重现这些完全相同的哈希值。

​类似这样在语句末尾的变化的数字叫做nonce(随机数)。Nonce是用来改变加密函数输出的,在这个示例中改变了这个语句的 SHA256指纹。

​为了使这个哈希算法变得富有挑战,我们来设定一个具有任意性的目标:找到一个语句,使之哈希值的十六进制表示以 0开头。幸运的是,这很容易!在例10-10中语句 “I am Satoshi Nakamoto13” 的哈希值是 0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5 ,刚好满足条件。我们得到它用了13次。用概率的角度 来看,如果哈希函数的输出是平均分布的,我们可以期望每16次得到一个以0开头的哈希值(十六进制个位数字为0到 F)。从数字的角度来看,我们要找的是小于 0x1000000000000000000000000000000000000000000000000000000000000000 的哈希值。

我们称这个为Target目标阈值,我们的目的是找到一个小于这个目标的哈希值。如果我们减小这个目标值,那找到一个小于它的哈希值会越来越难。

简单打个比方,想象人们不断扔一对骰子以得到小于一个特定点数的游戏。第一局,目标是12。只要你不扔出两个6, 你就会赢。然后下一局目标为11。玩家只能扔10或更小的点数才能赢,不过也很简单。假如几局之后目标降低为了5。

现在有一半机率以上扔出来的骰子加起来点数会超过5,因此无效。随着目标越来越小,要想赢的话,扔骰子的次数会 指数级的上升。最终当目标为2时(最小可能点数),只有一个人平均扔36次或2%扔的次数中,他才能赢。从一个知道骰子游戏目标为2的观察者的角度来看,如果有人要成功中奖,假设他平均尝试了36次。

换句话说,可以估计从实现目标难度取得成功所需的工作量。 当算法是基于诸如SHA256的确定性函数时,输入本身就成为证据,必须要一定的工作量才能产生低于目标的结果。 因此,称之为工作量证明。

​提示:尽管每次尝试产生一个随机的结果,但是任何可能的结果的概率可以预先计算。 因此,指定特定难度的结果构成了具体的工作量证明。

​在例10-10中,成功的nonce为13,且这个结果能被所有人独立确认。任何人将13加到语句 “I am Satoshi Nakamoto” 后面再计算哈希值都能确认它比目标值要小。这个正确的结果同时也是工作量证明(Proof of Work),因为它证明我们的确花时间找到了这个nonce。验证这个哈希值只需要一次计算,而我们找到它却花了13次。如果目标值更小(难度更大),那我们需要多得多的哈希计算才能找到合适的nonce,但其他人验证它时只需要一次哈希计算。此外,知道目标值后,任何人都可以用统计学来估算其难度,因此就能知道找到这个nonce需要多少工作。

提示:工作证明必须产生小于目标的哈希值。 更高的目标意味着找到低于目标的哈希是不太困难的。 较低的目标意味着在目标下方找到哈希更难。 目标和难度是成反比。

​比特币的工作量证明和例10-10中的挑战非常类似。矿工用一些交易构建一个候选区块。接下来,这个矿工计算这个区块头信息的哈希值,看其是否小于当前目标值。如果这个哈希值不小于目标值,矿工就会修改这个nonce(通常将之加 1)然后再试一次。按当前比特币系统的难度,矿工得试10^15次(10的15次方)才能找到一个合适的nonce使区块头信息哈希值足够小。

​ 例10-11是一个简化很多的工作量证明算法的实现。

​ 例10-11 简化的工作量证明算法

code/proof-of-work-example.py\[]

你可以任意调整难度值(按二进制bit数来设定,即哈希值开头多少个bit必须是0)。然后执行代码,看看在你的计算机 上求解需要多久。在例10-12中,你可以看到该程序在一个普通笔记本电脑上的执行情况。

例10-12 多种难度值的工作量证明算法的运行输出

$ python proof-of-work-example.py*
Difficulty: 1 (0 bits)
[...]
Difficulty: 8 (3 bits)
Starting search...
Success with nonce 9
Hash is 1c1c105e65b47142f028a8f93ddf3dabb9260491bc64474738133ce5256cb3c1
Elapsed Time: 0.0004 seconds
Hashing Power: 25065 hashes per second
Difficulty: 16 (4 bits)
Starting search...
Success with nonce 25
Hash is 0f7becfd3bcd1a82e06663c97176add89e7cae0268de46f94e7e11bc3863e148
Elapsed Time: 0.0005 seconds
Hashing Power: 52507 hashes per second
Difficulty: 32 (5 bits)
Starting search...
Success with nonce 36
Hash is 029ae6e5004302a120630adcbb808452346ab1cf0b94c5189ba8bac1d47e7903
Elapsed Time: 0.0006 seconds
Hashing Power: 58164 hashes per second
[...]
Difficulty: 4194304 (22 bits)
Starting search...
Success with nonce 1759164
Hash is 0000008bb8f0e731f0496b8e530da984e85fb3cd2bd81882fe8ba3610b6cefc3
Elapsed Time: 13.3201 seconds
Hashing Power: 132068 hashes per second
Difficulty: 8388608 (23 bits)
Starting search...
Success with nonce 14214729
Hash is 000001408cf12dbd20fcba6372a223e098d58786c6ff93488a9f74f5df4df0a3
Elapsed Time: 110.1507 seconds
Hashing Power: 129048 hashes per second
Difficulty: 16777216 (24 bits)
Starting search...
Success with nonce 24586379
Hash is 0000002c3d6b370fccd699708d1b7cb4a94388595171366b944d68b2acce8b95
Elapsed Time: 195.2991 seconds
Hashing Power: 125890 hashes per second
[...]
Difficulty: 67108864 (26 bits)
Starting search...
Success with nonce 84561291
Hash is 0000001f0ea21e676b6dde5ad429b9d131a9f2b000802ab2f169cbca22b1e21a
Elapsed Time: 665.0949 seconds
Hashing Power: 127141 hashes per second

​你可以看出,随着难度位一位一位地增加,查找正确结果的时间会呈指数级增长。如果你考虑整个256bit数字空间,每次要求多一个0,你就把哈希查找空间缩减了一半。在例10-12中,为寻找一个nonce使得哈希值开头的26位值为0,一共尝试了8千多万次。即使家用笔记本每秒可以达270,000多次哈希计算,这个查找依然需要10分钟。

​在写这本书的时候,比特币网络要寻找区块头信息哈希值小于

0000000000000000029AB9000000000000000000000000000000000000000000

​可以看出,这个目标哈希值开头的0多了很多。这意味 着可接受的哈希值范围大幅缩减,因而找到正确的哈希值更加困难。生成下一个区块需要网络每秒计算1.8 septa-hashes((thousand billion billion次哈希)。这看起来像是不可能的任务,但幸运的是比特币网络已经拥有3EH/sec的处理能力,平均每10分钟就可以找到一个新区块。

10.7.2 难度表示
​在例10-3中,我们在区块中看到难度目标,其被标为”难度位”或简称”bits”。在区块277,316中,它的值为 0x1903a30c。 这个标记的值被存为系数/指数格式,前两位十六进制数字为幂(exponent),接下来得六位为系数(coefficient)。在这个区块里,0x19为幂,而 0x03a30c为系数。

计算难度目标的公式为:

target = coefficient \* 2^\(8 \* \(exponent – 3\)\)

​由此公式及难度位的值 0x1903a30c,可得:

    target = 0x03a30c * 2^(0x08 * (0x19 - 0x03))^
     =
>
 target = 0x03a30c * 2^(0x08 * 0x16)^
    =
>
 target = 0x03a30c * 2^0xB0^

​按十进制计算为:

=
>
 target = 238,348 * 2^176^
=
>
 target = 22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328

转化为十六进制后为:

=
>
 target = 0x0000000000000003A30C00000000000000000000000000000000000000000000

​也就是说高度为277,316的有效区块的头信息哈希值是小于这个目标值的。这个数字的二进制表示中前60位都是0。在 这个难度上,一个每秒可以处理1万亿个哈希计算的矿工(1 tera-hash per second 或 1 TH/sec)平均每8,496个区块才能找到一个正确结果,换句话说,平均每59天,才能为某一个区块找到正确的哈希值。

10.7.3 难度目标与难度调整

​如前所述,目标决定了难度,进而影响求解工作量证明算法所需要的时间。那么问题来了:为什么这个难度值是可调整 的?由谁来调整?如何调整?

​比特币的区块平均每10分钟生成一个。这就是比特币的心跳,是货币发行速率和交易达成速度的基础。不仅是在短期内,而是在几十年内它都必须要保持恒定。在此期间,计算机性能将飞速提升。此外,参与挖矿的人和计算机也会不断 变化。为了能让新区块的保持10分钟一个的产生速率,挖矿的难度必须根据这些变化进行调整。事实上,难度是一个动 态的参数,会定期调整以达到每10分钟一个新区块的目标。简单地说,难度被设定在,无论挖矿能力如何,新区块产生 速率都保持在10分钟一个。

​那么,在一个完全去中心化的网络中,这样的调整是如何做到的呢?难度的调整是在每个完整节点中独立自动发生的。 每2,016个区块中的所有节点都会调整难度。难度的调整公式是由最新2,016个区块的花费时长与20,160分钟(即 这些区块以10分钟一个速率所期望花费的时长)比较得出的。难度是根据实际时长与期望时长的比值进行相应调整的 (或变难或变易)。简单来说,如果网络发现区块产生速率比10分钟要快时会增加难度。如果发现比10分钟慢时则降低难度。

​这个公式可以总结为如下形式:

New Difficulty = Old Difficulty \* \(Actual Time of Last 2016 Blocks / 20160 minutes\)

​ 例10-13展示了比特币核心客户端中的难度调整代码。

例10-13 工作量证明的难度调整 源文件( pow.cpp文件钟的CalculateNextWorkRequired() 函数)

第43行函数 GetNextWorkRequired()// Limit adjustment step

// Limit adjustment step
    int64_t nActualTimespan = pindexLast-
>
GetBlockTime() - nFirstBlockTime;
    LogPrintf("  nActualTimespan = %d  before bounds\n", nActualTimespan);
    if (nActualTimespan 
<
 params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    if (nActualTimespan 
>
 params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;
    // Retarget
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    arith_uint256 bnOld;
    bnNew.SetCompact(pindexLast-
>
nBits);
    bnOld = bnNew;
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;
    if (bnNew 
>
 bnPowLimit)
        bnNew = bnPowLimit;

注意:虽然目标校准每2,016个块发生,但是由于Bitcoin Core客户端的一个错误,它是基于之前的2,015个块的总时间(不应该是2,016个),导致重定向偏差向较高难度提高0.05%。

​参数Interval(2,016区块)和TargetTimespan(1,209,600秒即两周)的定义在文件chainparams.cpp中。

​为了防止难度的变化过快,每个周期的调整幅度必须小于一个因子(值为4)。如果要调整的幅度大于4倍,则按4倍调 整。由于在下一个2,016区块的周期不平衡的情况会继续存在,所以进一步的难度调整会在下一周期进行。因此平衡哈 希计算能力和难度的巨大差异有可能需要花费几个2,016区块周期才会完成。

​提示:寻找一个比特币区块需要整个网络花费10分钟来处理,每发现2,016个区块时会根据前2,016个区块完成的时间对难度进行调整。

​值得注意的是目标难度与交易的数量和金额无关。这意味着哈希算力的强弱,即让比特币更安全的电力投入量,与交易 的数量完全无关。换句话说,当比特币的规模变得更大,使用它的人数更多时,即使哈希算力保持当前的水平,比特币 的安全性也不会受到影响。哈希算力的增加表明更多的人为得到比特币回报而加入了挖矿队伍。只要为了回报,公平正 当地从事挖矿的矿工群体保持足够的哈希算力,”接管”攻击就不会得逞,让比特币的安全无虞。

​目标难度和挖矿电力消耗与将比特币兑换成现金以支付这些电力之间的关系密切相关。高性能挖矿系统就是要用当前硅 芯片以最高效的方式将电力转化为哈希算力。挖矿市场的关键因素就是每度电转换为比特币后的价格。因为这决定着挖 矿活动的营利性,也因此刺激着人们选择进入或退出挖矿市场。