1. 程式人生 > >區塊鏈: 技術驅動金融 - 第3章 比特幣的運行機制

區塊鏈: 技術驅動金融 - 第3章 比特幣的運行機制

理解 金融 escrow nonce 尖括號 腳本 mic 好的 正常

這一章將真正近距離地了解比特幣所使用的數據結構、實際腳本及語言,會有大量的細節性信息,極具挑戰性,但本章可以幫助我們真正懂得比特幣的本質。

3.1 比特幣的交易

比特幣的交易過程其實就是不停地創造區塊的過程。我們先看一個簡單模式的賬簿,盡管比特幣並不使用這種模式,但有助於我們理解比特幣的賬簿模式。

建立一個以賬戶為核心的系統,可以創造新的幣並放入某人的賬號中,然後就可以轉給其他人了。這樣的話,交易的信息如下圖所示:

技術分享圖片

3.1.1 基於賬戶的賬簿中保存的交易信息

這樣就會帶來一個問題,當別人要驗證一筆交易是否有效,就必須跟蹤每一個賬戶的余額。這樣是相當麻煩的,而且還增加了記賬的工作量。

在比特幣系統中,每個交易都有輸入值輸出值。輸入可以看成是將被消費掉的幣(這些幣是前一個交易創造出來的,所以我們回想一下第2章中提到的兩類不同的哈希指針),輸出可以看成是在本次交易中創造出來的幣。每一筆交易都有一個獨一無二的ID。每一筆交易可以有多個輸出,輸出的索引從0開始,所以我們稱第一個輸出為“輸出0”。

現在讓我們來看一下如下圖所示的賬本。

技術分享圖片

3.1.2 與比特幣類似的基於交易的賬本

交易1是鑄造新幣的交易,沒有輸入,有1個輸出,輸出0:轉25個幣給Alice

交易21個輸入,2個輸出,輸入來源於交易1的輸出0,而本次交易的輸出0:轉17個幣給Bob,輸出1

:轉8個幣給自己。由Alice簽名。

其他的交易類似。

地址轉換。在這個例子中,為何Alice需要把幣轉給自己?事實上,一個交易中輸出的幣,要麽在另一個交易中被完全消費掉,要麽一個都不被消費,不存在只消費部分的情況。在交易2中,Alice只需要轉17個幣給Bob,那剩下的8個幣就需要轉到屬於她自己的另一個地址上。

有效驗證。在這個例子中,我們要核查Alice引用的交易輸出,確認她確實有25個幣沒有花掉。為此,我們只需要從Alice所引用的交易開始,一直核查到賬本上最新的交易記錄即可。當然,在這裏,Alice還是花費了一部分幣。註意,這和前面所說的不存在部分消費的情況是不一樣的,前面說的是在一筆交易中,輸入可以被分為兩部分:一部分用於Alice

Bob的交易,另一部分是轉給了Alice自己,以保證輸入等於輸出。事實上,在比特幣中,還應該有另一部分,用於作為生成區塊節點的交易費,否則,提議下一個區塊的節點可能不會考慮把這個交易打包到區塊中。所以我們可能會聽到,在比特幣中,輸入應該大於輸出,其實指的是,輸出不包含交易費,也就說,輸入=輸出+交易費

資金合並。假設Bob在兩筆不同的交易中分別收到17個幣和2個幣,現在他想把這兩筆錢合並起來,那麽,他只需要發起以一個交易,交易裏有兩個輸入和一個輸出,輸出地址是他自己的地址,這樣,就把兩個交易合二為一了。

共同支付CarolBob想要共同支付給David,他們可以發起一個交易,交易有兩個輸入和一個輸出。不同的是,兩個輸入所引用的“上一筆交易”的輸出地址不同,因此,這筆交易需要兩個簽名:Carol的和Bob的。

交易語法。比特幣交易涉及的概念就是以上這些。下面看看比特幣在底層是如何實現的。實際上,比特幣在網絡上傳輸的數據結構都是一串字符,使用json格式。

技術分享圖片

3.1.3 一個真實的比特幣交易程序段

  • 元數據。存放一些內部處理信息。主要有:hash:這筆交易本身的哈希值,也就是獨一無二的IDvin_sz:輸入數量;vout_sz:輸出數量;lock_time:鎖定時間;size:這筆交易的規模。其他的一些字段根據應用需求添加。
  • 輸入in:輸入的json數組。數組中每個輸入包括的內容為:prev_out:上一筆交易的哈希值(hash)+索引(n),用於指明這個輸入是來自於哪一筆交易的哪一個輸出scriptSig:輸入腳本(有的書上稱為“解鎖腳本”),包含這筆交易創建者的簽名(私鑰簽名)及其公鑰。
  • 輸出out:輸出的json數組。數組中每個輸出包含的內容為:value:輸出金額;scriptPubKey:輸出腳本(有的書上稱為“鎖定腳本”),包含接收者的公鑰(或公鑰的哈希值)及一些腳本指令。註意,所有輸出的金額之和必須小於等於輸入的金額之和,如果輸出小於輸入,那麽這個差額就作為交易費付給為這筆交易記賬的礦工。

