04-BTC-實現

聲明:本文是要點筆記,介紹和系列筆記均收錄在專題:區塊鏈技術與應用

UTXO

區塊鏈是去中心化的賬本,比特幣使用的是基於交易的這種賬本模式——基於交易的賬本(transaction based ledger),系統當中並不會顯示每個賬戶有多少錢。

比特幣系統的全節點要維護一個叫 UTXO(unspent transaction output,還沒有被花出去的交易的輸出)的數據結構。區塊鏈上有很多交易,有些交易的輸出可能已經被花掉,有些還沒有被花掉。所有沒有被花掉的輸出的集合就叫做 UTXO。

一個交易可能有多個輸出。假如 A 給 B,5 個比特幣,B 花掉了。A 也給了 C,3 個比特幣,C 沒有花掉。這時 5 個比特幣就不算 UTXO,而 3 個比特幣算。UTXO 集合當中的每個元素要給出產生輸出的交易的哈希值,以及它在這個交易裏是第幾個輸出。這兩個信息就可以定位到 UTXO 中的輸出。

要 UTXO 集合有什麼作用?
爲了檢測是否雙花(double spending),即檢測新發布的交易是否合法。因此全節點要在內存中維護 UTXO
這樣一個數據結構,以便快速檢測雙花(double spending)。

每個交易要消耗掉一部分輸出,也會產生新的輸出。還看上面的例子,B 花掉的 5 個比特幣,雖然不在 UTXO 裏面,但如果他轉賬給 D,而 D 沒有花掉,那麼這 5 個比特幣又要保存在 UTXO 裏面。如果 D 始終不花,那麼這個信息要永久保存在 UTXO 裏面。有可能是不想花,也有可能是把密鑰丟了。

每個交易可以有多個輸入,也可以有多個輸出,所有輸入金額之和要等於輸出金額之和。即 total inputs=total outputs。因此一個交易可能來自多個地址,可能有多個簽名。

有些交易 total inputs 略微大於 total outputs。假如輸入 1 比特幣,輸出 0.99 比特幣,另外 0.01 比特幣作爲交易費給獲得記賬權發佈區塊的節點。

區塊獎勵也不能完全作爲挖礦的獎勵,發佈區塊的節點爲什麼一定要把你的交易打包在區塊呢?他們還要驗證你的交易的合法性,如果交易較多佔用的帶寬會比較大,網絡傳播速度也會更慢。所以只有區塊獎勵是不夠的。

因此比特幣系統設計了第二個激勵機制:交易費(transaction fee)。也就是你把我的交易打包在區塊裏,我給你一些小費。交易費一般很小,也有一些簡單的交易沒有交易費。

21 萬個區塊大概要挖多長時間呢?
大約是 4 年。比特幣系統設計的平均出塊時間是 10 分鐘,就是整個系統平均 10 分鐘會產生一個新的區塊。

除了比特幣這種基於交易的模式,與之對應的還有基於賬戶的模式(account-based ledger),比如以太坊系統。在這種模式中,系統直接顯示的記錄是每個賬戶上有多少幣。

比特幣這種基於交易的模式,隱私保護性較好。缺點是比特幣當中的轉賬交易要說明幣的來源,而基於賬戶的模式就不用。

區塊例子

區塊例子

上圖是一個區塊的例子,下面是主要解釋:

左邊:

  • Number Of Transactions:表明該區塊包含了 686 個交易,
  • Output total:總輸出 XXX 個比特幣,
  • Transaction Fees:總交易費(686 個交易的交易費之和),
  • Height:區塊的高度(序號),
  • Timestamp:區塊的時間戳,
  • Difficulty:挖礦的難度(每隔 2016 個區塊要調整挖礦的難度,保持出塊時間在 10 分鐘左右),
  • Nonce:挖礦時嘗試的隨機數,
  • Block Reward:區塊獎勵(礦工挖礦的主要動力)。

右邊:

  • Hash:該區塊塊頭的哈希值,
  • Previous Block:前一個區塊塊頭的哈希值,(注意:計算哈希值都只算塊頭),
  • Merkle Root:是該區塊中包含的那些交易構成的 merkle tree 的根哈希值。

兩個哈希值的共同點:前面都有一串 0。是因爲,設置的目標預值,表示成 16 進制,就是前面一長串的 0。所以凡是符合難度要求的區塊,塊頭的哈希值算出來都是要有一長串的 0。

區塊例子

