本篇是"用Go構(gòu)建區(qū)塊鏈"系列的第二篇,主要對原文進行翻譯。對應(yīng)原文如下:
Building Blockchain in Go. Part 2: Proof-of-Work
1、介紹
在上一篇文章,我們構(gòu)建了一個非常簡單的數(shù)據(jù)結(jié)構(gòu),這是區(qū)塊鏈數(shù)據(jù)庫的本質(zhì)。我們可以通過它們之間的鏈式關(guān)系為它添加區(qū)塊:每個區(qū)塊和前一個區(qū)塊相鏈接。哎,我們實現(xiàn)的區(qū)塊鏈有一個嚴重的缺陷:添加區(qū)塊到鏈上非常簡單而且成本很低。區(qū)塊鏈和比特幣的關(guān)鍵之一在于添加新的區(qū)塊是一項非常困難的工作。今天,我們要解決這個缺陷。
2、工作量證明
區(qū)塊鏈的一個宗旨是,人們必須執(zhí)行一些困難的工作才能將數(shù)據(jù)放入到區(qū)塊鏈中。正是這項困難的工作才使得區(qū)塊鏈更加安全和一致。此外,這項困難的工作有相應(yīng)的獎勵(這是人們?nèi)绾瓮ㄟ^挖礦獲得幣)。
這種機制與現(xiàn)實生活中的機制非常相似:必須努力工作以獲得報酬并維持??生活。在區(qū)塊鏈中,網(wǎng)絡(luò)的一些參與者(礦工)負責維護網(wǎng)絡(luò),為其添加新的區(qū)塊,并為這份工作獲得相應(yīng)的獎勵。他們工作的結(jié)果是,一個區(qū)塊以一種安全的方式加入到區(qū)塊鏈中,從而保持整個區(qū)塊鏈數(shù)據(jù)庫的穩(wěn)定性。值得注意的是,完成這項工作的人必須證明這一點。
這整個“努力工作和證明”的機制被稱為工作量證明。這很難,因為它需要大量的計算能力:即使是高性能的計算機也無法快速完成。此外,這項工作的難度不斷增加,以保持新的區(qū)塊在每小時6個區(qū)塊的速率。在比特幣中,這種工作的目標是為一個區(qū)塊找到一個符合要求的哈希。這就是這個哈希作為證明。因此,找到一個證明就是實際的工作。
最后要注意的是。工作量證明算法必須滿足這樣一項要求:完成工作很難,但驗證很容易。證明通常交給其他人,所以對他們來說,不需要太多時間來驗證它。
3、哈希
在這一段中,我們將討論哈希。如果您熟悉這個概念,您可以跳過這個部分。
哈希計算是指一個獲得指定數(shù)據(jù)的哈希值的過程。哈希值,是它所計算數(shù)據(jù)的唯一表示。哈希函數(shù)是一種可以獲取任意大小數(shù)據(jù)并生成固定長度哈希值的函數(shù)。以下是哈希的一些關(guān)鍵特性:
1.原始數(shù)據(jù)無法從哈希值中恢復(fù)。因此,哈希不是加密。
2.特定的數(shù)據(jù)只能有一個哈希值,而且哈希值是唯一的。
3.更改輸入數(shù)據(jù)中的一個字節(jié)將導(dǎo)致完全不同的哈希值。

哈希函數(shù)廣泛用于檢查數(shù)據(jù)的一致性。除軟件包外,某些軟件提供商還發(fā)布校驗和。下載文件后,您可以將其提供給哈希函數(shù),并將生成的哈希值與軟件開發(fā)人員提供的哈希值進行比較。
4、Hashcash
比特幣使用 Hashcash ,這是一種最初用于防止電子郵件垃圾郵件的工作量證明算法。它可以分成以下幾個步驟:
1.拿一些公開的數(shù)據(jù)(如電子郵件,它是接收者的電子郵件地址;比特幣的情況下,它是區(qū)塊頭)。
2.給它添加一個計數(shù)器。計數(shù)器從0開始。
3.獲取 data + counter 組合的哈希。
4.檢查哈希是否符合某些要求。
- 如果符合,則完成
- 如果不符合,增加計數(shù)器,重復(fù)步驟3和4
因此,這是一個暴力算法:改變計數(shù)器,計算一個新的哈希值,檢查它,增加計數(shù)器,計算哈希值等。這就是為什么它的計算成本很高。
現(xiàn)在我們來看看哈希必須滿足的要求。在最初的Hashcash實現(xiàn)中,需求看起來是“哈希值的前20位必須為零”。在比特幣中,需求會隨時調(diào)整,因為在設(shè)計上,盡管計算能力隨著時間的推移而增加,越來越多的礦工加入到網(wǎng)絡(luò)中,但必須每隔10分鐘生成一個區(qū)塊。
為了演示這種算法,我從前面的例子("I like donuts")中獲取數(shù)據(jù),并找到一個前3個字節(jié)為0的哈希。

