区块链poW 工作量证明

挖矿原理
在讲poW之前我们先来讲讲以比特币为例的挖矿原理,其实说挖矿其实并不准确,我们应该称其为记账。记账是把交易记录、交易时间、账本序号、上一个Hash值等信息计算Hash打包的过程。这一过程必然需要某个计算机来实现,这类计算机我们下面统称为“节点”。因为区块链是分布式的,所以就需要很多节点,而且计算需要消耗很多资源。如果没有一点奖励机制,大家肯定都不情愿,把自己的设备作为免费节点。因此在比特币中,中本聪设定了只要完成记账的节点给予一定的比特币作为奖励,因此大家把记账形象的称为“挖矿”。

记账原理
上面的我说了,成功的完成记账后就会有比特币奖励,因此就出现大家争相记账,大家一起记账就会引起问题:出现记账不一致的问题,怎么判断你的记账是正确的。比特币系统引入工作量证明来解决这个问题,规则如下:
1.一段时间内只有一人可以记账成功
2.通过解决密码学难题(即工作量证明)竞争获得唯一记账权
3.其他节点复制记账结果那么我们通过什么来判断你完成了记账。
在进行工作量证明之前,记账的节点会做一些工作:

收集广播中海没有被记录的原始交易信息
检查交易信息中付款地址有没有足够的余额
验证交易是否有正确的签名
把验证通过的交易信息进行打包记录
添加一个奖励交易:给自己的地址增加特币
poW
poW全称Proof-of-Work 即工作量证明。通过记账原理我们知道,每次记账就是把上一个块的Hash值和当前块的信息一起作为原始信息进行Hash。如果仅仅这么轻松点完成了记账,相信比特币也不值钱。因此为了保证一段时间内只有一个人能完成记账,就需要提高记账难度。而且,随着时间的推移,难度会越来越大,因为要保证每小时有6个区块的诞生,越到后面,区块越来越少,要保证这个速率只能运算更多,提高难度。在比特币中,运算的目标是计算出一串符合要求的hash值。而这个hash就是证明。所以说,找到证明(符合要求的hash值)才是实际意义上的工作。
我们知道改变Hash的原始信息的任何一部分,Hash值也会随之不断的变化,因此在运算Hash时,不断的改变随机数的值,总可以找的一个随机数使的Hash的结果以若干个0开头(下文把这个过程称为猜谜),率先找到随机数的节点就获得此次记账的唯一记账权。Hash值示例:

00000017504a984ab0339f7d1517691e63099fb83d6ae9d6ebd722c25755f2cc
1
计算量分析
以我写这篇文件的时间来看(2018-10-4),挖一个比特币的成本需要3W多人民币。为什么成本这么高呢?我们通过比特币来简单分析下挖矿难度有多大。Hash值是由数字和大小写字母构成的字符串,每一位有62种可能性,假设任何一个字符出现的概率是均等的,那么第一位为0的概率是1/62(其他位出现什么字符先不管),理论上需要尝试62次Hash运算才会出现一次第一位为0的情况,如果前两2位为0,就得尝试62的平方次Hash运算,以n个0开头就需要尝试62的n次方次运算。

我们以Block #544228为例,它当前hash为:000 000 000 000 000 000 14d90bb35d84eaf8b5ed2ae85f42b9d37c715ab3a02b06。 可以看到它前面有24个0,那么理论上就需要尝试 64^24 ,大概需要2.230074519853062e43 次这是一个非常大的数字,需要消耗很大的资源(电能、算力),所以比特币目前来说非常难挖。

设计逻辑
poW区块设计
poW结构设计
实现工作量证明
区块链构建
验证区块
实现poW
上面花了点篇幅介绍了工作量证明的原理。现在我们根据上篇的内容进行修改,来实现poW。

定义挖矿难度
先定义20位0作为挖矿难度,我们这里的难度是全局的,并且不作改变。
ps:在实际区块链中,targetBit是变化的,挖矿是随着时间变的越来越难。

const targetBit =20
因为我们这里只做演示,所以targetBit不能太大,不然会消耗很多时间。

区块结构
相比于上篇的代码我们添加了一个新的属性 Nonce ,用于生成工作量证明的哈希。

type Block struct {
	Index  int64
	TimeStamp int64
	Data  []byte
	PrevBlockHash []byte
	Hash []byte
	Nonce int64
}

Nonce 用来保存一个随机值,poW算法中Nonce和区块其他信息一起进行Hash计算,使计算符合一定的条件,如比特币的Hash值是需要前几位为0,像000017504… 这样我们需要找出这样的一个随机数使得Hash值前四位为0.