上圖是塊頭的數據結構。

  • nonce:是 32 位的無符號整數。nonce 只有 2 的 32 次方個可能的取值。按照比特幣現在的挖礦情況來說,很可能把 2 的 32 次方個取值都驗了一遍也找不到合適的。那怎麼辦呢?block header 的數據結構裏還有哪些域是可以調整的呢?

  • version:比特幣協議的版本號(無法更改的),

  • previous block header hash:前一個區塊的塊頭的哈希值(無法更改),

  • merkle root hash:merkle tree 的根哈希值(可以更改),

  • time:區塊產生的時間(可以調整),比特幣系統不要求特別精確的時間,可以在一定範圍內調整,

  • nBits:目標預值,編碼後的版本,只能按協議中的要求定期調整。

  • nonce:隨機數

所以,挖礦時只改隨機數不夠,還可以更改根哈希值。

交易例子

鑄幣交易沒有輸入,它有一個 coinbase,可以寫入任何的內容。也可以把 digital commitment 裏的 commit 的哈希值寫入裏面。也可以把第一節講到的預測股市的內容寫入裏面,coinbase 的內容是沒有人會檢查的,甚至可以寫你的心情。

那這個域對我們有什麼用呢?

上圖,對應的是最後一個區塊的 block header 裏的根哈希值對應的默克爾樹(merkle tree),左下角的交易是 coinbase,把它的域改了之後,其上的哈希值就發生了變化,然後沿着默克爾樹(merkle tree)的結構往上傳遞。

最後導致 block header 裏的根哈希值發生變化(merkle root 是 block header 的一部分)。塊頭裏 4 個字節的 nonce 不夠用,還有其他字節可以用,比如 coinbase 域的前八個字節當做 extra nonce 來用,這樣子搜索空間就增大到了 2 的 96 次方。

所以,真正挖礦的時候只有兩層循環,外層循環調整 coinbase 域的 extra nonce。算出 block
header 裏的根哈希值之後,內層循環再調整 header 裏的 nonce。

轉賬交易例子

如上圖,該交易有兩個輸入和兩個輸出。

  • 左上角:這裏的 output 其實是輸入,指的是之前交易的 output。
  • 右上角:這裏的 output 都是 unspent,都沒有被花掉,會保存在 UTXO 裏面。

右邊表格:

  • Total Input:輸入的總金額,
  • Total Output:輸出的總金額,
  • Fees:上面兩者之間的差值,表示交易費。

兩表格下面:可以看出輸入和輸出都是用腳本的形式來指定的。

比特幣系統中驗證交易的合法性,就是把 input scripts 和 output script 配對後執行來完成的。

注意:不是把圖中的 input scripts 和 output scripts 配對,因爲這兩個腳本是一個交易中的腳本。不是把同一個交易裏的輸入腳本和輸出腳本配對,而是把這裏的輸入腳本和前面提供幣來源的交易的輸出腳本配對。如
果輸入輸出腳本拼接在一起,能順利執行不出現錯誤,那麼該交易就是合法的。

比特幣求解的puzzle

注意:求哈希時只用到了 block header 的內容,而交易的具體信息在 block header 裏面是沒有的。block header 裏面只有 merkle tree 的根哈希值,這個就已經能保證交易是沒有被篡改的。

挖礦過程每次嘗試一個 nonce 可以看作是一個伯努利實驗(Bernoulli trial)。每一個隨機的伯努利實驗就構成了一個伯努利過程。它的一個性質是:無記憶性。

每嘗試一個 nonce 成功的概率是很小的,要進行大量的實驗。這時可以用泊松過程(Posisson process)來代替伯努利過程(Bernoulli process)。我們真正關心的是系統出塊時間,出塊時間是服從指數分佈。如下圖,可以畫出一個座標軸,縱軸表示概率密度,橫軸表示出塊時間(整個系統的出塊時間,並不是每個礦工的出塊時間)。具體到每一個礦工,他能挖到下一個區塊的時間取決於礦工的算力佔系統算力的百分比。

假如一個人的算力佔系統總算力的 1%,那麼系統出 100 個區塊,就有一個區塊是這個人挖的。

指數分佈也是無記憶性的。因爲概率分佈曲線的特點是:隨便從一個地方截斷,剩下一部分曲線跟原來是一樣的。比如:已經等十分鐘了,還沒有人找到合法的區塊,那麼還需要等多久呢?仍然參考概率密度函數分佈 ,平均仍然要等十分鐘。將來還要挖多長時間,跟過去已經挖了多長時間是沒有關係的。這個過程也叫:progress free

