1. 程式人生 > >go語言實現最小區塊鏈教程2-工作證明機制

go語言實現最小區塊鏈教程2-工作證明機制

1 介紹

前面一篇文章當中我們構建了一個簡單的但卻是區塊鏈資料庫的核心的資料結構。同時,我們也實現了向該資料庫當中新增鏈式關係(chain-like relation)區塊的方法:每一個區塊都連結到它的前一個區塊。令人遺憾的是,我們所實現的區塊鏈有一個致命缺陷:新增新的區塊太容易,成本也太低。區塊和比特幣的重要基本特徵之一就是新增新的區塊是一項非常難的工作。今天我們將處理完善這個缺陷。

2 工作證明(Proof-of-Work)

區塊鏈的重要設想就是如果你要往裡面新增新的區塊就要完成一些艱難的工作。而正是這種機制確保了區塊鏈的安全和資料的一致性。同時,給這些艱難的工作適當的獎勵(這也是人們挖礦獲取比特幣的機制)。

這種機制與現實非常類似:一個人必須通過努力工作獲得回報以維持生計。在區塊鏈當中,網路上的參與者(礦工)的工作維持網路的正常執行,向區塊鏈中加入新的區塊,並因為他們的努力工作而獲得回報。他們的工作結果是將一個個區塊以安全的方式連成一個完整的區塊鏈,這也維護了整個區塊鏈資料庫的穩定性。更有價值的是,誰完成了工作必須進行自我證明。

這一整個的“努力工作並證明”的機制被稱為“工作證明”(PoW)。它難在需要大量的計算資源:即便是高效能的計算機,也無法快速完成工作。甚至,為了保證新的區塊增加速度維持在6個每小時,這個計算工作會越來越繁重。在比特幣當中,計算工作的目的是為了給區塊找一個匹配的並滿足一些特定要求的雜湊值。同時這個雜湊值也為工作服務。因此,實際的工作就是尋找證明。

最後一點需要注意的,PoW演算法必須滿足一項要求:雖然計算很困難,但是工作證明的驗證要非常容易。因為證明通常會傳給網路上的其他參與者進行,不應該消耗他們的太多時間了驗證這個證明。

3 雜湊計算(hashing)

在這一段當中,我們將討論下雜湊值及其計算。如果你對這一概念已經熟悉,可以跳過這部分內容。

雜湊計算是取得特定資料對應的雜湊值的過程。對於計算出來的雜湊值可以作為相應資料的特定代表。雜湊函式是針對任意大小的資料產生固定長度的雜湊值。雜湊的主要特徵如下:

  1. 元資料無法通過雜湊恢復。這樣,雜湊本身並不是加密的過程。
  2. 特定資料只能有唯一的雜湊值,雜湊值是獨一無二的。
  3. 即便只是修改輸入資料的一個位元組,也會導致完全不同的雜湊值。

雜湊函式被廣泛應用於檢驗資料的一致性。一些軟體提供商除了軟體包以外會額外發布軟體包對應的雜湊檢驗值。在你下載了軟體包以後可以將其代入一個雜湊函式看生成的雜湊值與軟體商所提供的是否一致。

在區塊鏈當中,雜湊過程被用於確保一個區塊的一致性。雜湊演算法的輸入資料包含前一個區塊的雜湊值,使得修改區塊鏈當中的區塊變得不太可能(至少,非常困難),因為修改便意味著你必須計算該區塊以及其之後所有區塊的雜湊值,而這個計算工作量是非常之大的。

4 Hashcash演算法

關於雜湊值的計算,比特幣採用Hashcash演算法,一種最早用於垃圾郵件過濾的帶PoW機制的演算法。它可以分解為以下的幾個步驟:

  1. 採用一些公開資料(在郵件過濾當中,比如接收者的郵箱地址;在比特幣當中,區塊頭部資料)
  2. 給它加一個計數器(counter)。計數器從0開始計數
  3. 將公開資料和計數器組合在一起(Data + counter),並獲取組合資料的雜湊值
  4. 檢驗獲得的雜湊值是否滿足特定的要求
  • 如果滿足,則計算結束
  • 如果不滿足,計數器加1並重復步驟3、4

