用 Go 構建一個區塊鏈 -- Part 2: 工作量證明

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

翻譯的系列文章我已經放到了 GitHub 上:blockchain-tutorial,後續如有更新都會在 GitHub 上,可能就不在這裡同步了。如果想直接運行代碼,也可以 clone GitHub 上的教程倉庫,進入 src 目錄執行 make 即可。

在前面一文中,我們構造了一個非常簡單的資料結構,這個資料結構也是整個區塊鏈資料庫的核心。目前所完成的區塊鏈原型,已經可以通過鏈式關係把區塊相互關聯起來:每個塊都被串連到前一個塊。

但是,我們實現的區塊鏈有一個巨大的缺點:向鏈中加入區塊太容易和廉價了。而區塊鏈和比特幣的其中一個核心就是,要想加入新的區塊,必須先完成一些非常困難的工作。在本文,我們將會解決這個缺點。

工作量證明

區塊鏈的一個關鍵點就是,一個人必須經過一系列困難的工作,才能將資料放入到區塊鏈中。正是這種困難的工作,才使得區塊鏈是安全和一致的。此外,完成這個工作的人也會獲得獎勵(這也就是通過挖礦獲得幣)。

這個機制與生活的一個現象非常類似:一個人必須通過努力工作,才能夠獲得回報或者獎勵,用以支撐他們的生活。在區塊鏈中,是通過網路中的參與者(礦工)不斷的工作來支撐整個網路,也就是礦工不斷地向區塊鏈中加入新塊,然後獲得相應的獎勵。作為他們努力工作的結果,新產生的區塊就能夠被安全地被加入到區塊鏈中,這種機制維護了整個區塊鏈資料庫的穩定性。值得注意的是,完成了這個工作的人必須要證明這一點,他必須要證明確實是他完成了這些工作。

整個 “努力工作並進行證明” 的機制,就叫做工作量證明(proof-of-work)。要想完成工作非常地不容易,因為這需要大量的計算能力:即便是高效能運算機,也無法在短時間內快速完成。此外,這個工作的困難度會隨著時間不斷增長,以保持每個小時大概出 6 個新塊的速度。在比特幣中,這個工作的目的是為了找到一個塊的雜湊,同時這個雜湊滿足了一些必要條件。這個雜湊,也就充當了證明的角色。因此,尋求證明(尋找有效雜湊),就是實際要做的事情。

雜湊計算

在本節中,我們會討論雜湊計算。如果你已經熟悉了這個概念,可以跳過這一節。

獲得指定資料的一個雜湊值的過程,就叫做雜湊計算。一個雜湊,就是對所計算資料的一個唯一的表示。一個雜湊函數輸入任意大小的資料,輸出一個固定大小的雜湊值。下面是雜湊的幾個關鍵特性:

  1. 無法從一個雜湊值恢複未經處理資料。也就是說,雜湊並不是加密。
  2. 對於特定的資料,只能有一個雜湊,並且這個雜湊是唯一的。
  3. 即使是僅僅改變輸入資料中的一個位元組,也會導致輸出一個完全不同的雜湊。

雜湊函數被廣泛用於檢測資料的一致性。一些軟體提供者除了提供軟體包以外,還會發布校正和。當下載完一個檔案以後,你可以用雜湊函數對下載好的檔案計算一個雜湊,並與作者提供的雜湊進行比較,以此來保證檔案下載的完整性。

在區塊鏈中,雜湊被用於保證一個塊的一致性。雜湊演算法的輸入資料包含了前一個塊的雜湊,因此使得不太可能(或者,至少很困難)去修改鏈中的一個塊:因為如果一個人想要修改前面一個塊的雜湊,那麼他必須要重新計算這個塊以及後面所有塊的雜湊。

Hashcash

比特幣使用 Hashcash ,一個最初用來防止垃圾郵件的工作量證明演算法。它可以被分解為以下步驟:

  1. 取一些公開的資料(比如,如果是 email 的話,它可以是接收者的郵件地址;在比特幣中,它是區塊頭)
  2. 給這個公開資料添加一個計數器。計數器預設從 0 開始
  3. data(資料)counter(計數器) 組合到一起,獲得一個雜湊
  4. 檢查雜湊是否符合一定的條件:

    1. 如果符合條件,結束

      1. 如果不符合,增加計數器,重複步驟 3-4

因此,這是一個暴力演算法:改變計數器,計算一個新的雜湊,檢查,增加計數器,計算一個雜湊,檢查,如此反覆。這也是為什麼說它是在計算上是非常昂貴的,因為這一步需要如此反覆不斷地計算和檢查。

現在,讓我們來仔細看一下一個雜湊要滿足的必要條件。在原始的 Hashcash 實現中,它的要求是 “一個雜湊的前 20 位必須是 0”。在比特幣中,這個要求會隨著時間而不斷變化。因為按照設計,必須保證每 10 分鐘產生一個塊,而不論計算能力會隨著時間增長,或者是會有越來越多的礦工進入網路,所以需要動態調整這個必要條件。

為了闡釋這一演算法,我從前一個例子(“I like donuts”)中取得資料,並且找到了一個前 3 個位元組是全是 0 的雜湊。

ca07ca 是計數器的 16 進位值,十進位的話是 13240266.

實現

好了,完成了理論層面,來開始寫代碼吧!首先,定義挖礦的難度值:

const targetBits = 24

