Bitcoin程式碼之MerkleTree
1.Merkle Tree
概念
Merkle
樹是一種雜湊二叉樹,它是一種用作快速歸納和校驗大規模資料完整性的資料結構
2.Merkle Tree
在區塊鏈作用
merkle
數在bitcoin
裡有2個主要作用
2.1歸納一個區塊裡全部交易為一個32
位元組值hash
值。
比特幣使用Merkle
樹的方式進行驗證,相對於雜湊列表,Merkle
樹是一種雜湊二叉樹,它的明顯的一個好處是可以單獨拿出一個分支來(作為一個小樹)對部分資料進行校驗,更加高效。
我們回看下上面的區塊結構圖,區塊體就包含這樣一個Merkle
樹,Merkle
樹被用來歸納一個區塊中的所有交易
2.2區塊中缺少哪些交易,可以快速驗證,複雜度在2log2(N)
當N個資料元素經過加密後插入Merkle
樹時,你至多計算2*log2(N)
次就能檢查出任意某資料元素是否在該樹中,這使得該資料結構非常高效
3.bitcoin
裡的MerkleTree
3.1程式碼位置
bitcoin
裡merkle
程式碼使用與移位<<
和與&
操作,程式碼不是很好理解
程式碼位於merkle.h/merkle.cpp
裡
3.2程式碼詳解
入口函式,bitcoin
在構建一個區塊呼叫該函式計算merkle
的根hash
值
**uint256 BlockMerkleRoot(const CBlock& block, bool* mutated)**
{
std::vector<uint256> leaves;
//獲取一個區塊交易的數量,也就是merkle的(呼叫resize設定vector大小,避免)
//避免在下面賦值需要重新分配記憶體
leaves.resize(block.vtx.size());
for (size_t s = 0; s < block.vtx.size(); s++)
{
leaves[s] = block.vtx[s]->GetHash(); //獲取每一筆交易的hash值
}
return ComputeMerkleRoot(leaves, mutated);
}
uint256 ComputeMerkleRoot(const std::vector<uint256>& leaves, bool* mutated)
{
//uint256繼承模板類base_blob的特化base_blob<256>,base_blob<256>可以看做是對char[32]的一個封裝類
//hash函式特點就是任何輸入,hash後輸出都是32位元組
uint256 hash;
MerkleComputation(leaves, &hash, mutated, -1, nullptr);
return hash;
}
//merkle tree的根hash256計算(程式碼進行了簡化,只保留根節點hash計算邏輯)
static void MerkleComputation(const std::vector<uint256>& leaves, uint256* proot, bool* pmutated, uint32_t branchpos, std::vector<uint256>* pbranch)
{
//如果沒有葉子節點,
if (leaves.size() == 0)
{
if (proot) *proot = uint256();
return;
}
uint32_t count = 0;
/*
計算所有葉子節點的hash值計算,儲存在inner陣列
如果leaves.size()個數如果是2的冪次方,在下面第一個迴圈一次可獲得根的hash值
,並且儲存在2^k=leaves.size(),inner[k]的位置,假設有8個葉子節點
那麼計算完 inner[3]就是根的hash值,8個葉子節點程式碼計算過程如下。程式碼65行
1.計算hash(ab) 儲存在inner[1]
2.計算hash(cd) 儲存在inner[2]
3.計算hash(ab+cd=N) 儲存在inner[2] (程式碼83行 2,3步驟在一個迴圈執行)
4.計算hash(ef) 儲存在inner[1],這是會覆蓋步驟1
5.計算hash(gh) (5,6,7在一個迴圈連續執行,程式碼73)
6.計算hash(ef+gh=M)
7.計算hash(M+N) 儲存在inner[3]
abcdefgh
/\
abcd efgh
/\ /\
ab cd ef gh
/\ /\ /\ /\
a b c d e f g h
*/
uint256 inner[32];
while (count < leaves.size())
{
uint256 h = leaves[count];
count++;
int level;
//count為偶數迴圈條件滿足,奇數葉子節點hash先暫時儲存在inner[0]
for (level = 0; !(count & (((uint32_t)1) << level)); level++)
{
//迴圈執行次數和count相關
//count作為二進位制看,從右到左0的個數就是迴圈次數
//為什麼有時候要執行多次,其實是和前面的hash節點在做拼接後做hash計算
//如果count=4(0100),會執行迴圈2次,第一次計算hash(cd),迴圈第二次計算就是hash(ab+cd)
CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
}
inner[level] = h;
}
int level = 0;
/*
這裡用迴圈計算level分三種情況
1.count是2的冪 ,假設count=8,那麼inner[2^3] = 8 ,inner[level]就是樹根hash值
後面第二個迴圈不會被執行,函式返回root的hash。
abcdefgh == inner[3]
/\
abcd efgh
/\ /\
ab cd ef gh
/\ /\ /\ /\
a b c d e f g h
2.count不是2的冪並且是奇數,假設count=5,那麼inner[0]儲存的是最後一個節點hash值,
這個節點找不到配對節點來計算hash,只能複製一份和自己做hash。
abcdefee
/\
inner[1]==abcd efee
/\ /\
ab cd ef [ee]
/\ /\ /\
a b c d e [e] 複製一份和自己做hash(e+e)
3.count不是2的冪並且是偶數,假設count=6,level[1]==hash(ef),進入後面迴圈線執行hash(ef+ef)
,然後在執行hash(abcd+efef)
abcdefee
/\
abcd efef
/\ /\
ab cd ef [ef] 複製一份和自己做hash
/\ /\ /\
a b c d e f
*/
while (!(count & (((uint32_t)1) << level)))
{
level++;
}
//這裡的level值參考上面說明
uint256 h = inner[level];
while (count != (((uint32_t)1) << level)) {
//當merkle樹level層節點是奇數個,需要自己和自己算一次hash
CHash256().Write(h.begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
/*
//不管count是多少,計算出第一個大於count的2的冪的數
//如果count是6,計算結果是8
//如果count是7,計算結果是8
//如果count是13,計算結果是16
*/
count += (((uint32_t)1) << level);
level++;
//如果count不是2的冪的,下面迴圈不會被執行
while (!(count & (((uint32_t)1) << level)))
{
//和前面節點hash值拼接再次計算hash
CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin());
level++;
}
}
// Return result.
if (proot)
*proot = h;
}
老版本的merkle
,這個很好理解
static uint256 BlockBuildMerkleTree(const CBlock& block, bool* fMutated, std::vector<uint256>& vMerkleTree)
{
vMerkleTree.clear();
vMerkleTree.reserve(block.vtx.size() * 2 + 16); // Safe upper bound for the number of total nodes.
for (std::vector<CTransactionRef>::const_iterator it(block.vtx.begin()); it != block.vtx.end(); ++it)
vMerkleTree.push_back((*it)->GetHash());
int j = 0;
bool mutated = false;
for (int nSize = block.vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
{
for (int i = 0; i < nSize; i += 2)
{
//因為迴圈步驟是2,取一個最小值防止陣列越界
int i2 = std::min(i+1, nSize-1);
if (i2 == i + 1 && i2 + 1 == nSize && vMerkleTree[j+i] == vMerkleTree[j+i2]) {
// Two identical hashes at the end of the list at a particular level.
mutated = true;
}
//hash計算2個字串拼接的hash256
//從前到後,每次取相鄰2個節點計算hash,並放入陣列尾部
vMerkleTree.push_back(Hash(vMerkleTree[j+i].begin(), vMerkleTree[j+i].end(),
vMerkleTree[j+i2].begin(), vMerkleTree[j+i2].end()));
}
j += nSize;
}
if (fMutated) {
*fMutated = mutated;
}
return (vMerkleTree.empty() ? uint256() : vMerkleTree.back());
}
相關文章
- bitcoin原始碼分析原始碼
- MerkleTree in BTC
- studying Bitcoin
- 帶你瞭解比特幣Bitcoin原始碼比特幣原始碼
- 比特幣原始碼研讀(1)bitcoin原始碼結構比特幣原始碼
- Bitcoin類錢包命令
- MerkleTree在資料校驗上的應用
- Bitcoin序列化庫使用
- 通過 Golang 買賣 BitcoinGolang
- bitcoin: 何為燃燒地址
- Bitcoin 實驗的定論
- bitcoin 與存在性證明
- Bitcoin: 計算 Merkle Tree
- 比特幣原始碼研讀(0)bitcoin本地編譯與使用比特幣原始碼編譯
- 程式碼壞味道之程式碼臃腫
- Bitcoin Gold 遭遇雙花攻擊Go
- webpack之程式碼拆分Web
- 程式碼之美_感悟
- 《程式碼之美》 ——序
- 享受程式碼之美
- 程式碼之美---遞迴之美遞迴
- Bitcoin Node Numbers Fall After Spam Transaction "Attack"
- BTCC<Bitcoin+Blockchain> Hiring GolangerBlockchainGolang
- bitcoin OP_CHECKSIG 交易驗籤
- 程式碼安全之程式碼混淆及加固(Android)?Android
- 體面編碼之程式碼提交
- linux程式碼之namespaceLinuxnamespace
- node之koa核心程式碼
- iOS逆向之 程式碼注入iOS
- 《程式碼之美》的故事
- 快速程式碼展示之快速的例子程式碼片段(轉)
- 如何驗證 Bitcoin Core 軟體簽名
- 重談 Bitcoin: 只是一種可能性
- elasticsearch之Java呼叫原生程式碼ElasticsearchJava
- 程式碼的架構之說架構
- 程式碼安全之檔案包含
- 《程式碼之髓》讀後感
- SqlServer——神奇程式碼1之UpdateSQLServer