默克尔树(⼜叫哈希树)是⼀种典型的⼆叉树结构,有⼀个根节点、⼀组中间节点和⼀ 组叶节点组成。
曾⼴泛⽤于⽂件系统和P2P系统中。⽐如 git 、 区块链 、 IPFS 等
主要特点:类似二叉树, 最下⾯的叶节点包含存储数据或其哈希值; ⾮叶⼦节点(包括中间节点和根节点)都是它的两个⼦节点内容的哈希值
比特币中的默克尔树
MerkleTree的校验
判断某个叶⼦节点是否存在于树中,不需要知道整棵树的所有 节点的hash值,只需要找到需要校验的叶⼦节点的相邻节点的HASH,以及祖先节点相 邻节点的HASH即可
空投白名单应用
步骤简单概括
- 创建默克尔树, 拿到
merkleRoot
和对应地址的Proof, 其中merkleRoot上传到智能合约 - 使用地址和对应的Proof调用合约去验证
相关开源库
合约开源库: https://docs.openzeppelin.com/contracts/4.x/api/utils#MerkleProof
前端开发库:https://github.com/miguelmota/merkletreejs
步骤
-
前端配置的⽩名单地址,⽣成根节点hash。
-
合约存根节点hash值。
-
前端传⼊领取空投的address,和兄弟节点以及祖先的兄弟节点hash
合约代码
pragma solidity ^0.8.9;
pragma abicoder v2;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract Merkletree{
bytes32 public rootHash;
function setRootHash(bytes32 _rootHash) public{
// 设置节点hash,需要做权限管理 ,方便测试,不做限制
rootHash = _rootHash;
}
function claimable(bytes32[] memory proof, bytes32 leaf) public view returns(bool) {
// 校验
return MerkleProof.verify(proof, rootHash, leaf);
}
function claim(bytes32[] memory proof, bytes32 leaf) public {
// 验证当前地址是否是msg.sender
require(leaf == keccak256(abi.encodePacked(msg.sender)), "only by sender");
// 验证叶⼦是否在⽩名单
require(claimable(proof, leaf), 'auth faild');
// mint Token and transfer to leaf address
}
}
js代码
import {
MerkleTree } from "merkletreejs";
import keccak256 from "keccak256";
import {
newContract,ChainId, multicallClient } from "@chainstarter/multicall-client.js";
const WhiteList = [
"0xd4D75e2eB480D61822B9cA40EB54cb322F08d920",
"0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5",
"0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
"0x8b8a11755a3f2B9f2cE384bc42Cb25053Eb4FF33",
"0xd2Ece3F5A7738E4B14c13C0336E3e879755FCd77"
]
const leaves = WhiteList.map((x) => keccak256(x));
const merkleTree = new MerkleTree(leaves, keccak256);
const rootHash = merkleTree.getRoot().toString("hex");
const leaf = keccak256(inputAccount);
const proof = merkleTree.getProof(leaf);
//前端校验
const isWhiteList = merkleTree.verify(proof, leaf, rootHash)
// 合约校验
// 在此之前,需要调用setRootHash,将rootHash存入合约
const leaf32 = `0x${
leaf.toString("hex")}`;
const proof32 = proof.map((x) => "0x" + x.data.toString("hex"));
const contract = newContract(
MerkleTreeAbi,
MerkleTreeAddress,
ChainId.RINKEBY
);
multicallClient([
contract.claimable(proof32, leaf32),
contract.rootHash(),
]).then((res) => {
console.log("是否存在白名单", res[0].returnData);
console.log("合约rootHash", res[1].returnData);
setIsWhiteList(!!res[0].returnData);
});