3.2 比特幣的腳本

最常見的比特幣交易,就是通過某人的簽名去取得他在前一筆交易(可能是別人發起的向他轉賬的交易,或自己發起的轉到自己地址上的交易如資金合並)中獲得的資金,因此,一個交易的輸出應該這樣描述:憑借哈希值為X的公鑰,以及這個公鑰所有者的簽名,才可以獲得這筆資金

仔細體會這句話,我們就能理解比特幣的輸入腳本和輸出腳本之間的關系。

假設Alice想要創建一筆交易,來花費掉Bob轉給她的錢。讓我們從Bob創建的交易(也就是Alice引用的上一筆交易)的輸出腳本(即鎖定腳本)說起。Bob在輸出腳本中放入Alice的公鑰的哈希值,並且指定一條規則:為了確保來取這筆錢的是Alice本人,那麽必須滿足兩個條件:正確的公鑰哈希值,真正的Alice簽名。當然,這條規則是由輸出腳本中的多個指令來完成的。現在,讓我們來看一下Alice創建的這筆交易到底有沒有資格獲得這筆錢,或者更直接地說,這筆交易是否正當有效。為此,Alice在輸入腳本(即解鎖腳本)中放入自己的公鑰,並用私鑰對整個交易簽名。

Alice創建了這筆交易並進行廣播之後,其他節點就可以對這筆交易進行驗證了。驗證的方式是把Alice這筆交易的輸入腳本和Alice引用的前一筆交易(即Bob創建並簽名的交易)的輸出腳本合在一起形成串聯腳本,並執行這個腳本來驗證這筆交易是否正當有效。下圖是一個串聯腳本的範例。

技術分享圖片

3.2.1 結合輸入腳本和輸出腳本的腳本範例

比特幣腳本語言是堆棧式的,每個指令只執行一次。執行比特幣腳本只能產生兩種結果:要麽被成功執行,表示交易有效;要麽出現錯誤,整個交易無效,拒絕記入區塊鏈。下面讓我們看一下上圖腳本的執行結果是如何的。

尖括號<>裏的是數據指令,每執行一個數據指令,就在堆棧的頂端添加一個數據。OP開頭的是工作碼指令,執行一些操作。下圖是比特幣腳本執行的堆棧狀態圖。

技術分享圖片

3.2.2 比特幣腳本執行的堆棧狀態圖

結合AliceBob的例子,看看比特幣腳本的執行如何確認交易是否正當有效。

  • <sig><pubKey>:把Alice提供的簽名和公鑰入棧。
  • OP_DUP:復制Alice提供的公鑰,入棧。
  • OP_HASH160:計算Alice提供的公鑰的哈希值,替換公鑰並入棧。
  • <pubKeyHash?>Bob指定的公鑰哈希值入棧。
  • OP_EQUALVERIFY:(應該是沒有尖括號的)比較Alice提供的和Bob指定的公鑰哈希值。如果相等,兩個公鑰哈希值出棧,繼續執行;如果不等,提示出錯,整個交易無效。
  • OP_CHECKSIG:驗證Alice提供的簽名,驗證成功,返回true;驗證失敗,返回false。公鑰和簽名出棧,返回結果入棧。回想第一章通用數字簽名方案,使用公鑰驗證簽名。

當然了,一筆交易是否有效,除了上面的驗證,還有一個直觀的條件,就是輸出必須小於等於輸入

實際情況

實際中還會用到其他的一些腳本指令,如多重簽名MULTISIG,支付給腳本的哈希值Pay-to-script-hash(P2SH)。但除此之外,平常用到的腳本指令並不多,每個節點都有一份標準腳本的白名單,它們會拒絕接受不在名單上的腳本。這倒不是說不能執行其他的腳本,只是使用起來有點麻煩。

銷毀證明

銷毀證明(proof of burn)腳本,用於銷毀比特幣。實際應用中主要用來引導客戶使用其他數字貨幣系統,即將比特幣銷毀,以便獲得另一個數字貨幣系統發行的新幣。

支付給腳本的哈希值