如果沒有 progress free,會出現什麼現象?算力強的礦工會有不成比例的優勢。因爲算力強的礦工過去做的工作是更多的,過去嘗試了那麼多不成功的 nonce 之後,後面 nonce 成功的概率就會增大。以此 progress free 是挖礦公平性的保證。

出塊獎勵是系統中產生新的比特幣的唯一途徑。產生的比特幣構成了一個幾何序列。21 萬*50+21 萬*25+21 萬*12.5+......=21 萬*50*(1+1/2+1/4+......)=2100 萬。

比特幣求解的 puzzle,除了比拼算力之外,沒有其他實際意義。比特幣的稀缺性是人爲造成的。

雖然挖礦求解 puzzle 本身沒有實際意義,但是挖礦的過程對於維護比特幣系統的安全性是至關重要的。挖礦提供一種憑藉算力投票的有效手段,只要大部分算力是掌握在誠實的節點手裏,系統的安全性就能夠得到保證。

雖然挖礦獎勵越來越小,難度越來越大,但這幾年挖礦的競爭是越來越激烈的,因爲比特幣的價格是飆升的。最終區塊獎勵爲 0 了,是不是就沒有動力挖礦了呢?不是的,因爲還有交易費激勵機制。

假設大部分算力是掌握在誠實的礦工手裏,我們能得到什麼樣的安全保證?能不能保證寫入區塊鏈的交易都是合法的。挖礦給出的只是概率上的保證,只能說有比較大的概率下一個區塊是由誠實的礦工發佈的,但是不能保證記賬權不會落到有惡意的節點手裏。

比如好的礦工佔 90% 的算力,壞的礦工佔 10% 的算力。那麼 10% 的概率下記賬權會落在有惡意的礦工手裏,這時候會出現什麼情況?

先考慮第一個問題:他能不能偷幣?能不能把別人賬上的錢轉給自己?不能,因爲他沒有辦法僞造別人的簽名。

假設 M 是有惡意的,他想把 A 賬上的錢轉走,所以他發佈一個 A 轉給 M 的交易,但這個交易需要有 A 的簽名,M 雖然獲得記賬權,但他不知道 A 的私鑰,所以僞造不了簽名。

如果 M 把交易硬寫在區塊鏈上,誠實的節點不會接受這個區塊,因爲它包含有非法的交易。所以誠實的節點會繼續沿前一個區塊挖,生成新的區塊代替非法的區塊,其他誠實的區塊會沿着這個合法的區塊繼續挖。比特幣要求是擴展正常合法鏈,M 生成的不是合法區塊,所以該區塊作廢。這對他造成的代價是很大的,因爲沒有了區塊獎勵,又沒有偷到錢。

第二個問題:他能不能把已經花了的幣再花一遍,即雙花(double spending)?假如他把 M→A 的交易寫在了一個區塊裏面,現在他獲得了記賬權,他又發佈另一個交易,把這個錢轉回給自己,即 M→M'。同樣,這很明顯是雙花(double spending),只要是誠實的節點都不會接受這個區塊。

他如果想發佈這個區塊,只能連在寫了 M→A 交易區塊的前一個區塊。注意:區塊插在哪個位置,在剛挖礦時就是要決定的,因爲設置的 block header 裏要填上前一個 block header 的哈希。所以他想插到那個區塊的話,一開始就要認定,而不是等獲得記賬權以後再認定。

如下圖,這樣生成的兩條區塊鏈,都是合法的。要看其他節點沿着哪一個鏈往下擴展,最後一個勝出,一個作廢。

這種攻擊的目的是什麼?如果 M→A 的交易,產生了某種不可逆的外部效果,然後 M→M' 再把 M→A 的交易回滾了,那麼 M 就可以從中不當獲利。

比如:網上購物時,M 購買一些商品,然後該網站接受比特幣支付,M 發起一個交易把賬轉給網站。網站監聽到交易寫入了區塊鏈裏,以爲支付成功了,所以就把商品給了 M。M 拿到商品之後,又發起一個交易,把支出的錢轉給自己,然後把下面的鏈拓展成最長合法鏈。這樣的結果是:既得到了商品,又收回了花掉的錢,就達到了雙花(double spending)的目的。

