介紹
在[前面的文章中]我們構(gòu)建了一個(gè)非常簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),這是區(qū)塊鏈數(shù)據(jù)庫(kù)的本質(zhì)。 我們可以通過(guò)它們之間的鏈?zhǔn)疥P(guān)系為它添加塊:每個(gè)塊鏈接到前一個(gè)塊。 不過(guò),我們的區(qū)塊鏈實(shí)施存在一個(gè)重大缺陷:向鏈條添加區(qū)塊非常簡(jiǎn)單且便宜。 區(qū)塊鏈和比特幣的關(guān)鍵之一在于添加新塊是一項(xiàng)艱巨的工作。 今天我們要解決這個(gè)缺陷。
驗(yàn)證的工作
區(qū)塊鏈的一個(gè)關(guān)鍵想法是,人們必須執(zhí)行一些艱苦的工作才能將數(shù)據(jù)放入其中。 正是這項(xiàng)艱巨的工作才使得區(qū)塊鏈更加安全和一致。 此外,這項(xiàng)艱苦的工作還有獎(jiǎng)勵(lì)(這是人們?nèi)绾潍@得用于采礦的硬幣)。
這種機(jī)制與現(xiàn)實(shí)生活中的機(jī)制非常相似:必須努力工作以獲得回報(bào)并維持??生活。 在區(qū)塊鏈中,網(wǎng)絡(luò)的一些參與者(礦工)努力維持網(wǎng)絡(luò),為其添加新塊,并為他們的工作獲得獎(jiǎng)勵(lì)。 作為他們工作的結(jié)果,一個(gè)區(qū)塊以一種安全的方式整合到區(qū)塊鏈中,從而保持了整個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的穩(wěn)定性。 值得注意的是,完成這項(xiàng)工作的人必須證明這一點(diǎn)。
這整個(gè)“努力工作和證明”機(jī)制被稱為工作證明。 這很難,因?yàn)樗枰罅康挠?jì)算能力:即使是高性能的計(jì)算機(jī)也無(wú)法快速完成。 此外,這項(xiàng)工作的難度不斷增加,以保持新的區(qū)塊率在每小時(shí)6塊左右。 在比特幣中,這種工作的目標(biāo)是為一個(gè)塊找到一個(gè)符合要求的散列。 這就是這個(gè)散列作為證明。 因此,找到一個(gè)證明就是實(shí)際的工作。
最后要注意的是。 工作量證明算法必須滿足一項(xiàng)要求:做這項(xiàng)工作很難,但驗(yàn)證證明是容易的。 證明通常交給其他人,所以對(duì)他們來(lái)說(shuō),不需要太多時(shí)間來(lái)驗(yàn)證它。
哈希(Hashing)
在這一段中,我們將討論哈希。 如果你熟悉這個(gè)概念,你可以跳過(guò)這個(gè)部分。
散列是為指定數(shù)據(jù)獲取散列的過(guò)程。 哈希是它計(jì)算的數(shù)據(jù)的唯一表示。 散列函數(shù)是一種可以獲取任意大小數(shù)據(jù)并生成固定大小散列的函數(shù)。 以下是哈希的一些關(guān)鍵特性:
原始數(shù)據(jù)無(wú)法從散列中恢復(fù)。 因此,哈希不是加密。
某些數(shù)據(jù)只能有一個(gè)散列,散列是唯一的。
更改輸入數(shù)據(jù)中的一個(gè)字節(jié)將導(dǎo)致完全不同的散列。

散列函數(shù)廣泛用于檢查數(shù)據(jù)的一致性。 除軟件包外,某些軟件提供商還發(fā)布校驗(yàn)和。 下載文件后,您可以將其提供給哈希函數(shù),并將生成的哈希與軟件開(kāi)發(fā)人員提供的哈希進(jìn)行比較。
在區(qū)塊鏈中,散列用于保證塊的一致性。 散列算法的輸入數(shù)據(jù)包含前一個(gè)塊的散列,因此不可能(或者至少非常困難)修改鏈中的塊:必須重新計(jì)算其后的所有塊的散列和散列。
Hashcash
比特幣使用[Hashcash] ,這是一種最初用于防止電子郵件垃圾郵件的Proof-of-Work算法。它可以分成以下幾個(gè)步驟:
- 拿一些公開(kāi)的數(shù)據(jù)(如電子郵件,它是接收者的電子郵件地址;比特幣的情況下,它是塊頭)。
- 給它添加一個(gè)計(jì)數(shù)器。 計(jì)數(shù)器從0開(kāi)始。
- 獲取
data + counter組合的散列。 - 檢查哈希是否符合某些要求。
- 如果是這樣,你就完成了。
- 如果沒(méi)有,請(qǐng)?jiān)黾佑?jì)數(shù)器并重復(fù)步驟3和4。
因此,這是一個(gè)蠻力算法:你改變計(jì)數(shù)器,計(jì)算一個(gè)新的哈希,檢查它,增加計(jì)數(shù)器,計(jì)算哈希等。這就是為什么它的計(jì)算成本很高。
現(xiàn)在我們來(lái)看看哈希必須滿足的要求。 在最初的Hashcash實(shí)現(xiàn)中,需求聽(tīng)起來(lái)像“哈希的前20位必須為零”。 在比特幣中,需求會(huì)隨時(shí)進(jìn)行調(diào)整,因?yàn)樵谠O(shè)計(jì)上,盡管計(jì)算能力隨著時(shí)間的推移而增加,越來(lái)越多的礦工加入網(wǎng)絡(luò),但必須每10分鐘創(chuàng)建一個(gè)塊。
為了演示這種算法,我從前面的例子(“我喜歡甜甜圈”)中獲取數(shù)據(jù),并找到一個(gè)以3個(gè)零字節(jié)開(kāi)頭的散列:

ca07ca 是計(jì)數(shù)器的十六進(jìn)制值,即十進(jìn)制系統(tǒng)中的13240266。
履行
好的,我們已經(jīng)完成了理論,讓我們編寫代碼! 首先,我們來(lái)定義挖掘的難度:
const targetBits = 24
在比特幣中,“目標(biāo)比特”是存儲(chǔ)塊開(kāi)采難度的塊頭。 目前我們不會(huì)執(zhí)行目標(biāo)調(diào)整算法,因此我們可以將難度定義為全局常量。
24是一個(gè)任意數(shù)字,我們的目標(biāo)是在內(nèi)存中占用少于256位的目標(biāo)。 我們希望差異足夠大,但不要太大,因?yàn)椴町愒酱?,找到合適的散列越困難。
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結(jié)構(gòu),它包含一個(gè)指向塊的指針和一個(gè)指向目標(biāo)的指針。 “目標(biāo)”是前一段描述的要求的另一個(gè)名稱。 我們使用一個(gè)大整數(shù),因?yàn)槲覀儗⒐Ec目標(biāo)進(jìn)行比較:我們將哈希轉(zhuǎn)換為大整數(shù),并檢查它是否小于目標(biāo)。
在NewProofOfWork函數(shù)中,我們初始化一個(gè)值為1的big.Int并將其左移256 - targetBits位。 256是以位為單位的SHA-256哈希的長(zhǎng)度,它是我們要使用的SHA-256哈希算法。 target的十六進(jìn)制表示是:
0x10000000000000000000000000000000000000000000000000000000000
它在內(nèi)存中占用29個(gè)字節(jié)。 下面是它與前面例子中的哈希值的視覺(jué)比較:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
第一個(gè)散列(根據(jù)“我喜歡甜甜圈”計(jì)算)大于目標(biāo),因此它不是有效的工作證明。 第二個(gè)散列(以“我喜歡donutsca07ca”計(jì)算)小于目標(biāo),因此這是一個(gè)有效的證明。
您可以將目標(biāo)視為范圍的上限:如果數(shù)字(哈希)低于邊界,則該數(shù)字有效,反之亦然。 降低邊界將導(dǎo)致更少的有效數(shù)字,因此,找到一個(gè)有效數(shù)字需要更困難的工作。
現(xiàn)在,我們需要數(shù)據(jù)來(lái)散列。 讓我們來(lái)準(zhǔn)備它:
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
}
這件事很簡(jiǎn)單:我們只是將塊字段與目標(biāo)和隨機(jī)數(shù)合并。 nonce這里是來(lái)自上面Hashcash描述的計(jì)數(shù)器,這是一個(gè)加密術(shù)語(yǔ)。
好的,所有的準(zhǔn)備工作都完成了,我們來(lái)實(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的整數(shù)表示; nonce是計(jì)數(shù)器。 接下來(lái),我們運(yùn)行一個(gè)“無(wú)限”循環(huán):它受限于maxNonce ,它等于math.MaxInt64 ; 這樣做是為了避免nonce可能溢出。 盡管我們的PoW實(shí)現(xiàn)的難度太低而不能使計(jì)數(shù)器溢出,但為了以防萬(wàn)一,進(jìn)行此項(xiàng)檢查仍然更好。
在循環(huán)中我們:
準(zhǔn)備數(shù)據(jù)。
用SHA-256對(duì)其進(jìn)行散列。
將散列轉(zhuǎn)換為一個(gè)大整數(shù)。
將整數(shù)與目標(biāo)進(jìn)行比較。
和前面解釋的一樣簡(jiǎn)單。 現(xiàn)在我們可以刪除Block的SetHash方法并修改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屬性。 這是必要的,因?yàn)閚once需要驗(yàn)證證據(jù)。 Block結(jié)構(gòu)現(xiàn)在看起來(lái)如此:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
好的! 讓我們運(yùn)行該程序,看看是否一切正常。
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
好極了! 您可以看到每個(gè)散列現(xiàn)在都以三個(gè)零字節(jié)開(kāi)始,并且需要一些時(shí)間來(lái)獲取這些散列。
還有一件事要做:讓我們可以驗(yàn)證作品的證明。
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
}
這就是我們需要保存的隨機(jī)數(shù)的地方。
讓我們?cè)俅螜z查一切正常:
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
結(jié)論
我們的區(qū)塊鏈更接近其實(shí)際架構(gòu):現(xiàn)在添加區(qū)塊需要努力工作,因此可以進(jìn)行挖掘。 但它仍然缺乏一些關(guān)鍵特征:區(qū)塊鏈數(shù)據(jù)庫(kù)不是持久性的,沒(méi)有錢包,地址,交易,并且沒(méi)有共識(shí)機(jī)制。 所有這些我們將在未來(lái)的文章中實(shí)現(xiàn),而現(xiàn)在,快樂(lè)的挖掘!