昨日介紹了如何使用 mapping 來作出一個簡單的白名單,而今天會專注在另一種白名單方式 -- Merkle Tree NFT Whitelists
Merkle Tree 的中文稱「砸湊樹」,是一種二元樹的資料結構。
其有重要的三個結構:
下方就是一個由四個 leaf 構成的 Merkle Tree:
sited from wikipedia
而比特幣與以太坊(以太坊準確地來說是 Sparse Merkle Tree)的架構中 Merkle Tree 都扮演了非常重要的角色。
那 Merkle Tree 要如何拿來當作白名單來使用呢?
其實仔細想想白名單的運作流程就像是一個驗證系統,這時可以退一步來討論,我們該如何使用它來達到驗證的功能?
舉一個 Leaf 為 A、B、C、D 四個元素的 Merkle Tree,如下:
由於 hash function 是非對稱加密的特色,一個 element 經過 hash()
雜湊後會使得很難透過得到的資訊回推原本的 element 是什麼。
因此我們多只能透過將正確的資訊填回去後獲得同樣的結果來驗證。
例如我們今天只擁有 C element 的資訊,這時我們只要提供 hash(D)、hash(AB),並經由正確的 hash()
流程便可以得到一個與原本相同的 Root。
雖然要手刻一個 Merkle Tree 好像不是不行(好啦其實對我來說有點難),但是還是稍嫌麻煩了億點。
因此這邊推薦使用 merkletreejs 這個套件,讓我們可以在鏈下建構屬於我們自己的 merkle tree whitelist。
首先先安裝需要的套件:
npm i merkletreejs
npm i keccak256
注意!如果出現Buffer is not defined 的 error 時需要使用:
npm install buffer
並在 home.js
或是 main.js
中引用:
import { Buffer } from "buffer/";
window.Buffer = window.Buffer || Buffer;
接著引用需要使用的物件
import { MerkleTree } from 'merkletreejs';
import keccak256 from 'keccak256';
首先建立 whitelist merkle tree,其實就是把每一個 whitelist address 當成一個 leaf,並將 leaf 再作成一顆樹。
我們假設 A ~ G 是我們的白名單地址。
const whitelistAddresses = [
"A",
"B",
"C",
"D",
"E",
"F",
"G"
]
const leafNodes = whitelistAddresses.map(addr => keccak256(addr));
const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
// 檢查 merkle tree 的樣子
console.log('Whitelist Merkle Tree:\n', merkleTree.toString());
在 MerkleTree()
中需要傳入三個 parameter:
isBitcoinTree
、hashLeaves
、sortLeaves
與 sortPairs
,這邊使用 sortPairs 是會在 hash 時將兩次的 hash 值再作排列,詳細可看程式碼或下方的 Buffer & Hash 部分。
成功的得到了一個 merkle tree ><
一個完整的 verify 需要滿足三個東西:
這邊我們已經有了 leaf,因此還需要 proof 與 root。
const buf2hex = x => '0x' + x.toString('hex');
const leaf = leafNodes[0];
const proof = merkleTree.getHexProof(address);
const root = buf2hex(merkleTree.getRoot());
console.log(merkleTree.verify(hexProof, leaf, root));
首先需要定義一個 buf2hex()
將 Buffer(二進位制) 轉換成 Hex(十六進位制)。
這邊取得 proof 的方式是使用 .getHexProof(leaf)
這個 method 而不是 .getProof(leaf)
,原因也是因為不同進位制。
最後可以用 merkleTree.verify(proof, leaf, root)
來檢查是否這個 leaf 存在於 Merkle Tree 中。
若今天使用者的地址不在這個 whitelist 中,merkleTree.verify()
則會回傳 false
在整個過程中有一件令我好奇的事:在我們宣告一個 new MerkleTree()
時傳入的第三個 parameter 到底代表什麼。
(以下會依照我們使用的 sortPairs 來解析)
在仔細的端詳了 merkletreejs
中刻出的程式中,我發現其實在 hash 前,左與右 leaf 會先被丟到一個 宣告為 conbine 的 array 中,並透過 array.sort(comparison function)
來改動他們的順序,而其中使用的 comparison function 是 Buffer.compare
。
Buffer
是在 Node.js
中用來將資料轉換成二進位的函式,在 Merkle Tree 的實作中會很常遇到,詳細可見此。
而 Buffer.compare(a, b)
則會比較兩者的二進位值何者較大:
透過這個方式可以將 combine
中的左與右 leaf 依照大小排序。
接下來會 Buffer.concat()
將 combine 中的資料結合,最後用一開始設定好的 hash function 完成 hash,當然最後會再將結果紀錄在一個 array 中。
今天學會了如何使用 merkletreejs
來建造一個 whitelist Merkle Tree,並學會如何使用其中的 method 來取得 proof 與 root。而明天會使用 Openzepplin 的 MerkleProof.sol
來達到在鏈上驗證的效果。
另外今天我還差點把我全部的 node modules 全部刪掉重載...,因為 google 跑出來的 error 後竟然有人說全部刪除,再 npm i
就可以了,我差一點也跟著做下去了,還好理性告訴我不准這麼做。
若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!
歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4