1. 程式人生 > >區塊鏈(比特幣)的學習筆記

區塊鏈(比特幣)的學習筆記

比特幣的基本概念及相關認識

區塊鏈本質上是分散式的資料庫系統,是開放式的賬簿系統(ledger)。

本質上,區塊鏈就是一個有著特定結構的資料庫,是一個有序,每一個塊都連線到前一個塊的連結串列。也就是說,區塊按照插入的順序進行儲存,每個塊都與前一個塊相連。這樣的結構,能夠讓我們快速地獲取鏈上的最新塊,並且高效地通過雜湊來檢索一個塊。

挖礦(工作量證明)

工作量證明是一種共識演算法,在比特幣中,這個工作就是找到一個塊的有效雜湊,並證明其有效性。

獲得指定資料的一個雜湊值的過程,就叫做雜湊計算。一個雜湊,就是對所計算資料的一個唯一表示。對於一個雜湊函式,輸入任意大小的資料,它會輸出一個固定大小的雜湊值。下面是雜湊的幾個關鍵特性:

  1. 無法從一個雜湊值恢復原始資料。也就是說,雜湊並不是加密。

  2. 對於特定的資料,只能有一個雜湊,並且這個雜湊是唯一的。

  3. 即使是僅僅改變輸入資料中的一個位元組,也會導致輸出一個完全不同的雜湊。

在區塊鏈中,雜湊被用於保證一個塊的一致性。雜湊演算法的輸入資料包含了前一個塊的雜湊,因此使得不太可能(或者,至少很困難)去修改鏈中的一個塊:因為如果一個人想要修改前面一個塊的雜湊,那麼他必須要重新計算這個塊以及後面所有塊的雜湊。

尋找一個合適的雜湊,需要大量的計算,平均是10分鐘才能找到一個合適的雜湊,生成1個新的區塊。

交易

比特幣中交易分為兩種:

  1. 普通交易,輸入會引用之前一筆交易的輸出。即花費之前的交易輸出

  2. 例外,coinbase交易,即挖礦獎勵,只有輸出

在交易中,需要注意的是:

  • 一筆交易的輸入可以引用之前多筆交易的輸出

  • 一個輸入必須引用一個輸出

  • 沒有被引用的輸出,即為未花費交易輸出

  • 輸出是不可再分的,即一個輸出你無法僅引用它的其中某一部分,要麼不要,要麼一次性用完。當它的值比需要的值大,那麼就會產生一個找零,找零會返還給傳送方。

那麼,交易的話,需要的是傳送方,接收方,交易金額。整個交易的過程如下:

  1. 從傳送方中找到足夠交易金額的未花費交易輸出s(可能是多個輸出的疊加),作為交易的輸入

  2. 確定輸出,輸出有可能為2個,一個是接收方,一個是傳送者的找零

值得注意的是,交易生成後,並不是立刻生效,而是先將交易放到一個記憶體池中,然後當礦工準備挖出一個新塊時,它就從記憶體池中取出所有交易,建立一個候選塊,當包含這些交易的塊被挖出來新增到區塊鏈以後,裡面的交易才開始確認。

地址

當交易發生時,如何校驗傳送方和接收方呢,沒有使用者賬戶,如何查驗自己所有的未花費輸出呢。在比特幣中,校驗方式為,金鑰對,公鑰和私鑰。私鑰代表的就是你,所有的交易中含有的資訊為公鑰相關。

根據協議,公鑰的長度是512位。這個長度不太方便傳播,因此協議又規定,要為公鑰生成一個160位的指紋。所謂指紋,就是一個比較短的、易於傳播的雜湊值。160位是二進位制,寫成十六進位制,大約是26到35個字元,即地址。

交易產生時,需要對資料進行簽名,簽名過程需要被簽名的資料和私鑰,這個簽名會被儲存在交易輸入中。為了對一個簽名進行驗證,需要被簽名的資料,簽名,公鑰。所以在一個交易的輸入中,這三者都會儲存。那麼,什麼時候進行驗證呢,當把交易從記憶體池中取出來放入到一個塊之前,對每一筆交易都要進行驗證,驗證意味著什麼呢,其一是檢查交易輸入有權使用來自之前交易的輸出,其二是檢查交易簽名是正確的。