顯然,這是一個暴力求解演算法:改變計數器計算新的雜湊值,檢驗,遞增計數器,再計算一個雜湊值,周而復始。這是其計算開銷高昂的原因所在。

現在我們從頭細看一個雜湊值需要滿足的具體要求。在Hashcash演算法的原始實現當中,對雜湊值的要求是“前20位必須為0”。在比特幣當中,要求隨時間變化有所調整,因為,根據設計,必須每10分鐘產生一個區塊,不管算力如何增加並有越來越多的礦工加入網路當中。

為了證明這個演算法,我以前面例子當中的資料(“I like donuts”)為例併產生前面三個位元組為0開頭的雜湊值:

ca07ca是計數器的十六進位制數,對應的十進位制數為13240266。

5 實現(Implementation)

好了,理論部門已經清晰明瞭,讓我們開始寫程式碼吧。首先,讓我們來設定下挖礦(區塊產生)的難度:

const targetBits = 24

在比特幣當中,“目標位數”(targetBits)是儲存在區塊頭部資料用以指示挖礦難度的指標。目前,我們並不打算實現難度可調的演算法。因此,我們可以將難度係數定義為一個全域性常量。

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))

	pow := &ProofOfWork{b, target}

	return pow
}

上述程式碼建立了 ProofOfWork 結構體來儲存一個區塊的指標和一個目標(target)的指標。這裡的“target”與我們之前討論的對雜湊值的要求等同。我們之所以用Big整型來定義“target”在於我們將雜湊值與目標比較的方式。我們將一個雜湊值轉換為一個Big整型然後檢驗其是否小於目標值。

然後在NewProofOfWork函式當中,我們初始化了一個Big整型資料為1並將其賦值給target隨後將其左移(二進位制的位操作)(256-targetBits)位。256是SHA-256雜湊值的總體位數,在本文當中targetBits是24,因此這裡總共左移了232位。後面我們也將採用SHA-256演算法來產生雜湊值。計算後的target的十六進位制表示如下:

0x10000000000000000000000000000000000000000000000000000000000

在記憶體當中佔據29個位元組空間,下面是將其與我們之前例子中產生的雜湊值的直觀比較:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一個雜湊(基於“I like donuts”計算)比target大,不是一個有效的工作證明。第二個雜湊(基於“I like donutsca07ca”)比target小,是一個有效的證明。

你可以將target理解成一個範圍的上邊界:假如一個數(一個雜湊值)比這個邊界小,有效,反之,則無效。減小邊界的數值,會導致更少的有效數字的個數,這樣得到一個有效雜湊值的難度將加大。

現在,我們來準備需要計算雜湊值的資料。

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
}

上述的 prepareData 方法(因為在prepareData之前有ProofOfWork的結構體宣告,這樣的一種特殊的函式在Go語言當中叫做method)比較簡單明瞭:我們通過呼叫bytes包的Join函式將區塊資訊與targetBits(在比特幣當中,難度係數也是屬於區塊頭部資料,這裡也把它當做公開資料的一部分)以及nonce(臨時值)相合並。nonce變數在這裡就是前面Hashcash演算法中描述的counter(計數器),它是一條加密後的資料。

OK,所有的準備已經就緒,來讓我們實現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 是計數器。接下來我們開始一個迴圈:迴圈次數由 maxNonce 來控制,maxNonce等同於math.MaxIn64;這樣是為了避免 nonce 的溢位。雖然我們所設定的難度想溢位還是比較困難的,以防萬一,最好還是這樣設定一下。

在這個迴圈體內,我們主要做了以下幾件事情:

  1. 準備資料
  2. 用SHA-256演算法計算該資料的雜湊值
  3. 將雜湊值轉換成Big整型資料
  4. 將轉換後的雜湊值與target進行比較

像之前解釋的一樣簡單。現在我們可以刪除 Block 結構體的 SetHash 方法然後修改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
}

在此你可以看到 nonce 儲存為 Block 的一部分。這是很有必要的,因為 nonce 要用於工作證明的驗證。同時 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

(^-^)V 現在可以可以看到每一個雜湊值以三個位元組的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
}

這裡也是我們需要將 nonce 儲存下來的原因。

