從事C++服務器開發六年多了,主要是做並發服務器和游戲相關開發,區塊鏈技術新興起,自己也是很感興趣,我是零基礎學區塊鏈的,給自己設定了一個規划,先讀一讀區塊鏈相關的基礎和概念,以及基本算法,然后用成熟的引擎做一個demo,接下來不斷深入學習。
什么是區塊鏈?
一兩句話很難解釋清楚,至少我自己還不能概括的很全面。我自己的理解是區塊鏈技術包含了很多功能,如點對點傳輸,分布式數據存儲,利用加密和共識算法實現數據的統一。區塊鏈是多個技術的合理應用和創新,我覺得應該在以后的學習中不斷去理解。
什么是比特幣?
比特幣是區塊鏈比較成熟的應用,比特幣依賴於區塊鏈技術。
區塊鏈的基本構成
區塊鏈由一個個區塊按照規律鏈接構成,每個區塊中包含了很多交易。區塊鏈的形成過程:交易是簽過名的數據結構,該數據結構會在區塊鏈網絡中廣播,並且被收集到區塊中,然后區塊會在區塊鏈網絡中廣播,一個區塊引用上一個區塊從而形成區塊鏈。區塊鏈包括成千上萬個區塊, 而一個區塊內又包含一個或多個交易, 上下關聯的交易組成了一個交易鏈, 一個交易鏈內部可能又包含了多個交易。
4 比特幣地址
比特幣地址通常代表收款方,代表一對公鑰和私鑰的所有者,也可以代表其他。比特幣地址是由數字和字母組成的字符串,由公鑰生成的比特幣地址以數字“1”開頭。比特幣地址有公鑰經過Hash函數生成
交易的原理
交易實質上是包含一組輸入列表和輸出列表的數據結構,交易包含的結構如下:
一個交易的輸入和輸出樣式:
Input表示交易的輸入,一個交易的輸入引用的是另一個交易的輸出,該交易的輸入從交易
f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6的0號輸出中導入了50個比特幣, 然后該輸出發送50個比特幣到一個比特幣地址的公鑰Hash值,404371705fa9bd789a2fcd52d2c580b65d35549d。同樣道理,如果接受者想花掉50比特幣,需要用output作為自己交易的輸入。輸入是對其他交易的輸出的引用,一個交易可能有很多輸入,Previous tx是以前交易的Hash值,Index是被引用交易的特定輸出號,
ScriptSig是腳本的簽名, Scriptpubkey是腳本的公鑰,公鑰屬於交易輸出的收款人, 並且表明交易創建者允許收款人獲得的輸出金額; 另一個部分是ECDSA簽名, 是通過對交易的Hash值進行ECDSA簽名而得到的。 簽名和公鑰一起, 證明原地址的真正所有者創建了該支付交易。
Value表示支付的金額,1BTC=100000000聰。
找零問題
當需要支付的金額小於可用余額時, 在交易信息中必須告訴比特幣網絡零錢將要被發送至哪個地址,即“找零地址”。 找零地址可能是也可能不是原先的發送地址。地址A發起付款到地址B, 但此時將找零地址更改為新生成的地址C,此時能保證交易的安全性。
如果找零地址不是C而是A,很多情況會被人追蹤到一筆交易的流程。
上圖中交易流程很難被推斷出來。
區塊和區塊鏈
數據會以文件的形式被永久記錄, 我們稱這些文件為區塊。 一個區塊是一些或所有最新比特幣交易的記錄集, 且未被其他先前的區塊記錄。 新區塊會被加入到記錄的最后, 一旦寫上, 就再也不能改變或刪除。
區塊結構:
hashPrevBlock值為前一個塊的256位Hash值,也就是前一個塊的hashMerkleRoot。該字段使得各個區塊之間可以連接起來,形成一個巨大的“鏈條”。 每個區塊都必須要指向前一個區塊, 否則無法通過驗證。 這個區塊鏈條會一直追溯到源頭, 也就是指向創世區塊。 創世區塊的hashPrevBlock的值為零或為空。 在區塊頭中, 隨機數Nonce對每個塊唯一。求Nonce沒有固定的算法,Nonce有很多值,找到一個滿足條件即可。所以要求不斷嘗試找到合適的Nonce為止,這個過程就是挖礦。
區塊內包含許多交易, 它們通過Merkle根節點間接被散列, 以保證礦工能及時追蹤一個正在打包的區塊內交易的變化情況。 一旦生成Merkle根節點, 那么對包含一個交易的區塊做散列所花的時間,對包含1萬個交易的區塊做散列所花的時間是一樣的。目標Hash值的壓縮格式是一個特殊的浮點編碼類型, 首字節是指數 , 后3個字節是尾數, 它能表示256位的數值。
一個區塊頭的SHA-256(一種單向函數的算法, 可形成長度為256位的串) 值必定要小於或等於目標Hash值, 該區塊才能被網絡所接受。 目標Hash值越低, 產生一個新區塊的難度就越大。Merkle樹是Hash的二叉樹。 在比特幣中會兩次使用SHA-256算法來生成Merkle樹, 如果葉子個數為奇數, 則要重復計算最后一個葉子的兩次SHA-256值, 以達到偶數葉子節點的要求。想象有3個交易, a、 b、 c, 那么Merkle根的生成過程如下所示:
計算a交易的hash值
d1 = dhash(a)
d2 = dhash(b)
d3 = dhash(c)
由於只有3個元素, 是奇數, 因而將最后一個元素重算一次
d4 = dhash(c)
接下來將上面產生的hash值兩兩計算
d5 = dhash(d1 concat d2)
d6 = dhash(d3 concat d4)
d7 = dhash(d5 concat d6)
這里的d7就是以上三個交易的Merkle根。 需要注意的是, Merkle樹的Hash值是小頭位序。
區塊鏈原理和分叉
對於區塊鏈中的任何區塊來說, 只有一條通向創世塊的路徑。 然而, 從創世塊出發, 卻可能有分叉。 當兩個區塊產生的時間僅相差幾秒時, 可能會產生包含一個區塊的分叉。 當出現以上現象時, 礦工節點會根據收到區塊的時間, 在先收到的區塊的基礎上繼續挖礦。 哪個區塊的后續區塊先出現, 那么這個區塊就會被包括進主鏈, 因為這條塊鏈更長。短區塊鏈(或有效區塊鏈) 中的區塊沒有作用, 當比特幣客戶端轉向另一個長區塊鏈時, 短區塊鏈中所有有效的交易都將被重新加入到交易隊列池中, 並被包括到另一個區塊中。 短區塊鏈中的區塊收益不會在長鏈中出現, 因而這些收益實際上是丟失了, 這就是比特幣網絡設定100個區塊成熟時間的原因。
挖礦原理
比特幣的挖礦和節點軟件是基於對等網絡、 數字簽名來發起和驗證交易的。 節點向網絡廣播交易, 這些廣播出來的交易需要經過礦工的驗證, 礦工們會用自己的工作證明結果來表達確認, 確認后的交易會被打包到數據塊中, 數據塊會串起來形成連續的數據塊鏈。
每一個比特幣的節點都會收集所有尚未確認的交易, 並且會將其歸集到一個數據塊中, 這個數據塊將和前面一個數據塊集成在一起。 礦工節點會附加一個隨機調整數, 並計算前一個數據塊的SHA-256 Hash運算值。 挖礦節點不斷進行重復嘗試, 直到它找到的隨機調整數使得產生的
Hash值低於某個特定的目標為止。 由於Hash運算是不可逆的, 因此尋找到符合要求的隨機調整數將會非常困難, 因此需要一個可以預計總次數的不斷試錯的過程。 這時, 工作量證明機制就發揮作用了。 當一個節點找到了符合要求的解, 那么它就可以向全網廣播自己的結果。 其他節點就可以接收這個新解出來的數據塊, 並檢驗其是否符合規則。 只要其他節點通過計算Hash值發現其確實滿足要求(比特幣要求的運算目標) ,那么該數據塊就是有效的, 其他的節點就會接受該數據塊, 並將其附加在自己已有的鏈條之后.
Nonce隨機數通常都不會相同, 但是它以嚴格的線性方式在增長,從0開始, 每次執行散列時都會增長, 當Nonce溢出時(此事經常發生) , 挖礦交易的extraNonce項就會增長, 其將改變Merkle樹的根節點.
挖礦難度
挖礦難度是對挖礦困難程度的度量, 即指計算符合給定目標的一個Hash值的困難程度。 比特幣網絡有一個全局的區塊難度, 有效的區域必須有一個Hash值, 該Hash值必須小於給定的目標Hash值。
難度計算公式如下:
diff = diff_1_target / target
目標值是一個很大的數字,這里出現了一個 diff_1_target,這是常數,是一個很大的數字,這個數字也被稱作是礦池難度,即礦池挖礦時的最大難度。這個最大難度值是標記為0x1d00ffff的數,這個標記是壓縮標記,它的實際值是:
1 |
0x00ffff * 2**(8*(0x1d - 3)) = 0x00000000FFFF00000000000000000000000000000000000000000000000000。 |
計算時,后面三個字節作為底,前面一個字節1d表示的是次方數,最終得出上面這個數字,挖礦時礦池也可以保留的尾數,即
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
而比特幣的挖礦難度,根據上面的公式可知,它和diff_1_target及當前網絡目標值(target)有關,將diff_1_target值代入,得到:
diff = 0x1d00ffff / target
在調整難度的時候,只需要調整target大小即可,target越小,難度越大,反之,難度越小。礦池一般用后面全是FF的值來代表diff_1_target,計算出來的難度就叫做礦池難度pdiff,如果用后面全是00的值來代表diff_1_target,那么計算出來的難度值是比特幣客戶端難度bdiff。這里只是說明,它們有兩個值,代表不同計算方式,實際上,它們計算出來的難度並不會相差很遠。
網絡調整難度的目的,是為了調整出塊的速度保持在平均10分鍾1個塊,每2016個塊作為一個周期調整,這樣剛好2周作為一個周期,如果這2016個塊中,平均出塊速率快過10分鍾1個塊,那么難度將會增大到,維持這個難度的情況下,滿足10分鍾出一個塊的水平。什么時候調整難度呢?由於2016的周期從來沒有變過,那么該周期內還剩余的區塊數量是可以計算出來的:
該周期剩余區塊數量 = 2016 - (當前區塊高度 % 2016)
以當前最新一個塊 482017 為例
當前高度 % 2016 = 482017 % 2016 = 193
該周期剩余區塊數量 = 2016 - 193 = 1823
也即該周期內才出193個區塊,得等到1823個塊以后才會調整難度,對於BCC來說,也是一樣的。
挖礦收益計算
挖礦時,計算出來的區塊哈希值,是要小於當前target值的,這個哈希值是一個范圍很大的值(從0到(2^256)-1),只有靠礦機的暴力破解,才能算出這個值。
diff_1_target,即0x00000000FFFF0000000000000000000000000000000000000000000000000000 , FFFF后面有26個字節,
1 |
即8*26 = 208位,所以diff_1_target又可以寫成 0xffff*(2**208)。 |
腳本系統
腳本操作碼很多,自己去查查吧。下面簡述下腳本工作原理。
以下為三個交易的關系圖,一個交易的輸出是下一個交易的輸入。
交易的關系數據圖:
交易a hash值為
交易b hash值為
交易a的輸出腳本, 若干個腳本指令和轉賬接收方的公鑰Hash
交易b的輸入腳本, 這么一長串只是兩個元素, 簽名和公鑰
首先執行的是輸入腳本。 因為腳本是從左向右執行的, 那么先入棧的是簽名, 隨后是公鑰。 接着, 執行的是輸出腳本。 從左向右執行, 第
一個指令是OP_DUP——復制棧頂元素
OP_HASH160用於計算棧頂元素Hash, 得到pubkeyhash
然后將輸出腳本中的公鑰Hash入棧, 為了與前面計算中所得到的Hash區別開來, 這里稱它為pubkeyhash’
OP_EQUALVERIFY則會檢查棧頂前兩個元素是否相等, 如果相等則繼續執行, 否則中斷執行, 返回失敗
OP_CHECKSIG使用棧頂前兩個元素執行簽名校驗操作, 如果相等, 則返回成功, 否則返回失敗
沒看懂?再來一遍
以著名的Pizza Transaction為例,來驗證一個交易是否是有效的
在交易cca75078…4d79中,唯一的TxIn輸入提供的sigScript是:
1 |
8b4830450221009908144ca6539e09512b9295c8 |
該sigScript實際上由兩部分構成:
簽名:30450221…ee0e01(71字節+1字節簽名類型),實際簽名是去掉最后一個字節01的30450221…ee0e,簽名類型是SIGHASH_ALL(0x01)。
公鑰:042e930f…cabb(65字節)
為了驗證該交易是否有效,我們首先要根據TxIn所聲明的Previous Output Hash:a1075db5…d48d和索引0找到上一筆交易的輸出a1075db5…d48d。
這筆交易輸出的腳本是:
1976a91446af3fb481837fadbb421727f9959c2d32a3682988ac
比特幣的腳本由一系列指令和數據構成,每個指令占用一個字節,數據由數據頭部的長度決定。上述二進制腳本翻譯后的比特幣指令如下
OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
現在,我們有了簽名,公鑰和腳本:
sig: 30450221…ee0e01
pubkey: 042e930f…cabb
script: OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
就可以運行這個腳本來驗證交易是否有效。
比特幣腳本被設計成以棧來運行的虛擬機指令,它只有有限的幾種指令,並且故意被設計成沒有循環、條件跳轉,所以,比特幣腳本不是圖靈完備的語言。
比特幣腳本的執行非常簡單。我們首先要准備一個空棧,然后把簽名和公鑰入棧:
緊接着,我們就可以執行TxOut的腳本:
OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
首先執行OP_DUP,這條指令把棧頂的元素復制一份,所以結果變成:
緊接着執行OP_HASH160,它對棧頂元素計算SHA256/RipeMD160,實際上是計算公鑰Hash,所以運行結果變成
接下來的指令實際上是一個數據,我們直接把數據入棧:
然后,執行OP_EQUALVERIFY,這條指令會比較棧頂的兩個元素是否相等,如果不等,整個腳本就執行失敗了,如果相等,腳本會繼續執行,所以運行結果變成
最后,執行指令OP_CHECKSIG,這條指令會驗證簽名.
其實腳本的驗證過程就是根據自己的輸入將簽名和公鑰入棧,根據上一個交易的輸出腳本指令依次執行,判斷結果是否和上一個交易要求的公鑰地址匹配。換言之,誰能根據我輸出腳本的規則計算出符合我要求的公鑰地址,誰就能使用這筆交易。
由於引入了腳本,我們可以看到,比特幣實際上通過編程腳本實現了一個嚴格以計算機程序驗證為基礎的數字貨幣所有權的轉移機制。由於計算機程序的可擴展性,比特幣支付其實並不限定在必須支付給某一個公鑰地址。利用腳本,我們可以構造出各種支付條件,例如,多重簽名驗證條件:
2 3 OP_CHECKMULTISIGN
這種提供多個公鑰地址,並且需要多個簽名驗證的多重簽名腳本,允許在M個簽名種至少給出N個簽名即可使用。上述腳本允許提供3個公鑰地址中的任意兩個有效簽名。
當我們把比特幣托管在某個第三方的在線錢包中時,就可以使用多重簽名來保證只有自己和第三方錢包共同簽名后才可動用輸出,這樣保證了黑客在攻擊了第三方錢包后也無法花掉用戶的比特幣。
通過OP_CHECKLOCKTIMEVERIFY,我們可以指定一個交易的鎖定時間,在此之前,該交易輸出無法被花掉。這個指令其實實現了支付寶的7天資金鎖定然后再支付給賣家的功能。
還有一些交易並沒有指定一個公鑰Hash,例如,這個交易的腳本如下:
OP_HASH256 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL
它的意思是說,誰能夠提供一個數據,它的SHA256是6fe28c0a…0000,誰就可以花費這筆交易
我的公眾號:
QQ交流群:685914495