熊市学技术—《挖矿=POW=工作量证明》

2017-09-18 14:14 来源:肉摩陀 阅读:6957
在《比特币:四十八万页的账本》中提到,货币的本质是记账方式,比特币是一种分布式的记账方式,那问题来了,这账是谁来记,怎么记?

在《比特币:四十八万页的账本》中提到,货币的本质是记账方式,比特币是一种分布式的记账方式,那问题来了,这账是谁来记,怎么记?

答案:矿工通过挖矿,来记账。

pow

说起挖矿这两字给人的第一印象是一帮黑不溜秋的矿工拿着锄头和探照灯,在漆黑的地下挖掘,在无数声叮叮当当的敲打声后,突然有个矿工拿起一枚通透的宝石或者黄灿灿的金子叫喊到,“挖到了,挖到了”,这是电影或者小说中的情节,真实情况是,在区块链的网络世界,“挖矿”这俩字用的并不准确。

 

不过“挖矿”这俩字如今已经深入人心,我也只好顺着往下说。

 

“挖矿”其实是对比特币等数字货币记账和发行的一种形象化称呼,这个行为背后的真正动作是—网络上无数台电脑为争夺记账权,对某一个特定的问题求解,哪台计算机先解出了这个问题,在全网所有计算机的见证下,那台求出解的计算机就会完成记账(一个区块的打包),并得到网络奖励的比特币。

 

那这个问题是什么?

是对某个区块的区块头求一个特殊的hash值,这个特殊的hash值要满足小于预先设定的目标值

 

这句话怎么理解呢?

方便起见,先举个简单的例子,区块链世界中用到一种叫做SHA256的算法,它的特点是,给定任何一段语句,在经过该算法后都能得到一个hash值。

 

比如输入“power overwhelming”(打过星际的都知道这人是啥, 嘿嘿),经过SHA256算法,得到一个32字节的hash值:

’8fe12dec3dc8433bfe04ca1e4381feaa58d30c5ece06d37756d88face23aa3e1′

 

由于sha256算法的特殊性,使得对这段语句修改或增减任何一个字符,输出的hash值都会改变:

比如在原句后加一个“1”,输入“power overwhelming1”,得到一个新的32字节的hash值:

‘d9e940764a410ecf7eae1f27a63248070ab45292e41de1cdfc89433ba2c20ac7′

 

现在设个目标,通过修改语句尾巴上的数字,使得输出十六进制hash值的首位为0(相当于256位的二进制hash的前四位都是0),下面通过一段简单的代码,来实现对语句尾部迭代不同数字,并统计满足要求的hash值

 

代码如下

图1

执行程序,分别输入语句“power overwhelming”和数字“4”,数字“4”表示目标hash值的十六进制第一位为0

图2

以上运行结果显示,

- 目标hash值是:

0×1000000000000000000000000000000000000000000000000000000000000000

- 一共计算了32次,满足条件的hash值出现了两次,平均每算16次出现一个,这是因为一位十六进制代表的是0-16(0-f)的任意一位,所以满足要求的尾部数字的出现概率为1/16。

 

如果再次执行程序,输入“show me the money”,数字“4”改成“8”,表示目标hash的前两位都是0(或者256位二进制的前8位都是0)

- 目标hash值是:

0×0100000000000000000000000000000000000000000000000000000000000000

- 一共计算了1024次,满足条件的hash值出现了5次,数据量不够大,如果足够大的话,出现的概率应为1/256。

 

所以,规律就很明显了,hash值的0位要求的越多,则满足目标的计算次数越多,比如从比特币区块链第477016块的数据来看,其hash值是:

00000000000000000097cf400e2634e1b42cb0b55bbd25476a3e97a5e0d481d5

转换成二进制后,其要求的0位高达72个,也就是说每计算4.7×10^21次,大概会出现一次正确的hash值,这种靠更改尾部数值反复迭代计算,以得到正确的hash值的做法就叫做工作量证明机制-POW-proof of work,尾部不断迭代的数字叫做nonce值。

 

那真正的区块头hash如何计算的呢?

真正的区块头hash值计算中,会用到以下6个数据:

图3

其中前五个都是已知的值,分别是版本号,前一个区块的hash值,从创始区块到现在的时间戳,根merkle值,难度标记。

 

merkle值是将区块中等待打包的所有交易递归产生的一个值-详情请见Day2文章,网络上不同的矿机可能由于各种原因,使得等待打包的区块内的交易记录和时间戳不完全一样。

 

而bits难度标记是整个比特币网络自行调整的一个参数,调整的依据就是比特币总量和年产量,根据中本聪的规划,比特币总量2100万枚,开采量每四年减半,该参数就是为了保持这个节奏而生。它有两个功能:

 

第一,保证无论全网的hash算力多快,每两个区块产生的间隔始终保持在10分钟左右;

第二,该参数同时也是某等待打包的区块的目标hash值。

 

回到第477016号区块,其难度目标为18015dcc,头两位是幂,后六位是系数,经过下列公式展开,可以得到目标HASH值。

 