讓我們再來測試一下一切是否正常:

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 結論

Our blockchain is a step closer to its actual architecture: adding blocks now requires hard work, thus mining is possible. But it still lacks some crucial features: the blockchain database is not persistent, there are no wallets, addresses, transactions, and there’s no consensus mechanism. All these things we’ll implement in future articles, and for now, happy mining!

我們的區塊鏈離它的實際架構又近了一步:往區塊鏈裡面增加區塊需要複雜的計算工作,這樣挖礦變得可能。然後它依然缺少一些關鍵的特性:區塊鏈資料庫無法持續存在,程式完成後資料就丟失了,也沒有錢包、地址、交易記錄、共識機制(consensus mechanism)。所有這些特性將會在後面的文章中實現,然後現在,先讓我們快樂的挖礦吧!

7 連結

相關推薦

go語言實現區塊教程2-工作證明機制

1 介紹在前面一篇文章當中我們構建了一個簡單的但卻是區塊鏈資料庫的核心的資料結構。同時,我們也實現了向該資料庫當中新增鏈式關係(chain-like relation)區塊的方法:每一個區塊都連結到它的前一個區塊。令人遺憾的是,我們所實現的區塊鏈有一個致命缺陷:新增新的區塊太

go語言實現區塊教程7-網路

1 介紹 Introduction到目前為止,我們構建了一個含有以下特徵的區塊鏈:匿名、安全、以及隨機產生地址;區塊鏈資料儲存;PoW系統;可靠的交易記錄儲存方式。這些特徵都非常關鍵,但是這還不夠。能夠讓這些特徵昇華的,並且讓加密貨幣變得可能的,是網路(network)。這樣

go語言實現區塊教程1-基礎原型

Go語言是由google開發並於2009年釋出的一種靜態、強型別、編譯型、併發型,並具有垃圾回收(GC)功能的程式語言,特別適用於分散式網路系統開發,而區塊鏈(blockchain)本質上是一本在網路上分佈儲存的賬本,這兩者具有天然的匹配性,目前火熱的Ethereum Pro

用C語言實現二乘法演算法

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

matlab和C語言實現二乘法

參考:https://blog.csdn.net/zengxiantao1994/article/details/70210662 Matlab程式碼: N = 8; x = [1 2 3 4 5 6 7 8 ]; y = [67 84 102 120 137 1

Go 構建一個區塊 -- Part 2: 工作量證明

翻譯的系列文章我已經放到了 GitHub 上:blockchain-tutorial,後續如有更新都會在 GitHub 上,可能就不在這裡同步了。如果想直接執行程式碼,也可以 clone GitHub 上的教程倉庫,進入 src 目錄執行 make 即可。

區塊Go語言實現】第一部分:區塊基本原型

ont 構建 獲得 列表 append 檢查 世紀 正常 私有 0x00 介紹 區塊鏈(Blockchain)是21世紀最具革命性的技術之一,目前它仍處於逐漸成熟階段,且其發展潛力尚未被完全意識到。從本質上講,區塊鏈只是一種記錄的分布式數據庫。但它之所以獨特,是因為它並

Go語言實現區塊與加密貨幣-Part3(交易優化,單機模擬多節點通訊)

交易(二) 在這個系列文章的一開始,我們就提到了,區塊鏈是一個分散式資料庫。不過在之前的文章中,我們選擇性地跳過了“分散式”這個部分,而是將注意力都放到了“資料庫”部分。到目前為止,我們幾乎已經實現了一個區塊鏈資料庫的所有元素。今天,我們將會分析之前跳過的一些機制。而在本篇文章中,我們將

Go語言實現區塊與加密貨幣-Part2(交易與地址,餘額翻倍漏洞)

準備工作: 安裝依賴包:$ go get golang.org/x/crypto/ripemd160 安裝失敗請檢視:https://blog.csdn.net/ak47000gb/article/details/79561358 交易 交易(transaction)是比特幣

區塊開發零基礎必備技能之GO語言從入門到高階視訊教程