交易過程,一筆交易就是一個地址的比特幣轉移到另一個地址。申報交易時,除了交易金額,轉出比特幣的一方還必須提供以下資料:

  • 上一筆交易的hash(你從哪裡得到的這些比特幣)

  • 本次交易雙方的地址

  • 支付方的公鑰

  • 支付方的私鑰生成的數字簽名

驗證這筆交易是否屬實,需要三步:

  1. 找到上一筆交易,確認支付方的比特幣來源

  2. 算出支付方公鑰的指紋,確認與支付方的地址一直,從而保證公鑰屬實

  3. 使用公鑰去解開數字簽名,保證私鑰屬實

交易確認,交易必須寫入區塊鏈,才算生效。首先,所有的交易資料都會傳送到礦工那裡,礦工負責把這些交易寫入區塊鏈。根據比特幣協議,一個區塊的大小最大是 1MB,而一筆交易大概是500位元組左右,因此一個區塊最多可以包含2000多筆交易。礦工負責把這2000多筆交易打包在一起,組成一個區塊然後計算這個區塊的 Hash(工作量證明)。另外,鑑於區塊鏈分叉的情況,根據比特幣協議規定,分叉點最先達到6個區塊的那個分支,被認定為正式的區塊鏈,其他分支都將被放棄,所以,一筆交易真正生效,必須等待至少1個小時。

貼上一點資料結構,供參考。

輸入結構體定義為

type TXInput struct {
    Txid      []byte  //一個輸入引用之前交易的一個輸出,txid為之前交易的ID
    Vout      int  //之前交易的輸出可能有多個,需要指明是具體哪一個
    Signature []byte //簽名
    PubKey    []byte //公鑰
}

輸出結構體定義為

type TXOutput struct {
    Value      int  
    PubKeyHash []byte //公鑰雜湊
}

看到兩個結構體的定義,可以想到,簽名的作用僅在於到被驗證完畢。輸出中公鑰雜湊的使用場景在於校驗,例如查詢未花費輸出時,判斷條件為輸出中的公鑰雜湊和當前公鑰進行雜湊之後是否匹配,還有另外的場景..下面來回顧一下一個交易完整的生命週期。

  1. 起初,創世塊裡面包含了一個coinbase交易,在coinbase交易中,沒有輸入,就不需要簽名,coinbase交易的輸出包含了一個雜湊過的公鑰(使用的是 RIPEMD16(SHA256(PubKey)) 演算法)

  2. 當有人傳送幣時,就會建立一筆交易,這筆交易的輸入會引用之前交易的輸出,還會儲存一個公鑰(沒有被雜湊)和整個交易的一個簽名

  3. 比特幣網路中接收到交易的其他節點會對該交易進行驗證,除了一些其他事情,他們還會檢查,在一個輸入中,公鑰雜湊與所引用的輸出雜湊相匹配(這保證了傳送方只能花費屬於自己的幣);簽名是正確的(這保證了交易是由幣的實際擁有者所建立)。

  4. 當一個礦工準備挖一個新塊時,他會將交易放到塊中,然後開始挖礦。

  5. 當新塊被挖出來以後,網路中的所有其他節點會接收到一條訊息,告訴其他人這個塊已經被挖出並被加入到區塊鏈。

  6. 當一個塊被加入到區塊鏈以後,交易就算完成,它的輸出就可以在新的交易中被引用。

接下來,就是這其中的一些加密演算法啦~

  • 私鑰生成演算法,橢圓曲線加密

  • 地址生成演算法,Base58, 如果你想要給某個人傳送幣,只需要知道他的地址就可以了。當我們給某個人傳送幣時,我們只知道他的地址,因為這個函式使用一個地址作為唯一的引數。然後,地址會被解碼,從中提取出公鑰雜湊並儲存在 PubKeyHash 欄位

  • 簽名的資料,因為用於簽名的這個資料,必須要包含能夠唯一識別資料的資訊。所以需要簽名的資料必須包括以下部分

    1. 儲存在已解鎖輸出的公鑰雜湊。它識別了一筆交易的“傳送方”。

    2. 儲存在新的鎖定輸出裡面的公鑰雜湊。它識別了一筆交易的“接收方”。

    3. 新的輸出值。

    比特幣裡, 所簽名的並不是一個交易,而是一個去除部分內容的輸入副本。

UTXO集

在查詢某個公鑰的所有未花費輸出時,需要查詢所有的區塊,區塊中的每一筆交易,但是如果區塊特別多的情況下,迭代需要的成本就太大了。解決方式就是有一個僅有未花費輸出的索引,這就是UTXO集要做的事情,這是一個從所有區塊鏈交易中構建(對區塊進行迭代,但是隻須做一次)而來的快取,然後用它來計算餘額和驗證新的交易。

