首页 > 世链号 > 区块链之工作量证明(POW)shan
区块资讯室  

区块链之工作量证明(POW)shan

摘要:目前,Proof of 系列中比较出名的一致性协议包括 PoW 和 PoS,都是通过经济惩罚来限制恶意参与。

目前,Proof of 系列中比较出名的一致性协议包括 PoW 和 PoS,都是通过经济惩罚来限制恶意参与。

POW工作量证明

工作量证明,Proof of Work,通过计算来猜测一个数值(nonce),得以解决规定的 hash 问题(来源于 hashcash)。保证在一段时间内,系统中只能出现少数合法提案。

同时,这些少量的合法提案会在网络中进行广播,收到的用户进行验证后会基于它认为的最长链上继续难题的计算。因此,系统中可能出现链的分叉(Fork),但最终会有一条链成为最长的链。

hash 问题具有不可逆的特点,因此,目前除了暴力计算外,还没有有效的算法进行解决。反之,如果获得符合要求的 nonce,则说明在概率上是付出了对应的算力。谁的算力多,谁最先解决问题的概率就越大。当掌握超过全网一半算力时,从概率上就能控制网络中链的走向。这也是所谓 51% 攻击 的由来。

有一个很直观的例子可以说明为何这种经济博弈模式会确保系统中最长链的唯一。

Hash哈希计算

在本节,我们会讨论哈希计算。如果你已经熟悉了这个概念,可以直接跳过。

获得指定数据的一个哈希值的过程,就叫做哈希计算。一个哈希,就是对所计算数据的一个唯一表示。对于一个哈希函数,输入任意大小的数据,它会输出一个固定大小的哈希值。

下面是哈希的几个关键特性:

  1. 无法从一个哈希值恢复原始数据。也就是说,哈希并不是加密。

  2. 对于特定的数据,只能有一个哈希,并且这个哈希是唯一的。

  3. 即使是仅仅改变输入数据中的一个字节,也会导致输出一个完全不同的哈希。

区块链之工作量证明(POW)shan

区块链中,哈希被用于保证一个块的一致性。哈希算法的输入数据包含了前一个块的哈希,因此使得不太可能(或者,至少很困难)去修改链中的一个块:因为如果一个人想要修改前面一个块的哈希,那么他必须要重新计算这个块以及后面所有块的哈希。

 

HashCash

比特币使用 Hashcash ,一个最初用来防止垃圾邮件的工作量证明算法。它可以被分解为以下步骤:

  1. 取一些公开的数据(比如,如果是 email 的话,它可以是接收者的邮件地址;在比特币中,它是区块头)

  2. 给这个公开数据添加一个计数器。计数器默认从 0 开始

  3. 将 data(数据) 和 counter(计数器) 组合到一起,获得一个哈希

  4. 检查哈希是否符合一定的条件:

  •   如果符合条件,结束

  •   如果不符合,增加计数器,重复步骤 3-4

因此,这是一个暴力算法:改变计数器,计算新的哈希,检查,增加计数器,计算哈希,检查,如此往复。这也是为什么说它的计算成本很高,因为这一步需要如此反复不断地计算和检查。

现在,让我们来仔细看一下一个哈希要满足的必要条件。在原始的 Hashcash 实现中,它的要求是 “一个哈希的前 20 位必须是 0”。在比特币中,这个要求会随着时间而不断变化。因为按照设计,必须保证每 10 分钟生成一个块,而不论计算能力会随着时间增长,或者是会有越来越多的矿工进入网络,所以需要动态调整这个必要条件。

为了阐释这一算法,我从前一个例子(“I like donuts”)中取得数据,并且找到了一个前 3 个字节是全是 0 的哈希。

区块链之工作量证明(POW)shan

ca07ca 是计数器的 16 进制值,十进制的话是 13240266.

实战操作

好了,完成了理论层面,来动手写代码吧!首先,定义挖矿的难度值:

const targetBits = 24

在比特币中,当一个块被挖出来以后,“target bits” 代表了区块头里存储的难度,也就是开头有多少个 0。这里的 24 指的是算出来的哈希前 24 位必须是 0,如果用 16 进制表示,就是前 6 位必须是 0,这一点从最后的输出可以看出来。目前我们并不会实现一个动态调整目标的算法,所以将难度定义为一个全局的常量即可。

24 其实是一个可以任意取的数字,其目的只是为了有一个目标(target)而已,这个目标占据不到 256 位的内存空间。同时,我们想要有足够的差异性,但是又不至于大的过分,因为差异性越大,就越难找到一个合适的哈希。

type ProofOfWork struct {  

block *Block  

target *big.Int

}

func NewProofOfWork(b *Block) *ProofOfWork {  

target := big.NewInt(1)  

target.Lsh(target, uint(256-targetBits))  

pow := &ProofOfWork{b, target}  

return pow

}