一、    以太坊和區塊鏈的關係      從區塊鏈歷史上來說,先誕生了比特幣,當時並沒有區塊鏈這個技術和名詞,然後業界從比特幣中提取了技術架構和體系,稱之為區塊鏈技術。從比特幣提取的區塊鏈技術稱之為區塊鏈1.0時代,那個時候的應用主要以電子貨幣和

Go語言實現區塊與加密貨幣(用3臺計算機通過區域網模擬3個節點通訊)

本文要實現3臺計算機通過區域網模擬3個節點通訊。(windows環境下) 這是在上一步的基礎上修改而來的。 三個節點所扮演的角色分別是:中心節點、錢包節點、礦工節點 首先3臺計算機要通過區域網連線在一起(可以通過wifi等方式)。 注意要把各自的防火牆關掉。 可以用ping命令測試,互相間的

Go語言實現區塊與加密貨幣(用3臺計算機通過區域網模擬3節點通訊)

本文要實現3臺計算機通過區域網模擬3節點通訊。(windows環境下) 這是在上一步的基礎上修改而來的。 首先3臺計算機要通過區域網連線在一起(可以通過wifi等方式)。 注意要把各自的防火牆關掉。 可以用ping命令測試,互相間的通訊是否正常。 一切正常後,需

區塊背後的資訊保安(3)橢圓曲線加解密及簽名演算法的技術原理及其Go語言實現

# 橢圓曲線加解密及簽名演算法的技術原理及其Go語言實現橢圓曲線加密演算法,即:Elliptic Curve Cryptography,簡稱ECC,是基於橢圓曲線數學理論實現的一種非對稱加密演算法。相比RSA,ECC優勢是可以使用更短的金鑰,來實現與RSA相當或更高的安全。據研究,160位ECC加密安全性相當

區塊背後的資訊保安(1)AES加密演算法原理及其GO語言實現

# AES加密演算法原理及其GO語言實現AES是作為DES的替代標準出現的,全稱Advanced Encryption Standard,即:高階加密標準。AES加密演算法,經歷了公開的選拔,最終2000年,由比利時密碼學家Joan Daemen和Vincent Rijmen設計的Rijndael演算法被選中

200行Go代碼實現自己的區塊——區塊生成與網絡通信

type hash lazy avi present lte () cti 裏的 在第一篇文章[1]中,我們向大家展示了如何通過精煉的Go代碼實現一個簡單的區塊鏈。如何計算每個塊的 Hash 值,如何驗證塊數據,如何讓塊鏈接起來等等,但是所有這些都是跑在一個節點上的。文章

區塊教程Fabric1.0源代碼分析語言平臺

pro tor append config 長度 emp time delet let Fabric 1.0源代碼筆記 之 Chaincode(鏈碼) #platforms(鏈碼語言平臺) 1、platforms概述 platforms代碼集中在core/chaincode

區塊教程Fabric1.0原始碼分析Peer peer channel命令及子命令實現

  區塊鏈教程Fabric1.0原始碼分析Peer peer channel命令及子命令實現,2018年下半年,區塊鏈行業正逐漸褪去發展之初的浮躁、迴歸理性,表面上看相關人才需求與身價似乎正在回落。但事實上,正是初期泡沫的漸退,讓人們更多的關注點放在了區塊鏈真正的技術之上。 Fabric1.0原始碼筆記之P

區塊教程Fabric1.0源代碼分析Peer peer channel命令及子命令實現

erro urn nonce conn end common entity 而後 back 區塊鏈教程Fabric1.0源代碼分析Peer peer channel命令及子命令實現,2018年下半年,區塊鏈行業正逐漸褪去發展之初的浮躁、回歸理性,表面上看相關人才需求與身價似

區塊教程Fabric1.0源代碼分析Peer peer chaincode命令及子命令實現

重新 實現 nsa lse error pro all env mman 區塊鏈教程Fabric1.0源代碼分析Peer peer chaincode命令及子命令實現,2018年下半年,區塊鏈行業正逐漸褪去發展之初的浮躁、回歸理性,表面上看相關人才需求與身價似乎正在回落。但

C語言實現簡單的2048遊戲

網上解釋很多了,直接上程式碼吧,這個功能很簡單,易於學習,後期有時間會完善功能 #include<stdio.h> #include<stdlib.h> #include<string.h> #define Key_Up 0x4800