Merkle樹

Merkle樹為1種優化機制,實現的是每個塊中,都有1個Merkle樹,樹的葉子節點為一個交易雜湊,從下往上,兩兩成對,連線兩個節點雜湊,將組合雜湊作為新的雜湊,新的雜湊就成為新的樹節點,一直到樹根,根雜湊就會當作是整個塊交易的唯一標識,將它儲存到區塊頭,然後用於工作量證明。

Merkle樹的好處就是一個節點可以在不下載整個塊的情況下,驗證是否包含某筆交易。並且這些只需要一個交易雜湊,一個 Merkle 樹根雜湊和一個 Merkle 路徑。

P2PKH

在比特幣中有一個 指令碼(Script)程式語言,它用於鎖定交易輸出;交易輸入提供瞭解鎖輸出的資料。這個個指令碼叫做 Pay to Public Key Hash(P2PKH),這是比特幣最常用的一個指令碼。它所做的事情就是向一個公鑰雜湊支付,也就是說,用某一個公鑰鎖定一些幣。這是比特幣支付的核心:沒有賬戶,沒有資金轉移;只有一個指令碼檢查提供的簽名和公鑰是否正確。有了一個這樣的指令碼語言,實際上也可以讓比特幣成為一個智慧合約平臺:除了將一個單一的公鑰轉移資金,這個語言還使得一些其他的支付方案成為可能。

資料庫結構

簡單來說,Bitcoin Core使用兩個"bucket"來儲存資料:

  1. blocks, 它儲存了描述一條鏈中所有塊的元資料

  2. chainstate, 儲存了一條鏈的狀態,也就是當前所有的未花費的交易輸出,和一些元資料

此外,出於效能的考慮,Bitcoin Core 將每個區塊(block)儲存為磁碟上的不同檔案。如此一來,就不需要僅僅為了讀取一個單一的塊而將所有(或者部分)的塊都載入到記憶體中。

網路

區塊鏈網路是去中心化的,這意味著沒有伺服器,客戶端也不需要依賴伺服器來獲取或處理資料。在區塊鏈網路中,有的是節點,每個節點是網路的一個完全(full-fledged)成員。節點就是一切:它既是一個客戶端,也是一個伺服器。這一點需要牢記於心,因為這與傳統的網頁應用非常不同。

區塊鏈網路是一個 P2P(Peer-to-Peer,端到端)的網路,即節點直接連線到其他節點。它的拓撲是扁平的,因為在節點的世界中沒有層級之分。每個節點必須與很多其他節點進行互動,它必須請求其他節點的狀態,與自己的狀態進行比較,當狀態過時時進行更新。

當你發生了一筆支付,你所在的節點就會把這筆交易告訴另一個節點,直至傳遍整個網路。礦工從網上收集各種新發生的交易,將它們打包寫入區塊鏈。一旦寫入成功, 礦工所在節點的區塊鏈,就成為最新版本,其他節點都會來複制新增的區塊,保證全網的區塊鏈都是一致的。

節點角色:

  • 礦工 這樣的節點運行於強大或專用的硬體(比如 ASIC)之上,它們唯一的目標是,儘可能快地挖出新塊。礦工是區塊鏈中唯一可能會用到工作量證明的角色,因為挖礦實際上意味著解決 PoW 難題。在權益證明 PoS 的區塊鏈中,沒有挖礦。

  • 全節點 這些節點驗證礦工挖出來的塊的有效性,並對交易進行確認。為此,他們必須擁有區塊鏈的完整拷貝。同時,全節點執行路由操作,幫助其他節點發現彼此。對於網路來說,非常重要的一段就是要有足夠多的全節點。因為正是這些節點執行了決策功能:他們決定了一個塊或一筆交易的有效性。

  • SPV SPV 表示 Simplified Payment Verification,簡單支付驗證。這些節點並不儲存整個區塊鏈副本,但是仍然能夠對交易進行驗證(不過不是驗證全部交易,而是一個交易子集,比如,傳送到某個指定地址的交易)。一個 SPV 節點依賴一個全節點來獲取資料,可能有多個 SPV 節點連線到一個全節點。SPV 使得錢包應用成為可能:一個人不需要下載整個區塊鏈,但是仍能夠驗證他的交易。