使用 Go 實作區塊鏈(二):建立工作量證明機制

前言

本文為「Building a Blockchain in Golang」教學影片的學習筆記。

工作量證明

工作量證明(Proof of Work,簡稱 POW)是一種對應服務與資源濫用,或是阻斷服務攻擊的經濟對策。

在區塊鏈中的工作量證明是指,需要成功完成大量運算處理的人才能成為交易的驗證人,並可以獲得權利將新的區塊打包到區塊鏈的機制。

工作量證明最常用的技術原理是雜湊函式。由於輸入雜湊函式 h 的任意值 n,會對應到一個 h(n) 結果,而 n 只要變動一個位元,就會引起雪崩效應,所以幾乎無法從 h(n) 反推回 n,因此藉由指定尋找 h(n) 的特徵,讓使用者進行大量的窮舉運算,就可以達成工作量證明。

實作

blockchain 資料夾裡建立一個 proof.go 檔。

1
touch blockchain/proof.go

設置一個「難度」為 12 的常數,用來算出一個經過左移運算後產生的目標值。如果數值越大,設定的目標值就會越小(其雜湊值開頭的 0 就會越多)。

1
const Difficulty = 12

建立一個 ProofOfWork 結構體。

1
2
3
4
type ProofOfWork struct {
Block *Block // 區塊
Target *big.Int // 目標值(經過左移運算後的大數)
}

建立一個 NewProof 方法,回傳一個 ProofOfWork 結構體的實體。目標值是一個將 1 經過左移運算後產生的大數,難度越高,則目標值越小。

1
2
3
4
5
func NewProof(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(255-Difficulty))
return &ProofOfWork{b, target}
}

建立一個 ToHex 方法,幫忙把 int64 型別轉為 []byte 型別。

1
2
3
4
5
6
7
func ToHex(num int64) []byte {
buff := new(bytes.Buffer)
if err := binary.Write(buff, binary.BigEndian, num); err != nil {
log.Println(err)
}
return buff.Bytes()
}

ProofOfWork 結構體建立一個 InitData 方法,將區塊的 PrevHashDatanonceDifficulty 合併。

1
2
3
4
5
6
7
8
9
func (pow *ProofOfWork) InitData(nonce int) []byte {
return bytes.Join(
[][]byte{
pow.Block.PrevHash,
pow.Block.Data,
ToHex(int64(nonce)),
ToHex(int64(Difficulty)),
}, []byte{})
}

ProofOfWork 結構體建立一個 Run 方法,建立一套找出 nonce 值的演算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (pow *ProofOfWork) Run() (int, []byte) {
var initHash big.Int
var hash [32]byte
// 從 0 開始遞增
nonce := 0
for nonce < math.MaxInt64 {
data := pow.InitData(nonce)
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
initHash.SetBytes(hash[:])
// 一旦 initHash 小於目標值,就停止,並回傳 nonce 值和雜湊值
if initHash.Cmp(pow.Target) == -1 {
break
}
nonce++
}
fmt.Println()
return nonce, hash[:]
}

修改 Block 結構體,增加一個 Nonce 屬性。

1
2
3
4
5
6
type Block struct {
Hash []byte
Data []byte
PrevHash []byte
Nonce int
}

block.go 檔的 DeriveHash 方法移除。

1
2
3
4
5
func (b *Block) DeriveHash() {
info := bytes.Join([][]byte{b.Data, b.PrevHash}, []byte{})
hash := sha256.Sum256(info)
b.Hash = hash[:]
}

修改 block.go 檔的 CreateBlock 方法,在建立區塊的時候執行,尋找 nonce 值和雜湊值。

1
2
3
4
5
6
7
8
func CreateBlock(data string, prevHash []byte) *Block {
block := &Block{[]byte{}, []byte(data), prevHash, 0}
pow := NewProof(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}

ProofOfWork 結構體建立一個 Validate 方法,使用區塊的 nonce 來進行驗證,即 initHash 如果剛好小於目標值,則代表驗證成功。

1
2
3
4
5
6
7
func (pow *ProofOfWork) Validate() bool {
var initHash big.Int
data := pow.InitData(pow.Block.Nonce)
hash := sha256.Sum256(data)
initHash.SetBytes(hash[:])
return initHash.Cmp(pow.Target) == -1
}

修改 main.go 檔,將工作量證明結果印出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
chain := blockchain.InitBlockChain()

chain.AddBlock("First Block after Genesis")
chain.AddBlock("Second Block after Genesis")
chain.AddBlock("Third Block after Genesis")

for _, block := range chain.Blocks {
fmt.Printf("Previous Hash: %x\n", block.PrevHash)
fmt.Printf("Data in Block: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)

pow := blockchain.NewProof(block)
fmt.Printf("Pow: %s\n", strconv.FormatBool(pow.Validate())) // 驗證
fmt.Println()
}
}

執行程式。

1
go run main.go

顯示結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
00031a02a972efd4fa6ea999407149b85b03ccecb8c2bb8eb5a1d068862309d0
0004458722d47515269d8ddbe22e2a2b5a260bd9359a3b7d72a9888b14f9f5f5
000589525b1a774b7d1ffcbf471d32eccea3a8f826c463dffdf09a2261c0be12
000666528af011921e9f471b07eb46a4d08edce58435df868e9dc85726ff0eda
Previous Hash:
Data in Block: Genesis
Hash: 00031a02a972efd4fa6ea999407149b85b03ccecb8c2bb8eb5a1d068862309d0
Pow: true

Previous Hash: 00031a02a972efd4fa6ea999407149b85b03ccecb8c2bb8eb5a1d068862309d0
Data in Block: First Block after Genesis
Hash: 0004458722d47515269d8ddbe22e2a2b5a260bd9359a3b7d72a9888b14f9f5f5
Pow: true

Previous Hash: 0004458722d47515269d8ddbe22e2a2b5a260bd9359a3b7d72a9888b14f9f5f5
Data in Block: Second Block after Genesis
Hash: 000589525b1a774b7d1ffcbf471d32eccea3a8f826c463dffdf09a2261c0be12
Pow: true

Previous Hash: 000589525b1a774b7d1ffcbf471d32eccea3a8f826c463dffdf09a2261c0be12
Data in Block: Third Block after Genesis
Hash: 000666528af011921e9f471b07eb46a4d08edce58435df868e9dc85726ff0eda
Pow: true

程式碼

參考資料