前言
在上一篇文章中,我们构建了一个非常简单的数据结构,这是区块链数据库的本质所在。我们可以通过它们之间的类链关系为它添加块:每个块都链接到前一个块。但是,我们的区块链实现有一个重大缺陷:向链中添加块很容易且低廉。区块链和比特币的关键之处是添加新块是一项艰苦的工作。今天我们来修复这个缺陷。
工作量证明
区块链的一个关键思想是,必须进行一些困难的工作才能将数据放入其中。正是这项艰苦的工作使区块链变得安全且一致。此外,这项困难的工作也得到了回报(这就是人们为什么通过挖矿获取货币)。
这种机制与现实生活中的机制非常相似:人们必须努力获得报酬得以继续生活。在区块链中,网络的一些参与者(矿工)努力维护网络,并向其添加新块,来获得相应的报酬。由于他们的工作,区块得以以安全的方式被整合到区块链中,这保持了整个区块链数据库的稳定性。值得注意的是,完成工作的人必须能够证明这一点。
整个“努力工作和证明”机制被称为工作量证明。这项工作需要大量的算力:即使是高性能的计算机也无法快速完成。此外,这项工作的难度不时增加,每次一小时会有6个区块的增长。在比特币中,这项工作的目标是找到一个块的哈希,而这个哈希可以作为一个证明。因此,找到特定需求的证明就是实际的工作。
最后要注意的一点是:工作量证明算法必须满足要求,完成工作很难,验证证明很容易。证明通常会交给其他人,验证不必花费太多时间。
哈希算法(散列法)
在这一段中我们将会讨论hash算法。如果你已经对这个概念比较熟悉了,那么可以跳过这部分。
散列法是获取指定数据的散列的过程。哈希值是计算数据的唯一表示。散列函数是一种获取任意大小数据并生成固定大小散列的函数。以下是散列的一些主要功能:
1、无法从散列中恢复原始数据。因此,散列不是加密。
2、某些数据只能有一个哈希值,哈希值是唯一的。
3、改变输入数据中的一个字节将导致完全不同的散列。
散列函数广泛用于检查数据的一致性。除软件包外,某些软件提供商还会发布校验和。下载文件后,您可以将其提供给散列函数,并将生成的散列与软件开发人员提供的散列进行比较。
在区块链中,散列用于保证块的一致性。散列算法的输入数据包含前一个块的散列,因此无法(或者至少很难)修改链中的块,这必须重新计算其散列及其后所有块的散列值。
Hashcash
比特币使用Hashcash,这是一种最初为防止电子邮件垃圾邮件而开发的工作量证明的算法。它可以分为以下几个步骤:
1、提供一些公开的数据(如果是电子邮件,它是接收者的电子邮件地址;对于比特币,它是区块头)。
2、添加一个计数器。计数器从0开始。
3、 获取data(数据) + counter(计算器)组合的哈希值。
4、 检查哈希符合某些要求。
如果符合则完成。
如果不符合,请增加计数器并重复步骤3和4。
因此,这是一个强力算法:修改计数器,计算新的哈希并检查,增加计数器在检查,以此类推。这就是为什么它的计算成本很高。
现在让我们仔细看看哈希值必须满足的要求:在最初的Hashcash实现中,要求听起来是“散列的前20位必须为零”。在比特币中,需求会不时调整,因此,尽管计算能力随着时间的推移而增加,并且越来越多的矿工加入网络,但设计必须每10分钟生成一个区块。
为了演示这个算法,我从前面的例子中获取了数据(I like donuts“我喜欢甜甜圈”)并找到了一个以3个零字节开头的散列:
履行
我们完成了理论,让我们来编写代码!首先,让我们来定义挖掘的难度:
在比特币中,“目标位”是存储块被挖掘的难度的区块头。目前我们不会实现目标调整算法,因此我们可以将难度定义为全局常量。
24是一个任意数字,我们的目标是在内存中占用少于256位的目标。我们希望差异足够大,但也不要太大,因为差异越大,找到合适的散列越困难。
这里创建一个ProofOfWork结构,它包含一个指向块的指针和一个指向目标值的指针。“目标”是前一段中描述的要求的另一个名称。我们使用golang的big包,因为我们将哈希与目标进行比较:我们将哈希转换为一个大整数并检查它是否小于目标值。
在NewProofOfWork函数中,我们big.Int用值为1 ,初始化a 并将其向左移位256 - targetBits。256是以位为单位的SHA-256哈希的长度,也是我们将要使用的SHA-256哈希算法。十六进制表示target为:
它在内存中占用29个字节。这里是与前面例子中的哈希的视觉比较:
第一个哈希(根据“I like donuts”计算)比目标值大,因此它不是有效的工作证明。第二个哈希(在“I like donutsca07ca”上计算)小于目标值,因此它是一个有效的证据。
您可以将目标视为范围的上边界:如果数字(哈希)低于边界,则它是有效的,反之亦然。降低边界将导致有效数字减少,因此找到有效数字所需的工作更加困难。
现在,我们需要哈希数据。我们准备吧:
这篇文章很简单:我们只是将块字段与目标和随机数合并。nonce在这里是上面Hashcash描述的计数器,这是一个加密术语。
好的,所有准备工作都已完成,让我们实现PoW算法的核心:
首先,我们初始化变量:hashInt表示hash的整型标识; nonce是一个计数器。接下来,我们运行一个“无限”循环:它受限于maxNonce,等于math.MaxInt64; 这样做是为了避免nonce的溢出。尽管难度对于我们实现的PoW算法来说,溢出的可能性很低,但我们最好还是检查一下,以防万一。
在循环中我们需要:
1、准备数据。
2、用SHA-256哈希。
3、将哈希值转换为大整数。
4、将整数与目标值进行比较。
就像之前解释的那样简单。现在我们可以去掉Block中的SetHash方法了,并修改NewBlock函数:
在这里,您可以看到它nonce被保存为Block属性。这是必要的,因为nonce需要验证证明。该Block结构现在看起来这样:
好的!让我们运行该程序,看看是否一切正常:
好极了!您可以看到每个哈希现在以3个0字节开始,并且需要一些时间来获取这些哈希值。
还有一件事要做:让我们可以验证这个工作的证明。
这就是我们需要保存的随机数的地方。
让我们再检查一切都没问题:
输出:
结论
我们的区块链更接近其实际架构:现在添加块需要进行一系列复杂的工作,因此需要进行挖掘。但它仍然缺乏一些关键特征:区块链数据库不是持久性的,没有钱包,地址,交易,也没有共识机制。
(以上仅供参考,不构成投资意见)
领取专属 10元无门槛券
私享最新 技术干货