這一章將真正近距離地瞭解比特幣所使用的數據結構、實際腳本及語言,會有大量的細節性信息,極具挑戰性,但本章能夠幫助咱們真正懂得比特幣的本質。node
3.1 比特幣的交易算法
比特幣的交易過程其實就是不停地創造區塊的過程。咱們先看一個簡單模式的帳簿,儘管比特幣並不使用這種模式,但有助於咱們理解比特幣的帳簿模式。json
創建一個以帳戶爲核心的系統,能夠創造新的幣並放入某人的帳號中,而後就能夠轉給其餘人了。這樣的話,交易的信息以下圖所示:數組
圖3.1.1 基於帳戶的帳簿中保存的交易信息安全
這樣就會帶來一個問題,當別人要驗證一筆交易是否有效,就必須跟蹤每個帳戶的餘額。這樣是至關麻煩的,並且還增長了記帳的工做量。網絡
在比特幣系統中,每一個交易都有輸入值和輸出值。輸入能夠當作是將被消費掉的幣(這些幣是前一個交易創造出來的,因此咱們回想一下第2章中提到的兩類不一樣的哈希指針),輸出能夠當作是在本次交易中創造出來的幣。每一筆交易都有一個獨一無二的ID。每一筆交易能夠有多個輸出,輸出的索引從0開始,因此咱們稱第一個輸出爲「輸出0」。數據結構
如今讓咱們來看一下以下圖所示的帳本。函數
圖3.1.2 與比特幣相似的基於交易的帳本區塊鏈
交易1是鑄造新幣的交易,沒有輸入,有1個輸出,輸出0:轉25個幣給Alice。優化
交易2有1個輸入,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個幣,如今他想把這兩筆錢合併起來,那麼,他只須要發起以一個交易,交易裏有兩個輸入和一個輸出,輸出地址是他本身的地址,這樣,就把兩個交易合二爲一了。
共同支付。Carol和Bob想要共同支付給David,他們能夠發起一個交易,交易有兩個輸入和一個輸出。不一樣的是,兩個輸入所引用的「上一筆交易」的輸出地址不一樣,所以,這筆交易須要兩個簽名:Carol的和Bob的。
交易語法。比特幣交易涉及的概念就是以上這些。下面看看比特幣在底層是如何實現的。實際上,比特幣在網絡上傳輸的數據結構都是一串字符,使用json格式。
圖3.1.3 一個真實的比特幣交易程序段
3.2 比特幣的腳本
最多見的比特幣交易,就是經過某人的簽名去取得他在前一筆交易(多是別人發起的向他轉帳的交易,或本身發起的轉到本身地址上的交易如資金合併)中得到的資金,所以,一個交易的輸出應該這樣描述:憑藉哈希值爲X的公鑰,以及這個公鑰全部者的簽名,才能夠得到這筆資金。
仔細體會這句話,咱們就能理解比特幣的輸入腳本和輸出腳本之間的關係。
假設Alice想要建立一筆交易,來花費掉Bob轉給她的錢。讓咱們從Bob建立的交易(也就是Alice引用的上一筆交易)的輸出腳本(即鎖定腳本)提及。Bob在輸出腳本中放入Alice的公鑰的哈希值,而且指定一條規則:爲了確保來取這筆錢的是Alice本人,那麼必須知足兩個條件:正確的公鑰哈希值,真正的Alice簽名。固然,這條規則是由輸出腳本中的多個指令來完成的。如今,讓咱們來看一下Alice建立的這筆交易到底有沒有資格得到這筆錢,或者更直接地說,這筆交易是否正當有效。爲此,Alice在輸入腳本(即解鎖腳本)中放入本身的公鑰,並用私鑰對整個交易簽名。
當Alice建立了這筆交易並進行廣播以後,其餘節點就能夠對這筆交易進行驗證了。驗證的方式是把Alice這筆交易的輸入腳本和Alice引用的前一筆交易(即Bob建立並簽名的交易)的輸出腳本合在一塊兒造成串聯腳本,並執行這個腳原本驗證這筆交易是否正當有效。下圖是一個串聯腳本的範例。
圖3.2.1 結合輸入腳本和輸出腳本的腳本範例
比特幣腳本語言是堆棧式的,每一個指令只執行一次。執行比特幣腳本只能產生兩種結果:要麼被成功執行,表示交易有效;要麼出現錯誤,整個交易無效,拒絕記入區塊鏈。下面讓咱們看一下上圖腳本的執行結果是如何的。
尖括號<>裏的是數據指令,每執行一個數據指令,就在堆棧的頂端添加一個數據。OP開頭的是工做碼指令,執行一些操做。下圖是比特幣腳本執行的堆棧狀態圖。
圖3.2.2 比特幣腳本執行的堆棧狀態圖
結合Alice和Bob的例子,看看比特幣腳本的執行如何確認交易是否正當有效。
固然了,一筆交易是否有效,除了上面的驗證,還有一個直觀的條件,就是輸出必須小於等於輸入。
實際狀況
實際中還會用到其餘的一些腳本指令,如多重簽名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,而是發起一個多重簽名的交易,並規定:三我的中有兩人簽名以後,資金才能被支取。這三我的是Alice、Bob和第三方仲裁人Judy。Judy負責調解可能發生的糾紛。這個交易被歸入區塊鏈以後,資金被第三方監管,這三我的中的任意兩人能夠決定資金的去向。一般狀況下,若是Alice和Bob都是誠實的,Bob會按照Alice的要求發貨,Alice在收貨以後和Bob共同簽名,把資金轉給Bob。
但若是Bob並未發貨,或者貨物中途丟失,或者Bob發的貨不是Alice想要的。這時,Alice不想支付給Bob,而是想從第三方監管帳戶中取回來。那麼,Alice不會簽名完成真正付款,而Bob也不會認可問題而主動放棄收款,這時,就須要Judy的仲裁。若是Judy認爲Alice被騙了,那麼就和Alice共同簽名,把資金退回給Alice;若是Judy認爲Bob應該收款,那麼就和Bob共同簽名,把資金轉給Bob。
綠色地址
綠色地址(green addresses):假設Alice在Bob的店裏買了一根熱狗,而且向Bob支付了必定的比特幣。通常來講,一個交易須要得到6次確認,咱們才能確信它已經確實被加入到區塊鏈中,可是這大約須要一個小時,這對於僅僅是買一根熱狗來講是不可接受的。
爲了解決這個問題,比特幣採用第三方銀行的作法。實際上,「銀行」多是某個交易所或金融機構。當Alice向Bob轉帳時,銀行會從Alice的帳號中扣錢,而後從銀行控制的某個帳戶——稱之爲「綠色帳戶」,轉給Bob,並且,銀行保證不會雙重支付這筆錢,若是Bob也相信這一點,那麼當他看到銀行簽名的交易時,就能夠爲Alice提供服務,即給Alice一根熱狗,由於Bob相信,最終會收到銀行轉給他的這筆錢,只要區塊鏈確認銀行簽名的這筆交易。
注意,這不是比特幣系統技術的保證,而是現實世界中銀行的保證。銀行爲了保護它的聲譽,不會雙重支付比特幣。
高效小額支付(efficient micro-payments)
假設Alice是Bob的客戶,須要持續向Bob支付小額費用,如Bob是Alice的手機流量提供商,根據Alice每分鐘使用的流量計費。可是,每分鐘支付一次是不現實的,即便技術上可行,交易手續費也讓人吃不消。
一個有效的作法是,把每分鐘的費用累積起來,最後一次性支付。實現的方式是:Alice先發起一個MULTISIG交易,把可能花費的最大金額轉到MULTISIG地址上,但這個交易須要Alice和Bob共同簽名才能生效。Alice在使用流量時,每隔一分鐘建立和簽名一次交易,交易的輸入是前面發起的MULTISIG交易的輸出,表示要支取這筆款項,一部分用於向Bob支付這分鐘所產生的流量費用,剩餘的錢轉給本身。每分鐘充重複一次,直到掛機。這樣,一系列的交易就產生了,這些交易中付給Bob的錢是逐漸遞增的,表示累積的流量費用,而返還給Alice的錢就愈來愈少。最後,Bob會在Alice發起的最後一個交易上進行簽名,把它放入區塊鏈中,這樣就完成了Alice向Bob一次性支付流量費用。值得注意的是,除了最後一個交易外,全部以前的交易都是咱們主動建立的雙重支付交易,即都來源於前面發起的MULTISIG交易的輸出。實際上,若是雙方都是正常的,那麼Bob只會在最後一個交易上簽名,咱們不會在區塊鏈上看到其餘中間產生的雙重支付交易。
可是,有一個問題:若是Bob拒絕在最後一個交易上簽名呢?Bob可能會說「就讓這些比特幣留在第三方託管地址中吧」。這樣,Alice就會失去她一開始轉到MULTISIG地址的全部比特幣。解決這個問題的方法是,前面提到的一個代碼——鎖定時間。
鎖定時間(lock_time)
在開始小額支付以前,Alice和Bob要達成一個約定,在鎖定時間到了以後,若是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」、一個時間戳、一個點數(用來表示挖礦難度),以及交易樹的樹根。
每個區塊的默克爾樹上都有一筆特殊的交易,稱爲「幣基交易」,這個交易創造新的比特幣,以下圖所示,和普通交易有幾個區別:
圖3.4.2 幣基交易
3.5 比特幣網絡
比特幣網絡是一個點對點網絡,沿用了不少已有的點對點網絡理論。比特幣網絡運行在TCP網絡上,有任意的網絡拓撲結構,每一個節點和其餘的隨機節點相連,新節點能夠隨時加入,舊節點能夠隨時離開,只要一個節點有3個小時沒有音訊,就會慢慢地被其餘節點忘記,經過這種方式,網絡很是緩和地處理節點下線問題。
交易的傳播
當你啓動一個新節點,你先向一個你知道的節點(稱爲種子節點)發送簡單的消息,詢問是否還知道其餘節點,而後在連接到一個新節點後,重複屢次,直到隨機鏈接了一些節點,這樣,你就加入了比特幣網絡。
加入網絡是爲了維護區塊鏈。當咱們發起一個交易,咱們就經過泛洪(flooding)算法完成向整個網絡的傳播過程。假設Alice要轉帳給Bob,她的客戶端發起一個交易,而後把這筆交易告知全部和她的客戶端相連的其餘節點,其餘節點會對這個交易進行一系列的核驗,決定是否接受並轉播這筆交易。若是覈驗經過,這些節點就把這筆交易傳播給與其相連的其餘節點,並把這筆交易放入本身的交易池中。若是節點接收到的交易已經在交易池中,就不會再次把它傳播出去,這樣就確保了泛洪協議會自動終結,而不是讓一個交易在網絡中循環傳播。
節點核驗新交易信息時,有四大關卡:1.最重要的一個驗證,驗證交易在當前區塊鏈中是有效的,經過運行比特幣腳原本確保;2.檢查是否有雙重支付;3.如前所述,檢查交易是否已經在交易池中,由於每一筆交易都有一個獨一無二的哈希值,因此很容易查詢到交易是否已經存在;4.節點只會接收和傳遞在白名單上的標準腳本。
因爲網絡傳遞有延遲,每一個節點的交易池不盡相同。考慮雙重支付的場景:假設Alice想把同一個幣支付給Bob和Charlie,她幾乎同時發起兩筆交易,Alice->Bob好Alice->Charlie,並廣播給其餘節點。有些節點先聽到Alice->Bob,有些節點則先聽到Alice->Charlie。當節點接收到任何一個,那麼就把它放入交易池,以後,這個節點聽到另外一個交易,看上去像是雙重支付交易,那麼就會丟棄。結果就是衆多節點會對「哪個交易應該被歸入區塊鏈」產生分歧。這種狀況稱爲競態條件(race condition)(也能夠理解爲紊亂)。
這種分歧將有礦工打破,礦工會決定哪一個交易最終被打包進入區塊。不管哪個被打包進區塊,節點都會從交易池中刪除相應的交易,由於節點收到的要麼是雙重支付交易,應該剔除;要麼是已經被放入區塊的交易,沒有必要再保留在交易池中,所以從交易池中刪除。
區塊的傳播
前面是交易的傳播,下面看看區塊的傳播。區塊傳播和交易傳播相似,一樣受到競態條件的限制。若是兩個有效區塊同時被挖到,只有其中一個區塊能夠進入長期共識鏈,哪一個被最終歸入長期共識鏈取決於其餘節點選擇在哪一個區塊上擴展區塊鏈,未被歸入的一個即被丟棄(成爲孤塊)。
覈驗一個區塊要比覈驗一個交易複雜得多,除了確認區塊頭,肯定裏面的哈希值知足哈希謎題,節點還必須確認區塊中的每一個交易。最後,一個節點往外傳播的區塊必須是最長的一條區塊鏈上新加入的區塊(固然,「最長的區塊鏈」取決於節點對區塊鏈當前狀態的認識)。
網絡大小
比特幣網絡大小很難測量,由於它隨時變化。
存儲空間需求
徹底有效的節點(簡稱全節點)必須永久在線,才能接收到全部的交易數據。一個節點離線越久,當它從新鏈接到網絡後,就須要更多時間來更新全部的交易數據。這些節點還需把完整的共識區塊鏈都存儲下來。目前存儲空間大約幾十個GB,一臺臺式機就能知足需求。
徹底有效的節點必須維護在交易中產生的(交易的輸出)、未被消費掉的比特幣的完整列表,這個列表最好存放在內存而非硬盤,這樣,在接收到一個交易信息時,節點就能夠快速查看、運行腳本,驗證簽名是否有效,而後把交易放入交易池中。目前,通常的電腦都能知足內存的需求。
輕量節點
除了徹底有效的節點,還有一種輕量節點(nightweight nodes),或者稱爲輕客戶端,也叫簡單付款驗證(Simple Payment Verification,SPV)客戶端。實際上,比特幣系統中大部分的節點都是輕量節點,它們不會存儲整個區塊鏈,而是隻存儲所關心的、須要進行覈驗的部分交易及區塊頭。
一個SPV節點的安全等級遠不如全節點。可是,做爲一個SPV節點能夠節省不少空間,區塊頭部的大小隻是整個區塊鏈的千分之一,所以只需幾十MB便可,甚至一部智能手機也能成爲比特幣網絡的一個輕量節點。
3.6 限制和優化
這部分主要討論因爲比特幣初始設計帶來的一些侷限性以及如何優化,包括因爲區塊容量過小帶來的交易處理速度太慢,如何解決硬分叉和軟分叉等問題。