这里,我们构造了 ProofOfWork 结构,里面存储了指向一个块(block)和一个目标(target)的指针。这里的 “目标” ,也就是前一节中所描述的必要条件。这里使用了一个 大整数 ,我们会将哈希与目标进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标。

在 NewProofOfWork 函数中,我们将 big.Int 初始化为 1,然后左移 256 - targetBits 位。256 是一个 SHA-256 哈希的位数,我们将要使用的是 SHA-256 哈希算法。target(目标) 的 16 进制形式为:

0x10000000000000000000000000000000000000000000000000000000000

它在内存上占据了 29 个字节。下面是与前面例子哈希的形式化比较:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 0000010000000000000000000000000000000000000000000000000000000000 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一个哈希(基于 “I like donuts” 计算)比目标要大,因此它并不是一个有效的工作量证明。第二个哈希(基于 “I like donutsca07ca” 计算)比目标要小,所以是一个有效的证明。

译者注:上面的形式化比较有些“言不符实”,其实它应该并非由 “I like donuts” 而来,但是原文表达的意思是没问题的,可能是疏忽而已。下面是一个小实验:

package main

import (  

"crypto/sha256"  

"fmt"  

"math/big"

)

func main() {  

data1 := []byte("I like donuts")  

data2 := []byte("I like donutsca07ca")  

targetBits := 24  target := big.NewInt(1)  

target.Lsh(target, uint(256-targetBits))  

fmt.Printf("%x ", sha256.Sum256(data1))  

fmt.Printf("%64x ", target)  

fmt.Printf("%x ", sha256.Sum256(data2))

}

输出:

区块链之工作量证明(POW)shan

现在,我们需要有数据来进行哈希,准备数据:

func (pow *ProofOfWork) prepareData(nonce int) []byte {  

data := bytes.Join(    

[][]byte{     

 pow.block.PrevBlockHash,      

pow.block.Data,      

IntToHex(pow.block.Timestamp),      

IntToHex(int64(targetBits)),      

IntToHex(int64(nonce)),   

 },    

[]byte{},  

)  

return data}

这个部分比较直观:只需要将 target ,nonce 与 Block 进行合并。这里的 nonce,就是上面 Hashcash 所提到的计数器,它是一个密码学术语。

很好,到这里,所有的准备工作就完成了,下面来实现 PoW 算法的核心:

func (pow *ProofOfWork) Run() (int, []byte) { 

var hashInt big.Int  

var hash [32]byte  

nonce := 0  

fmt.Printf("Mining the block containing "%s" ", pow.block.Data)  

for nonce < maxNonce {    

data := pow.prepareData(nonce)    

hash = sha256.Sum256(data)    

hashInt.SetBytes(hash[:])    

if hashInt.Cmp(pow.target) == -1 {      

fmt.Printf(" %x", hash)      

break   

 } else {     

 nonce++    }  }  

fmt.Print(" ")  return nonce, hash[:]

}

首先我们对变量进行初始化:

  • HashInt 是 hash 的整形表示;

  • nonce 是计数器。

然后开始一个 “无限” 循环:maxNonce 对这个循环进行了限制, 它等于 math.MaxInt64,这是为了避免 nonce 可能出现的溢出。尽管我们 PoW 的难度很小,以至于计数器其实不太可能会溢出,但最好还是以防万一检查一下。

 

在这个循环中,我们做的事情有:

  1. 准备数据

  2. 用 SHA-256 对数据进行哈希

  3. 将哈希转换成一个大整数

  4. 将这个大整数与目标进行比较

跟之前所讲的一样简单。现在我们可以移除 Block 的 SetHash 方法,然后修改 NewBlock 函数:

func NewBlock(data string, prevBlockHash []byte) *Block { 

 block := &Block{time.Now().Unix(),

[]byte(data), prevBlockHash, []byte{}, 0}  

pow := NewProofOfWork(block)  

nonce, hash := pow.Run()  

block.Hash = hash[:]  

block.Nonce = nonce  

return block

}

在这里,你可以看到 nonce 被保存为 Block 的一个属性。这是十分有必要的,因为待会儿我们对这个工作量进行验证时会用到 nonce 。Block 结构现在看起来像是这样:

type Block struct { 

 Timestamp  int64 

 Data     []byte  

PrevBlockHash []byte  

Hash     []byte  

Nonce    int

}

好了!现在让我们来运行一下是否正常工作:

Mining the block containing "Genesis Block"

00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1

Mining the block containing "Send 1 BTC to Ivan"

00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804

Mining the block containing "Send 2 more BTC to Ivan"

000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe

Prev. hash:

Data: Genesis Block

Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1

Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1

Data: Send 1 BTC to Ivan

Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804

Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804

Data: Send 2 more BTC to Ivan

Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe

成功了!你可以看到每个哈希都是 3 个字节的 0 开始,并且获得这些哈希需要花费一些时间。

本文来源: 区块链研究实验室

免责声明
世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。