如何防範這種攻擊呢?
如果 M→A 的交易所在的區塊不是最後一個區塊,那麼這種攻擊的難度就會大大增加。要是想回滾 M→A 的交易,還是要插在它之前的一個區塊,然後想辦法成爲最長合法鏈。這個難度是很大的。因爲誠實的節點不會沿着它生成的區塊往下擴展,因爲它不是最長合法鏈。因此防範這種攻擊的方法就是多等幾個區塊,或者叫多等幾個確認(confirmation)。

M→A 交易剛剛寫入區塊裏時,我們把它叫作 one confirmation。這時後面加的區塊,依次叫 two confirmation、three confirmation...比特幣協議當中,缺省(系統默認)的是要等六個 confirmation。有了六個 confirmation,才認定 M→A 的交易是不可篡改的。這需要等多長時間呢?平均出塊時間是 10 分鐘,因此要等一個小時。

區塊鏈是不可篡改的賬本,那是不是意味着凡是寫入區塊鏈中的內容就永遠改不了呢?經上述分析可以看出,這種分析只是一種概率上的保證。剛剛寫入區塊鏈的內容,還是比較容易被改動的。經過一段等待時間之後,或者後面幾個區塊被確認之後,被篡改的概率就大幅度下降(指數級別的下降)。

其實還有一種,叫零確認,如上圖所示。意思是說,這個轉賬交易發佈出去了,但又還沒被寫入區塊鏈裏。即 M→A 的交易已經發布,但下面包含 M→M' 的區塊還沒有被挖出來。

這個概念相當於電商購物的例子中,在支付時你發佈一個轉賬交易,告訴電商自己已經轉過錢了。電商運行一個全節點或委託一個全節點監聽區塊鏈上的交易,他收到轉賬交易之後要驗證該交易的合法性(有合法的簽名,以前沒有被花過),甚至不用等到該交易寫入區塊鏈裏。這種操作聽起來風險很大,交易剛發佈出去,都沒往區塊鏈裏寫呢。其實,零確認在實際當中,用的還是比較普遍的。爲什麼呢?

這其中有兩個原因:1、比特幣協議缺省的設置是節點接收最先監聽到的那個交易。所以在零確認的位置,M→A 的節點收到後,再發 M→M' 的交易,有比較大的概率誠實的節點是不會接受的。2、很多購物網站,從支付成功,到發貨,是有一定的時間間隔的,即有一定的處理時間。

回到前面的問題:假設某個有惡意的節點獲得記賬權,它還能做什麼壞事?能不能故意不把某些合法的交易寫入區塊鏈裏?即發佈的區塊故意不包含某些交易。這是可以的。

比特幣協議並沒有規定獲得記賬權的節點一定要把那些交易發佈到區塊裏。但出現這種情況問題也不大,因爲這些合法的交易一定會被寫入下一個區塊裏,總有誠實的節點願意發佈這些交易。

其實,區塊鏈在正常工作下,也會出現合法的交易沒有被包含進去的情況,可能就是這段時間交易的數目太多了。比特幣協議中規定,每個區塊的大小是有限制的,最多不能超過一兆字節。所以如果交易的數目太多了,那麼有些交易可能就只能等到下一個區塊再發布。

會不會出現這種情況?M→M' 的交易所在的區塊,所在的鏈條雖然短,但是先偷偷的生成比上面更多的區塊,然後等上面的鏈條公佈後再公佈,就能夠勝過上面的幾個區塊了?這種方法叫作 selfish mining。

正常情況下挖到一個區塊馬上就發佈,原因是你不發佈別人可能就發佈了,那樣就拿不到區塊獎勵了。而 selfish mining 是先藏着不急着發佈,這是分叉工具的一種手段。

但這樣成功的概率並不大,因爲有惡意的節點本來算力佔比就不高,還要生成更多的區塊,就非常困難。

以上是 selfish mining 的其中一個目的,它還有另一個目的。假如 A 挖了兩個區塊都沒有發佈,而在 B 挖到一個區塊公佈後立馬公佈,這樣 B 挖的區塊就作廢了。這樣的好處就是減少競爭,因爲 A 在挖第二個區塊時,別人還在挖第一個區塊(前提是 A 算力足夠強)。

但這樣也有不好的地方,假如 A 挖出一個區塊,A 以爲他能趕在別人面前再挖一個區塊,結果這時有人挖出了第一個區塊,那這樣的話 A 就要在別人發佈之後立馬發佈,去爭取區塊獎勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章