1. 程式人生 > 實用技巧 >[比特幣]比特幣的實現

[比特幣]比特幣的實現

比特幣系統是一個基於交易的賬本,這意味著儲存在賬本中的是一筆筆交易。那麼在使用者進行轉賬交易時,首先就需要確保使用者所持有的數量要大於或等於將要交易的。這要做的目的是為了避免雙花問題。

這種基於交易的賬本好處在於隱私保護性比較好,缺點在於每次交易都需要說明幣的來源。

在比特幣的全節點中,會在記憶體中維護一個名為UTXO(Unspent Transaction Output)的資料結構 -- 未消費的交易輸出,用以記錄哪些交易的輸出已經被消費了,哪些交易的輸出還沒有被消費。已經消費的交易輸出會從UTXO中移除。

隨著比特幣系統中交易的不斷增加,UTXO的體積也在不斷的增加。雖然被消費的交易輸出會被移除,但UTXO中也會有一些陳舊的交易輸出存在。其中有些是所有者不想使用的(像中本聰賬戶下的),也有一些是丟了私鑰花不了的。

所以在執行交易過程中,首先會從UTXO中檢索交易輸入的比特幣的數量是否大於或等於交易輸出的數量,即 \(total \quad inputs >= total \quad outputs\),差額就是付給記賬節點的小費。所以比特幣的獎勵不僅僅是出塊獎勵,還有交易過程支付的小費,不過在目前的環境下,各個節點爭奪記賬權主要還是為了出塊獎勵。

求解Puzzle

比特幣中的block header結構如下:

class CBlockHeader
{
public:
    // header
    int32_t nVersion;           // 當前使用的比特幣版本,不可更改
    uint256 hashPrevBlock;      // 前一個區塊的Hash,不可更改
    uint256 hashMerkleRoot;     // MerkleTree的根Hash
    uint32_t nTime;             // 時間戳,因為比特幣網路不要求絕對的時間同步,所以這個值可以小範圍的調整
    uint32_t nBits;             // 目標閾值,根據協議定期調整
    uint32_t nNonce;            // 隨機數
    ......
}

nonce是一個無符號的整數,這意味著它的取值範圍在\(0 \sim 2^{32}\)。按照現在的挖礦難度來說,很可能把這\(2^{32}\)個值都算了一遍也找不到合適的值,這個時候該怎麼辦呢?

從上面比特幣的block header的定義中,可以發現除了nonce可以修改外,還可以修改hashMerkleRoot

上一節說過,每個區塊中都包含一個鑄幣交易,在該交易中包含一個coinbase域。在比特幣交易中,這個域是不做檢測的,這意味著可以在這個域中隨意寫入。在《資料結構》章節中介紹MerkleTree時講過,MerkleTree任意葉子節點的修改都會影響到MerkleTree的根Hash值。因此,當block header中的4個位元組的nonce不夠用的時候,可以使用其它位元組,比如將coinbase域的前8個位元組當做extra-nonce

來使用,這樣搜素空間就擴充套件到了\(2^{96}\)

所以真正挖礦的時候會有兩層迴圈,外層迴圈調整coinbase域的extra-nonce,算出block header裡的根Hash值之後,內層迴圈再調整block header裡的nonce。

在比特幣中,求解puzzle的過程又被稱為“挖礦”,因為都是在一個很大的空間中尋找一件極有價值的物品。

挖礦過程中的每一次嘗試都可以看做是一個Bernoulli trial,也就是說每次計算的結果都不會影響到下一次計算的結果。

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

比特幣系統安全性分析

假設大部分算力是掌握在誠實的礦工手裡,那我們能得到什麼樣的安全保證?能不能保證寫入區塊鏈的交易都是合法的?

挖礦給出的只是概率上的保證,只能說有比較大的概率下一個區塊是由誠實的礦工釋出的,但不能保證記賬權不會落在有惡意的節點手裡。

偷幣

惡意節點能不能偷幣?答案是不能,因為他沒有別熱的私鑰,無法偽造別人的簽名。

如果惡意節點硬將偷幣的交易寫到區塊鏈上,誠實節點是不會接受這個這個區塊的,因為它包含有非法交易。所以誠實節點會繼續沿著前一個區塊挖,生成新的區塊來代替非法區塊,其他誠實節點會沿著這個合法區塊繼續往下挖。比特幣的要求是擴充套件正常合法鏈,這樣做的結果不僅沒有得到區塊獎勵,沒有偷到錢,還白白浪費了算力。

雙花

惡意節點能不能實現雙花攻擊?答案也是不能的。假如M轉賬給A的交易已經寫到了一個區塊裡面,現在他獲得了記賬權,又發起一筆交易把轉給A的錢再轉給自己,發起雙花攻擊。誠實節點是不會接受這個區塊的。

如果他還想釋出這個區塊,那就只能將這個區塊連線到記錄上次轉賬交易區塊的前一個區塊。而區塊要插在哪個位置,這是在挖礦開始時就決定的,因為block header中要填寫上一個block header的Hash。這樣操作的話,會產生兩條鏈,且這兩條鏈都是合法的。之後就要看其他節點會按哪一條鏈往下擴充套件了,最終只會有一條鏈勝出,另一條鏈會作廢。

雙花攻擊的目的是為了在向A轉賬得到某些東西后,再將花出去的錢轉給自己,從而達到不當獲利的目的。但按照上面的操作,只會產生兩種結果:向A支付比特幣購買物品,或者將錢轉給自己,什麼也沒得到。顯然這兩種結果都沒達成雙花攻擊的目的。

那麼針對使用比特幣支付購買商品時,怎樣才能避免上面的問題呢?

在比特幣協議中,預設在包含該交易的區塊後有6個新增的區塊,這時才能認為交易成功。以平均10分鐘的出塊時間記,需要在交易發起一小時後才能確認交易。


宣告:本作品採用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。