2.2 工做量證實

Proof-of-Work 工做量證實

區塊裏有一個很是關鍵的點,就是節點必須執行足夠多且困難的運算才能將數據新增在區塊中。這一困難的運算保證了區塊鏈安全、一致。而爲了獎勵這一運算,該節點會得到數字貨幣(如比特幣)的獎勵(從運算到收到獎勵的過程,也叫做挖礦)。 
這一機制和現實生活中也是類似的:人們辛苦工做獲取報酬來維持生活,在區塊鏈中,鏈絡中的參與者(好比礦工)辛苦運算來維繫這個區塊鏈網絡,不斷增長新的區塊到鏈絡中,而後獲取回報。正是由於這些運算,新的區塊基於安全的方式加到區塊鏈中,保證了區塊鏈數據庫的穩定。 
是否是發現了什麼問題呢?你們都在計算,憑什麼怎麼證實你作的運算就是對的,且是你的。 
努力工做並證實(do hard work and prove),這一機制被稱爲工做量證實。須要多努力呢,須要大量計算機資源,即便使用高速計算機也不能作得快多少。並且,隨着時間的推移,難度會愈來愈大,由於要保證每小時有6個區塊的誕生,越到後面,區塊愈來愈少,要保證這個速率,只能運算更多,提升難度。在比特幣中,運算的目標是計算出一串符合要求的hash值。而這個hash就是證實。因此說,找到證實(符合要求的hash值)纔是實際意義上的工做。 
工做量證實還有一個重要知識點。也即工做困難,而證實容易。由於若是你的工做困難,而證實也困難,那麼你的工做在圈子效率意義就不大,對於須要提供給別人證實的工做,別人證實起來越簡單就越好。算法

Hash 哈希

Hash運算是區塊鏈最主要使用的工做算法。哈希運算是指給特殊數據計算一串hash字符的過程。對於一筆數據而言,它的hash值是惟一的。hash函數就是能夠把任意大小的數據計算出指定大小hash值的函數。 
Hash運算有如下幾個主要的特色: 
1. 原始數據不能從hash值中逆向計算獲得 
2. 肯定的數據只有一個hash值且這個hash值是惟一的 
3. 改變數據的任一byte都會形成hash值變更數據庫

Hash運算普遍應用於數據一致性的驗證。不少線上軟件商店都會把軟件包的hash值公開,用戶下載後自行計算hash值後驗證和供應商的是否一致,就可判斷軟件是否被篡改過。 
在區塊鏈中,hash也是用於保證數據的一致性。區塊的數據,還有區塊的前一區塊的hash值也會被用於計算本區塊的hash值,這樣就保證每一個區塊是不可變:若是有人要改動本身區塊的hash值,那麼連他後面的區塊hash也要跟着改,這顯然是不可能的或者極其困難的(要說服不是本身的區塊一同更改很困難)。編程

Hashcash 哈希現金

比特幣中的工做證實使用的是Hashcash技術,起初,這一算法開發出來就是用於防止垃圾電子郵件。它的工做主要有如下幾個步驟: 
1. 獲取公開的信息,好比郵件的收件人地址或者比特幣的頭部信息 
2. 增長一個起始值爲0的計數器 
3. 計算出第1步中的信息+計數器值組合的hash值 
4. 按規則檢測hash值是否知足需求(通常是指定前20位是0) 
1. 知足 
2. 不知足則重複3-4步驟 
這個算法看上去比較暴力:改變計數器值,計算新的hash,檢測,增長計數器值,計算新的hash…,因此這個算法比較昂貴。 
郵件發送者預備好郵件頭部信息而後附加用於計算隨機數字計數值。而後計算160-bit長的hash頭,若是前20bits 
如今進一步分析區塊鏈hash運算的要求。在原始的hashcash實現中,必須根據頭信息算出前20位爲0的hash值。而在比特幣中,這一規則則是根據時間的推移變更的,由於比特幣的設計就是10分鐘出一塊新區塊,即便計算機算力提高或者更多的礦工加入挖礦行列中也不會改變,也就是說,算出hash值,會愈來愈困難。 
下面演示這一算法,和上面第一張圖同樣使用「I like donuts」做爲數據,再在數據後加面附加計數器,而後使用SHA256算法找出前面6位爲0的hash值。安全