在比特幣中,當一個塊被挖出來以後,“target bits” 代表了區塊頭裡儲存的難度。這裡的 24 指的是算出來的雜湊前 24 位必須是 0,用 16 進位表示化的話,就是前 6 位必須是 0,這一點可以在最後的輸出可以看出來。目前不會實現一個動態調整目標的演算法,所以將難度定義為一個全域的常量即可。

24 其實是一個可以任意取的數字,目的是要有一個目標(target)而已,這個目標佔據不到 256 位的記憶體空間。同時,我們想要有足夠的差異性,但是又不至於大的過分,因為差異性越大,就越難找到一個合適的雜湊。

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 結構,裡面儲存了指向一個塊和一個目標的指標。“目標” ,也就是前一節中所描述的必要條件。這裡使用了一個 大 整數,我們將雜湊與目標進行比較:先把一個雜湊轉換成一個大整數,然後檢測它是否小於目標。

NewProofOfWork 函數中,我們將 big.Int 初始化為 1,然後左移 256 - targetBits 位。256 是一個 SHA-256 雜湊的位元,我們將要使用的是 SHA-256 雜湊演算法。target(目標) 的 16 進位形式為:

0x10000000000000000000000000000000000000000000000000000000000

它在記憶體上佔據了 29 個位元組。下面是與前面例子雜湊的形式化比較:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e300000100000000000000000000000000000000000000000000000000000000000000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一個雜湊(基於 “I like donuts” 計算)比目標要大,因此它並不是一個有效工作量證明。第二個雜湊(基於 “I like donutsca07ca” 計算)比目標要小,所以是一個有效證明。

譯者註:評論有人提出上面的形式化比較有些“言不符實”,其實它應該並非由 “I like donuts” 而來,但是原文作者表達的意思是沒問題的,可能是疏忽而已。下面是我做的一個小實驗:

package mainimport (    "crypto/sha256"    "fmt"    "math/big")func main() {    data1 := []byte("I like donuts")    data2 := []byte("I like donutsca07ca")    targetBits := 24    target := big.NewInt(1)    target.Lsh(target, uint(256-targetBits))    fmt.Printf("%x\n", sha256.Sum256(data1))    fmt.Printf("%64x\n", target)    fmt.Printf("%x\n", sha256.Sum256(data2))}

輸出:

你可以把目標想象為一個範圍的上界:如果一個數(由雜湊轉換而來)比上界要小,那麼這是有效,反之無效。因為要求比上界要小,所以會導致更少的有效數字。因此,也就需要通過困難的工作(一系列反覆的計算),才能找到一個有效數字。

現在,我們需要有資料來進行雜湊,準備資料:

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}

這個部分比較直觀:只需要將 target ,nonce 與 Block 進行合并。這裡的 nonce ,就是上面 Hashcash 所提到的計數器,它是一個密碼學術語。

很好,到這裡,所有的準備工作就完成了,下面來實現 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)        hashInt.SetBytes(hash[:])        if hashInt.Cmp(pow.target) == -1 {            fmt.Printf("\r%x", hash)            break        } else {            nonce++        }    }    fmt.Print("\n\n")    return nonce, hash[:]}

首先我們對變數進行初始化:

  • HashInthash 的整形表示;
  • nonce 是計數器。

然後開始一個 “無限” 迴圈:maxNonce 對這個迴圈進行了限制, 它等於 math.MaxInt64。這是為了避免 nonce 可能出現的溢出。儘管我們的 PoW 實現的難度太小了,以至於計數器其實不太可能會溢出,但最好還是以防萬一檢查一下。

在這個迴圈中,我們做的事情有:

  1. 準備資料
  2. 用 SHA-256 對資料進行雜湊
  3. 將雜湊轉換成一個大整數
  4. 將這個大整數與目標進行比較

跟之前所講的一樣簡單。現在我們可以移除 BlockSetHash 方法,然後修改 NewBlock 函數:

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}

好了!現在讓我們來運行一下是否正常工作:

Mining the block containing "Genesis Block"00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Mining the block containing "Send 1 BTC to Ivan"00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Mining the block containing "Send 2 more BTC to Ivan"000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbePrev. hash:Data: Genesis BlockHash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Data: Send 1 BTC to IvanHash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Data: Send 2 more BTC to IvanHash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe

成功了!你可以看到每個雜湊都是 3 個位元組的 0 開始,並且獲得這些雜湊需要花費一些時間。

還剩下一件事情需要做,對工作量證明進行驗證:

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。

再來檢測一次是否正常工作:

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 BlockHash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038PoW: truePrev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038Data: Send 1 BTC to IvanHash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062bPoW: truePrev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062bData: Send 2 more BTC to IvanHash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57aPoW: true

從可以看出,這次我們產生三個塊花費了一分多鐘,比沒有工作量證明之前慢了很多(也就是成本高了很多):

總結

我們的區塊鏈離真正的區塊鏈又進了一步:現在需要經過一些困難的工作才能加入新的塊,因此挖礦就有可能了。但是,它還缺少一些至關重要的特性:區塊鏈資料庫並不是持久化的,沒有錢包,地址,交易,也沒有共識機制。不過,所有的這些,我們都會在接下來的文章中實現,現在,愉快地挖礦吧!

連結:

  • Full source codes
  • Blockchain hashing algorithm
  • Proof of work
  • Hashcash

本文原始碼:part_2

原文:

Building Blockchain in Go. Part 2: Proof-of-Work

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.