经典共识PoW的原理及实现
一、PoW简介
PoW(Proof of Work)是工作量证明的简称,最早使用在防止拒绝服务攻击以及过滤垃圾邮件,现在成为区块链公链中最常见也是最有效的共识算法之一,当下最流行的比特币、以太坊等公链目前均使用PoW共识。
PoW是通过对一个复杂难题的求解,来保证区块链交易打包出块的公平性,即最先解决难题的矿工,可以获得记账权,并将打包好的区块发送至其他节点验证上链,从而获得激励。在保证能够在不可信的环境下创造可信的同时,运用PoW还可以天然的防御女巫攻击等针对区块链的攻击。
由于PoW要求所有矿工节点都解决无意义的复杂难题,导致了巨大的资源浪费,因此,最新的以太坊版本考虑使用PoS代替PoW成为其共识算法。
二、PoW原理及实现
在介绍PoW原理之前,需要先了解一些知识。
2.1 区块头
在区块链中,区块分为区块头和区块体两部分,区块体以默克尔树的数据结构存储了交易数据,区块头存储了上一个区块的Hash、默克尔树树根值、时间戳、难度值、区块高度等等非交易数据信息。
2.2 哈希计算
哈希计算实际上就是使用哈希函数(散列函数)对不同长度的数据都能计算出等长的输出,而且对数据微小差距都十分敏感,比如"hello world"和"Hello world"的哈希值差距都是巨大的。
2.3 原理及实现
PoW的原理十分简单,实际上就是计算一个随机数Nounce,要求这个Nounce和区块头拼接后做的哈希值小于我们预设的值。具体实现如下:
1.预设一个难度值targetBit,比如我们想PoW计算出的Hash值最前面开始有16个0,则设置该值为16。
2.找到判断是否找出正确Nounce值的临界值target,在比特币中,PoW使用的是SHA256,输出的哈希值都是256位的,比如设置的难度值是16,那么我们最终应该得到的值的形式应该是 0000 0000 0000 0000 xxxx…xxxx(共256位),那么我们可以得知,当我得到的值小于0000 0000 0000 0001 0000…0000(共256位)时,该值合法,因为这个值已经小于了前面有15个0的最小值,那么其前面一定有16个0。
所以我们只要将临界值target先设置为1,二进制表示为0000 0000…0001(共256位),再将这个值左移256-targetBit = 256 - 16 = 240位,即可得到前面有15个0的最小值,即0000 0000 0000 0001 0000…0000(共256位)。
3.将随机数Nounce置为0,再拼接上区块头的内容,计算其哈希值,如果大于了我们的目标值target,则将Nounce加1,再拼接区块头的内容计算哈希。重复上述过程,直到计算出小于target的哈希值,此时的Nounce就是最终的解。
三、PoW的go语言实现
1.首先定义工作量证明对象
type ProofOfWork struct {
Block *Block //当前要验证的区块
target *big.Int //大数存储
}
2.定义难度值
//256位hash里面至少有16个零
const targetBit = 16
3.初始化工作量证明对象,计算出临界值target
func NewProofOfWork(block *Block) *ProofOfWork {
//创建一个初始值为1的target
target := big.NewInt(1)
//左移256-targetBit
target = target.Lsh(target, 256-targetBit)
return &ProofOfWork{
block,
target,
}
}
4.拼接Nounce值和区块头中的内容
func (proofOfWork *ProofOfWork) prepareData(nonce int) []byte {
join := bytes.Join(
[][]byte{
proofOfWork.Block.PreBlockHash,//前一个区块的hash
proofOfWork.Block.HashTransactions(),//默克尔树根
Utils.IntToHex(proofOfWork.Block.TimeStamp),//时间戳
Utils.IntToHex(int64(targetBit)),//难度值
Utils.IntToHex(int64(nonce)),//Nounce
Utils.IntToHex(int64(proofOfWork.Block.Height)),//区块高度
},
[]byte{},
)
return join
}
5.开始进行工作量证明计算
func (proofOfWork *ProofOfWork) Run() ([]byte, int64) {
nonce := 0
var hashInt big.Int //存储我们新生成的hash
var hash [32]byte
for {
// 将BLOCK属性拼接成字节数组
dataBytes := proofOfWork.prepareData(nonce)
// 生成hash, sum256返回32位需要转换为64位
hash = sha256.Sum256(dataBytes)
// 将hash存储到hashInt,采取hash[:]将切片转换为64位
hashInt.SetBytes(hash[:])
fmt.Printf("\r%x", hash)
// 判断hashInt是否小于Block里面的target
// x < y -1
// x == y 0
// x > y 1
if proofOfWork.target.Cmp(&hashInt) == 1 {
//判断有效性,如果满足条件,跳出循环
break
}
nonce = nonce + 1
}
return hash[:], int64(nonce)
}
6.编写当需要验证Nounce值是否合法时的验证函数
func (proofOfWork *ProofOfWork) IsValid() bool {
var hashInt big.Int
hashInt.SetBytes(proofOfWork.Block.Hash)
//判断提供的hash是否小于target
if proofOfWork.target.Cmp(&hashInt) == 1 {
return true
}
return false
}