Bitcoin程式碼之MerkleTree

風靈使發表於2018-08-13

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程式碼位置

bitcoinmerkle程式碼使用與移位<<和與&操作,程式碼不是很好理解
程式碼位於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());
}

相關文章