【用Python自幹一個區塊鏈(1)】

Image for post
Image for post

前言

Image for post
Image for post
Image for post
Image for post

如果時間可以重來,我會選擇當個Hodler…..(多希望當初我沒有開通bitmex或ftx的帳號 嗚嗚),順帶一提去年我學到最慘痛的一課就是:

放空獲利有限但虧損無限,無論如何不要放空

Image for post
Image for post

我很喜歡前諾貝爾物理獎得主 Richard Phillips Feynman 曾說過的一句話:

「What I cannot create, I do not understand.」

當初為了搞懂區塊鏈的基本原理,便自己用 Python 刻出了一個簡單但具有基本功能的區塊鏈,並且當作去年 it邦幫忙鐵人賽的部分內容,這幾天趁著比特幣再創歷史新高,也把過去寫過的文章整理一下,但因為文章較長,共分成四集如下:

  1. 定義基本架構、功能與格式(本文)
  2. 利用非對稱加密簽署並發送交易
  3. 製作節點與用戶端程式
  4. 節點間的廣播與同步

區塊鏈的願景:Be your own bank

貨幣購買力下降的主因除了經濟成長外,也與政府不斷發行新貨幣進入市場有相關,就像是去年因為肺炎疫情重創經濟,美國政府帶頭進行了無上限的QE,所以實際上貨幣流通量增加的速度實際上遠遠超出你我的想像,下圖是美金流通量的歷史。

Image for post
Image for post
圖片來源:Gold Silver Worlds

你要怎麼信任手中的貨幣在未來是有價值的?

除了政府與銀行外,又有誰能發行可信的貨幣?

怎能保證發行者在未來不會繼續超發?

Bitcoin在一開始便是為了解決誰可以是有公信力的發行者、誰又可以是公正的保管者的問題。比特幣的發明者─中本聰(Satoshi Nakamoto)提供的解決方案就是把貨幣的發行與詮釋權發還給大眾,並搭配密碼學來確保大眾手上的資料與紀錄難以被竄改,這也是為什麼有時候虛擬貨幣會被叫成加密貨幣的原因。

換言之,中本聰的理想就是人人手上都有帳本、每個人都是銀行,藉此來保證了共識的公正與執行,也因此Bitcoin的最初願景便是”Be your own bank.”

交易的組成

Transaction

Image for post
Image for post

Blocks of transactions

Image for post
Image for post

交易格式

  1. 發送方(sender):交易的發起方,同時要確認是發送方底下餘額否足夠
  2. 收款方(receiver):交易的收受方,通常無須使用者同意即可收款
  3. 金額大小(amounts):這筆交易的金額數目大小
  4. 手續費(fee):交易時所需支付的手續費多寡
  5. 訊息(message):如同轉帳的備註一般留下資訊,通常是給收款方看

區塊格式

可以把哈希值看做是每個區塊上的鎖頭,而礦工挖掘出的nonce則代表了能夠匹配這個鎖頭的鑰匙(或另一把鎖),而且下一個區塊的哈希值又根據這個nonce值而產生,如此一來只要其中任何一個交易紀錄、區塊被竄改,則整個鍊上的noncehash都需要修正,並且需要在新的區塊產生前計算/修正完畢,這需要擁有異常龐大的計算量,也因此竄改區塊鏈是幾近不可能的事情。

Image for post
Image for post

因此,一個區塊裡頭至少需要有這些資訊:

  1. 前個區塊的哈希值(previous_hash):為了加密需要我們會使用到前一個區塊的哈希值,藉此保障區塊鏈上的資料安全
  2. 此次區塊的哈希值(hash):目前區塊計算後的哈希值
  3. nonce:礦工找到能夠解開上個區塊鎖的鑰匙
  4. 當前難度(difficulty):挖出這個區塊時所使用的困難度
  5. 區塊產生時的時間戳(timestamp):紀錄了這是該區塊是在何時產生,在調整挖礦難度會使用到
  6. 交易紀錄(transactions):紀錄了這個區塊中所有的交易紀錄
  7. 挖掘礦工(miner):紀錄了這個區塊中是由誰挖掘出來的
  8. 礦工獎勵(miner_rewards):紀錄了這個區塊產出時分給礦工的獎勵

