摘要:哈希函數(shù)被廣泛用于檢測(cè)數(shù)據(jù)的一致性。在區(qū)塊鏈中,哈希被用于保證一個(gè)塊的一致性。比特幣使用,一個(gè)最初用來防止垃圾郵件的工作量證明算法。下面是與前面例子哈希的形式化比較第一個(gè)哈希基于計(jì)算比目標(biāo)要大,因此它并不是一個(gè)有效的工作量證明。
翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉庫,進(jìn)入 src 目錄執(zhí)行 make 即可。
在前面一文中,我們構(gòu)造了一個(gè)非常簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)也是整個(gè)區(qū)塊鏈數(shù)據(jù)庫的核心。目前所完成的區(qū)塊鏈原型,已經(jīng)可以通過鏈?zhǔn)疥P(guān)系把區(qū)塊相互關(guān)聯(lián)起來:每個(gè)塊都被連接到前一個(gè)塊。
但是,我們實(shí)現(xiàn)的區(qū)塊鏈有一個(gè)巨大的缺點(diǎn):向鏈中加入?yún)^(qū)塊太容易和廉價(jià)了。而區(qū)塊鏈和比特幣的其中一個(gè)核心就是,要想加入新的區(qū)塊,必須先完成一些非常困難的工作。在本文,我們將會(huì)解決這個(gè)缺點(diǎn)。
工作量證明區(qū)塊鏈的一個(gè)關(guān)鍵點(diǎn)就是,一個(gè)人必須經(jīng)過一系列困難的工作,才能將數(shù)據(jù)放入到區(qū)塊鏈中。正是這種困難的工作,才使得區(qū)塊鏈?zhǔn)前踩鸵恢碌摹4送猓瓿蛇@個(gè)工作的人也會(huì)獲得獎(jiǎng)勵(lì)(這也就是通過挖礦獲得幣)。
這個(gè)機(jī)制與生活的一個(gè)現(xiàn)象非常類似:一個(gè)人必須通過努力工作,才能夠獲得回報(bào)或者獎(jiǎng)勵(lì),用以支撐他們的生活。在區(qū)塊鏈中,是通過網(wǎng)絡(luò)中的參與者(礦工)不斷的工作來支撐整個(gè)網(wǎng)絡(luò),也就是礦工不斷地向區(qū)塊鏈中加入新塊,然后獲得相應(yīng)的獎(jiǎng)勵(lì)。作為他們努力工作的結(jié)果,新生成的區(qū)塊就能夠被安全地被加入到區(qū)塊鏈中,這種機(jī)制維護(hù)了整個(gè)區(qū)塊鏈數(shù)據(jù)庫的穩(wěn)定性。值得注意的是,完成了這個(gè)工作的人必須要證明這一點(diǎn),他必須要證明確實(shí)是他完成了這些工作。
整個(gè) “努力工作并進(jìn)行證明” 的機(jī)制,就叫做工作量證明(proof-of-work)。要想完成工作非常地不容易,因?yàn)檫@需要大量的計(jì)算能力:即便是高性能計(jì)算機(jī),也無法在短時(shí)間內(nèi)快速完成。此外,這個(gè)工作的困難度會(huì)隨著時(shí)間不斷增長(zhǎng),以保持每個(gè)小時(shí)大概出 6 個(gè)新塊的速度。在比特幣中,這個(gè)工作的目的是為了找到一個(gè)塊的哈希,同時(shí)這個(gè)哈希滿足了一些必要條件。這個(gè)哈希,也就充當(dāng)了證明的角色。因此,尋求證明(尋找有效哈希),就是實(shí)際要做的事情。
哈希計(jì)算在本節(jié)中,我們會(huì)討論哈希計(jì)算。如果你已經(jīng)熟悉了這個(gè)概念,可以跳過這一節(jié)。
獲得指定數(shù)據(jù)的一個(gè)哈希值的過程,就叫做哈希計(jì)算。一個(gè)哈希,就是對(duì)所計(jì)算數(shù)據(jù)的一個(gè)唯一的表示。一個(gè)哈希函數(shù)輸入任意大小的數(shù)據(jù),輸出一個(gè)固定大小的哈希值。下面是哈希的幾個(gè)關(guān)鍵特性:
無法從一個(gè)哈希值恢復(fù)原始數(shù)據(jù)。也就是說,哈希并不是加密。
對(duì)于特定的數(shù)據(jù),只能有一個(gè)哈希,并且這個(gè)哈希是唯一的。
即使是僅僅改變輸入數(shù)據(jù)中的一個(gè)字節(jié),也會(huì)導(dǎo)致輸出一個(gè)完全不同的哈希。
哈希函數(shù)被廣泛用于檢測(cè)數(shù)據(jù)的一致性。一些軟件提供者除了提供軟件包以外,還會(huì)發(fā)布校驗(yàn)和。當(dāng)下載完一個(gè)文件以后,你可以用哈希函數(shù)對(duì)下載好的文件計(jì)算一個(gè)哈希,并與作者提供的哈希進(jìn)行比較,以此來保證文件下載的完整性。
在區(qū)塊鏈中,哈希被用于保證一個(gè)塊的一致性。哈希算法的輸入數(shù)據(jù)包含了前一個(gè)塊的哈希,因此使得不太可能(或者,至少很困難)去修改鏈中的一個(gè)塊:因?yàn)槿绻粋€(gè)人想要修改前面一個(gè)塊的哈希,那么他必須要重新計(jì)算這個(gè)塊以及后面所有塊的哈希。
Hashcash比特幣使用 Hashcash ,一個(gè)最初用來防止垃圾郵件的工作量證明算法。它可以被分解為以下步驟:
取一些公開的數(shù)據(jù)(比如,如果是 email 的話,它可以是接收者的郵件地址;在比特幣中,它是區(qū)塊頭)
給這個(gè)公開數(shù)據(jù)添加一個(gè)計(jì)數(shù)器。計(jì)數(shù)器默認(rèn)從 0 開始
將 data(數(shù)據(jù)) 和 counter(計(jì)數(shù)器) 組合到一起,獲得一個(gè)哈希
檢查哈希是否符合一定的條件:
如果符合條件,結(jié)束
如果不符合,增加計(jì)數(shù)器,重復(fù)步驟 3-4
因此,這是一個(gè)暴力算法:改變計(jì)數(shù)器,計(jì)算一個(gè)新的哈希,檢查,增加計(jì)數(shù)器,計(jì)算一個(gè)哈希,檢查,如此反復(fù)。這也是為什么說它是在計(jì)算上是非常昂貴的,因?yàn)檫@一步需要如此反復(fù)不斷地計(jì)算和檢查。
現(xiàn)在,讓我們來仔細(xì)看一下一個(gè)哈希要滿足的必要條件。在原始的 Hashcash 實(shí)現(xiàn)中,它的要求是 “一個(gè)哈希的前 20 位必須是 0”。在比特幣中,這個(gè)要求會(huì)隨著時(shí)間而不斷變化。因?yàn)榘凑赵O(shè)計(jì),必須保證每 10 分鐘生成一個(gè)塊,而不論計(jì)算能力會(huì)隨著時(shí)間增長(zhǎng),或者是會(huì)有越來越多的礦工進(jìn)入網(wǎng)絡(luò),所以需要?jiǎng)討B(tài)調(diào)整這個(gè)必要條件。
為了闡釋這一算法,我從前一個(gè)例子(“I like donuts”)中取得數(shù)據(jù),并且找到了一個(gè)前 3 個(gè)字節(jié)是全是 0 的哈希。
ca07ca 是計(jì)數(shù)器的 16 進(jìn)制值,十進(jìn)制的話是 13240266.
實(shí)現(xiàn)好了,完成了理論層面,來開始寫代碼吧!首先,定義挖礦的難度值:
const targetBits = 24
在比特幣中,當(dāng)一個(gè)塊被挖出來以后,“target bits” 代表了區(qū)塊頭里存儲(chǔ)的難度。這里的 24 指的是算出來的哈希前 24 位必須是 0,用 16 進(jìn)制表示化的話,就是前 6 位必須是 0,這一點(diǎn)可以在最后的輸出可以看出來。目前不會(huì)實(shí)現(xiàn)一個(gè)動(dòng)態(tài)調(diào)整目標(biāo)的算法,所以將難度定義為一個(gè)全局的常量即可。
24 其實(shí)是一個(gè)可以任意取的數(shù)字,目的是要有一個(gè)目標(biāo)(target)而已,這個(gè)目標(biāo)占據(jù)不到 256 位的內(nèi)存空間。同時(shí),我們想要有足夠的差異性,但是又不至于大的過分,因?yàn)椴町愋栽酱螅驮诫y找到一個(gè)合適的哈希。
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 }
這里,我們構(gòu)造了 ProofOfWork 結(jié)構(gòu),里面存儲(chǔ)了指向一個(gè)塊和一個(gè)目標(biāo)的指針。“目標(biāo)” ,也就是前一節(jié)中所描述的必要條件。這里使用了一個(gè) 大 整數(shù),我們將哈希與目標(biāo)進(jìn)行比較:先把一個(gè)哈希轉(zhuǎn)換成一個(gè)大整數(shù),然后檢測(cè)它是否小于目標(biāo)。
在 NewProofOfWork 函數(shù)中,我們將 big.Int 初始化為 1,然后左移 256 - targetBits 位。256 是一個(gè) SHA-256 哈希的位數(shù),我們將要使用的是 SHA-256 哈希算法。target(目標(biāo)) 的 16 進(jìn)制形式為:
0x10000000000000000000000000000000000000000000000000000000000
它在內(nèi)存上占據(jù)了 29 個(gè)字節(jié)。下面是與前面例子哈希的形式化比較:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 0000010000000000000000000000000000000000000000000000000000000000 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
第一個(gè)哈希(基于 “I like donuts” 計(jì)算)比目標(biāo)要大,因此它并不是一個(gè)有效的工作量證明。第二個(gè)哈希(基于 “I like donutsca07ca” 計(jì)算)比目標(biāo)要小,所以是一個(gè)有效的證明。
譯者注:評(píng)論有人提出上面的形式化比較有些“言不符實(shí)”,其實(shí)它應(yīng)該并非由 “I like donuts” 而來,但是原文作者表達(dá)的意思是沒問題的,可能是疏忽而已。下面是我做的一個(gè)小實(shí)驗(yàn):
package main import ( "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 ", sha256.Sum256(data1)) fmt.Printf("%64x ", target) fmt.Printf("%x ", sha256.Sum256(data2)) }
輸出:
你可以把目標(biāo)想象為一個(gè)范圍的上界:如果一個(gè)數(shù)(由哈希轉(zhuǎn)換而來)比上界要小,那么這是有效的,反之無效。因?yàn)橐蟊壬辖缫。詴?huì)導(dǎo)致更少的有效數(shù)字。因此,也就需要通過困難的工作(一系列反復(fù)的計(jì)算),才能找到一個(gè)有效的數(shù)字。
現(xiàn)在,我們需要有數(shù)據(jù)來進(jìn)行哈希,準(zhǔ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 }
這個(gè)部分比較直觀:只需要將 target ,nonce 與 Block 進(jìn)行合并。這里的 nonce ,就是上面 Hashcash 所提到的計(jì)數(shù)器,它是一個(gè)密碼學(xué)術(shù)語。
很好,到這里,所有的準(zhǔn)備工作就完成了,下面來實(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" ", 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(" %x", hash) break } else { nonce++ } } fmt.Print(" ") return nonce, hash[:] }
首先我們對(duì)變量進(jìn)行初始化:
HashInt 是 hash 的整形表示;
nonce 是計(jì)數(shù)器。
然后開始一個(gè) “無限” 循環(huán):maxNonce 對(duì)這個(gè)循環(huán)進(jìn)行了限制, 它等于 math.MaxInt64。這是為了避免 nonce 可能出現(xiàn)的溢出。盡管我們的 PoW 實(shí)現(xiàn)的難度太小了,以至于計(jì)數(shù)器其實(shí)不太可能會(huì)溢出,但最好還是以防萬一檢查一下。
在這個(gè)循環(huán)中,我們做的事情有:
準(zhǔn)備數(shù)據(jù)
用 SHA-256 對(duì)數(shù)據(jù)進(jìn)行哈希
將哈希轉(zhuǎn)換成一個(gè)大整數(shù)
將這個(gè)大整數(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 的一個(gè)屬性。這是十分有必要的,因?yàn)榇龝?huì)兒我們需要用 nonce 來對(duì)這個(gè)工作量進(jìn)行證明。Block 結(jié)構(gòu)現(xiàn)在看起來像是這樣:
type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int }
好了!現(xiàn)在讓我們來運(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è)哈希都是 3 個(gè)字節(jié)的 0 開始,并且獲得這些哈希需要花費(fèi)一些時(shí)間。
還剩下一件事情需要做,對(duì)工作量證明進(jìn)行驗(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 }
這里,就是我們就用到了上面保存的 nonce。
再來檢測(cè)一次是否正常工作:
func main() { ... for _, block := range bc.blocks { ... pow := NewProofOfWork(block) fmt.Printf("PoW: %s ", 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
從下圖可以看出,這次我們產(chǎn)生三個(gè)塊花費(fèi)了一分多鐘,比沒有工作量證明之前慢了很多(也就是成本高了很多):
總結(jié)我們的區(qū)塊鏈離真正的區(qū)塊鏈又進(jìn)了一步:現(xiàn)在需要經(jīng)過一些困難的工作才能加入新的塊,因此挖礦就有可能了。但是,它還缺少一些至關(guān)重要的特性:區(qū)塊鏈數(shù)據(jù)庫并不是持久化的,沒有錢包,地址,交易,也沒有共識(shí)機(jī)制。不過,所有的這些,我們都會(huì)在接下來的文章中實(shí)現(xiàn),現(xiàn)在,愉快地挖礦吧!
鏈接:
Full source codes
Blockchain hashing algorithm
Proof of work
Hashcash
本文源代碼:part_2
原文:
Building Blockchain in Go. Part 2: Proof-of-Work
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/23943.html
摘要:在區(qū)塊鏈中,存儲(chǔ)有效信息的是區(qū)塊。存儲(chǔ)的是前一個(gè)塊的哈希。正是由于這個(gè)特性,才使得區(qū)塊鏈?zhǔn)前踩摹_@樣的結(jié)構(gòu),能夠讓我們快速地獲取鏈上的最新塊,并且高效地通過哈希來檢索一個(gè)塊。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉...
摘要:到目前為止,我們幾乎已經(jīng)實(shí)現(xiàn)了一個(gè)區(qū)塊鏈數(shù)據(jù)庫的所有元素。使用根據(jù)在區(qū)塊鏈中找到一筆交易。是一個(gè)比特幣輕節(jié)點(diǎn),它不需要下載整個(gè)區(qū)塊鏈,也不需要驗(yàn)證區(qū)塊和交易。到目前為止,我們只是將一個(gè)塊里面的每筆交易哈希連接了起來,將在上面應(yīng)用了算法。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果...
摘要:盡管我們不會(huì)實(shí)現(xiàn)一個(gè)真實(shí)的網(wǎng)絡(luò),但是我們會(huì)實(shí)現(xiàn)一個(gè)真是,也是比特幣最常見最重要的用戶場(chǎng)景。不過,這并不是處于禮貌用于找到一個(gè)更長(zhǎng)的區(qū)塊鏈。意為給我看一下你有什么區(qū)塊在比特幣中,這會(huì)更加復(fù)雜。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...
摘要:引言到目前為止,我們已經(jīng)構(gòu)建了一個(gè)有工作量證明機(jī)制的區(qū)塊鏈。在今天的內(nèi)容中,我們會(huì)將區(qū)塊鏈持久化到一個(gè)數(shù)據(jù)庫中,然后會(huì)提供一個(gè)簡(jiǎn)單的命令行接口,用來完成一些與區(qū)塊鏈的交互操作。這同樣也意味著,一個(gè)也就是區(qū)塊鏈的一種標(biāo)識(shí)符。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼...
摘要:引言交易是比特幣的核心所在,而區(qū)塊鏈的唯一目的,也正是為了能夠安全可靠地存儲(chǔ)交易。比特幣使用了一個(gè)更加復(fù)雜的技術(shù)它將一個(gè)塊里面包含的所有交易表示為一個(gè),然后在工作量證明系統(tǒng)中使用樹的根哈希。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...
閱讀 2971·2021-11-08 13:20
閱讀 1038·2021-09-22 15:20
閱讀 668·2019-08-30 15:53
閱讀 1974·2019-08-30 15:43
閱讀 1287·2019-08-29 17:21
閱讀 545·2019-08-29 12:15
閱讀 2384·2019-08-28 17:51
閱讀 3151·2019-08-26 13:26