​​​​,​Merkle Tree​ also called Merkle tree or hash tree, is the underlying encryption technology of the blockchain and is widely used by the BTC and Ethereum blockchains. ​​​​is​Merkle Tree​ a bottom-up encrypted tree, each leaf is the hash of the corresponding data, and each non-leaf is the hash of its ​2​child nodes.

Solidity implements Merkle tree Merkle Tree_blockchain

​​​​Allows​Merkle Tree​ efficient and secure verification of the contents of large data structures (​Merkle Proof​​ ​) . For ​N​a ​leaf node ​Merkle Tree​, in the case of knowing ​root​the root value, verifying whether a piece of data is valid (belonging to ​Merkle Tree​a leaf node) only requires ​log(N)​a piece of data (also called ​proof​​), Very efficient. If the data is wrong, or the root is given ​proof​incorrectly , ​root​the root cannot be restored. In the following example, the leaves ​L1​of ​Merkle proof​are ​Hash 0-1​and are ​Hash 1​: Knowing these two values, you can verify whether the value ​L1​of is ​Merkle Tree​in the leaf of. why? Because ​L1​we ​Hash 0-0​, and we know ​Hash 0-1​, then and​Hash 0-0​ can be jointly calculated , and then we know , and can be jointly calculated . , which is the hash of the root node.​Hash 0-1​​Hash 0​​Hash 1​​Hash 0​​Hash 1​​Top Hash​

Solidity implements Merkle Tree Merkle Tree_Solidity_02

​Merkle Tree​Generate

We can use webpages or Javascript library merkletreejs to generate ​Merkle Tree​.

Here we use a web page to generate ​4​an address as a leaf node ​Merkle Tree​. Leaf node input:

[
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
]

Select the ​Keccak-256​​​, ​hashLeaves​​​and ​sortPairs​​​options in the menu, and then click ​Compute​​​, ​Merkle Tree​and the ​​is generated. expands​Merkle Tree​ to:

└─ 根: eeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097
├─ 9d997719c0a5b5f6db9b8ac69a988be57cf324cb9fffd51dc2c37544bb520d65
│ ├─ 叶子0:5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229
│ └─ 叶子1:999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb
└─ 4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c
├─ 叶子2:04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54
└─ 叶子3:dfbe3e504ac4e35541bebad4d0e7574668e16fefa26cd4172f93e18b59ce9486

Solidity implements Merkle Tree Merkle Tree_Blockchain_03

​Merkle Proof​​​​​Verify

Through the website, we can ​地址0​get ​proof​as follows, which is the hash value of the blue node in Figure 2:

[
"0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
"0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c"
]

Solidity implements Merkle Tree Merkle Tree_Merkle Tree_04

We leverage ​MerkleProof​the library to verify:

library MerkleProof {
          
          
/**
* @dev 当通过`proof`和`leaf`重建出的`root`与给定的`root`相等时,返回`true`,数据有效。
* 在重建时,叶子节点对和元素对都是排序过的。
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}

/**
* @dev Returns 通过Merkle树用`leaf`和`proof`计算出`root`. 当重建出的`root`和给定的`root`相同时,`proof`才是有效的。
* 在重建时,叶子节点对和元素对都是排序过的。
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}

// Sorted Pair Hash
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? keccak256(abi.encodePacked(a, b)) : keccak256(abi.encodePacked(b, a));
}
}

The library​MerkleProof​ has three functions:

  1. ​​​​Function​verify()​ : use proofthe number to verify leafwhether it belongs to the root of the root root, Merkle Treeand if so, return it true. It calls processProof()the function.
  2. ​​​​Function​processProof()​ : Calculated using proofand in turn . It calls the function.leafMerkle Treeroot_hashPair()
  3. ​​​​Function​_hashPair()​ : Use keccak256()the function to calculate the hash of the two child nodes corresponding to the non-root node (after sorting).

我们将​​地址0​​,​​root​​和对应的​​proof​​输入到​​verify()​​函数,将返回​​ture​​。因为​​地址0​​在根为​​root​​的​​Merkle Tree​​中,且​​proof​​正确。如果改变了其中任意一个值,都将返回​​false​​。

利用​​Merkle Tree​​发放​​NFT​​白名单

一份拥有800个地址的白名单,更新一次所需的gas fee很容易超过1个ETH。而由于​​Merkle Tree​​验证时,​​leaf​​和​​proof​​可以存在后端,链上仅需存储一个​​root​​的值,非常节省​​gas​​,项目方经常用它来发放白名单。很多​​ERC721​​标准的​​NFT​​和​​ERC20​​标准代币的白名单/空投都是利用​​Merkle Tree​​发出的,比如​​optimism​​的空投。

这里,我们介绍如何利用​​MerkleTree​​合约来发放​​NFT​​白名单:

contract MerkleTree is ERC721 {
          
          
bytes32 immutable public root; // Merkle树的根
mapping(address => bool) public mintedAddress; // 记录已经mint的地址

// 构造函数,初始化NFT合集的名称、代号、Merkle树的根
constructor(string memory name, string memory symbol, bytes32 merkleroot)
ERC721(name, symbol)
{
root = merkleroot;
}

// 利用Merkle树验证地址并完成mint
function mint(address account, uint256 tokenId, bytes32[] calldata proof)
external
{
require(_verify(_leaf(account), proof), "Invalid merkle proof"); // Merkle检验通过
require(!mintedAddress[account], "Already minted!"); // 地址没有mint过
_mint(account, tokenId); // mint
mintedAddress[account] = true; // 记录mint过的地址
}

// 计算Merkle树叶子的哈希值
function _leaf(address account)
internal pure returns (bytes32)
{
return keccak256(abi.encodePacked(account));
}

// Merkle树验证,调用MerkleProof库的verify()函数
function _verify(bytes32 leaf, bytes32[] memory proof)
internal view returns (bool)
{
return MerkleProof.verify(proof, root, leaf);
}
}

​MerkleTree​​合约继承了​​ERC721​​标准,并利用了​​MerkleProof​​库。

状态变量

合约中共有两个状态变量:

  • ​root​​存储了​​Merkle Tree​​的根,部署合约的时候赋值。
  • ​mintedAddress​​是一个​​mapping​​,记录了已经​​mint​​过的地址,某地址mint成功后进行赋值。

函数

合约中共有4个函数:

  • 构造函数:初始化​​NFT​​的名称和代号,还有​​Merkle Tree​​的​​root​​。
  • ​mint()​​函数:利用白名单铸造​​NFT​​。参数为白名单地址​​account​​,铸造的​​tokenId​​,和​​proof​​。首先验证​​address​​是否在白名单中,验证通过则把序号为​​tokenId​​的​​NFT​​铸造给该地址,并将它记录到​​mintedAddress​​。此过程中调用了​​_leaf()​​和​​_verify()​​函数。
  • ​_leaf()​​函数:计算了​​Merkle Tree​​的叶子地址的哈希。
  • ​_verify()​​函数:调用了​​MerkleProof​​库的​​verify()​​函数,进行​​Merkle Tree​​验证。

​remix​​验证

我们使用上面例子的​​4​​个地址作为白名单并生成​​Merkle Tree​​。我们部署​​MerkleTree​​合约,​​3​​个参数分别为:

name = "WTF MerkleTree"
symbol = "WTF"
merkleroot = 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097

Solidity implements Merkle Tree Merkle Tree_web3_05

接下来运行​​mint​​函数给地址0铸造​​NFT​​,​​3​​个参数分别为:

account = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
tokenId = 0
proof = [ "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c" ]

Solidity implements Merkle Tree Merkle Tree_Smart Contract_06

我们可以用​​ownerOf​​函数验证​​tokenId​​为0的​​NFT​​已经铸造给了地址0,合约运行成功!

Solidity implements Merkle Tree Merkle Tree_Merkle Tree_07

此时,若再次调用mint函数,虽然该地址能够通过​​Merkle Proof​​验证,但由于地址已经记录在​​mintedAddress​​中,因此该交易会由于​​"Already minted!"​​被中止。

总结

这一讲,我们介绍了​​Merkle Tree​​的概念,如何生成简单的​​Merkle Tree​​,如何利用智能合约验证​​Merkle Tree​​,以及用它来发放​​NFT​​白名单。

In actual use, complex data ​Merkle Tree​can be generated and managed using the​javascript​ library , and only one root value needs to be stored on the chain, which is very economical . Many project parties choose to use to issue whitelists.​merkletreejs​​gas​​Merkle Tree​