公式: Target hash=coefficient*2^(8*(exponent-3)

coefficient=0x015dcc

exponent=0×18

Target hash=

0x0000000000000000015dcc000000000000000000000000000000000000000000

 

随后对区块头进行nonce值迭代,找到小于目标值的hash

公式如下:

hash=SHA256(SHA256(区块头))

区块头=十六进制小端结尾(版本信息+上一个区块头+根merkle+时间戳+难度目标+nonce值)

 

不过,因为该十六进制的目标hash一共有17个0,转换成二进制后,0位高达72个,每计算4.7×10^21次,大约会有一个正确的hash值,可谓是丧心病狂了。不过目前比特币全网的hash算力为—6200Ph/s,也就是每秒可以进6.2×10^18次hash计算,于是乎每个区块的产生时间大约是750秒,折合12.5分钟,略高于比特币每个区块的产生时间-10分钟,这个节奏也就是比特币网络的心跳。

 

普通笔记本的算力大概是1*10^8次/s,这个数据可能不正确,不过无所谓,因为按照如此变态的hash算力要求,多一个零少一个零,区别也就是15万年,或者150万年,对于人类昙花一现的存在都一样。

 

但是,一旦找到正确的nonce,算出小于目标的hash值后,所有人就能用一套相同的参数来验证该计算是否正确,比如对于第477016号区块,其区块头信息如下

图4

根据公式hash=SHA256(SHA256(区块头)),写python代码如下:

图5

运行后,分别输入以下6个信息:

版本信息

上一个区块头

根merkle

时间戳

难度目标

nonce值

得到一个hash值,该值正等于第477016号区块的hash值

00000000000000000097cf400e2634e1b42cb0b55bbd25476a3e97a5e0d481d5

图6

一旦某台挖矿机算出了正确的hash值,就意味着该区块的区块头打包成功,成功者获得网络奖励的比特币,没有成功的矿机开始进行下一个区块的计算,重复上面的过程。

正因为计算量如此巨大,才会有所谓的专业矿机出现,他们都装备了专业的GPU阵列,这点和大数据运算很像,事实也确实差不多,如此海量的同质化数据,用GPU搬运才更快更高效,一个比特币矿场通常是下面这样子的,这么多专业矿机一起工作时,能耗惊人。
图7

前文提到,比特币全网的hash算力为—6200Ph/s,在这个算力下,全球矿场每小时耗电量大于230万度,每年190亿度,目前国内民用电大约0.55rmb/度,按这个价结算,每年耗电105亿人民币,当前比特币每年产量约66万个,按照目前28000元/枚的价格,价值约180亿rmb。

由于耗电量巨大,为了降低成本,矿场大多建在水利发电充沛的区域,比如中国西南部,电价约合0.35rmb/度,下图是偏远西部的某矿场外景,配合周围的风景,还真有“矿场”的样子。
图8


 

 

下面是上文提到的代码,有python界面的可以拿回家试试看

#hash-nonce
import hashlib

text =raw_input(“please enter a sentence:”)
# iterate nonce from 0 to xxx
difficult=raw_input(“please enter a 0-32 number:”)
bit=int(difficult)
count=0
list=[]
target=2**(256-bit)
hextar=hex(target)
for nonce in xrange(4*(2**bit)):
# add the nonce to the end of the text
input = text + str(nonce)

# calculate the SHA-256 hash of the input (text+nonce)
hash = hashlib.sha256(input).hexdigest()
# show the input and hash result
print input, ‘=>’, hash
if long(hash, 16)<target:
count=count+1
list.append(hash)

print “There are %d HASH meet target”%(count)
print “The target HASH is:”, hextar
print “Following HASH are meet target:”
for a in list:
print a

 

#hash-cal
import hashlib

#ask for input
ver=raw_input(“please enter 6 digi of version number:”)
phash=raw_input(“please enter hash of previous block:”)
rmerkle=raw_input(“please enter root merkle value:”)
t=raw_input(“please enter time in digital:”)
bits=raw_input(“please enter hex bits number in hex:”)
nonce=raw_input(“please enter digital nonce value:”)
#convert time and nonce to int
t=int(t)
n=int(nonce)
#convert all input to hex format and small end
sver=”0x”+str(ver)
hexver=sver[2::].decode(‘hex’)[::-1].encode(‘hex’)
hexphash=phash.decode(‘hex’)[::-1].encode(‘hex’)
hexrmerkle=rmerkle.decode(‘hex’)[::-1].encode(‘hex’)
hextime=hex(t)[2::].decode(‘hex’)[::-1].encode(‘hex’)
hexbits=bits.decode(‘hex’)[::-1].encode(‘hex’)
hexnonce=hex(n)[2::].decode(‘hex’)[::-1].encode(‘hex’)
print “hexver is:”, hexver
print “hexphash is:”, hexphash
print “hexrmerkle is:”, hexrmerkle
print “hextime is:”, hextime
print “hexbits is:”, hexbits
print “hexnonce is:”, hexnonce
#calculate hash value
header_hex=(hexver+hexphash+hexrmerkle+hextime+hexbits+hexnonce)
header_bin=header_hex.decode(‘hex’)
hash=hashlib.sha256(hashlib.sha256(header_bin).digest()).digest()
new_hash=hash[::-1].encode(‘hex_codec’)
print “hash is:”, new_hash

 

Winter is coming,多了解点技术,为下一波春天做准备吧


声明:此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。本网站所提供的信息,只供参考之用。

点击阅读全文