目前NFT白名单校验主要有两种方式
其中签名验证的详细介绍在这篇文章。
这篇文章我们主要来介绍 Merkle Tree 的实现方法。关于其原理,我们这里就不再多赘述了,不太了解的朋友可以 Google 一下,今天主要讨论一下实现细节。
一般要实现 Merkle Tree 校验,主要有两部分的工作:
这里的校验逻辑可以直接使用 Openzeppelin 的现成合约库,可以看到,需要的参数有:
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 白名单校验的代码逻辑就是这些。一般来说,整个白名单业务顺序如下: