xyyme.eth

Posted on Mar 10, 2022Read on Mirror.xyz

NFT白名单校验-Merkle Tree

目前NFT白名单校验主要有两种方式

  • Merkel Tree
  • 后台签名

这篇文章我们主要来介绍 Merkle Tree 的实现方法。关于其原理,我们这里就不再多赘述了,不太了解的朋友可以 Google 一下,今天主要讨论一下实现细节。

一般要实现 Merkle Tree 校验,主要有两部分的工作:

  • 后台根据白名单地址生成一个 Merkle Tree,包括 Merkle Root。管理员将生成的 Merkle Root 设置到合约中
  • 前端(或者后端)根据当前的用户,生成一个 Merkle Proof。将 Proof 作为参数传入合约中,与 msg.sender 和之前设置的 Merkle Root 进行校验

合约部分

这里的校验逻辑可以直接使用 Openzeppelin 的现成合约库,可以看到,需要的参数有:

  • proof(由链下生成传入,每个用户的 proof 都不同)
  • root(链下生成好之后设置为状态变量,所有用户均相同)
  • leaf(由用户的地址生成)
function verify(
    bytes32[] memory proof,
    bytes32 root,
    bytes32 leaf
) internal pure returns (bool) {
    return processProof(proof, leaf) == root;
}

那么如何调用 verify 方法呢:

function verify(address addr, bytes32[] calldata _merkleProof) public view returns (bool) {
    bytes32 leaf = keccak256(abi.encode(addr));
    return MerkleProof.verify(_merkleProof, merkleRoot, leaf);
}

通过 encode 编码之后再进行 hash 运算,便可以生成 Merkle Tree 的一个 leaf,这个 leaf 便是 verify 方法的参数 leaf。注意这里我们是将用户地址作为参数传给合约,实际开发时应该直接使用 msg.sender 作为原始参数,这是为了保证安全(否则不是白名单的地址就可以传递白名单地址来给自己 mint)。

verify 方法会返回一个 boolean 值,代表白名单校验是否成功。

链下计算部分

下面来说说链下的工作,前面说过,链下要根据白名单数据生成 Merkle Tree,我们这里使用 Javascript 来完成相关工作。

首先需要安装两个依赖包:

npm install --save keccak256 merkletreejs

实例代码如下:

const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');

// 白名单地址,这里采用了硬编码,实际开发应该从数据库读取
// 这里我们随机生成几个地址,作为白名单示例
let whitelistAddresses = [
    "0x978DCD67B155b3dBecd221Ec0D193f6fa7d3B8c2",
    "0x41fed4790A6137083fac595e00090b2D01d012b6",
    "0xFbC43c738d17F4d43627B8675A8cdC691A603BB3",
    "0xBD925b9Fab6Eb9f713238Cc688A91a7f5c7Ff4c8",
    "0xc6c74C251aa41FCB0De4c55fb751eec04f66774A"
]

// 计算 leaf 叶子结点的数据
const leafNodes = whitelistAddresses.map(addr => keccak256(addr));
// 生成 Merkle Tree
const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });

// 获取 Merkle Root
const rootHash = merkleTree.getRoot();
// 打印查看 Merkle Tree 全部数据
console.log('Whitelist Merkle Tree\n', merkleTree.toString());
// 打印查看 Root 数据,需要设置到合约中
console.log("Root Hash: ", rootHash.toString('hex'));

// 选择一个白名单地址进行校验
const claimingAddress = keccak256("0xc6c74C251aa41FCB0De4c55fb751eec04f66774A");

// 计算这个地址的 Merkle Proof,注意这就是我们要传给合约的参数 Proof
const hexProof = merkleTree.getHexProof(claimingAddress);
console.log(hexProof);

// 校验
console.log(merkleTree.verify(hexProof, claimingAddress, rootHash));

通过这段代码,我们就可以生成 Merkle Root,以及用户地址对应的 Merkle Proof

一般来说,合约中还会包括一个设置 Root 的方法,便于在合约部署之后,将 Root 设置进去:

function setRoot(bytes32 _root) external onlyOwner {
    root = _root;
}

总结

Merkle Tree 白名单校验的代码逻辑就是这些。一般来说,整个白名单业务顺序如下:

  1. 根据所有白名单地址计算出 Merkle Root,并设置到合约中
  2. 前端会根据用户地址请求后端获取对应 Merkle Proof
  3. 将 Proof 传入合约进行校验

使用 Merkle Tree 进行验证的合约

参考

https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9

NFT