默克尔树(⼜叫哈希树)是⼀种典型的⼆叉树结构,有⼀个根节点、⼀组中间节点和⼀ 组叶节点组成。
曾⼴泛⽤于⽂件系统和P2P系统中。⽐如 git 、 区块链 、 IPFS 等
主要特点:类似二叉树, 最下⾯的叶节点包含存储数据或其哈希值; ⾮叶⼦节点(包括中间节点和根节点)都是它的两个⼦节点内容的哈希值
比特币中的默克尔树
判断某个叶⼦节点是否存在于树中,不需要知道整棵树的所有 节点的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); });