標籤:比特幣 決定 star ali har image alt prefix class
在本系列前兩篇文章中,我們向大家展示了如何通過精鍊的Java代碼實現一個簡單的區塊鏈。包括產生塊,驗證塊資料,廣播通訊等等,這一篇讓我們聚焦在如何? PoW演算法。
大家都無不驚呼比特幣、以太坊及其他加密電子貨幣的持續狂熱,特別是對於剛接觸這個領域的新手,不斷得聽到張三李四通過 GPU “挖礦”而聚集價值數萬乃至數百萬加密電子貨幣。那麼“挖礦”到底是什嗎? 它是如何工作的? 相信對於程式員來說,沒有什麼比自己動手實踐一遍“挖礦”演算法更好的學習辦法了。
在這篇文章中,讓我們一起逐個解讀每一個問題,並最終編寫出自己的“挖礦”演算法。這個演算法稱為工作證明演算法(Proof-of-Work),它是比特幣和以太坊這兩種最流行的加密貨幣的基礎。
什麼是“挖礦”?
加密電子貨幣因為稀缺才具有價值。以現在的比特幣為例,如果任何人任何時候都可以隨意“製造”比特幣,那麼作為電子貨幣它會變得毫無價值。比特幣通過演算法來控制產出的速率並在大約122年內達到最大量。這種隨著時間推移緩慢、穩定並逐步產出的方式有效避免了通貨膨脹。
比特幣的產出是通過給予“獲勝礦工”獎勵來實現,為了擷取比特幣獎勵礦工之間會進行競爭。這個過程之所以被稱為“挖礦”,是因為它類似於“Gold Rush”時每個黃金礦工通過辛苦勞作並最終(希望)找到一點黃金。
“挖礦”是如何工作的?
如果 Google 一下這個問題,你會得到大量的結果。簡單來說,“挖礦”就是“解決一個數學難題”的過程。我們先來瞭解一些密碼學和雜湊演算法的知識。
密碼學簡要介紹
單向加密以人類可讀的文本(明文)作為輸入,比如“Hello world”這個字串,再通過一個數學函數產生出難以辨認的輸出(密文)。 這類函數或演算法的性質和複雜性各不相同。 演算法越複雜,逆向工程就越困難。
以流行的 SHA-256 演算法為例。 通過這個網站[5]可以讓你計算任意給定輸入的輸出,也就是 SHA-256 雜湊值。比如讓我們輸入“Hello world”,看看得到了什麼:
通過不斷嘗試計算“Hello world”的雜湊值。你會發現每次的結果都完全相同。 這個過程稱為等冪性。
密碼編譯演算法一個最基本的特性是,非常難以通過反向工程來求解輸入,但是非常容易驗證輸出。比如上面的例子,你可以很容易驗證給定輸入“Hello world”的SHA-256雜湊值是否正確,但很難通過給定的雜湊值判斷它的輸入是什麼。這就是為什麼將這種類型的演算法稱為單向加密。
比特幣使用 Double SHA-256,它將 SHA-256 求得的雜湊值作為輸入再次計算 SHA-256 雜湊值。 為了簡化,我們只使用一次SHA-256。
挖礦
回到加密電子貨幣中,比特幣就是通過讓參與者利用這樣的密碼編譯演算法求解出符合特定條件的雜湊值來實現“挖礦”過程。具體來說,比特幣要求參與者通過 double SHA-256 演算法計算出“前置0”超過若干位的雜湊值,第一個求解出來的參與者就是“獲勝的礦工”。
比如,我們求“886”這個字串的 SHA-256 雜湊值:
可以看到,是一個“前置0”為3位的雜湊值(前三位是0)。
回憶我們前面說到的“單向加密”的特點:
任何人都可以很容易地驗證“886”是否產生3位“前置0”的雜湊值。但為了找到這樣一個能產生3位“前置0”的輸入(就是這裡的“886”),我們做了大量繁瑣的計算工作:從一個很大的數字和字母集合中逐個計算它們的雜湊值並判斷是否滿足上述條件。如果我是第一個找到“886”的人,那其他人通過驗證就能判斷我做了這樣大量繁瑣的工作。在比特幣、以太坊中這樣的過程就稱為工作證明演算法。
“如果我運氣非常好,第一次嘗試就找到了一個合格(輸入)值呢?” —— 這是非常不可能的,你可以試試隨意輸入一些字母和數字。
比特幣中實際的演算法和約束要比上說要求複雜,當然也更難(要求更多位的“前置0”)。同時它也可以動態調整難度,目標是確保每隔10分鐘產出一次比特幣,不管參與“挖礦”的人多還是少。
差不多可以動手了
瞭解了足夠的背景知識,接著我們就用 Java語言來編碼實踐下工作量證明(Proof-of-Work)演算法。建議你閱讀之前的系列文章,因為下面工作證明演算法部分會涉及之前的代碼。
Proof-of-work
建立新塊並加入到鏈上之前需要完成“工作量證明”過程。我們先寫一個簡單的函數來檢查給定的雜湊值是否滿足要求。
下面就是 isHashValid
這個函數:
/** * 校正HASH的合法性 * * @param hash * @param difficulty * @return */ public static boolean isHashValid(String hash, int difficulty) { String prefix = repeat("0", difficulty); return hash.startsWith(prefix); } private static String repeat(String str, int repeat) { final StringBuilder buf = new StringBuilder(); for (int i = 0; i < repeat; i++) { buf.append(str); } return buf.toString(); }
我們定義prefix
變數,它代表“前置0”,接著檢查雜湊值是否具有滿足條件的“前置0”,然後返回 True
或 False
。
我們修改之前產生塊的generateBlock
函數:
public static Block generateBlock(Block oldBlock, int vac) { Block newBlock = new Block(); newBlock.setIndex(oldBlock.getIndex() + 1); newBlock.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); newBlock.setVac(vac); newBlock.setPrevHash(oldBlock.getHash()); newBlock.setDifficulty(difficulty); /* * 這裡的 for 迴圈很重要: 獲得 i 的十六進位表示 ,將 Nonce 設定為這個值,並傳入 calculateHash 計算雜湊值。 * 之後通過上面的 isHashValid 函數判斷是否滿足難度要求,如果不滿足就重複嘗試。 這個計算過程會一直持續,直到求得了滿足要求的 * Nonce 值,之後通過 handleWriteBlock 函數將新塊加入到鏈上。 */ for (int i = 0;; i++) { String hex = String.format("%x", i); newBlock.setNonce(hex); if (!isHashValid(calculateHash(newBlock), newBlock.getDifficulty())) { System.out.printf("%s do more work!\n", calculateHash(newBlock)); try { Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.error("error:", e); } continue; } else { System.out.printf("%s work done!\n", calculateHash(newBlock)); newBlock.setHash(calculateHash(newBlock)); break; } } return newBlock; }
篇幅有限,我已經將完整代碼發布在 Github上,可以從這裡獲得。
跑起來看看
啟動程式,在瀏覽器中訪問 http://localhost:8080
接著通過 RestClient 來發送一個包含vac資料的POST
請求。我們觀察命令列視窗,不斷得計算雜湊值,如果不滿足難度要求就繼續重試,直到找到滿足要求的雜湊值及 Nonce
可以看到最後一個雜湊值滿足我們設定的難度要求(1位“前置0”)。我們再來重新整理下瀏覽器:
可以看到第二個塊建立成功並加到鏈上了,其中Nonce
就是通過Proof-of-Work計算出來滿足難度要求的值。
下一步
到這裡要先祝賀你,上面的內容很有價值。儘管我們的樣本中使用了非常低的難度,但本質上,工作證明演算法就是比特幣、以太坊等區塊鏈的重要組成。
對於下一步應該深入區塊鏈的哪個方向,我們推薦可以學習如何通過 IPFS存取大檔案並與區塊鏈打通。[IPFS]
此外相比 Proof-of-Work,Proof-of-Stake 演算法正越來越受到關注和青睞,你也可以學習如何將本文的 PoW 演算法改為實現 PoS 演算法。
只用120行Java代碼寫一個自己的區塊鏈-3挖礦演算法