Hash算法是区块链中最核心的算法,在了解区块链前我们必须先了解关于Hash算法的一些基本概念。
2.1 Hash的种类:
Hash算法有很多种,其中有MD5、SHA,而SHA算法又分为SHA-1、SHA-224、SHA-256、SHA-384和SHA-512五种变体,区块链中用到的是SHA256,,所以我们在这里会重点关注,后面会讲到。
2.2、Hash算法的特点:
1、输入任意长度的字符串(x)可以得到长度固定的结果(H(x)),例如使用MD5算法:
MD5("version1") = "966634ebf2fc135707d6753692bf4b1e";
MD5("version2") = "2e0e95285f08a07dea17e7ee111b21c8";
2、输入敏感,输入数据的稍微改变就会引起Hash运算结果的面目全非,上面的例子中“version1”和“version2”仅仅是最后一位“1”和“2”的区别,得出来的结果却截然不同。
3、免碰撞,即不会出现输入x≠y但是H(x) = H(y)的情况,也就是强抗冲突性。
4、原像不可逆,通俗地说,指的是知道输入值,很容易通过哈希函数计算出哈希值;但知道哈希值,没有办法计算出原来的输入值。也就是说,对于一个给定的输出哈希结果H(x),想要逆推出输入x,在计算上是不可能的,比如上面将“version1”的字符串经过MD5算法得出"966634ebf2fc135707d6753692bf4b1e",但是没有方法在知道输出值"966634ebf2fc135707d6753692bf4b1e"的情况下,反推出原始值是“version1”。
4)难题友好性,不存在比穷举更好的方法,以使哈希结果H(x)落在特定的范围,换一种说法就是,没有便捷的方法去产生一满足特殊要求的哈希值。
哈希函数的难题友好性构成了基于工作量证明的共识算法的基础,例如,给定字符串“blockchain”,并在这个字符串后面连接一个整数值串x,对连接后的字符串进行SHA256哈希运算,要求得到的哈希结果(以十六进制的形式表示)以若干个0开头的。按照这个规则,由x=1出发,递增x的值,我们需要经过2688次哈希计算才能找到前3位均为0的哈希值,而要找到前6位均为0的哈希值,则需进行620969次哈希计算。也就是说,没有更快捷的方法来产生一个满足要求的哈希结果。
2.3、SHA-256算法
SHA-256属于Hash算法中的一种,由美国国家安全局研发,又属于SHA算法的变种之一,是SHA-1的后继者。
SHA256相对于其他的Hash算法来说,特点是对于任意长度的消息,SHA256都会产生一个256bit长的哈希值,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示。
Hash算法作为区块链中的主要算法对于区块链来说有着怎样的意义,和区块链的工作原理又有什么关系?
别急,慢慢往下看。
现在假设一种场景:
网络传输数据的时候,A收到B的传过来的文件,需要确认收到的文件有没有损坏。如何解决?
最简单的方法是对整个原始数据做Hash运算得到固定长度的Hash值,然后把得到的Hash值公布在网上,这样用户从可信的渠道下载到公布的Hash值之后,对数据再次进行Hash运算,比较运算结果和网上公布的Hash值进行比较,如果两个Hash值相等,说明数据在传输过程没有损坏(篡改),反之说明数据经过篡改或损坏。
如果从一个稳定的服务器(可信的渠道)进行下载,采用单一Hash是可取的。但如果数据源不稳定,一旦数据损坏,就需要整个数据重新下载,这种下载的效率是很低的。
为了解决这个问题,在P2P网络中做数据传输的时候,往往需要把文件拆分很多小的数据块各自传输,同时从多个机器上下载拆分过后不同的小数据块,这样的好处是,如果小数据块在传输过程中损坏了,那么只要重新下载这一块数据就行了,不用重新下载整个文件。
但是这么做的话,新的问题又来了,怎么去验证小的数据块有没有损坏?
答案是,下载之前我们会对每个小的数据块分别做Hash运算最终得到一个Hash List。下载的时候,在下载到真正数据之前,我们会先下载这个Hash List。
同样的问题,怎么确定这个Hash List是正确的呢?
有一种办法是对所有的小数据块进行Hash运算得到每个小数据块的Hash值然后再用遍历的办法与原数据块的Hash值做一一对比,只有hash list中的每一个hash都是正确的,我们才会认为这个hash list是有效的,嗯,好像是可以,但这种方法代价是不是太大呢,正常场景下数据量可是相当巨大的,这种方法的效率明显比较低下。
解决办法是最开始在生成HashList的时候把每个小块数据的Hash值拼到一起,然后对这个长字符串再做一次Hash运算,这样就能得到一个最终的hash,这个最终的Hash值我们称之为Hash Root(Top Hash)。
这就是Hash算法的作用,利用了Hash算法中的敏感性,某一条数据中只要有任何一点小的改动,会导致计算出来的Hash List中的某一条Hash值的不同,而Hash List中的某一条Hash值的不同又会导致最后算出来Hash root与原数据的Hash Root有着天差地别,简单来说,我们只需要一个长度固定的字符串(Hash Root)就能轻易识别任意大小的数据是否损坏或者经过篡改,识别成本很低。
所以,最终处理就是,下载数据的时候,首先确保从可信的数据源得到正确的根Hash,用它来校验整个Hash List,然后通过校验后的Hash List校验整个数据块。
如图,收件人可以在收到信息后,先比对节点1的Hash值,若节点1没有错误则无需进行进一步的比对。若发现节点1错误可以继续比对节点2和3的值。发现节点3无误则可以放弃比对节点6和7. 找到错误来自节点2,再依次对节点4和5运算Hash值并发现错误源于自节点4.然后收件人可以重新要求发信人发送节点4的数据从而使整个数据和发件人的保持一致。
现实生活中我们常用的BT(BitTorrent,一种分布式文件下载协议)下载就是采用这一套技术流程,代表性的有迅雷下载,所以大家在用迅雷下载时,有没有想过,为什么用迅雷下载的时候有时候需要先下载一个“种子”文件呢,这个“种子文件到底是什么?
其实这个种子文件就包含了我们前面所说的Hash List和Hash Root,用法自然也是前面的所说的——验证数据块是否损坏或者被篡改,保证下载原始数据的的完整性和正确性。
这里讲了这么多,好像没有感受到与区块链有任何联系,实际上这些只是对基本概念的普及,利用这些基础信息我们才能开展后面的工作。
OK,进入重点。
三、区块链工作原理—区块结构篇
在了解区块链之前,我们需要知道很重要的一点就是,区块链是由“区块”这个基本单元所构成。
字面意思就可以看出来,区块链是由“区块”组成的链条,从技术的角度来说,区块链是一种分布式数据库。
如果把区块链比作一支账本,那么区块就是这个账本的一页。
“区块”的概念如此重要,以至于我们只有真正理解了区块,才能真正理解区块链。
所以,下面我们将会详细“解剖”区块,看看区块的“五脏六腑”,由内往外窥探整个区块链的原理。
3.1、先来区块基本组成,如图
首先,一个完整的区块分为区块头(Block Header)和区块体(Block Body)。
什么是区块体?区块体内包含的是一条条交易数据,列如张三给李四转了一笔账,转账者,接收者,金额,时间等等,这些信息构成一条交易数据,每一个区块的区块体都包含着无数条数据,可以理解为,区块体才是数据真正存储的地方。
了解了区块体,那什么是区块头?区块头是整个区块的核心也是区块的元数据,它的成员变量全都是公共的,这使得它可以很方便的向调用者提供关于Block属性的操作。区块头的组成相对复杂,我们一一来看。
1、Hash
由SHA256算法计算得出得当前区块的哈希值,也是代表当前区块的唯一值,可以理解成我们通常开发中Id的概念。每个区块都有自己的唯一Hash。
2、父哈希(Pre Hash)
上一个区块的哈希值。每个一区块当中都保存着上一个区块的Hash,可以理解为父子关系,因为除了第一个区块(创世区块),所有区块的产生都是基于前一个区块。
3、子哈希(Next Hash)
基于当前区块所产生的下一个区块的哈希值,当前区块也就是子区块的父区块。
看到1、2、3这里可以解决我们一直以来的一个疑惑,区块链里面的“链”到底是什么?其实就是由于每个区块有当前Hash 、父Hash、子Hash的概念,这些所谓的“父”,“子”形成了一套完整的顺序链条,换句话说,区块与区块之间并非杂乱无章,而是存在着严格的顺序关系。而这种顺序关系像极了链表的概念,二者的逻辑顺序均是通过链表中的指针链接次序实现的。
链表:
区块链:
注:有一点点小的区别是区块链是非循环的,而链表是可以有循环的。
4、版本号(Version)
系统版本号,可以理解成日常开发中开发的软件版本。
5、区块高度(Height)
区块链网络中第几个诞生的区块序号,例如第一个区块通常也被称为创世区块,高度为0(这很程序员),第十个产生的区块高度为9(区块并不是一下子就全有的,例如比特币区块就是每10分钟产生一个,也就说区块的“出生”都是有着先后顺序的,这个顺序我们会打上标签,这个标签就是”高度“)。
6、时间戳(TimeStamp)
区块创建的时间,同时也交易数据打包进区块的时间,区块的诞生是伴随着交易数据的打包记录,保存的地方就是我们前面提到的区块体。
7、随机数(Nonce)
一个从0开始,最大长度为32位的随机数,这里就涉及到挖矿了,所谓的挖矿,就是不停地变更区块头中的随机数,并对每次变更后的区块头(block_header,)做双重SHA-256运算(即SHA256 (SHA 256(Block_Header))),将结果值哈希反转并与当前网络的目标值对应的十进制字符串做对比,如果小于目标值,则解题成功,工作量证明完成。(这里先简单了解即可,后面会重点讲到)。
9、难度目标(Target Bits)
难度值是矿工们挖矿的重要参考指标,它决定了矿工大约需要经过多少次Hash运算才能产生一个合法的区块。比特币的区块大约每10分钟生成一个,如果要在不同的全网算力条件下,新区块的产生都基本保持这个速率,难度值必须根据全网算力的变化进行调整。简单地说,难度值被设定在无论挖矿能力如何,新区块产生速率都保持在10分钟一个。
难度值的调整是在每个完整节点中独立自动发生的。每隔2016个区块,所有节点都会按统一的公式自动调整难度值
公式如下:
新难度值 = 旧难度值*(过去2016个区块花费时长/20160分钟)
难度目标值= 最大目标值/新难度值
而最大目标值为一个恒定值(常量):0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
解题(挖矿)成功的依据是:
SHA256(SHA256(block_header)) < F(nBits)
| |
(挖矿结果) (目标值)
10、、Merkle Root(默克尔树根)
Merkle Root的计算过程相对复杂,一起放到最后的算法环节进行统一详细讲解。
区块链的工作原理—核心算法篇
在这一篇中我们将介绍几点核心算法,同时也是整个区块链的核心原理。
我们将会讲到:
1、上一篇中提到的Merkle树是什么?Merkle树的树根 —Merkle Root 的算法。
2、区块Hash值的算法。
3、挖矿合法性算法验证。
一、Merkle Tree
1、Merkle Tree是一种树,大多数是二叉树,也可以多叉树,无论是几叉树,它都具有树结构的所有特点;
2、Merkle Tree的叶子节点的value是数据集合(区块体中的数据)的单元数据或者单元数据HASH。
3、非叶子节点的value是根据它下面所有的叶子节点值,然后按照Hash算法计算而得出的。
Merkle Tree可以看做前面所说Hash List的泛化,Hash List可以看作一种特殊的Merkle Tree,即树高为2的多叉Merkle Tree,(前面Hash List没理解清楚的可以往回翻重新理解一下)。
在最底层,和哈希列表一样,我们把数据分成小的数据块,有相应地哈希和它对应。但是往上走,注意,这里并不是和Hash List一样直接去运算根哈希,而是把相邻的两个数据哈希合并成一个字符串,然后将这个由两个Hash值拼成的字符串计算出新的Hash,,这样每两个哈希就结婚生子,得到了一个”子哈希“。如果最底层的哈希总数是单数,那到最后必然出现一个单身哈希,这种情况就直接对它进行哈希运算,所以也能得到它的子哈希。于是往上推,依然是一样的方式,可以得到数目更少的新一级哈希,最终必然形成一棵倒挂的树,到了树根的这个位置,这一代就剩下一个根哈希了,我们把它叫做 Merkle Root,而这就是所谓的Merkle Root的生成过程。
在p2p网络下载网络之前,先从可信的源获得文件的Merkle Tree树根。一旦获得了树根,就可以从其他从不可信的源获取Merkle tree。通过可信的树根来检查接受到的Merkle Tree。如果Merkle Tree是损坏的或者虚假的,就从其他源获得另一个Merkle Tree,直到获得一个与可信树根匹配的Merkle Tree。
Merkle Tree和Hash List的主要区别是,Merkle Tree可以直接下载并立即验证Merkle Tree的一个分支。因为可以将文件切分成小的数据块,这样如果有一块数据损坏,仅仅重新下载这个数据块就行了。如果文件非常大,那么Merkle tree和Hash list都很难做到,但是Merkle tree可以一次下载一个分支,然后立即验证这个分支,如果分支验证通过,就可以下载数据了。而Hash list只有下载整个hash list才能验证。(前面花了比较大的篇幅介绍了Hash算法,就是为了让大家提前了解,如何利用Hash去保证数据的一致性,这里的merkle root也是这个作用,只不过算法上有区别)
说了这么多, Hash Root也好、Merkle Root也罢,不管算法如何,剥开层层外壳,对于我们而言,它们的核心价值就是——确保数据的真伪性,我们常说区块链的数据不可篡改性也就是基于这一点。
区块的工作原理—区块Hash的生成。
区块Hash是代表区块的唯一值,这个值并不是凭空创造,而是和merkle root 、timestamp、nonce息息相关。
我们采用验证法来看看这个值是如何产生的,以比特币区块277316为例。
其信息来自网站http://blockchain.info
以上是区块高度为277316的区块的区块头的所有数据。
现在开始对区块277316的Hash值进行验证。
第一步,准备数据
1、版本号的十进制:2
2、merkle root+pre_hash的16进制:
0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569 c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e
3、2013-12-27 23:11:54 的时间戳:1388185914
4、难度目标(Bits)的十进制:419668748
5、随机数(nonce)的十进制:924591752
第二步,全部转换为16进制
1、00000002
2、0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569
c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e
3、52be093a
4、1903a30c
5、371c2688
第三步,从big-endian转化为little-endian
先解释一下什么是endian?简单理解就是Big-endian是高字节在低地址,Litter-endian则高字节在高地址。
比如 ,int a = 0x05060708
在big-endian的情况下存放为:
字节号 0 1 2 3
数据 05 06 07 08
在little-endian的情况下存放为:
字节号 0 1 2 3
数据 08 07 06 05
发明人中本聪可能为了让机器计算更快,而变为了更接近机器的编码方式little-endian.
好,回到正题,所以第二步的数据 我们再进行一次转化,得到了
1、02000000
2、69054f28012b4474caa9e821102655cc74037c415ad2bba70200000000000000
2ecfc74ceb512c5055bcff7e57735f7323c32f8bbb48f5e96307e5268c001cc9(到这里的时候已经拼成一个字符串了)
3、3a09be52
4、0ca30319
5、88261c37
第四步,拼接字符串,开始验证
1、我们将上面准备的字符数据重新拼接成一个新的字符串,这个字符串我们称之为“block_header”:
02000000
+
69054f28012b4474caa9e821102655cc74037c415ad2bba702000000000000002ecfc74ceb512c5055bcff7e57735f7323c32f8bbb48f5e96307e5268c001cc9
+
3a09be52
+
0ca30319
+
88261c37
最终拼接得到的字符串block_header为:
0200000069054f28012b4474caa9e821102655cc74037c415ad2bba702000000000000002ecfc74ceb512c5055bcff7e57735f7323c32f8bbb48f5e96307e5268c001cc93a09be520ca3031988261c37
2、将上面的 block_header转成hex形式得到:
b'\x02\x00\x00\x00i\x05O(\x01+Dt\xca\xa9\xe8!\x10&U\xcct\x03|AZ\xd2\xbb\xa7\x02\x00\x00\x00\x00\x00\x00\x00.\xcf\xc7L\xebQ,PU\xbc\xff~Ws_s#\xc3/\x8b\xbbH\xf5\xe9c\x07\xe5&\x8c\x00\x1c\xc9:\t\xbeR\x0c\xa3\x03\x19\x88&\x1c7'
3、对这个值进行两次SHA256运算,为什么要两次,这是因为SHA1在2017年被攻破,采用的方法是birthday collision attack。社区觉得SHA2被攻破也是时间的问题,而抵御birthday collision attack的有效方法为双重哈希算法,当然这种说法只是网上传言,没有得到具体确认。
伪代码:SHA256(SHA256(Hex(block_header))),最终得到:
b'\xc4\xbd\xc7,\x1b\xb3\xa9D\xd9\xf2~\xb9(\xa9\xc4A\xdb\x96^\t;\xa1\xb9\xb6\x01\x00\x00\x00\x00\x00\x00\x00'
3、重新编码得到:
c4bdc72c1bb3a944d9f27eb928a9c441db965e093ba1b9b60100000000000000
看到后面有一堆0,感觉胜利的曙光快来了。其实这里实际上已经完成了,但是得到这个值是little-endian小端模式的,比特币区块上所记录的值也确实是这个值,但是我们在平常使用和网页浏览展示时,都是使用Big-endian大端顺序
所以这里我们再进行一次从little-endian转化为big-endian,就能得到:
0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4
大功告成,看看http://blockchain.info所查询到的,完全一致,说明我们的方法是正确的,区块Hash的算法就是我们前面的验算步骤。
总的来说,算法不难,最难的地方就在于亲自验算的过程中,要把所有的隐藏知识都挖掘出来。中文资料中,极少有人做详细的通篇验算,而一旦真正理解了验算的过程,我们会发现比特币的算法真的不难。
附:
整个第四步的Python代码如下:
有兴趣的可以将这一段代码copy到python开发工具中运行试试,看看得到的值是否如上所说。也可以依照上面的步骤验证其他区块加强理解。
区块的合法性验证
如何确认区块的合法性,前面在介绍区块头时有提及过,简而言之就是区块Hash必须要小于Target Bits (目标难度值),这里我们继续采用验算法。
区块Hash值在上一节已经得到验证,我们算出的是
0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4
Target Bits在上面也已经记录了,是
419668748
数据都准备好了,要验证区块是否合法还不简单吗,直接对比两个值的大小不就完了?
真有这么简单吗?
事实上所谓的“对比大小”也是有一套完整的换算流程的。
具体怎么换算,这里还们还是按照老套路,采用验证法一步步走。
1、准备数据
Bits :419668748
最大目标值:0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
Hash:0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4
2、将Bits 转成 16进制得到:
1903a30c
3、将1903a30c进行拆分,前2位我们称为幂者指数,后面6位称为系数,拆分后得到
19 03a30c
3、将拆分的值代入固定公式:
目标值=系数*2^(8*(幂者指数-3))次方
即:目标值 = 0x03a30c*2^(8*(0x19-3))
最终计算出来的就是真正意义上的目标值了。
0000000000000003a30c00000000000000000000000000000000000000000000
4、验算结果
target bits:
0000000000000003a30c00000000000000000000000000000000000000000000
target:
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
>
0000000000000003a30c00000000000000000000000000000000000000000000
计算结果小于难度目标,符合要求,那这个结果就一定是网站上公布的数字了。
正确的hash值
在挖矿时,nonce随机数是未知的,要从0试到2^32,但是这个数字其实不大,只有4294967296次。以现在的一台矿机动辄14T每秒的算力,全部算完到上限也不需要一秒。所以需要使用创币交易中的附带信息,额外的字符串成为extra nonce,例如中本聪在比特币的创世区块中写道“The Times 03/Jan/2009 Chancellor on brink of second ballout for banks”(英国泰晤士报2009年1月3日文,财政大臣正处于实施第二轮银行紧急援助的边缘)嘲讽中心化的银行。
在2013年年底时,区块产生,这需要算力达到8T/s的设备,即每秒8*10^15次暴力验证,连续工作10分钟。这对于2018年的现在来说的确不算什么,一台矿机,不比两三块砖头大多少,就拥有14T/s的算力,只需要6,7分钟单独就可以挖到。但在当时,8T也是全网千分之一的算力了,需要当时最好的矿机上百台一起工作。而如果这个计算使用一台普通的桌面电脑,需要26年。如果使用一台iphoneX的话,每秒可以做70次计算,那么将需要四百万年。
通过上面的算法我们完整地回顾了比特币区块链的工作量证明算法,如果各位完全理清了其中的思路,也就可以手动实现自己的挖矿程序,或者另外尝试设计一些新的区块链产品了。最艰深的技术,我们希望能够在底层去了解,然而拨开云雾,其实底层的逻辑并不难。
不过比特币里面的技术远不止挖矿算法,加密算法,Script智能合约,各种协议,各种网络,交易的验证,每一个都充满了魔性,进出之间,不由得让人惊叹发明人知识的深度与广度。无论比特币是什么,将会怎样,但是以比特币为第一个大规模应用的区块链技术,已经扩散了开来,整个系统的严密与逻辑的复杂,的确让人着迷。
更多
对于创世区块,版本号是1,高度为0(这很程序员),。
前一区块的hash值,猜想会是什么呢?对于创世区块,是没有前一区块,中本聪默认写成了0000000000000000000000000000000000000000000000000000000000000000。
难度目标是1,这是定义为一个sha256结果的前32位是0,也就是对应的16进制字符串要有8个0,那么难度bits此时是0x1d00ffff。这个数字的得出需要概率论的知识,同时也是中本聪的一个规定。
更多区块信息可以前往http://blockchain.info查询,或许会有更多发现哦。
其他工具
https://hash.online-convert.com/sha256-generatorSHA-256在线验证
https://webbtc.com/ 获取区块的json数据
http://blockexplorer.com/q/getdifficulty查询比特币的当前难度
http://bitcoin.sipa.be/查看难度的变化情况
http://blockexplorer.com/q/getblockcount查询比特币的区块数
https://bitcoin.org/en/developer-reference比特币区块链更详细的讲解(英文)
参考文章:
https://blog.csdn.net/wo541075754/article/details/54632929
https://www.jianshu.com/p/c6ef67755105
http://www.cnblogs.com/zhaoweiwei/p/difficulty.html
https://blog.csdn.net/jason_cuijiahui/article/details/79011118
https://blog.csdn.net/chaiyu2002/article/details/81237971
https://www.8btc.com/article/9688
第三章—java简单实现区块链
前面两章说了许多关于区块链的理论知识,这一章我们将会将前面的所有的理论知识结合起来,用java简答实现一套区块链。
基于面向对象的思想,首先
1、定义区块链的类块
import java.util.Date;
public class Block {
public String hash;//区块Hash
public String previousHash;//前导Hash
private String data; //可以理解为前面讲到的区块结构里的区块体里打包的交易数据
private long timeStamp; //时间戳
//Block Constructor.
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
}
}
2、创建数字签名
创建一个StringUtil,封装了sha256方法,方便后面调用,因为这是一个工具类,所以我们可以不关心它的具体实现方式,只要知道它是sha256的java实现即可。
import java.security.MessageDigest;
public class StringUtil {
//Applies Sha256 to a string and returns the result.
public static String applySha256(String input){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
//Applies sha256 to our input,
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
}
接下来让我们在Block类中应用 方法 applySha256 方法,其主要的目的就是计算hash值,我们计算的hash值应该包括区块中所有我们不希望被恶意篡改的数据,再加上preHash,data和timeStamp,这在上面已经演示过了,是一套固定公式,所以这里没有什么争议。
//简化了计算方式
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
data
);
return calculatedhash;
}
然后把这个方法加入到Block的构造函数中去
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}
3、测试
在主方法中让我们创建一些区块,并把其hash值打印出来,来看看是否一切都在我们的掌控中。
第一个块称为创世纪区块,因为它是头区块,所以我们只需输入“0”作为前一个块的previous hash。
public class NoobChain {
public static void main(String[] args) {
Block genesisBlock = new Block("Hi im the first block", "0");
System.out.println("Hash for block 1 : " + genesisBlock.hash);
Block secondBlock = new Block("Yo im the second block",genesisBlock.hash);
System.out.println("Hash for block 2 : " + secondBlock.hash);
Block thirdBlock = new Block("Hey im the third block",secondBlock.hash);
System.out.println("Hash for block 3 : " + thirdBlock.hash);
}
}
打印:
Hash for block 1: f6d1bc5f7b0016eab53ec022db9a5d9e1873ee78513b1c666696e66777fe55fb
Hash for block 2: 6936612b3380660840f22ee6cb8b72ffc01dbca5369f305b92018321d883f4a3
Hash for block 3: f3e58f74b5adbd59a7a1fc68c97055d42e94d33f6c322d87b29ab20d3c959b8f
每一个区块都必须要有自己的数据签名即hash值,这个hash值依赖于自身的信息(data)和上一个区块的数字签名(previousHash),但这个还不是区块链,下面让我们存储区块到数组中,这里我会引入gson包,目的是可以用json方式查看整个一条区块链结构。
import java.util.ArrayList;
import com.google.gson.GsonBuilder;
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static void main(String[] args) {
//add our blocks to the blockchain ArrayList:
blockchain.add(new Block("Hi im the first block", "0"));
blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash));
blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash));
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println(blockchainJson);
}
}这样的输出结构就更类似于我们所期待的区块链的样子。
4、检查区块链的完整性
在主方法中增加一个isChainValid()方法,目的是循环区块链中的所有区块并且比较hash值,这个方法用来检查hash值是否是于计算出来的hash值相等,同时previousHash值是否和前一个区块的hash值相等。(第二章讲到过,验证区块的有效性即重新计算区块的Hash值并与链上记录的区块Hash值是否一致,而上一个区块的Hash值—preHash,又影响了当前Hash的计算)
所以我会的验证方法就会这么写:
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
//遍历整个区块链
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//当重新计算区块Hash发现和链上记录的不相等时
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//当上一个区块上的Hash和当前区块记录的preHash不相等时
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
}
return true;
}
任何区块链中区块的一丝一毫改变都会导致这个函数返回false,也就证明了区块链无效了。
5、挖矿
这里我们要求挖矿者做工作量证明,具体的方式是在区块中尝试不同的参数值直到它的hash值是从一系列的0开始的。让我们添加一个名为nonce的int类型以包含在我们的calculatehash()方法中,以及需要的mineblock()方法
public class Block {
public String hash;
public String previousHash;
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.
private int nonce;
//区块的构造
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}
//基于交易数据信息和其他数据生成当前区块的hash
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
Integer.toString(nonce) +
data
);
return calculatedhash;
}
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0'); //计算出的Hash要求前面多少个0(理论上前面的0要求越多代表挖矿难度越大)
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
}
}
mineBlock()方法中引入了一个int值称为difficulty难度,低的难度比如1和2,普通的电脑基本都可以马上计算出来,我的建议是在4-6之间进行测试,普通电脑大概会花费3秒时间,在莱特币中难度大概围绕在442592左右,而在比特币中每一次挖矿都要求大概在10分钟左右,当然根据所有网络中的计算能力,难度也会不断的进行修改。
我们在NoobChain类 中增加difficulty这个静态变量。
public static int difficulty = 5;
这样我们必须修改主方法中让创建每个新区块时必须触发mineBlock()方法,而isChainValid()方法用来检查每个区块的hash值是否正确,整个区块链是否是有效的。
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static int difficulty = 5;
public static void main(String[] args) {
//手动创建几个区块
blockchain.add(new Block("Hi im the first block", "0"));
System.out.println("Trying to Mine block 1... ");
blockchain.get(0).mineBlock(difficulty);
blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash));
System.out.println("Trying to Mine block 2... ");
blockchain.get(1).mineBlock(difficulty);
blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash));
System.out.println("Trying to Mine block 3... ");
blockchain.get(2).mineBlock(difficulty);
System.out.println("\nBlockchain is Valid: " + isChainValid());
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println("\nThe block chain: ");
System.out.println(blockchainJson);
}
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
//遍历区块,验证有效性
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//当重新计算区块Hash发现和链上记录的不相等时
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//当上一个区块上的Hash和当前区块记录的preHash不相等时
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
//这里涉及到的其实就挖矿算法,通过不断变化的nonce值来生成Hash值,如果生成的Hash值的前几位的都是0且和target要求的位数一致,代表这个随机数生成的区块是有效的。
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("This block hasn't been mined");
return false;
}
}
return true;
}
}
打印:
Trying to Mine block 1...
Block Mined!!! : 0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082
Trying to Mine block 2...
Block Mined!!! : 000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a
Trying to Mine block 3...
Block Mined!!! : 000000576987e5e9afbdf19b512b2b7d0c56db0e6ca49b3a7e638177f617994b
Blockchain is Valid: true
[
{
"hash": "0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082",
"previousHash": "0",
"data": "first",
"timeStamp": 1520659506042,
"nonce": 618139
},
{
"hash": "000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a",
"previousHash": "0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082",
"data": "second",
"timeStamp": 1520659508825,
"nonce": 1819877
},
{
"hash": "000000576987e5e9afbdf19b512b2b7d0c56db0e6ca49b3a7e638177f617994b",
"previousHash": "000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a",
"data": "third",
"timeStamp": 1520659515910,
"nonce": 1404341
}
]
经过测试增加一个新的区块即挖矿必须花费一定时间,大概是3秒左右,你可以提高difficulty难度来看,它是如何影响数据难题所花费的时间的
如果有人在你的区块链系统中恶意篡改数据:
他们的区块链是无效的。
他们无法创建更长的区块链
网络中诚实的区块链会在长链中更有时间的优势
因为篡改的区块链将无法赶上长链和有效链,除非他们比你网络中所有的节点拥有更大的计算速度,可能是未来的量子计算机或者是其他什么。
如果你耐心的看到这里了,恭喜,我们已经基本创建了属于自己的区块链
它有:
有很多区块组成用来存储数据
有数字签名让你的区块链链接在一起
需要挖矿的工作量证明新的区块
可以用来检查数据是否是有效的同时是未经篡改的
结语:
坦白说,这篇文章更多的是从代码讲解的角度来剖析区块链的工作原理,为的是让大家更好的吸收前面所讲解的知识点,虽然有一些伪代码的成分,但是我们确实完成了区块链所有应该有的功能。当然,真正的区块链实现远不止这么一点,大量的实现细节值得我们去研究、探讨和实现,更多的我们后续会再讲到。