2020 區(qū)塊鏈 golang 版本(9) 工作量證明

封面

在上一篇分享中,我們定義了一個(gè)非常簡單的數(shù)據(jù)結(jié)構(gòu),雖然簡單但是我們已經(jīng)可以看出區(qū)塊鏈數(shù)據(jù)庫的雛形。通過代碼實(shí)現(xiàn)了創(chuàng)建區(qū)塊鏈以及如何將區(qū)塊添加到區(qū)塊鏈中,區(qū)塊鏈中每個(gè)區(qū)塊都通過 hash 指針連接到前一個(gè)區(qū)塊。這樣將區(qū)塊一個(gè)一個(gè)連接起來。但是實(shí)際上我們都知道區(qū)塊鏈出塊并沒有那么容易。

區(qū)塊鏈設(shè)計(jì)的一個(gè)巧妙之處,人們必須進(jìn)行一些工作才能將區(qū)塊添加到區(qū)塊鏈中。正是這些艱苦的工作,也就是我們熟知的挖礦,才為區(qū)塊鏈安全性和一致性提供保證。而且這項(xiàng)具有一定難度的挖礦工作也會(huì)得到回報(bào)(通過挖礦獎(jiǎng)勵(lì)礦工會(huì)得到一定額度的比特幣)。

因?yàn)橛辛送诘V具有一定難度,這樣將出塊時(shí)間限制在 10 分鐘左右,為什么這樣做可以保證區(qū)塊鏈的安全性。如果出塊太快,這樣就很容易造成區(qū)塊鏈具有多個(gè)分叉。在區(qū)塊鏈中認(rèn)為大部分算力是掌握在大多數(shù)誠實(shí)可靠的礦工,但是出現(xiàn)大量分支就削弱了大多數(shù)礦工的算力,惡意節(jié)點(diǎn)就有機(jī)會(huì)。

這一機(jī)制設(shè)計(jì)與現(xiàn)實(shí)生活中的非常相似:一個(gè)人必須通過努力工作才能獲得回報(bào),過上好日子。在區(qū)塊鏈中,網(wǎng)絡(luò)中的一些節(jié)點(diǎn)(礦工)努力維持網(wǎng)絡(luò),為區(qū)塊鏈添加新的區(qū)塊,因此獲得獎(jiǎng)勵(lì),這種獎(jiǎng)勵(lì)同時(shí)也是區(qū)塊鏈法幣方式。由于他們的工作,一個(gè)區(qū)塊以安全的方式并入?yún)^(qū)塊鏈,從而保持整個(gè)區(qū)塊鏈數(shù)據(jù)庫的穩(wěn)定性。值得注意的是,完成這項(xiàng)工作的人必須證明這一點(diǎn)。

工作量證明也是 hash 密切相關(guān)的,所以在開始之前我們回顧一下 hash 一些特征。hash 運(yùn)算是計(jì)算給定數(shù)據(jù)的 hash 值的過程。hash 運(yùn)算可以將任意大小的數(shù)據(jù)通過 hash 函數(shù)生成一個(gè)固定大小 hash 值。以下是 hash 的一些特性:

  • hash 值無法進(jìn)行逆運(yùn)算反推出原始值
  • hash 值具有唯一性
  • 原始數(shù)據(jù)一點(diǎn)點(diǎn)改動(dòng)都會(huì)導(dǎo)致計(jì)算出 hash 值面目全非

hash 函數(shù)主要用于檢查數(shù)據(jù)的一致性。除了軟件包外,下載文件,可以通過對(duì)比 hash 值可以堅(jiān)持下載文件是否和源文件一致。

比特幣使用 Hashcash,這是一種工作證明算法,最初是為了防止電子郵件垃圾郵件而開發(fā)的??梢苑譃橐韵虏襟E:

  • 獲取一些公開的數(shù)據(jù)(對(duì)于電子郵件,這些公開數(shù)據(jù)是收件人的電子郵件地址,而對(duì)于比特幣,是區(qū)塊頭)
  • 在上面加一個(gè)計(jì)數(shù)器(隨機(jī)數(shù)),計(jì)數(shù)器從 0 開始,就是在公開數(shù)據(jù)添加一個(gè)隨機(jī)數(shù)
  • 計(jì)算這個(gè)公開數(shù)據(jù)+計(jì)數(shù)器組合的 hash 值
  • 然后檢查 hash 是否滿足某些要求。
  • 如果滿足這些要求,任務(wù)就結(jié)束否則增加隨機(jī)數(shù)來重新計(jì)算 hash 值也就是
    如果沒有,增加計(jì)數(shù)器并重復(fù)步驟3和4。

因?yàn)?hash 具有以上我們提及特性,所以這是一個(gè)蠻力算法,沒有捷徑,需要一次一次調(diào)整隨機(jī)數(shù)然后計(jì)算 hash 值,所以需要計(jì)算一定成本。

那么在區(qū)塊鏈?zhǔn)侵腥绾斡?jì)算 hash 來滿足的什么樣的需求呢? 通過調(diào)整隨機(jī)數(shù)然后計(jì)算出一個(gè)在一定范圍內(nèi)的 hash 值。

