簡介
POW是proof-of-work的縮寫,中譯為:工作量證明,是比特幣中采用的共識機制,也被許多公有區塊鏈系統所采用(比如以太坊)。工作量證明機制基礎是哈希運算,因此要理解pow首先要明白哈希函數(比特幣大量采用了sha256,以及rimped160)。
本篇文章重點介紹pow共識算法的原理,不具體介紹比特幣的相關細節。
什么是哈希函數?
哈希運算是密碼學的重要組成部分,一般用來保護數據的完整性。給定一段消息,通過哈希函數可以將消息映射為固定長度的哈希值(比如sha256,將任意長度的消息映射為256位的哈希值)。哈希函數具有兩個重要的特點:“無碰撞“和”不可逆“。
1. 所謂不可逆,就是當你知道x的HASH值,無法求出x;因此哈希函數在數學上是一種單向函數。
2. 所謂無沖突,就是當你知道x,無法求出一個y, 使x與y的HASH值相同。簡單來說就是,無法找到一個不同的消息y,使得y的哈希值與x的哈希值相同。(當然,因為消息的比特空間 << 所求哈希值的比特空間,因此其實這樣的y是存在的,只是這樣的y很難找,只是一個概率上的不可能)
哈希函數的應用
從哈希函數的特性我們可以看出,當x被改變時,x所對應的哈希值也會發生改變,因此可以通過一段消息的哈希值是否改變來間接反應消息是否被修改,也就是我們說的數據完整性驗證。(當然這個一般還要也公鑰密碼體制相結合來使用,這里不解釋公鑰密碼體制,自行google)。
### 在比特幣中的使用
比特幣主要使用了sha256哈希函數,有三個用途:
1. 比特幣地址是公鑰的兩次sha256運算結果
2. 保證每筆交易的完整性
3. pow工作量證明機制的基礎
比特幣共識算法
pow工作量證明機制,我們從“工作量”和“證明”兩個概念上來理解pow。
1. 什么是工作量?
比特幣一直被人詬病做了大量無意義的運算,耗費了巨量的電能。這里的運算就是比特幣共識機制中的工作量,而這個運算就是哈希運算。具體是下面的運算:
SHA256(SHA256(Block_Header))
這里的Block_Header可以簡單理解為:一堆比特幣交易txs + 一個隨機數數nonce。在生成比特幣區塊的時候,我們可以認為txs是固定的,因此pow可以簡化為:選取隨機數nonce,然后求SHA256(SHA256(txs || nonce)),我們下面把這個計算叫做POWHash。
這個不斷重復選取隨機數nonce,計算POWHash的過程什么時候結束呢?在比特幣中POWHash的前n位為0的時候,我們認為找到了一個有效的nonce,然后就可以生成比特幣區塊並廣播給其他的礦工。這里的n是一個可調節的數,比特幣中沒生成2016個塊,會重新調整n,n越大有效的POWHash越難計算,相反越容易。
2. 怎么證明?
pow中的證明值得是驗證nonce是否有效,同樣是計算SHA256(SHA256(txs || nonce)),不過這里的txs和nonce都是已知的,我們把運算結果記為POWHash‘,我們只需要驗證POWHash’是否滿足前n為0,就可以證明這個區塊是不是一個有效的比特幣區塊。
通過分析我們發現,如果要偽造交易(修改txs),需要重新找到一個nonce,因此對於非法修改區塊,需要重新進行大量哈希運算來找到一個有效的nonce。由於每個區塊都包含前一個區塊的hash值,因此后面所有的區塊都需要重新進行調整。因此修改比特幣中的交易信息是計算上不可能的。
--------------------------------Talk is cheap, show me your code-------------------------------
下面是簡單的pow實現,僅供參考
var ( target, _ = new(big.Int).SetString("ffffffffffffffff", 16) //64bits ) func pow(merkelRootHash, previousHash []byte, difficulty int, h hash.Hash) { target = target.Rsh(target, uint(difficulty)) //variable to computed. 1. from the beginning 0, 2. random a nounce. Here we choose 1. nounce := new(big.Int).SetInt64(0) for { //time stamp now := fmt.Sprintf("%v", time.Now()) var header []byte header = append(header, []byte(now)...) header = append(header, previousHash...) header = append(header, merkelRootHash...) header = append(header, nounce.Bytes()...) h.Write(header) res := new(big.Int).SetBytes(h.Sum(nil)[:8]) //substract first 8*8 bits from sha256 if res.Cmp(target) < 0 { fmt.Printf("%b\n", nounce) //found a valid nonce, print it. break } nounce.Add(nounce, new(big.Int).SetUint64(1)) } }
----------------------------------End Line-------------------------------------------------