定义poW

type ProofOfWork struct {
	block *Block   
	target *big.Int 
}

func  NewProofOfWork (b *Block) *ProofOfWork  {
	target:= big.NewInt(1)
	target.Lsh(target,uint(256-targetBit))
	pow:=&ProofOfWork{b,target}
	return  pow
}

target这里使用了一个 大整数 ,将会用hash与target进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标。

在 NewProofOfWork 函数中,我们将 big.Int 初始化为 1,然后左移 256 - 1 位。则target(目标) 的 16 进制形式为:
0x10000000000000000000000000000000000000000000000000000000000

准备数据
我们先使用IntToHex函数将int型数据转行为16进制。

func IntToHex(data int64) []byte {
	buffer := new(bytes.Buffer) // 新建一个buffer
	err := binary.Write(buffer, binary.BigEndian, data)
	if nil != err {
		log.Panicf("int to []byte failed! %v\n", err)
	}
	return buffer.Bytes()
}

上面说过,poW算法中Nonce和区块其他信息一起进行Hash计算,使计算符合一定的条件。现在我们就用prepareData函数将Nonce与区块其他信息合并。

func  (pow *ProofOfWork)prepareData(nonce int64)[]byte{
	data:=bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.Data,
			IntToHex(pow.block.Index),
			IntToHex(pow.block.TimeStamp),
			IntToHex(int64(targetBit)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
		)
	return  data
}

工作量证明
这段是实现poW的核心代码:

func  (pow *ProofOfWork)Run()(int64 ,[]byte)  {
	var hashInt big.Int
	var hash [32]byte
	var nonce  int64 =0
	fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
	for {
		dataBytes :=pow.prepareData(nonce) //获取准备的数据
		hash =sha256.Sum256(dataBytes)  //对数据进行Hash
		hashInt.SetBytes(hash[:])
		fmt.Printf("hash: \r%x",hash)
		if pow.target.Cmp(&hashInt) ==1 { //对比hash值
			break
		}
		nonce++   //充当计数器,同时在循环结束后也是符合要求的值

	}
	fmt.Printf("\n碰撞次数: %d\n", nonce)
	return  int64(nonce),hash[:]
}

通过Run函数我们可以看到,函数不断的循环查找符合要求的nonce值。
当target值大于hashInt 退出循环,并返回计数次数和正确hash。
循环体内工作主要是:

准备块数据
计算SHA-256值
转成big int
与target比较
创建新的区块
下面我将对对上篇文章中的NewBlock 函数进行适当的修改。

func  NewBlock(index int64,data string ,prevBlockHash []byte )*Block{
	block :=&Block{index,time.Now().Unix(),[]byte(data),prevBlockHash,[]byte{},0}
	pow:= NewProofOfWork(block)
	nonce ,hash :=pow.Run()
	block.Hash =hash[:]
	block.Nonce =nonce
	return  block
}

由于改造后新区块的定义添加了Nonce ,所以我们要对Nonce也进行赋值,并且我们要通过poW算法来重新生成区块。

验证区块
本文在前面说过,区块的数据有任意改动,哪怕是一个字节的改动,Hash值都会随着变化。因此在对于新生成的区块,我们非常有必要对它重新计算hash,验证是否合法。但由于本文代码仅是区块链简单演示,区块验证并非必要。

func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int
	data := pow.prepareData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])
	isValid := hashInt.Cmp(pow.target) == -1
	return isValid
}

运行

func main()  {
	bc := NewBlockchain()
	fmt.Printf("blockChain : %v\n", bc)
	bc.AddBlock("Aimi send 100 BTC	to Bob")
	bc.AddBlock("Aimi send 100 BTC	to Jay")
	bc.AddBlock("Aimi send 100 BTC	to Clown")
	length := len(bc.blocks)
	fmt.Printf("length of blocks : %d\n", length)
	for i := 0; i < length; i++ {
		pow :=NewProofOfWork(bc.blocks[i])
		if pow.Validate() {
			fmt.Println("—————————————————————————————————————————————————————")
			fmt.Printf(" Block: %d\n",bc.blocks[i].Index)
			fmt.Printf("Data: %s\n",bc.blocks[i].Data)
			fmt.Printf("TimeStamp: %d\n",bc.blocks[i].TimeStamp)
			fmt.Printf("Hash: %x\n",bc.blocks[i].Hash)
			fmt.Printf("PrevHash: %x\n",bc.blocks[i].PrevBlockHash)
			fmt.Printf("Nonce: %d\n",bc.blocks[i].Nonce)

		}else {
			fmt.Println("illegal block")
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_30505673/article/details/83688828