在前一篇文章中,我们构建了一个最简单的额区块链,本文中我们将改进这个区块链,加入PoW机制,使得这个区块链变成一个可以挖矿的区块链。原文见https://jeiwan.cc/posts/building-blockchain-in-go-part-2/
1 Proof-of-Work(PoW)
区块链的PoW机制的思想就是,一个节点必须做艰难的工作才能够推送数据到区块链中。这个艰难工作保证了区块链的安全和一致性。同时,区块链也会为这个工作提供奖励。这有点像我们的人生,一个人必须努力工作才能获得他想要的额生活。
2 Hash算法
Hash算法就是为特定的数据计算其Hash值的过程。一个hash函数可以输入任意长度的数据,但是其输出结果总是固定长度的hash值。Hash算法的特征有:
- 不能根据Hash值推导原始数据,所以Hash算法并不是一种加密算法。
- 特定数据只会对应一个唯一特定的hash值。
- 输入数据即使只改变一个数据位,hash结果也会产生很大的变化。
Hash算法被广泛的用于数据的一致性检查。在区块链中,hash算法被用于验证区块的一致性。hash算法的输入数据包含上一个区块的hash值,这使得修改区块链数据成为不可能的任务:一个人必须计算这个区块的hash,以及其后所有区块的hash。
3 Hashcash
比特币使用Hashcash,一种最早设计用来拦截垃圾邮件的算法。Hashcash算法包含以下几步:
(1)准备一些已知的公共数据data,比如区块头。
(2)给这些数据额外添加一个计数器counter,counter从0开始递增。
(3)计算data+counter拼接后字符串的Hash值
(4)检查计算出的Hash值是否满足要求,如果满足,则工作完成;如果不满足,则counter增1,重新执行(3)、(4)步骤。
在最开始的Hashcash算法中,对Hash值的检查要求是Hash值的最开始的20位全为0。在比特币中,这个要求会随时调整以使得计算难度满足没10分钟生产一个区块。
为了验证这个算法,以字符串“I like donuts”为例,要找到一个counter使得I like donuts N的Hash值前面3个字节都为0,
找到的counter将是ca07ca,换算成10进制是13240266。
4 PoW算法实现
首先我们定义挖矿难度系数为:
const targetBits = 24
哈希函数SHA256()计算出的hash值是256位,我们希望前面24位都是0。
然后我们定义一个挖矿结构体:
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,block中保存了要挖出区块的数据,target中保存了跟挖矿难度相对应的目标值。
在NewProofOfWork
函数中,我们先将target赋值为1,然后再左移256-targetBits位。如果一个Hash值小于这个target,则说明它的前面24位都是0。比如我们现在构造的target等于:
0x10000000000000000000000000000000000000000000000000000000000
它在内存中占用了29个字节,对于下面3个数:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
第一个数大于target,它不满足要求;第3个数小于target,我们可以看到它前面24位都为0。
现在我们需要构造给Hash函数计算的数据:
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
}
在这里我们准备计算Hash值的数据为block.PrevBlockHash+block.Data+block.Timestamp+target+nonce拼接而成的数据,其中nonce就是上面的计数器counter。
所有数据准备完毕,现在开始实现PoW算法:
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}
我们在for循环里面做的事情:
(1)准备数据
(2)计算Hash值
(3)将Hash值转成一个整数
(4)比较整数和目标值的大小
(5)结束或递增Nonce
为了配合挖矿算法,我们需要更改块结构:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
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
}
最后贴上完整代码:
package main
import (
"strconv"
"bytes"
"crypto/sha256"
"time"
"fmt"
"math/big"
"encoding/binary"
"log"
"math"
)
const targetBits = 24
var maxNonce = math.MaxInt64
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
type ProofOfWork struct {
block *Block
target *big.Int
}
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
pow := &ProofOfWork{b, target}
return pow
}
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
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
}
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Println()
fmt.Println(nonce)
return nonce, 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 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
}
type Blockchain struct {
blocks []*Block
}
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
}
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
for _, block := range bc.blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
}
运行结果:
Mining the block containing "Genesis Block"
00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9
7470848
Mining the block containing "Send 1 BTC to Ivan"
000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba
32065795
Mining the block containing "Send 2 more BTC to Ivan"
0000002250ba1c3254ad765b4a0a47af2e5452c9bb27102cf5e0fe3f5de10070
29669735
Prev. hash:
Data: Genesis Block
Hash: 00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9
Prev. hash: 00000040831b867e4a7e75460eb76f9a3b32c5be4043467d11d09240522d25d9
Data: Send 1 BTC to Ivan
Hash: 000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba
Prev. hash: 000000f49ad3dbc76bce70f317082dbbf1eac00f044e8807865bba35a30d2fba
Data: Send 2 more BTC to Ivan
Hash: 0000002250ba1c3254ad765b4a0a47af2e5452c9bb27102cf5e0fe3f5de10070
Process finished with exit code 0