區塊鏈,工作證明(POW)代碼+原理 golang版剖析

介紹

在之前的文章中,我們構建了一個非常簡單的數據結構,這是塊鏈數據庫的本質。 而且我們可以用它們之間的鏈式關係向它添加區塊:每個區塊與前一個鏈接。 唉,然而在現實中添加一個區塊添加到鏈是艱鉅的工作。

工作證明

塊鏈的一個關鍵思想是,必須通過工作證明才能將數據放入其中。這是一個艱鉅的工作,使塊鏈安全和一致。此外,這筆辛苦的工作也得到了獎勵(這是人們獲得採礦硬幣的方式)。

這種機制與現實生活中的機制非常相似:人們必須工作獲酬勞勵並維持生命。在網絡中,網絡的一些參與者(礦工)努力維持網絡,爲其添加新的塊,併爲他們的工作獲得獎勵。作爲其工作的結果,塊以安全的方式併入到塊鏈中,這保持了整個塊鏈數據庫的穩定性。值得注意的是,完成工作的人必須證明這一點。

這個整體“努力工作和證明工作價值”機制被稱爲工作證明。這很難因爲它需要很多的計算能力:即使是高性能的計算機也不能很快的完成。此外,這項工作的難度不時增加,以保持新的塊率每小時大約6個塊。在比特幣,這樣的工作的目標是找到一個塊的哈希,滿足一些要求。這是散列,作爲證明。因此,找到證據是實際工作。

最後要注意的事情。工作證明算法必須滿足要求:做完工作不易完成,證明工作容易完成。證明通常交給非工作者,所以對他們來說,驗證它不應該花太多的時間。

哈希算法加密

在本文中,我們將討論哈希值。 如果你熟悉這個概念,你可以跳過這個部分。

哈希是獲取指定數據的哈希值的過程。 哈希值是對其計算的數據的唯一表示。 哈希函數是一個獲取任意大小的數據併產生固定大小的哈希的函數。 以下是哈希的一些主要功能:

  • 原始數據無法從哈希值恢復。 因此,散列不是加密。

  • 數據只能有一個哈希值,散列是唯一的。

  • 更改輸入數據中的一個字節將導致完全不同的散列。

1.png

哈希函數被廣泛用於檢查數據的一致性。在區塊鏈中,使用哈希來保證塊的一致性。 哈希算法的輸入數據包含前一個塊的哈希值,從而使得已經生成的鏈難以修改之前產生的區塊(或至少相當困難):必須重新計算其後的所有塊的哈希值。

哈希現金 、 Hashcash

比特幣使用Hashcash,哈希現金的發明最初是爲防止電子郵件垃圾郵件而開發的。它可以分爲以下幾個步驟:

  1. 獲取公開的數據(在電子郵件的情況下,它是接收者的電子郵件地址;在比特幣的情況下,它是塊標題)。

  2. 添加一個計數器。計數器從0開始。

  3. 獲取數據+計數器組合的散列。

  4. 檢查哈希值是否符合要求。

    1. 如果滿足要求,結束過程。

    2. 如果不滿足要求,增加計數器並重復步驟3和4。

因此,這是一個強力算法:您更改計數器,計算一個新的哈希,檢查它,增加計數器,計算哈希等。這就是爲什麼它在計算上是昂貴的。

現在讓我們看看一個哈希必須滿足的要求。在原來的Hashcash實現中“哈希的前20位必須是零”。然而在比特幣中,哈希要求是不時進行調整的,因爲儘管計算能力隨着時間的推移而增加,越來越多的礦工加入網絡,因此設計必須每10分鐘生成一個塊

爲了演示這個算法,我從前面的例子中獲取了數據(“我喜歡甜甜圈”),並發現一個以0個零字節開頭的哈希:

編寫代碼

程序員小提醒:go和python都是不用加分號的語言

好的,我們完成了理論,讓我們編寫代碼! 首先,我們來定義挖掘的難度:

const targetBits = 24

In Bitcoin, “target bits” is the block header storing the difficulty at which the block was mined. We won’t implement a target adjusting algorithm, for now, so we can just define the difficulty as a global constant.

24 is an arbitrary number, our goal is to have a target that takes less than 256 bits in memory. And we want the difference to be significant enough, but not too big, because the bigger the difference the more difficult it’s to find a proper hash.

在比特幣中,“目標位(target bit)”是存儲塊被挖掘的困難的塊頭。 我們現在不會實現目標調整算法,所以我們可以將難度定義爲全局常數

24是一個任意數字,我們的目標是在內存中佔用少於256位的目標。 而且我們希望差異足夠大,但不要太大,因爲差異越大,找到合適的哈希越難。

type ProofOfWork struct {
    block  *Block 
    target *big.Int //定義目標位
}

func NewProofOfWork(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))  //左移256個 target bits位

    pow := &ProofOfWork{b, target}

    return pow
}

這裏創建保存指向塊的指針的工作證明結構和指向目標的指針。 “目標”是上一段所述要求的另一個名稱。 我們使用一個大整數,因爲我們將哈希與目標進行比較:我們將哈希轉換爲一個大整數,並檢查它是否小於目標。

big: https://golang.org/pkg/math/big/

在新的工作證明的函數中,我們初始化一個值爲1的big.Int,並將其左移256個 - targetBits位。 256是SHA-256哈希的長度,以比特爲單位,它是我們要使用的SHA-256散列算法。 目標的十六進制表示爲:

0x10000000000000000000000000000000000000000000000000000000000

它在內存中佔用29個字節。 這是與以前的例子中的哈希的比較:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一個哈希(以“我喜歡甜甜圈”計算)大於目標,因此它不是有效的工作證明。 第二個哈希(以“我喜歡甜甜圈ca07ca”計算)小於目標,因此這是一個有效的證明。

您可以將目標視爲範圍的上限:如果數字(哈希)低於邊界,則它是有效的,反之亦然。 降低邊界將導致有效數量減少,因此找到有效數量所需的工作更加困難。

現在,對數據進行哈希處理。

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
}

我們只是將塊區域與目標和隨機數合併。 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) // SHA-256加密
        fmt.Printf("\r%x", hash)
        hashInt.SetBytes(hash[:])  // 講hash轉換成Big Integer

        if hashInt.Cmp(pow.target) == -1 {
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}

首先,我們初始化變量:hashInt是哈希的整數表示; nonce是櫃檯。 接下來,我們運行一個“無限”循環:它受限於maxNonce,它等於math.MaxInt64; 這樣做是爲了避免可能的隨機數溢出。 雖然我們的PoW實施的難度太低,以至於防止溢出,但最好是進行此檢查,以防萬一。

在循環中我們:

  • 準備數據

  • 用SHA-256進行哈希。

  • 將散列轉換爲大整數。

  • 將整數與目標進行比較。

現在我們可以刪除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
}

Here you can see that nonce is saved as a Block property. This is necessary because nonce is required to verify a proof. The Blockstructure now looks so:

type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
}

驗證工作證明

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
}

主函數代代碼再次檢查

func main() {
    ...

    for _, block := range bc.blocks {
        ...
        pow := NewProofOfWork(block)
        fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
        fmt.Println()
    }
}

結論

我們的塊鏈是一個更接近其實際架構的一步:添加塊現在需要努力工作,因此挖掘是可能的。 但是它仍然缺乏一些關鍵的特徵:塊鏈數據庫不是持久的,沒有錢包,地址,交易,沒有共識機制。 所有這些我們將在以後的文章中實現的,現在,開採開採!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章