- 引言
- 位元幣基本結構
- 位元幣基礎知識
- 1)哈希演算法
- 2)非對稱加密技術
- 3)數字簽名
- 4)MerkleTree
- 5)哪有位元幣,有的是UTXO
- 6)位元幣挖礦與共識
- 7)區塊驗證(共識)
- 總結
引言
上一篇我們已經知道了什么是區塊鏈,此篇說一下區塊鏈的第一個應用——位元幣,其實先有位元幣,后有的區塊鏈,位元幣是中本聰提出的一種P2P去中心化的支付系統,其底層原理為:去中心化、資料不可篡改、可溯源;資料以區塊的形式保存,并且像鏈條一樣連接起來,很形象地稱為區塊鏈,位元幣就是一條上文說過的公有鏈,
位元幣基本結構
上一節我們說過,賬本以交易的形式記錄,那交易是如何存盤在區塊鏈上呢?
交易存盤在區塊之中,區塊分為區塊頭以及區塊體,區塊頭包含區塊的概要資訊,區塊體包含交易資訊,
- 區塊頭
- version(版本號):用于協議區分或者升級
- height:區塊高度
- hash(區塊Hash):區塊的唯一指紋
- preBlockHash(父區塊Hash):用于向前追溯
- merkleRoot(默克爾樹根Hash):可用于節點快速驗證區塊的交易是否被篡改或者SPV(Simplified Payment Verification 簡單支付驗證)客戶端驗證某一個交易是否存在于位元幣鏈上,
- time(區塊生成時間)
- dificultyTarget(目標難度:nBits):挖礦難度
- nonce(亂數):位元幣協議中“難題的解”
- 區塊體
- transactions(區塊包含的交易串列):被礦工打包在區塊中的交易,實際為用戶的轉賬交易,
- version(版本號):用于升級
- Inputs:交易輸入串列,指向待花費的UTXO,可以有零個至多個,coinbase(創幣交易)沒有輸入,
- Outputs:交易輸出串列:指向即將產生的UTXO
- lockTime:交易鎖定時間,實際為UNIX時間戳(相對于1970年1月1日0點后流逝的秒數,)礦工打包交易時,會判斷是否到達此時間,到達后才會將此交易進行打包,
- time:交易的生成時間
- memo:備注欄位,用戶可自由填寫,
位元幣基礎知識
1)哈希演算法
哈希演算法就是將任意長度的資料轉變為一個定長的資料(也叫哈希或者摘要),常見的哈希演算法有CRC32、MD5、SHA1、SHA2、SHA3、Hmac等,
哈希演算法具有以下特點:
-
單向不可逆性:想要通過哈希值逆推出原來的資料,只有通過暴力破解的方法實作,但這幾乎無法做到,可能量子計算機出來后就可以破解了,
-
確定性:如果hash值不同, 可以確定原資料一定不同,如果相同,則還需要進一步判斷,因為可能hash碰撞了,
-
雪崩效應:原始資料任何微小的變動都會導致哈希值完全不一樣
-
不變性:同一個資料,多次hash結果相同,
2)非對稱加密技術
非對稱加密有公鑰和私鑰兩個概念,私鑰自己擁有,不能給別人,公鑰公開,根據應用的不同,我們可以選擇使用不同的密鑰加密:
-
私鑰簽名:使用私鑰簽名(加密),公鑰驗簽(解密),用于讓公鑰所有者驗證私鑰所有者的身份并且用來防止私鑰所有者發布的內容被篡改,但是不用來保證內容不被他人獲得,(內容本身非加密的,別人可以獲得資訊但無法篡改)
-
公鑰加密:用公鑰加密,私鑰解密,公鑰所有者可用于加密發布的資訊,這個資訊可能被他人篡改,但是無法被他人獲得,
? 舉例:比如小黑持有公鑰,將 “我愛你” 加密后的訊息 “xxx” 發送給喜歡的人小花(小花持有私鑰),但小灰也喜歡小花并且也有公鑰,小灰劫持了小黑的訊息(訊息內容已加密,小灰也不知道內容是什么),并將 “我恨你” 加密為 “xxx”組裝為小黑的請求發送給小花,小花收到小黑的請求后,用私鑰解密,得到內容為:“我恨你”,這下小黑就涼涼,心機boy小灰就成功了,
簽名與加密可以混合使用,提高安全性與隱私性,
3)數字簽名
作用:保證資訊傳輸的完整性、發送者的身份驗證(鑒權)
原理:使用非對稱加密技術及hash技術,非對稱加密生成一對公私鑰對,私鑰用于簽名,公鑰用于驗簽,簽名的物件為內容的hash,
為什么簽名不直接簽署內容,而是簽署hash呢?
答:效率問題,如果你的原內容很大,直接對原內容簽名效率很低,而且簽名的結果也相對很大,傳輸也慢,
簽名驗簽程序
- 簽名程序:首先獲取通過哈希演算法獲取原文的摘要資訊,然后用私鑰加密,并將原文一起發送給接受者,
- 驗證程序:接收者只有使用發送者的公鑰才能解密被加密的摘要資訊,同樣通過哈希演算法獲取原文的摘要資訊,并與解密后的摘要資訊做對比,如果相同則說明資訊完整,確實是對方本人的簽名(因為公鑰和私鑰是一對的),
在位元幣系統中,公鑰(地址)用于接收位元幣,而私鑰則用于位元幣支付時的交易簽名,
在支付位元幣時,位元幣的所有者需要在交易中提交自己的公鑰和該交易的簽名,而位元幣網路中所有節點可以通過所提交的公鑰和簽名進行驗證,從而確認支付者對交易的位元幣的所有權,
4)MerkleTree
Merkle樹是?種哈希?叉樹,它是?種?作快速歸納和校驗?規模資料完整性的資料結構,這種?叉樹包含加密哈希值,
在?特幣?絡中,Merkle樹被?來歸納?個區塊中的所有交易,同時?成整個交易集合的hash,且提供了?種校驗區塊是否存在某交易的?效途徑,
當N個資料元素經過加密后插?Merkle樹時,時間復雜度為log(N)【因為Merkle樹可能為完全二叉樹或者滿二叉樹,而滿二叉樹的高度為log(N)】就能檢查出任意某資料元素是否在該樹中,這使得該資料結構?常?效,
計算merkleRoot時,如果交易數量為奇數,則復制最后一個交易hash進行計算,
Merkle樹的效率
交易數量 | 區塊的近似大小 | 路徑大小(Hash數量) | 路徑大小(位元組) |
---|---|---|---|
16筆交易 | 4KB | 4個Hash | 128位元組 |
512筆交易 | 128KB | 9個Hash | 288位元組 |
2048筆交易 | 512KB | 11個Hash | 352位元組 |
65535筆交易 | 16MB | 16個Hash | 512位元組 |
注:單個Hash32位元組
依表可得,當區塊??由16筆交易(4KB)急劇增加?65,535筆交易(16MB)時,為證明交易存在的Merkle路徑?度增?極其緩慢,僅僅從128位元組到512位元組,有了Merkle樹,?個節點能夠僅下載區塊頭(80位元組/區塊),然后通過從?個滿節點回溯?條?的Merkle路徑就能認證?筆交易的存在,?不需要存盤或者傳輸?量區塊鏈中?多數內容,這些內容可能有?個G的??,這種不需要維護?條完整的區塊鏈的節點,?被稱作簡單?付驗證(SPV)節點,它不需要下載整個區塊?通過Merkle路徑去驗證交易的存在,
Merkle樹和簡單?付驗證(SPV)
Merkle樹被SPV節點?泛使?,SPV節點不保存所有交易也不會下載整個區塊,僅僅保存區塊頭,它們使?認證路徑或者Merkle路徑來驗證交易存在于區塊中,?不必下載區塊中所有交易,
區塊頭只有80位元組,而一個區塊大小為1M(位元幣已擴容為2M),所以SPV節點驗證交易是否存在,只需要位元幣區塊容量的千分之一的資料就可以,大大節省容量,
例子:使用上面的merkelTree中的交易,假設SPV節點想驗證交易A是否存在與區塊內,
如圖所示,SPV節點只需要獲取該交易所在的區塊頭以及驗證路徑:即N1、N4即可,
驗證步驟:
- 計算交易A的Hash得到N0
- 計算出N3的Hash = Hash(N0+N1)
- 計算merkleRoot = Hash(N3+N4)
- 比較計算的merkleRoot與區塊頭的merkleRoot是否相同,相同則表明該交易A存在于此區塊中,
注意:SPV只能驗證某交易是否存在與區塊中,而無法驗證該交易的UTXO是否雙花,需要等待該區塊后是否累積了多個區塊,越多證明該交易被大多數人共識確認,越無法篡改,位元幣中,6個區塊就可以確認該交易基本無法篡改,篡改的機率很低,并且成本昂貴,
為什么位元幣的區塊大小為1M?
//For now it exists as an anti-DoS measure to avoid somebody creating a titanically huge but valid block and forcing everyone to download/store it forever.
public static final int MAX_BLOCK_SIZE = 1 * 1000 * 1000;
原始碼里有注釋:預防dos攻擊,防止有節點創建很大且有效的區塊發送到位元幣網路,這樣大家都會去驗證并廣播,造成網路擁堵,
5)哪有位元幣,有的是UTXO
什么是UTXO?
UTXO:unspent transaction output 未花費的交易輸出,如果單看位元幣,其實就是UTXO,擁有多少位元幣,實質是你的UTXO集合有多少,一個交易輸出就是一個UTXO,
我們再看一下交易的結構
TransactionOutput
-
txHash :交易的hash
-
Index : 交易的輸出串列中的下標,從0開始,
-
Value : 轉賬金額,位元幣最小單位為聰,1Btc = 10^8聰,轉賬單位也為聰
-
lockScpript:鎖定腳本,通常為地址,(表示轉賬的位元幣存盤的形式)
-
腳本型別
public enum ScriptType { P2PKH(1), // pay to pubkey hash (aka pay to address)通常為此形式,支付到地址 P2PK(2), // pay to pubkey P2SH(3), // pay to script hash P2WPKH(4), // pay to witness pubkey hash P2WSH(5); // pay to witness script hash public final int id; private ScriptType(int id) { this.id = id; } }
-
交易輸出表述為:轉賬給哪一個地址的金額,位于交易輸出串列中的哪個位置,
TransactionInput
- TransactionOutpoint:表示參考了哪一個UTXO,使用之前的txHash以及index找到唯一的UTXO,
- txHash :之前交易的hash
- Index : 之前交易的輸出串列中的下標,從0開始,
- unlockScpript:解鎖腳本:包含公鑰及簽名,
- pubKey:公鑰:用于驗簽
- Signature :簽名,用于位元幣網路中的礦工驗證該交易的發出者身份,是否有權利花費輸入中參考的UTXO,
6)位元幣挖礦與共識
挖礦是增加?特幣貨幣供應的?個程序,挖礦同時還保護著?特幣系統的安全,防?欺詐交易,避免“雙花” ,“雙花”是指多次花費同?筆?特幣,
**挖礦:指位元幣網路中的節點將網路中收到的合法交易進行打包,生成區塊的程序,**并且,交易串列的第一筆交易礦工會生成一個coinbase交易,即為創幣交易,(此交易沒有輸入,只有輸出,輸出到自己地址作為挖礦獎勵)礦?通過創造?個新區塊得到的?特幣數量?約每四年(或準確說是每210,000個塊)減少?半,開始時為2009年1?每個區塊獎勵50個?特幣,然后到2012年11?減半為每個區塊獎勵25個?特幣,之后將在2016年的某個時刻再次減半為每個新區塊獎勵12.5個?特幣,基于這個公式,?特幣挖礦獎勵以指數?式遞減,直到2140年,屆時所有的?特幣(20,999,999.98)全部發?完畢,換句話說在2140年之后,不會再有新的?特幣產?,現在(2020/7/02)為6.25BTC,
靈魂拷問:
位元幣網路中中的節點那么多,大家都可以挖礦?而最終確定的區塊只有一個或少數幾個,全網這么多礦工,大家怎么都承認是你的區塊被接受,我的不被接受呢?
位元幣中有一個挖礦難度,這個難度可以轉換為一個256bit的大數,位元幣的共識為:全網的礦工都去打包區塊,但有一個條件,區塊的hash轉換的256bit的大數一定要不大于比難度轉換的才行,(也就是大家常說的計算的Hash前置的0多于目標值Hash的前置0一個意思)
protected boolean checkProofOfWork(boolean throwException) throws VerificationException {
//將難度轉換為一個256bit的大數
BigInteger target = getDifficultyTargetAsInteger();
//區塊hash轉換為一個256bit的大數
BigInteger h = getHash().toBigInteger();
//如何區塊的數大于目標的,則不合法
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs "
+ target.toString(16));
else
return false;
}
return true;
}
如何計算Hash?
void writeHeader(OutputStream stream) throws IOException {
//版本
Utils.uint32ToByteStreamLE(version, stream);
//父區塊Hash
stream.write(prevBlockHash.getReversedBytes());
//交易merkleTree hash
stream.write(getMerkleRoot().getReversedBytes());
//交易時間
Utils.uint32ToByteStreamLE(time, stream);
//區塊難度
Utils.uint32ToByteStreamLE(difficultyTarget, stream);
//挖礦的嘗試次數
Utils.uint32ToByteStreamLE(nonce, stream);
}
可以看出,區塊的hash中,變化的量有交易的merkleRoot、區塊時間、區塊nonce,但如果挖礦時交易串列已經確定,區塊時間也是確定的,那就只有嘗試更改nonce來更改區塊hash,以達到在該難度下的合法區塊hash,
public void solve() {
while (true) {
try {
// 作業量證明
if (checkProofOfWork(false))
return;
// 增加nonce,重新計算
setNonce(getNonce() + 1);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
}
可以看到位元幣挖礦的程序就是尋找一個合法nonce的程序,
我們再來看一看,UTXO不是只有唯一的一個嗎?為什么還會產生雙花?
其實雙花在位元幣中對應于分叉,因為位元幣網路中的節點都是P2P的對等節點,每個節點打包的交易可能不同,時間也不同,nonce也不同,最終計算的區塊hash也不同,這樣就會在同一高度產生多個合法區塊,這就是分叉,
可能你的同一筆交易被打進了高度為101的兩個區塊中,分別對應于兩條鏈,只要被打進區塊中的交易,證明都是被礦工確認共識過后的合法交易,所以如果是一個作惡的用戶,則可以利用位元幣的分叉進行雙花攻擊,
位元幣網路為了避免雙花,引入了一個確認的機制,需要6個區塊才能確認一個區塊中的交易是否真正有效,(6個區塊是估算的)
7)區塊驗證(共識)
區塊驗證的程序就是共識的程序:驗證區塊是否按照位元幣協議產生的,是否都打包的合法交易,是否找到了合法nonce等,
-
每個全節點依據綜合標準對每個交易進?獨?驗證
?交易的語法和資料結構必須正確, ?輸?(coinbase交易除外)與輸出串列都不能為空,
?交易的位元組??是?于 MAX_BLOCK_SIZE(當前1M) 的, ?每?個輸出值,以及總量,必須在規定值的范圍內 (?于2,100萬個幣,?于0), ?交易的位元組??是?于或等于100的,
?交易的位元組??最大為100kb, ?交易中的簽名數量應?于簽名運算元量上限, ?解鎖腳本( scriptSig )只能夠將數字壓?堆疊中,并且鎖定腳本( scriptPubkey )必須要符合 isStandard 的格式 (該格式將會拒絕?標準交易), ?池中或位于主分?區塊中的?個匹配交易必須是存在的, ?對于每?個輸?,如果引?的輸出存在于池中任何的交易,該交易將被拒絕, ?對于每?個輸?,在主分?和交易池中尋找引?的輸出交易,如果輸出交易缺少任何?個輸?,該交易將成為?個孤?的交易,如果與其匹配的交易還沒有出現在池中,那么將被加?到孤?交易池中, ?對于每?個輸?,如果引?的輸出交易是?個coinbase輸出,該輸?必須?少獲得 COINBASE_MATURITY (100)個確認, ?對于每?個輸?,引?的輸出是必須存在的,并且沒有被花費, ?使?引?的輸出交易獲得輸?值,并檢查每?個輸?值和總值是否在規定值的范圍內 (?于2100萬個幣,?于0), ?如果輸?值的總和?于輸出值的總和,交易將被中?, ?如果交易費?太低以?于?法進??個空的區塊,交易將被拒絕, ?每?個輸?的解鎖腳本必須依據相應輸出的鎖定腳本來驗證,
-
通過完成?作量證明演算法的驗算,
-
**判斷區塊高度是否為2016的倍數,因為每2016個區塊會調整難度,**如果沒達到2016的倍數,只需要驗證目標難度是否相等,(位元幣10分鐘產生一個區塊,2016差不多需要2周時間)
-
如果需要調整,則需要計算前2016個區塊的實際產生時間,
-
往前遍歷2016個區塊,找到前2016個區塊的開始區塊
-
計算前2016個區塊產生的實際時間:當前已確認的區塊時間 - 前2016個區塊的開始區塊的時間
-
限制實際時間在調整系數(4)之內
//獲取前2016個區塊的實際使用時間 int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds()); // Limit the adjustment step. final int targetTimespan = this.getTargetTimespan(); if (timespan < targetTimespan / 4) timespan = targetTimespan / 4; if (timespan > targetTimespan * 4) timespan = targetTimespan * 4;
-
根據前2016個區塊的實際使用時間計算新的目標值(難度的另一種表示方式)
newTarget = timespan * preDifficulty/targetTimespan
targetTimespan = 14 * 24 * 60 * 60 兩個星期
目標值壓縮為4個位元組的表達方式,可以節約存盤,
//難度太大會限制為創世塊的默認難度(大約10分鐘一個區塊) if (newTarget.compareTo(this.getMaxTarget()) > 0) { log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16)); newTarget = this.getMaxTarget(); }
-
比較計算的難度與區塊中的目標難度是否相等,因為驗證的程序跟礦工挖礦的程序是一樣流程,
-
-
-
每個節點對區塊鏈進?獨?選擇,在?作量證明機制下選擇累計?作量最?的區塊鏈
-
如果該區塊是接著主鏈打的,則處理交易,(處理時會驗證交易,驗證程序就是第一步)將交易輸入參考的UTXO刪掉;交易的輸出轉換為UTXO存盤,
-
如果該區塊不是接著主鏈打的,會判斷區塊的作業量是否大于了主鏈的,如果沒有大于,則只是將區塊鏈接上,
什么叫作業量呢?
作業量被定義為:平均情況下生成一個合法區塊所需的嘗試次數(nonce)
/** * Returns the work represented by this block.<p> * * Work is defined as the number of tries needed to solve a block in the * average case. Consider a difficulty target that covers 5% of all possible * hash values. Then the work of the block will be 20. As the target gets * lower, the amount of work goes up. */ public BigInteger getWork() throws VerificationException { //將目標難度值轉換為256bit的大數,這個數值表示該難度下的目標hash值 //(假設這個hash值覆寫了所有hash范圍的5%,則作業量為20) BigInteger target = getDifficultyTargetAsInteger(); //LARGEST_HASH: 該數字比最大可表示的SHA-256哈希大1 return LARGEST_HASH.divide(target.add(BigInteger.ONE)); }
鏈累積的作業量計算:
/** * 構建區塊存盤的索引,包含了這條鏈從創創世塊到現在的所有作業量 */ public StoredBlock build(Block block) throws VerificationException { //僅代表了該鏈的作業量總和(這里的this指前一個區塊)每個區塊都如此計算,就是一個累和 BigInteger chainWork = this.chainWork.add(block.getWork()); int height = this.height + 1; return new StoredBlock(block, chainWork, height); }
如何判斷是否產生新的主鏈?
假設現在產生了分叉,則只需要計算出分叉鏈的鏈累積作業量,與當前主鏈的對比,如果大于,則新產生的鏈為主鏈,舊的主鏈則會作廢,
其實就是最長鏈原則,因為鏈越長,累積的作業量會越大,
//判斷當前鏈是否比主鏈的作業量大,如果大于,則為新主鏈 public boolean moreWorkThan(StoredBlock other) { return chainWork.compareTo(other.chainWork) > 0; }
-
位元幣處理分叉:
-
-
上圖中,區塊高度為103的區塊到來后,新的主鏈產生,這時候,會用當前主鏈的102,與新主鏈的103向前遍歷,找到分叉點101區塊,(注意:)
-
通過最長鏈原則,以6個區塊為確認主鏈,確認主鏈后,分叉鏈需要回退交易,花掉的utxo進行增加,而新增的utxo進行洗掉,
注意:6個區塊:判斷當前區塊時間是否大于等于最近11個區塊的區塊時間的中位數(6),所以只能回退最近6個區塊內的資料,反向意思為:6個之前的無法回退,即6個區塊確認主鏈,
-
如果區塊分叉,但還沒有成為最長鏈,只是保存了區塊,并沒有花費UTXO(因為可能已經花費過了)
對于新區塊102,不會處理交易,因為主鏈已經處理過了102區塊的交易(可能區塊中的交易不完全一樣,但也沒有關系,因為可能新區塊102中的新交易已經被主鏈的103區塊打包并處理了)
?特幣將區塊間隔設計為10分鐘,是在更快速的交易確認和更低的分叉概率間作出的妥協,更短的區塊產?間隔會讓交易清算更快地完成,也會導致更加頻繁地區塊鏈分叉,
什么叫51%攻擊?
大家可能也經常聽到,位元幣51%的概念,看上面分叉圖,如果有人掌握了全網51%的算力,則就可以比任何人都先計算出合法nonce,所以就可以連續接著102的新區塊打區塊,當下面這條鏈的長度長于主鏈,那就是一條新的主鏈,畢竟你這么厲害,都控制了51%的算力,那我們就跟著你這條鏈玩吧,你就是主鏈,
分叉的總類?
- 軟分叉:上面說的情況都是軟分叉的情況,因為可能有多個礦工在同一時間找到合法的nonce,加上網路原因等,
- 硬分叉:指強制升級,可能重大bug被發現,也或者被黑客攻擊,都可以使用硬分叉來修復,但需要全網很多礦工都同步升級,
什么叫孤兒區塊?
比如現在來了一個105高度的區塊,使用此區塊的preBlockHash無法找到父區塊,則此區塊就稱為孤兒區塊,
當收到一個區塊為孤兒區塊后,會將其存入孤兒區塊池中,并嘗試遍歷孤兒區塊池中的區塊,看是否能鏈接到主鏈上,因為處于分布式網路中,每個節點的網路情況、同步等有差異,可能節點會先收到高度更高的區塊,然后再收到高度低一點的區塊,然后再與本地的鏈高度鏈接上,
為什么coinbase中的UTXO需要100個確認才能使用呢?
因為如果使用了coinbase中的UTXO的交易被打包進入了兩個分叉的區塊中,上面說過,重新切換主鏈時,舊主鏈的交易需要回退,產生的UTXO需要洗掉,使用了的UTXO需要新增,所以,導致新主鏈中參考了coinbase中UTXO的交易無效,因為已經沒有這個UTXO了,這樣就會導致區塊驗證不通過,主鏈無法重新切換,所以位元幣假設沒有不可能有這么長遠的分叉,6個確認主鏈已經小于100個確認coinbase,
總結
此篇略說了一些位元幣相關的技術知識,以及位元幣的一些原理,文中有錯誤之處,敬請各位指出,也歡迎大家評論區一起交流學習,
參考資料
《精通位元幣》強烈推薦,
bitcoinJ原始碼
本文使用 mdnice 排版
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/631.html
標籤:區塊鏈