如前文所述,比特幣的工作機制要求發送者在交易時必須明確指定輸出腳本(這個輸出腳本將被用於下一筆交易)。但想象一下這樣的場景:你準備購買一件商品,詢問商家“請把付款地址告訴我,我可以付款了”,如果商家使用了多重簽名地址,那他會說“我們使用了多重簽名地址,你需要支付給一個腳本地址,而不是一個簡單的地址”,但是你會說“這個太復雜了,我只會支付給簡單的地址”。

比特幣的一個方法是:收款方告訴付款方“請把比特幣支付給某個腳本地址,其哈希值為xx,在取款時,我會提供上述哈希值對應的腳本,同時,提供數據通過腳本的驗證”。付款方通過P2SH即可實現上述交易。

P2SH不是比特幣的原始設計,是後來加上去的,它解決了兩個重要的問題:1.讓付款方的支付工作簡單化。在上述例子中,商家只需要告訴你一個哈希值即可,你不必關心商家到底用了哪種地址,是否用了多重簽名,因為這是商家在支取這筆款項是需要考慮的事情(註:換個角度考慮,當你作為收款人時,你也需要考慮同樣的問題)2.實現效率上的提升。礦工的工作是追蹤那些還沒有被消費掉的輸出腳本,采用P2SH的輸出腳本就只是一個哈希值而已,所有的復雜性都被放在輸入腳本中了。

3.3 比特幣腳本的應用

本節介紹比特幣腳本的一些應用場景。

第三方支付交易

例如,Alice用比特幣向Bob購買商品,Alice想貨到付款,Bob想見款發貨。該如何處理?一個好的方法是使用第三方支付交易(escrow transaction)第三方支付交易可以用多重簽名MULTISIG來實現。具體如下:Alice不直接付款給Bob,而是發起一個多重簽名的交易,並規定:三個人中有兩人簽名之後,資金才能被支取。這三個人是AliceBob和第三方仲裁員JudyJudy負責調解可能發生的糾紛。這個交易被納入區塊鏈之後,資金被第三方監管,這三個人中的任意兩人可以決定資金的去向。通常情況下,如果AliceBob都是誠實的,Bob會按照Alice的要求發貨,Alice在收貨之後和Bob共同簽名,把資金轉給Bob

但如果Bob並未發貨,或者貨物中途丟失,或者Bob發的貨不是Alice想要的。這時,Alice不想支付給Bob,而是想從第三方監管賬戶中取回來。那麽,Alice不會簽名完成真正付款,而Bob也不會承認問題而主動放棄收款,這時,就需要Judy的仲裁。如果Judy認為Alice被騙了,那麽就和Alice共同簽名,把資金退回給Alice;如果Judy認為Bob應該收款,那麽就和Bob共同簽名,把資金轉給Bob

綠色地址

綠色地址(green addresses):假設AliceBob的店裏買了一根熱狗,並且向Bob支付了一定的比特幣。一般來說,一個交易需要獲得6次確認,我們才能確信它已經確實被加入到區塊鏈中,但是這大約需要一個小時,這對於僅僅是買一根熱狗來說是不可接受的。

為了解決這個問題,比特幣采用第三方銀行的做法。實際上,“銀行”可能是某個交易所或金融機構。當AliceBob轉賬時,銀行會從Alice的賬號中扣錢,然後從銀行控制的某個賬戶——稱之為“綠色賬戶”,轉給Bob,而且,銀行保證不會雙重支付這筆錢,如果Bob也相信這一點,那麽當他看到銀行簽名的交易時,就可以為Alice提供服務,即給Alice一根熱狗,因為Bob相信,最終會收到銀行轉給他的這筆錢,只要區塊鏈確認銀行簽名的這筆交易。

註意,這不是比特幣系統技術的保證,而是現實世界中銀行的保證。銀行為了保護它的聲譽,不會雙重支付比特幣。

高效小額支付(efficient micro-payments)

假設AliceBob的客戶,需要持續向Bob支付小額費用,如BobAlice的手機流量提供商,根據Alice每分鐘使用的流量計費。但是,每分鐘支付一次是不現實的,即使技術上可行,交易手續費也讓人吃不消。