ca07ca 是計數(shù)器的十六進制值,即十進制系統(tǒng)中的13240266。
5、實現(xiàn)
好了,我們已經(jīng)完成了理論,讓我們編寫代碼!首先,我們來定義挖礦的難度:
const targetBits = 24
在比特幣中,"target bits"是在每個被挖出來的區(qū)塊頭上存儲的困難度。目前我們不會實現(xiàn)目標調(diào)整算法,因此我們可以將難度定義為全局常量。
24是一個任意數(shù)字,我們的目標是在內(nèi)存中占用少于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
}
這里創(chuàng)建 ProofOfWork 一個保存指向區(qū)塊的指針和指向目標的指針的結(jié)構(gòu)。"target"是前一段描述的要求的另一個名稱。我們使用一個大整數(shù),因為我們將哈希與目標進行比較:我們將哈希轉(zhuǎn)換為大整數(shù),并檢查它是否小于目標。
在 NewProofOfWork 函數(shù)中,我們 big.Int 用值1初始化a,并將它左移位 256 - targetBits 。 256 是以位為單位的SHA-256哈希長度,而且是我們要使用的SHA-256哈希算法。targetis 的十六進制表示是:
0x10000000000000000000000000000000000000000000000000000000000
它在內(nèi)存中占用29個字節(jié)。下面是它與前面例子中的哈希值的x形式化比較:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
第一個哈希(根據(jù)"I like donuts"計算)大于目標,因此它不是有效的工作證明。第二個哈希(根據(jù)"I like donutsca07ca"計算)小于目標,因此這是一個有效的證明。
您可以將目標視為范圍的上限:如果數(shù)字(哈希)低于邊界,則該數(shù)字有效,反之亦然。降低邊界將導(dǎo)致更少的有效數(shù)字,因此,找到一個有效數(shù)字需要更困難的工作。
現(xiàn)在,我們需要數(shù)據(jù)來哈希。讓我們來準備一下:
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
}
這件事很簡單:我們只是將區(qū)塊字段與目標和隨機數(shù)nonce合并。 這里的 nonce 是來自上面Hashcash描述的計數(shù)器,這是一個密碼學術(shù)語。
好了,所有的準備工作都完成了,我們來實現(xiàn)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)
fmt.Printf("\r%x", hash)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}
首先,我們初始化變量:hashInt 是 hash 的整型表示; nonce 是計數(shù)器。接下來,我們運行一個"無限"循環(huán):它受限于 maxNonce , 等于 math.MaxInt64 ;這是為了避免 nonce 可能出現(xiàn)的溢出。盡管我們的PoW實現(xiàn)的難度太低而不足以使計數(shù)器溢出,但為了以防萬一,進行此項檢查仍然更好。
在循環(huán)中我們:
1.準備數(shù)據(jù)。
2.用SHA-256計算哈希值。
3.將哈希轉(zhuǎn)換為一個大整數(shù)。
4.將這個大整數(shù)與目標進行比較。
和前面解釋的一樣簡單?,F(xiàn)在我們可以刪掉 Block 里的 SetHash 函數(shù),然后修改 NewBlock 函數(shù):
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 去驗證證明?,F(xiàn)在 Block 看起來像是這樣:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
好的!讓我們運行該程序,看看是否一切正常。
Mining the block containing "Genesis Block"
00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Mining the block containing "Send 1 BTC to Ivan"
00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Mining the block containing "Send 2 more BTC to Ivan"
000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe
Prev. hash:
Data: Genesis Block
Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Data: Send 1 BTC to Ivan
Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Data: Send 2 more BTC to Ivan
Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe
歐耶!您可以看到每個哈希都是以3個字節(jié)的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
}
這就是我們需要保存的隨機數(shù)的地方。
讓我們再次檢查一切是否正常:
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 Block
Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
PoW: true
Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
Data: Send 1 BTC to Ivan
Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
PoW: true
Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
Data: Send 2 more BTC to Ivan
Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a
PoW: true
6、結(jié)論
我們的區(qū)塊鏈離真實的架構(gòu)更近了一步:現(xiàn)在添加區(qū)塊需要困難的工作,這就讓挖坑成為了可能。但它仍然缺乏一些關(guān)鍵特征:區(qū)塊鏈數(shù)據(jù)庫不是持久的,沒有錢包,地址,交易,并且沒有共識機制。所有這些我們將在未來的文章中實現(xiàn),而現(xiàn)在,快樂的挖掘!
鏈接:
1.完整的源代碼
2.區(qū)塊鏈哈希算法
3.Proof of work(工作量證明)
4.Hashcash
由于水平有限,翻譯質(zhì)量不太好,歡迎大家拍磚。