區塊鏈架構

另外,區塊鏈裡頭也會設定每一區塊中所能存放的交易上限,避免無上限時造成資料傳遞上的堵塞,同時也會將目前區塊產出時會給予礦工的獎勵數目寫在區塊鏈裡頭。

  1. 難度調節區塊數(adjust_difficulty_blocks):每多少個區塊調節一次難度
  2. 目前難度(difficulty):希望每個區塊的產出時間盡量保持一致,也因此隨著挖掘nonce的機器數目與效能變動,挖掘難度也必須隨之調整以讓產出時間維持在動態平衡上,這個欄位代表了區塊鏈當下的難度
  3. 出塊時間(block_time):理想上多久能夠出一個區塊,當實際出塊時間塊於設定的理想值時,代表運算效能優於實際需要,因此必須將難度做相對應的提升,反之亦然,詳情在之後的教學中會有進一步的說明。
  4. 挖礦獎勵(miner_rewards):獎勵挖礦者的金額多寡,挖出新區塊的礦工可以得到獎勵,藉此鼓勵礦工參與區塊鏈營運
  5. 區塊容量(block_limitation):每一個區塊能夠容納的交易上限,上限的存在是因為當礦工挖掘出新的nonce時,他需要把所有被接受的交易連同區塊資料一併廣播給其他人知悉,因此如果容量過大會導致傳播過慢或是讓礦工需要的網速增加到不符合經濟效益的地步。
  6. 區塊鏈(chain):目前區塊鏈中儲存的所有區塊
  7. 等待中的交易(pending_pranscations):當使用者發送交易時,因為區塊鏈能夠吞吐的交易量有限,交易會先處在pending的狀況,當交易量過大時,礦工會首先選擇手續費高的交易先處理。

產生哈希數(Hash)

在定義上,哈希函數(hash function)必須同時滿足兩個條件:

  1. 同樣的輸入值必會得到相同的輸出值
  2. 輸出的哈希數無法反推回原本的資料

以下面為例,Hello World!的字串能夠透過SHA-1的哈希函數轉換成:

2ef7bde608ce5404e97d5f042f95f89f1c232871

但此時產生的2ef7bde608ce5404e97d5f042f95f89f1c232871無法反推回原本的Hello World!

由於輸入資料的不同,往往我們可以把哈希數視作幾近隨機的位元組所構成(但仍然會因為哈希函數的不同而有所變異)

Image for post
Image for post

在這裡我們先把下面這些資料連接後作為哈希函數的輸入:

  1. 前一個區塊的哈希值(previous_hash)
  2. 區塊產生當下的時間戳(timestamp)
  3. 所有的交易明細(transactions)
  4. 挖掘中的nonce

下面是轉換哈希數的程式碼,其中transaction_to_string負責把交易明細轉換成字串、get_transactions_string負責把區塊紀錄的所有交易明細轉換成一個字串、get_hash負責依據這四筆資料產生相對應的哈希數。

產生創世塊(genesis block)

以比特幣來說,比特幣的創世塊裡頭隱含了下面這句話。

The Times 03/Jan/2009 Chancellor on brink of second bailout for banks.

這句話被比特幣創始人中本聰寫入創世塊中,來源是是2009/01/03英國《泰晤士報》的頭版標題,那時候的世界還陷在2008金融風暴的危機中尚未復甦,這篇報導則敘述了當時的英國正考慮對銀行進行財務紓困,或許中本聰只是單純想證明這區塊確實是當天寫入的,又或許想透過《泰晤士報》的頭版標題又對政府與中心化金融機構進行一次諷刺。

Image for post
Image for post
圖片來源:The News Lens

由於這是我們的第一個區塊鏈,所以我們就在previous_hash的欄位給...........Hello World! 藉此紀念一下 ,並且難度與挖礦獎勵設定成區塊鏈的預設值,礦工這裡就直接填入我們的姓名,產生創世塊後就直接把創世塊加入到chain之中

放置交易紀錄至新區塊中

