2.2 工作量證明
Proof-of-Work 工作量證明
區塊裡有一個非常關鍵的點,就是節點必須執行足夠多且困難的運算才能將資料新增在區塊中。這一困難的運算保證了區塊鏈安全、一致。而為了獎勵這一運算,該節點會獲得數字貨幣(如比特幣)的獎勵(從運算到收到獎勵的過程,也叫作挖礦)。
這一機制和現實生活中也是相似的:人們辛苦工作獲取報酬來維持生活,在區塊鏈中,鏈絡中的參與者(比如礦工)辛苦運算來維繫這個區塊鏈網路,不斷增加新的區塊到鏈絡中,然後獲取回報。正是因為這些運算,新的區塊基於安全的方式加到區塊鏈中,保證了區塊鏈資料庫的穩定。
是不是發現了什麼問題呢?大家都在計算,憑什麼怎麼證明你做的運算就是對的,且是你的。
努力工作並證明(do hard work and prove),這一機制被稱為工作量證明。需要多努力呢,需要大量計算機資源,即使使用高速計算機也不能做得快多少。而且,隨著時間的推移,難度會越來越大,因為要保證每小時有6個區塊的誕生,越到後面,區塊越來越少,要保證這個速率,只能運算更多,提高難度。在比特幣中,運算的目標是計算出一串符合要求的hash值。而這個hash就是證明。所以說,找到證明(符合要求的hash值)才是實際意義上的工作。
工作量證明還有一個重要知識點。也即工作困難,而證明容易。因為如果你的工作困難,而證明也困難,那麼你的工作在圈子效率意義就不大,對於需要提供給別人證明的工作,別人證明起來越簡單就越好。
Hash 雜湊
Hash運算是區塊鏈最主要使用的工作演算法。雜湊運算是指給特殊資料計算一串hash字元的過程。對於一筆資料而言,它的hash值是唯一的。hash函式就是可以把任意大小的資料計算出指定大小hash值的函式。
Hash運算有以下幾個主要的特點:
1. 原始資料不能從hash值中逆向計算得到
2. 確定的資料只有一個hash值且這個hash值是唯一的
3. 改變資料的任一byte都會造成hash值變動
Hash運算廣泛應用於資料一致性的驗證。很多線上軟體商店都會把軟體包的hash值公開,使用者下載後自行計算hash值後驗證和供應商的是否一致,就可判斷軟體是否被篡改過。
在區塊鏈中,hash也是用於保證資料的一致性。區塊的資料,還有區塊的前一區塊的hash值也會被用於計算本區塊的hash值,這樣就保證每個區塊是不可變:如果有人要改動自己區塊的hash值,那麼連他後面的區塊hash也要跟著改,這顯然是不可能的或者極其困難的(要說服不是自己的區塊一同更改很困難)。
Hashcash 雜湊現金
比特幣中的工作證明使用的是Hashcash技術,起初,這一演算法開發出來就是用於防止垃圾電子郵件。它的工作主要有以下幾個步驟:
1. 獲取公開的資訊,比如郵件的收件人地址或者比特幣的頭部資訊
2. 增加一個起始值為0的計數器
3. 計算出第1步中的資訊+計數器值組合的hash值
4. 按規則檢測hash值是否滿足需求(一般是指定前20位是0)
1. 滿足
2. 不滿足則重複3-4步驟
這個演算法看上去比較暴力:改變計數器值,計算新的hash,檢測,增加計數器值,計算新的hash…,所以這個演算法比較昂貴。
郵件傳送者預備好郵件頭部資訊然後附加用於計算隨機數字計數值。然後計算160-bit長的hash頭,如果前20bits
現在進一步分析區塊鏈hash運算的要求。在原始的hashcash實現中,必須根據頭資訊算出前20位為0的hash值。而在比特幣中,這一規則則是根據時間的推移變動的,因為比特幣的設計就是10分鐘出一塊新區塊,即使計算機算力提升或者更多的礦工加入挖礦行列中也不會改變,也就是說,算出hash值,會越來越困難。
下面演示這一演算法,和上面第一張圖一樣使用“I like donuts”作為資料,再在資料後加面附加計數器,然後使用SHA256演算法找出前面6位為0的hash值。
而ca07ca就是不停運算找到的計數器值,轉換成10進位制就是13240266,換言之大概執行了13240266次SHA256運算才找到符合條件的值。
實現
上面花了點篇幅介紹了工作量證明的原理。現在我們用Golang來實現。先定24位0的作為挖礦的難度:
const targetBits = 24
注:在比特幣挖礦中,頭部中的target bits儲存該區塊的挖礦難度,但是上面說過隨著時間推移難度越來越大,所以這個target大小是會變的,這裡不實現target的適配演算法,這不影響我們理解挖礦。現在只定義一個常量作為全域性的難度。
當然,24也是比較隨意的,我們只用在記憶體中佔用少於256bits的的空間。差異也要大些,但是也不要太大,太大了就就很難找出來一個合規的hash。
然後定義ProofOfWork結構:
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”兩個成員。“target”就是上面段落中描述的hashcash的規則資訊。使用big.Int是因為要把hash轉成大整數,然後檢測是否比target要小。
NewProofOfWork 函式負責初始化target,將1往左偏移(256-targetBits)位。256是我們要使用的SHA-256標準的hash值長度。轉換成16進位制就是:
0x10000000000000000000000000000000000000000000000000000000000
這會佔用29位大小的記憶體空間。
現在準備建立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
}
這段程式碼做了簡化,直接把block的資訊和target、nonce合併在一起。nonce就是Hashcash中的counter,nonce(現時標誌)是加密的術語。
準備工作都OK了,現在實現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[:]
}
hashInt是hash值的int形式,nonce是計數器。然後執行math.MaxInt64次迴圈直到找符合target的hash值。為什麼是math.MaxInt64次,其實這個例子中是不用的考慮這麼大的,因為我們示例中的PoW太小了以致於還不會造成溢位,只是程式設計上要考慮一下,當心為上。
迴圈體內工作主要是:
1. 準備塊資料
2. 計算SHA-256值
3. 轉成big int
4. 與target比較
將上一篇中的NewBlock方法改造一下,扔掉SetHash方法:
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
}
然後執行 go run *.go
start: 2018-03-07 14:43:31.691959 +0800 CST m=+0.000721510
Mining the block containing "Genesis Block"
0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
end: 2018-03-07 14:44:32.488829 +0800 CST m=+60.798522580
elapsed time: 1m0.797798933s
start: 2018-03-07 14:44:32.489057 +0800 CST m=+60.798750578
Mining the block containing "Send 1 BTC to Ivan"
000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
end: 2018-03-07 14:46:32.996032 +0800 CST m=+181.307571128
elapsed time: 2m0.508818203s
start: 2018-03-07 14:46:32.996498 +0800 CST m=+181.308036527
Mining the block containing "Send 2 more BTC to Ivan"
0000001927685501c59f28c0bda3fdd0472e88d6eec3822b0ab98e5b1c28c676
end: 2018-03-07 14:46:53.90008 +0800 CST m=+202.211938702
elapsed time: 20.903900066s
可以看到生成了前6位都是0的hash字串,因為是16進位制的,就是24一位,共4*6=24位,也就是我們設定的targetBits。從時間上可以看到,計算出hashcash的時間有一定的隨機性,多著2分,少則20秒。
現在還需要去驗證是否是正確的。
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
}
可以看到,nonce在驗證是要用到的,告訴對方也用我們計算出來的資料進行二次計算,如果對方計算的符合要求,說明我們的計算合法。
把驗證方法加到main函式中:
func main() {
...
for _, block := range bc.blocks {
...
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
fmt.Println()
}
}
結果:
Prev. hash:
Data: Genesis Block
Hash: 0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
PoW: true
Prev. hash: 0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
Data: Send 1 BTC to Ivan
Hash: 000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
PoW: true
Prev. hash: 000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
Data: Send 2 more BTC to Ivan
Hash: 0000001927685501c59f28c0bda3fdd0472e88d6eec3822b0ab98e5b1c28c676
PoW: true
本章總結
本章我們的區塊鏈進一步接近實際的結構:增加了計算難度,這意味著挖礦成為可能。不過還是欠缺了一些特性,比如沒有把計算出來的資料持久化,沒有錢包(wallet)、地址、交易,以及實現一致性機制。接下來的幾篇abc中,我們會持續完善。
-
學院Go語言視訊主頁
https://edu.csdn.net/lecturer/1928 -
掃碼獲取海量視訊及原始碼 QQ群:721929980
相關文章
- 4.5 工作量證明——PoW
- 工作量證明挖礦
- Go實現Pow工作量證明Go
- 工作量證明(PoW)的內部攻擊模型模型
- LaravelZero 從零實現區塊鏈(二)工作量證明Laravel區塊鏈
- 比特幣如何挖礦(挖礦原理)-工作量證明比特幣
- BTCV欲開發「合併採礦」輔助工作量證明(AuxPoW)UX
- 基於Java語言構建區塊鏈(二)—— 工作量證明Java區塊鏈
- 使用Go構建區塊鏈 第2部分:工作量證明Go區塊鏈
- 區塊鏈小白入門019——工作量證明機制(POW)是什麼?區塊鏈
- 區塊鏈共識機制技術一--POW(工作量證明)共識機制區塊鏈
- 區塊鏈的基石:工作量證明機制,如何驅動數字貨幣革命?區塊鏈
- Chapter 4 證明技巧APT
- bitcoin 與存在性證明
- 初識零知識證明
- dijkstra 複雜度證明複雜度
- sin/cos(α+β) 的展開證明
- Java工作量統計系統Java
- 面試2.2面試
- 網路流複雜度證明複雜度
- 數學證明 學習筆記筆記
- Note - 高斯消元法(證明略)
- 【董天一】IPFSFilecoin和複製證明
- 證明arguments是個物件不是陣列物件陣列
- 帶你走進零知識證明
- [譯]零知識證明: an illustrated primer
- RSA演算法應用及證明演算法
- BRDF能量守恆屬性的證明
- 用Rolle中值定理證明Lagrange中值定理
- 數論裡的尤拉定理,試證明
- Bulletproof範圍證明之最佳化
- 2.2、函式函式
- 2.2-2.50
- flask專案1實戰:2.2 flask框架下使用圖片驗證碼Flask框架
- 勾股定理還能這樣證明?高中生一連發現10種證明方法,陶哲軒點贊
- Python機器學習筆記:SVM(3)——證明SVMPython機器學習筆記
- BEST 定理與矩陣樹定理的證明矩陣
- Jensen 不等式證明(數形結合)