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 进行验证的合约

参考

Subscribe to xyyme.eth
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.