通常礦工會根據自身的利益最大化而選擇手續費高的交易優先處理,因此在這裡我們選擇手續費最高的幾筆交易優先加入區塊中。

挖掘新區塊

產生的哈希數的”開頭有幾個0"

也就是每次改變nonce、產生一個新的hash後來確認有沒有符合要求(開頭有幾個0),如果符合就代表我們找到一個合規nonce值了!

但如果沒有,就只好持續的往下找了。也因為運算量越大能夠找到合規的nonce值的機率也越大,也又有權力驗證與廣播區塊,因此這個方法又被稱為工作量證明Proof of Work(POW)

Image for post
Image for post

但透過這個方式區塊的產生時間會非常地不穩定,你可以到bitcoin的區塊瀏覽器看看產出的時間,bitcoin預設是每十分鐘應該要產出一個區塊,但也可以發現實際上每個區塊的產生時間會跟十分鐘有點落差,這是POW的必然結果。

Image for post
Image for post

在這裡的實作中,我們生成一個區塊後不停計算不一樣的nonce值,直到我們能夠找到合規的nonce為止,在發現(挖掘)合規的nonce之後,就可以把挖出來的區塊置入鏈裡頭。

調整挖掘難度

那怎麼去評估區塊的產生時間呢?如果單純採用前一個區塊的產出時間很明顯的是不可行,因為POW的核心精神是利用隨機數去猜到可能可以符合的nonce,因此每一個區塊的產出時間會變動相當大:

Image for post
Image for post

根據上圖看到我們的區塊鏈在難度5的狀況下,連續十塊的出塊時間從0.47秒到39.44秒都有可能,因此根據單個區塊的出塊時間決定難度是萬萬不可行的,取而代之的方法便是取多個區塊的出塊時間再取平均,這其實就像是訊號處理中的均值濾波器(mean filter)。

我們設定如果平均出塊時間小於設定的出塊時間,就把難度加1,如果平均出塊時間大於設定的出塊時間,就把難度減1。

這裡難度的定義是挖到的nonce值必須要滿足讓Hash的頭幾個Bytes為0,因此難度每加1,實際上的運算量會增加16倍(位元組是兩兩16進位構成的),也因為調整幅度太大,所以其實這裡設計的並不是很好的難度調整算法。

計算帳戶餘額

  1. 區塊獎勵:挖出區塊的礦工能得到該區塊的出塊獎勵
  2. 手續費:挖出區塊的礦工能得到該比區塊內所有交易的手續費
  3. 匯款收入:收到別人匯款的款項

因此我們寫一個簡單的函式,從第一個區塊的第一筆交易開始檢查,一路檢查到最後一筆後便可以得到該帳戶的餘額。

確認哈希值是否正確

在昨天每個哈希數都由下面這四筆資料計算出來:

  1. 前一個區塊的hash(previous_hash)
  2. 區塊產生的時間戳
  3. 所有的交易紀錄
  4. nonce

所以檢查的方式就是從第一個區塊的哈希數一路算到最後一個,一旦中間開始的某個哈希數算完之後對不起來,那麼就代表其中的某筆交易紀錄被竄改過。

如果我們在其中一個區塊插入了一筆偽造的交易,那麼透過一連串哈希的確認與計算,我們便可以發現hash數是對不起來的!

Image for post
Image for post

如果某特定區塊被更改,那麼之後的所有區塊都必須重新被計算哈希數,否則會完全對不起來而被發現資料被竄改過!當然攻擊者可以選擇更改後重新計算所有的哈希數,但當主鏈夠長、礦工夠多時,重新計算所有的哈希數所需要的運算量與成本非常可怕,也因此保障了區塊鏈的不可竄改性。

更何況在重新計算時,正常的塊也在不停的被一般的礦工產出,要跟所有的礦工競爭幾近天方夜譚。

文章列表:

  1. 利用非對稱加密簽署並發送交易
    http://bit.ly/3og5jLF
  2. 製作節點與用戶端程式
    http://bit.ly/2KVXBb2
  3. 節點間的廣播與同步
    http://bit.ly/394aurx

工商服務

本篇文的程式碼如下

Written by

一個普通的肥宅QQ

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store