一個有效的做法是,把每分鐘的費用累積起來,最後一次性支付。實現的方式是:Alice先發起一個MULTISIG交易,把可能花費的最大金額轉到MULTISIG地址上,但這個交易需要AliceBob共同簽名才能生效。Alice在使用流量時,每隔一分鐘創建和簽名一次交易,交易的輸入是前面發起的MULTISIG交易的輸出,表示要支取這筆款項,一部分用於向Bob支付這分鐘所產生的流量費用,剩余的錢轉給自己。每分鐘充重復一次,直到掛機。這樣,一系列的交易就產生了,這些交易中付給Bob的錢是逐漸遞增的,表示累積的流量費用,而返還給Alice的錢就越來越少。最後,Bob會在Alice發起的最後一個交易上進行簽名,把它放入區塊鏈中,這樣就完成了AliceBob一次性支付流量費用。值得註意的是,除了最後一個交易外,所有之前的交易都是我們主動創建的雙重支付交易,即都來源於前面發起的MULTISIG交易的輸出。實際上,如果雙方都是正常的,那麽Bob只會在最後一個交易上簽名,我們不會在區塊鏈上看到其他中間產生的雙重支付交易。

但是,有一個問題:如果Bob拒絕在最後一個交易上簽名呢?Bob可能會說“就讓這些比特幣留在第三方托管地址中吧”。這樣,Alice就會失去她一開始轉到MULTISIG地址的所有比特幣。解決這個問題的方法是,前面提到的一個代碼——鎖定時間。

鎖定時間(lock_time)

在開始小額支付之前,AliceBob要達成一個約定,在鎖定時間到了之後,如果Bob還沒有簽名,那麽就把所有的比特幣從MULTISIG地址返回給Alice。這樣,Alice先發起一筆交易將比特幣轉到MULTISIG地址,即轉到第三方托管地址。隨後,Alice主動創建一系列小額雙重支付交易,用於累積流量費用。最後,當Alice簽名最後一筆交易之後,再發起一筆交易將比特幣從MULTISIG地址轉給自己,即退款交易,且設定了鎖定時間t>0。退款交易中的鎖定時間,是告訴礦工在記賬的時候,要等待t時間之後才能把這筆交易放入區塊鏈。想象一下,如果在給定的時間裏,Bob沒有簽名最後一筆交易,那麽退款交易就被執行,從而把MULTISIG地址中的所有比特幣轉回給Alice。如果Bob簽名了最後一筆交易,那麽MULTISIG中將不會有多余的比特幣(Alice創建的每一個小額支付交易有兩個輸出:一個支付流量費,一個返回給Alice),退款交易也就無效了,因為輸入的比特幣是0

3.4 比特幣的區塊

在比特幣系統中,所有的交易都被打包放入區塊,因為如果每一個交易都由礦工單獨去共識,那整個系統的交易處理速度將變得非常慢。

區塊鏈把兩個基於哈希值的數據結構結合起來:1.區塊的哈希鏈,每個區塊都有一個區塊頭,裏面有一個哈希指針指向上一個區塊;2.默克爾樹(也翻譯成梅克爾樹),以樹狀結構把區塊內所有交易的哈希值進行排列存儲。為了證明某個交易在某個區塊內,可以通過樹內路徑來進行搜索。如下圖所示。

技術分享圖片

3.4.1 比特幣區塊鏈中的兩個哈希結構

區塊頭部還包含了挖礦謎題的相關信息。區塊頭部的哈希函數必須以一大堆零開頭才有效,還包含一個礦工可以修改的“臨時隨機數nonce”、一個時間戳、一個點數(用來表示挖礦難度),以及交易樹的樹根。

每一個區塊的默克爾樹上都有一筆特殊的交易,稱為“幣基交易”,這個交易創造新的比特幣,如下圖所示,和普通交易有幾個區別:

  • 永遠只有一個輸入和一個輸出。
  • 不消費之前交易輸出的比特幣,即沒有指針指向“上一個交易”。
  • 輸出值目前大約是25個幣多一點點,這個輸出就是礦工的挖礦收入。包含兩部分:第一部分是獎勵的25個比特幣(前面提到每產生21萬個比特幣——大約間隔4年——獎勵減半),第二部分是所有交易的交易費。
  • 有一個特別的參數:“幣基”參數,礦工可以放入任何值。

技術分享圖片

3.4.2 幣基交易

3.5 比特幣網絡

比特幣網絡是一個點對點網絡,沿用了很多已有的點對點網絡理論。比特幣網絡運行在TCP網絡上,有任意的網絡拓撲結構,每個節點和其他的隨機節點相連,新節點可以隨時加入,舊節點可以隨時離開,只要一個節點有3個小時沒有音訊,就會慢慢地被其他節點忘記,通過這種方式,網絡非常緩和地處理節點下線問題。

交易的傳播

當你啟動一個新節點,你先向一個你知道的節點(稱為種子節點)發送簡單的消息,詢問是否還知道其他節點,然後在鏈接到一個新節點後,重復多次,直到隨機連接了一些節點,這樣,你就加入了比特幣網絡。

