探索比特币“挖矿”和“区块”的数学本质

探索比特币“挖矿”和“区块”的数学本质

注意:以下内容可能会让懵懵懂懂入门区块链的同学产生不适,建议配合我在 区块链是什么,如何简单易懂地介绍区块链?中的回答来阅读下面的文章,会存在一些前置因果关系。


这块内容我拖了差不多两个月才想起来完善掉,拖延症没办法。好了,废话不多说,让我们在硅基世界里好好研究下“区块”、“挖矿”的数学本质,相信还是有一小部分同学会对这底层的技术实现感兴趣的。想深入了解的同学,我们继续...


区块,在计算机的世界里本身就是一个容器,专业点的说法是用来存储交易记录的数据结构。他主要由两部分结构组成:区块头区块体


在区块头中,存放着一些经过结构化的数据,这些数据大多数都是已知的、或者可以轻易计算得出的,当然也有除外的,后面会讲到。对于计算机节点所有的计算行为,实际上就是对区块头中这些结构化的数据进行操纵和运算。


而区块体中则简单的存放着这段时间内产生的有效的交易记录(即我之前问题回答中描述的小纸条)。


下面我们开始一一讲述区块头中几个重要的结构化的数据到底是什么玩意儿:


PrevBlock

每一个区块都有一个名字,即区块ID,区块ID是通过下面公式计算得来:

区块ID = SHA256 (区块头信息)

而 PrevBlock 就是当前区块的上一个区块的ID,正是因为当前区块的产生是基于上一个区块的ID建立,这也就很好解释了前面为什么我们说在「某一个节点的视野里」以及「平行宇宙」的概念了。

这里的“区块头信息”即当前区块的“PrevBlock + MerkleRoot + Nonce + Time + Bits…”最后所形成的字符串;SHA256函数在之前的回答中已经做了讲解,这里就不再赘述。显然,对于当前区块而言,PrevBlock 是已知的。


MerkleRoot

Merkle Tree 是一种哈希二叉树,使用它可以快速校验大规模数据的完整性。在比特币网络中,使用 MerkleTree 这种算法和数据结构,可以很快的将所有交易记录最终压缩和归纳成一个统一的哈希值,即 MerkleRoot。

这个计算过程比较复杂,你只需要知道,区块体中任何一笔交易记录的改变都会使得使得区块头中的 MerkleRoot 发生翻天覆地的改变。显然,MerkleRoot 根据已知的交易记录可轻易得到,并且结果明确。


Difficulty & Bits

在讲Difficulty之前,先讲一个概念:比如说掷骰子,我们规定掷到小于等于数字 ⑥ 的难度为1,毫无疑问,这几乎是没有任何难度的,100% 会掷到一个数字满足这个条件;那么显然,掷到小于等于 ③ 的难度就是4,小于等于 ① 的难度则是6。

比特币协议里也有类似的规则,中本聪老先生首先定义了一个64位的十六进制整数0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(开头的两位0x代表的是十六进制),在当时全网算力的条件下,大约10分钟左右的哈希运算会找到一个数字小于等于这个整数。作为缔造者,规定了那个时间点、全网拥有的算力找到那个数字的难度是 1;后续以此为标准,根据变化中的全网算力,动态调整一个与之匹配且成正比的 Difficulty 难度值,来尽可能保证全网10分钟运算找到符合要求的数字。调整的周期约每两周一次,即每2016个区块被挖出来之后就调整一次Difficulty值。

Bits 是与 Difficulty 对应的另外一种数据表现形式,两者本质上表达了相同的目的,但是其转化的过程稍微有点复杂,没必要去纠结了。你只需要知道,当全网算力是固定已知的,那么 Difficulty & Bits 也就是能够轻易知道的。


Nonce

从0到N,一个只使用一次的随机数。前面我们在讲故事的时候讲到「区块埋在广阔的地下」、「工作量巨大」、「成果随机」等,真实的原因就是Nonce的随性性导致。


Time

当前区块产生的近似时间戳。为什么说“近似”呢?是因为在一个去中心化的区块链世界里,这个时间戳是根据产生该区块节点相关联的其他节点的时间戳求平均后计算得到,不完全等同于国际标准。该值显然也是固定已知的。


以上即区块头中我认为比较重要的几个数据字段,如果你已经对上述名词解释有了基本认识,我们终于可以继续下一步 —— 探索“挖矿”的数学本质啦:


## Step1:根据以下公式,计算得到一个新的Target目标值


Target = \frac{某一个常量}{Difficulty}

其中,某一个常量是就是前面我们说的那个64位的十六进制整数0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,比特币创世的时候,Difficulty 是1,后续这么多年来一直根据全网算力周期性的动态调整 Difficulty,我在写这篇文章的时候(2018年03月29日16:20),区块#515648 的 Difficulty 是 3462542391191.563,可以间接推算出现在的全网算力比2009年创世纪的时候增大了3462亿倍!


计算的结果 Target 最终也会是一个含有多个前导零的64位十六进制字符串。这个计算过程也较为复杂,但是你只需要简单理解里面的正负向关系即可,比如说:当全网算力越多 ↑,意味着挖矿难度 Difficulty 越大 ,根据公式更加可知 Target 也就越小 ,某种意义上意味着前导零更多 ↑(设想,要小于有两个前导零的Target “00100”,简单粗暴的方法是找到拥有更多前导零的数即可,比如有3个前导零的“00011”)


在现有的算法中,当前 Target 值的求解并不需要每次执行上面那个计算公式,因为中间存在着一个步骤的消耗,首先你需要计算出Difficulty,再去做整除,而Difficulty又是根据当前算力跟创世难度1的比较得到,步骤冗长。事实上的算法是会直接根据上一个区块的 Target 值进行求解而来,两者计算出来的效果其实是一样的:

Target_{new} = \frac{T_{2016}}{1,209,600} * Target_{prev}

T_{2016} 是实际环境下产生 2016 个区块的时间总和;1,209,600 是按照比特币的标准“平均每10分钟出一个区块” 产生2016个块所需要的秒数(2016*10*60=1,209,600),这是一个固定常量; Target_{prev} 是上一个区块周期内的 Target 值。

上述公式很好理解,产生2016个区块每个产生的平均时间是9分钟,意味着全网环境更容易挖出区块了,Difficulty 肯定需要相应的提升 10% 才能维持十分钟出块的效率,Target 也就相应的会降低 10% 。如此,我们仅需要通过对 Target_{new} 的调整就能够尽最大能力维持全网的平衡。


## Step2:计算当前区块的ID,使其满足以下公式


SHA256\left( PrevBlock + MerkleRoot + Nonce + Bits + … \right) \leq Target

显然,在上述公式中,除了Nonce以外,其他的参数都是已知或者可以通过简单计算得到。哈希映射运算通常被设计成不能逆向计算的,唯一的求解办法就是去暴力破解。所以,在比特币的世界里,挖矿的数学本质就是不停的执行上述公式,通过随机碰撞这个Nonce,使其最终满足公式即认为挖矿成功!


看起来是不是很 easy?其实就这么简单。


哦,对了,上述所有,实际上在区块链世界里就是POW共识算法的核心。其算法的优缺点都十分明显。

编辑于 2018-06-07

文章被以下专栏收录