名词解释:
公钥:非对称加密术语,公钥与私钥成对出现,公钥公开,私钥私密,A节点可在一些内容上用私钥签名,所有其他节点都能用其公钥验证签名有效性;其他节点要发送信息给A都可以用其公钥加密,由于只能用A的私钥解密,因此只有A能够读取原始信息。
Hash:是一类不可逆函数,具备两个特点,已知Hash值,无法计算出原始数据;已知数据A,无法计算得到数据B使得两者的Hash值相同。
字节:计算机术语,1字节即2进制的8bit,最大可表示28即256个数字。
最近半年,比特币走出了波澜壮阔的行情,区块链概念大热,引起所有人瞩目,关于比特币的争论也甚嚣尘上。要清晰认识区块链和比特币,不仅要大致了解其原理还必须搞清楚比特币的核心-挖矿到底在做什么。
比特币挖矿算法
比特币挖矿一共可分为三步:
- 验证筛选交易
自上个被挖出的区块记录的交易以来,比特币世界不停发生着新的交易,即一定数量的比特币从账户A转移至账户B。这些交易将被广播至全网,被所有计算节点接收,在挖矿时,节点C首先使用账户A的公钥验证交易的合法性,有问题的交易将被排除在外。另外,在有效的交易中,挖矿节点可以根据一定标准决定将哪些交易包含在新的区块内。这里,比特币给了节点较大的自由裁决权,不过因为交易的手续费归矿工所有,所以不会出现所有节点都拒绝记录某交易的情况。
- 计算Merkle Root
交易筛选好了之后,所有交易按照两两配对计算Hash值,最终得到一个包含所有交易信息的Hash值,这个Hash就是Merkle Root,而整个树形结构被称作Merkle Tree,如图1所示。Merkle Root的意义在于使得交易记录不可修改,如果这个区块被挖出来之后,其中的交易被进行了任何修改,都不能通过Merkle Root的校验。那为什么要这么麻烦两两配对进行Hash呢,如果把所有交易全部拼接起来进行Hash,在验证单笔交易时需要所有其他交易的信息,这样效率会很低,而使用Merkle Tree验证单笔交易的开销会小很多。因为验证交易是非常频繁的操作,所以有必要采用Merkle Tree这种方式。
ABCDEEEE
/ \
ABCD EEEE
/ \ /
AB CD EE 奇数时E与自己配对
/ \ / \ /
A B C D E 交易
图1 Merkle Tree示意图
图2 区块示意图
如图2所示,区块头包含6部分信息
- 版本号
根据比特币客户端版本而定,一般是不变的,如果要改变也必须由比特币社区发起,协调各个大的矿池统一时间进行升级,可认为是常数。此字段4字节。
- 上个区块的Hash值
这就是为何比特币被称为区块“链”,每个区块的计算中均包含了上个区块的信息,这样就使得历史区块不可更改。对历史区块任何的更改都将改变历史区块的Hash值而使得其与之后的区块无法继续关联。此字段32字节。
- Merkle Root
此为上述通过所有交易计算得到的Merkle Root,此字段使得本区块的所有交易记录不可更改且可验证,此字段32字节。
- 区块生成时间
比特币使用全球统一时间UTC时间戳,具体使用的是1970年1月1日以来的累计秒计数。不同计算节点可自主选择此时间戳。此字段4字节。
- 生成难度
此难度系数为一个4字节整数,通过一定计算规则,可转化为一个以若干个0开头的32字节整数,真正代表难度系数的是这个32字节整数,挖矿时必须穷举随机数得到一个小于此数字的Hash值,才算挖矿成功。随着参与挖矿的算力越来越强,必须提高难度即减小此32字节数从而使得区块被挖出的时间保持均衡,所以这个难度系数会在每挖出一些区块之后根据最近的挖矿速度进行调整,在触发调整的时间点之间,此难度系数为常数。2013年时,此难度系数前62个bits为0,最近的难度系数来看,前73个bits为0。因为Hash的不可逆性,就代表着2013年时最多需要进行262次Hash计算才能挖到比特币区块,现在最多需要进行273次Hash计算才能挖到比特币区块。
- 随机数
随机数就是挖矿过程中需要去穷举的一个4字节整数。有些人可能很奇怪,4字节的表示范围也就是232,远小于难度系数指示的计算次数。但实际上交易选择范围和交易的顺序以及时间戳也是可变的,而且在交易数据块中包含一个属性Coinbase,可以保存一个任意数据,另外在挖矿的同时也会收到新的交易数据,这就是说Merkle Root的可变范围是非常大的,所以区块头可穷举的字段不仅有随机数一个字段。
选取随机数得到整个区块头之后,就是对整个区块头进行Hash计算,比对Hash结果与难度系数的大小,如果不能满足要求则继续选取新的区块头进行Hash计算,直到得到满足要求的Hash值(挖矿成功)或者收到其他节点计算得到的新的可验证的区块(挖矿失败)。
比特币区块验证计算
清楚了比特币挖矿过程,我们可以拿一个实际的比特币区块进行验证计算。如图3所示,选取了一个2013年12月的编号为277318的比特币区块进行验证。此区块详细信息见https://blockchain.info/block/000000000000000221b806dc42f784a70c3234511c91e1c8291cb0d91365bb5c。
验证计算步骤如下:
- 准备原始数据
项目 | 值 |
---|---|
版本号 | 2 |
前区块Hash | 000000000000000010236c269dd6ed714dd5db39d36b33959079d78dfd431ba7 |
Merkle Root | 28c4e2e7f9199c77eb5eb8266d26795ac517c8975b3b43154f8334b3ee80cda8 |
UTC时间 | 2013-12-27 23:36:53 |
难度目标 | 419668748 |
随机数 | 986485502 |
- 将原始数据转换为16进制
项目 | 值 |
---|---|
版本号 | 00000002 |
前区块Hash | 000000000000000010236c269dd6ed714dd5db39d36b33959079d78dfd431ba7 |
Merkle Root | 28c4e2e7f9199c77eb5eb8266d26795ac517c8975b3b43154f8334b3ee80cda8 |
UTC时间 | 52be0f15 |
难度目标 | 1903a30c |
随机数 | 3acc92fe |
- 将数据转为little-endian编码
普通编码方式是从高位到低位顺序,little-endian是从低位到高位编码,比特币之所以要这么做可能是运算速度上的考量,但是这一点在比特币社区也存在争议。转换方法是按照字节为单位逆序,例如一个16进制数字0x12345678,转换为little-endian编码之后将变为0x78563412,16进制的两位数字组成一个字节即12为字节1,34为字节2,56为字节3,78为字节4,将此数按照字节逆序排列就得到了0x78563412。转换结果如下:
项目 | 值 |
---|---|
版本号 | 02000000 |
前区块Hash | a71b43fd8dd7799095336bd339dbd54d71edd69d266c23100000000000000000 |
Merkle Root | a8cd80eeb334834f15433b5b97c817c55a79266d26b85eeb779c19f9e7e2c428 |
UTC时间 | 150fbe52 |
难度目标 | 0ca30319 |
随机数 | fe92cc3a |
- 执行Hash
将上述一共80字节数据进行Hash然后再转换为big-endian编码,得到结果为:
000000000000000221b806dc42f784a70c3234511c91e1c8291cb0d91365bb5c
此Hash值与图3给出的Hash完全一致
- 验证难度目标
按照难度目标1903a30c 计算实际难度系数0x03a30c*28*(0x19 - 3)为:
0000000000000003a30c00000000000000000000000000000000000000000000上述Hash值小于此难度系数,至此,此区块已全部验证计算完毕。
比特币挖矿的内涵
根据上述两部分的分析计算,比特币核心的机制已经非常清晰了,通过非对称加密算法,保证交易的真实性和可验证性,区块头中包含前区块的摘要以及所有交易信息的摘要,这些摘要同样均有唯一性和可验证性,从而组成区块的链式结构。所以,挖矿本身是一个记账的过程,所有人都在记账但是谁记的才是有效的呢,通过难度系数实际上限定了Hash计算次数,在一定时间内计算Hash最快的节点记录的账本是有效的。
私以为,比特币的价值在于开创性的用全公开的方式维系一个安全性高的链式账簿系统,为一种不依赖国家信用,不依赖实物,非中心化的交换媒介提供了可能性,这种机制本身存在价值。其二,比特币这种使用密码学的记账方式具备天然的公信力,当这种公信力实现,越来越多的人信任比特币,使用比特币,参与挖矿,比特币本身就此过程中实现了价值。这就是为何比特币经历很多次大涨大跌,但是每次下跌的低点都比前期要高不少。
作为新兴的技术,区块链还有更广阔的发展空间。
附Python代码
import binascii def little_endian( value ): return binascii.hexlify(binascii.unhexlify(value)[::-1]) import datetime import calendar from hashlib import sha256 as s date = datetime.datetime(2013,12,27,23,36,53) #获取UTC timestamp times=format(calendar.timegm(date.timetuple()),'x') bits = format(419668748, 'x') nonce = format(986485502,'x') previoushash='000000000000000010236c269dd6ed714dd5db39d36b33959079d78dfd431ba7' merkle = '28c4e2e7f9199c77eb5eb8266d26795ac517c8975b3b43154f8334b3ee80cda8' version ='00000002' #编码转换 blockhead=little_endian(version )+little_endian(previoushash)+little_endian(merkle)+little_endian(times)+little_endian(bits)+little_endian(nonce) hk = binascii.unhexlify(blockhead) res = binascii.hexlify(s(s(hk).digest()).digest()[::-1])
文章评论