加入網絡是為了維護區塊鏈。當我們發起一個交易,我們就通過泛洪(flooding)算法完成向整個網絡的傳播過程。假設Alice要轉賬給Bob,她的客戶端發起一個交易,然後把這筆交易告知所有和她的客戶端相連的其他節點,其他節點會對這個交易進行一系列的核驗,決定是否接受並轉播這筆交易。如果核驗通過,這些節點就把這筆交易傳播給與其相連的其他節點,並把這筆交易放入自己的交易池中。如果節點接收到的交易已經在交易池中,就不會再次把它傳播出去,這樣就確保了泛洪協議會自動終結,而不是讓一個交易在網絡中循環傳播。

節點核驗新交易信息時,有四大關卡:1.最重要的一個驗證,驗證交易在當前區塊鏈中是有效的,通過運行比特幣腳本來確保;2.檢查是否有雙重支付;3.如前所述,檢查交易是否已經在交易池中,因為每一筆交易都有一個獨一無二的哈希值,所以很容易查詢到交易是否已經存在;4.節點只會接收和傳遞在白名單上的標準腳本。

由於網絡傳遞有延遲,每個節點的交易池不盡相同。考慮雙重支付的場景:假設Alice想把同一個幣支付給BobCharlie,她幾乎同時發起兩筆交易,Alice->BobAlice->Charlie,並廣播給其他節點。有些節點先聽到Alice->Bob,有些節點則先聽到Alice->Charlie。當節點接收到任何一個,那麽就把它放入交易池,之後,這個節點聽到另一個交易,看上去像是雙重支付交易,那麽就會丟棄。結果就是眾多節點會對“哪一個交易應該被納入區塊鏈”產生分歧。這種情況稱為競態條件(race condition)(也可以理解為紊亂)。

這種分歧將有礦工打破,礦工會決定哪個交易最終被打包進入區塊。無論哪一個被打包進區塊,節點都會從交易池中刪除相應的交易,因為節點收到的要麽是雙重支付交易,應該剔除;要麽是已經被放入區塊的交易,沒有必要再保留在交易池中,因此從交易池中刪除。

區塊的傳播

前面是交易的傳播,下面看看區塊的傳播。區塊傳播和交易傳播類似,同樣受到競態條件的限制。如果兩個有效區塊同時被挖到,只有其中一個區塊可以進入長期共識鏈,哪個被最終納入長期共識鏈取決於其他節點選擇在哪個區塊上擴展區塊鏈,未被納入的一個即被丟棄(成為孤塊)。

核驗一個區塊要比核驗一個交易復雜得多,除了確認區塊頭,確定裏面的哈希值滿足哈希謎題,節點還必須確認區塊中的每個交易。最後,一個節點往外傳播的區塊必須是最長的一條區塊鏈上新加入的區塊(當然,“最長的區塊鏈”取決於節點對區塊鏈當前狀態的認識)。

網絡大小

比特幣網絡大小很難測量,因為它隨時變化。

存儲空間需求

完全有效的節點(簡稱全節點)必須永久在線,才能接收到所有的交易數據。一個節點離線越久,當它重新連接到網絡後,就需要更多時間來更新所有的交易數據。這些節點還需把完整的共識區塊鏈都存儲下來。目前存儲空間大約幾十個GB,一臺臺式機就能滿足需求。

完全有效的節點必須維護在交易中產生的(交易的輸出)、未被消費掉的比特幣的完整列表,這個列表最好存放在內存而非硬盤,這樣,在接收到一個交易信息時,節點就可以快速查看、運行腳本,驗證簽名是否有效,然後把交易放入交易池中。目前,一般的電腦都能滿足內存的需求。

輕量節點

除了完全有效的節點,還有一種輕量節點(nightweight nodes),或者稱為輕客戶端,也叫簡單付款驗證(Simple Payment Verification,SPV)客戶端。實際上,比特幣系統中大部分的節點都是輕量節點,它們不會存儲整個區塊鏈,而是只存儲所關心的、需要進行核驗的部分交易及區塊頭。

一個SPV節點的安全等級遠不如全節點。但是,作為一個SPV節點可以節省很多空間,區塊頭部的大小只是整個區塊鏈的千分之一,因此只需幾十MB即可,甚至一部智能手機也能成為比特幣網絡的一個輕量節點。

3.6 限制和優化

這部分主要討論由於比特幣初始設計帶來的一些局限性以及如何優化,包括由於區塊容量太小帶來的交易處理速度太慢,如何解決硬分叉和軟分叉等問題。

區塊鏈: 技術驅動金融 - 第3章 比特幣的運行機制