而ca07ca就是不停運算找到的計數器值,轉換成10進制就是13240266,換言之大概執行了13240266次SHA256運算才找到符合條件的值。網絡

實現

上面花了點篇幅介紹了工做量證實的原理。如今咱們用Golang來實現。先定24位0的做爲挖礦的難度: 
const targetBits = 24 
注:在比特幣挖礦中,頭部中的target bits存儲該區塊的挖礦難度,可是上面說過隨着時間推移難度愈來愈大,因此這個target大小是會變的,這裏不實現target的適配算法,這不影響咱們理解挖礦。如今只定義一個常量做爲全局的難度。 
固然,24也是比較隨意的,咱們只用在內存中佔用少於256bits的的空間。差別也要大些,可是也不要太大,太大了就就很難找出來一個合規的hash。 
而後定義ProofOfWork結構:ide

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 有「block」和「target」兩個成員。「target」就是上面段落中描述的hashcash的規則信息。使用big.Int是由於要把hash轉成大整數,而後檢測是否比target要小。函數

NewProofOfWork 函數負責初始化target,將1往左偏移(256-targetBits)位。256是咱們要使用的SHA-256標準的hash值長度。轉換成16進制就是:區塊鏈

0x10000000000000000000000000000000000000000000000000000000000ui

這會佔用29位大小的內存空間。加密

如今準備建立hash的函數:

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
}

這段代碼作了簡化,直接把block的信息和target、nonce合併在一塊兒。nonce就是Hashcash中的counter,nonce(現時標誌)是加密的術語。 
準備工做都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值的int形式,nonce是計數器。而後執行math.MaxInt64次循環直到找符合target的hash值。爲何是math.MaxInt64次,其實這個例子中是不用的考慮這麼大的,由於咱們示例中的PoW過小了以至於還不會形成溢出,只是編程上要考慮一下,小心爲上。 
循環體內工做主要是: 
1. 準備塊數據 
2. 計算SHA-256值 
3. 轉成big int 
4. 與target比較 
將上一篇中的NewBlock方法改造一下,扔掉SetHash方法:

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
}

而後執行 go run *.go

start: 2018-03-07 14:43:31.691959 +0800 CST m=+0.000721510
Mining the block containing "Genesis Block"
0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
end:  2018-03-07 14:44:32.488829 +0800 CST m=+60.798522580
elapsed time:  1m0.797798933s
start: 2018-03-07 14:44:32.489057 +0800 CST m=+60.798750578
Mining the block containing "Send 1 BTC to Ivan"
000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
end:  2018-03-07 14:46:32.996032 +0800 CST m=+181.307571128
elapsed time:  2m0.508818203s
start: 2018-03-07 14:46:32.996498 +0800 CST m=+181.308036527
Mining the block containing "Send 2 more BTC to Ivan"
0000001927685501c59f28c0bda3fdd0472e88d6eec3822b0ab98e5b1c28c676
end:  2018-03-07 14:46:53.90008 +0800 CST m=+202.211938702
elapsed time:  20.903900066s

能夠看到生成了前6位都是0的hash字符串,由於是16進制的,就是24一位,共4*6=24位,也就是咱們設置的targetBits。從時間上能夠看到,計算出hashcash的時間有必定的隨機性,多着2分,少則20秒。 
如今還須要去驗證是不是正確的。

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在驗證是要用到的,告訴對方也用咱們計算出來的數據進行二次計算,若是對方計算的符合要求,說明咱們的計算合法。 
把驗證方法加到main函數中:

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: 0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
PoW: true
Prev. hash: 0000006f3387b588739cbcfe2cce521fcce27d4306776039e02c2904b116ab9a
Data: Send 1 BTC to Ivan
Hash: 000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
PoW: true
Prev. hash: 000000e9d5a266faa6a86f56a36ea09212ecad28e524a8b0599589fd5b800d13
Data: Send 2 more BTC to Ivan
Hash: 0000001927685501c59f28c0bda3fdd0472e88d6eec3822b0ab98e5b1c28c676
PoW: true

本章總結 本章咱們的區塊鏈進一步接近實際的結構:增長了計算難度,這意味着挖礦成爲可能。不過仍是欠缺了一些特性,好比沒有把計算出來的數據持久化,沒有錢包(wallet)、地址、交易,以及實現一致性機制。接下來的幾篇abc中,咱們會持續完善。

相關文章
相關標籤/搜索