目標(biāo)值

就不斷地嘗試給出不同 nonce 值,使得 hash 小于等于給定閾值(target)。
Hash(block header) \le target
target 就是閾值,target 越小挖礦難度就越大。其實(shí)調(diào)整挖礦難度就是調(diào)整目標(biāo)空間在整個(gè)輸出空間所占的比例。比特幣采用hash 算法是 SHA-256,那么輸出空間就是2^256。是通過 hash 值中前多少位為 0 來控制目標(biāo)空間的大小。所以要計(jì)算出小于目標(biāo)值,就需要保證計(jì)算出hash 值前幾位為 0。

挖礦難度與目標(biāo)之的關(guān)系

difficulty = \frac{difficulty 1 target}{target}

  • frac{difficulty 1 target:表示挖礦難度等于 1 時(shí)所對(duì)應(yīng)的目標(biāo)閾值,挖礦難度最小就是 1 ,所以為 1 時(shí)對(duì)應(yīng)挖礦難度是非常大的數(shù)。
  • 所謂挖礦難度與目標(biāo)閾值成反比

挖礦難度必要性

在比特幣系統(tǒng)通過調(diào)整挖礦難度來保證出塊的時(shí)間,出塊的間隔為 10 分鐘一個(gè)區(qū)塊。出塊時(shí)間間隔過短就很容易出現(xiàn)分叉。

比特幣系統(tǒng)假設(shè)大部分算力是掌握在誠實(shí)礦工手上。系統(tǒng)算力越強(qiáng)安全性就越好,這是因?yàn)橄胍l(fā)動(dòng) 51% 以上攻擊就需要算力越大。也就是惡意節(jié)點(diǎn)

const Difficulty = 12

在比特幣中,Difficult(target) 是保存在區(qū)塊頭中,存儲(chǔ)塊被挖掘的難度。我們暫時(shí)不會(huì)實(shí)現(xiàn)目標(biāo)調(diào)整算法,所以我們可以將難度定義為全局常數(shù)。

定義工作量證明結(jié)構(gòu)

type ProofOfWork struct {
    Block  *Block
    Target *big.Int
}
  • Block 區(qū)塊
  • Target 目標(biāo)閾值

定義創(chuàng)建工作量證明方法

func NewProof(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-Difficulty))

    pow := &ProofOfWork{b, target}

    return pow
}

這里 Difficulty 是暫時(shí)隨意定義數(shù)值 12,目標(biāo)值是有一個(gè)在內(nèi)存中占用少于 256 位的 target。我們希望 Difficutly 足夠大因?yàn)橐刂?10 分鐘出一個(gè)快,但 Difficulty 又不能太大,就越難找到合適的 hash 值。

現(xiàn)在創(chuàng)建一個(gè) ProofOfWork 結(jié)構(gòu)體,在結(jié)構(gòu)體中會(huì)包含一個(gè)區(qū)塊的指針和一個(gè)指向 Difficult (類型為 big.Int)的指針。Diffuclty 是前面前一段所述要求的另一個(gè)名稱。我們使用大整數(shù)是因?yàn)槲覀儗?hash 與目標(biāo)(目標(biāo)值)進(jìn)行比較的方式:我們將哈希轉(zhuǎn)換為大整數(shù),并檢查它是否小于目標(biāo)值。

在 NewProofOfWork 函數(shù)中,初始化類型為 big.Int 值為 1 的變量,并將其左移 256 - targetBits 位。256 是 SHA-256 哈希的長度(以bit為單位),將使用 SHA-256 hash 算法。Diffculty 的十六進(jìn)制(hexadecimal )表示為:

func main() {
    Difficulty := 200
    target := big.NewInt(1)
    target.Lsh(target, uint(256-Difficulty))

    fmt.Printf("target: %d", target)
}
target: 72057594037927936

7205-7594-0379-27936

func (pow *ProofOfWork) InitData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.Block.PrevHash,
            pow.Block.Data,
        },
        []byte{},
    )
    return data
}
func ToHex(num int64) []byte {
    buff := new(bytes.Buffer)
    err := binary.Write(buff, binary.BigEndian, num)
    if err != nil {
        log.Panic(err)
    }

    return buff.Bytes()
}

func (pow *ProofOfWork) Run() (int, []byte) {
    var intHash big.Int
    var hash [32]byte

    nonce := 0
    for nonce < math.MaxInt64 {
        data := pow.InitData(nonce)
        hash = sha256.Sum256(data)

        fmt.Printf("\r%x", hash)
        intHash.SetBytes(hash[:])
        if intHash.Cmp(pow.Target) == -1 {
            break
        } else {
            nonce++
        }
    }
    fmt.Println()
    return nonce, hash[:]
}

type Block struct {
    Hash     []byte
    Data     []byte
    PrevHash []byte
    Nonce    int
}
func CreateBlock(data string, prevHash []byte) *Block {
    block := &Block{[]byte{}, []byte(data), prevHash, 0}
    block.DeriveHash()
    return block
}
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
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容