# NFT智能合约白名单机制 **Published by:** [BlockGames](https://paragraph.com/@981225/) **Published on:** 2022-05-12 **URL:** https://paragraph.com/@981225/nft-3 ## Content 介绍 白部是推广和奖励的主要内容。现在有实验的3种实现方法,有几种方法介绍了他们自己的项目。我在本文中,并结合了自己的优点。 最原始的方式——储存在合同里 其他编程语言和现代计算系统的开发者可以说,将数据存储的方式和语言描述是非常简单的数据处理方式。 这种方式可能会导致你产生大量有效的气体,是非常低的方式。这种方式也有优势,通过etherscan或行来测试他是否在白上会更容易。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.11; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract PrimitiveWhiteList is ERC721Enumerable, Ownable { uint256 public constant MINT_PRICE = 0.1 ether; mapping(address => bool) public whitelist; constructor() ERC721("Primitive Whitelist", "PW") {} function whitelistMint(uint256 amount) external payable { require(msg.value == amount * MINT_PRICE, "Ether send below price"); require(whitelist[msg.sender], "Not in whitelist"); // start minting uint256 currentSupply = totalSupply(); for (uint256 i = 1; i <= amount; i++) { _safeMint(msg.sender, currentSupply + i); } } function addWhitelist(address _newEntry) external onlyOwner { whitelist[_newEntry] = true; } function removeWhitelist(address _newEntry) external onlyOwner { require(whitelist[_newEntry], "Previous not in whitelist"); whitelist[_newEntry] = false; } } 优点:简单有效,容易编码,容易添加地址或删除地址 缺点:效率真的很低,对发行方来说真的很贵 聪明的方法 I — Merkle Tree Airdrop 执行白名单的另一种方式是利用Merkle树。Merkle树是区块链中的一个重要角色。Merkle树利用了哈希的特性,即输入的轻微变化将导致完全不同的输出,以及两个输入导致相同输出的概率几乎不可能。 Merkle树的结构如下所示。当前节点的哈希值等于其左侧子节点、右侧子节点及其数据的哈希值。因此,从哈希的属性来看,任何改变都会导致完全不同的输出;我们可以利用这个属性来实现白名单。 哈希规则,如果哈希的输出与 Top Hash 进行比较一样,你可以确保 L1 同一个输入。 要完成这一天,需要生成一棵merkle树,你对你的圈子过程上拿走,以造点气。我们将使用javascript来生成merkle树。如果ether.js比较,你也很像也可以使用ethersutils .solidityK存储下面的k256哈希来为这另外的内容。在将JSON的包含在中。,把数据改成另一种类型来调整。 import fs from "fs"; import { MerkleTree } from "merkletreejs"; import Web3 from "web3"; import keccak256 from "keccak256"; import dotenv from "dotenv"; // Establish web3 provider dotenv.config(); const web3 = new Web3(process.env.MAINNET_RPC_URL); // hashing function for solidity keccak256 const hashNode = (account, amount) => { return Buffer.from( web3.utils .soliditySha3( { t: "address", v: account }, { t: "uint256", v: amount } ) .slice(2), "hex" ); }; // read list, Note: the root path is at cwd // the json file structure: {"<address>": <amount>, "<address>": <amount>, ...} const readRawList = (path) => { const rawdata = fs.readFileSync(path); const data = JSON.parse(rawdata); return data; }; const generateMerkleTree = (data) => { const leaves = Object.entries(data).map((node) => hashNode(...node)); const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }); const merkleRoot = merkleTree.getHexRoot(); return [merkleRoot, merkleTree]; }; const checkTree = (pairs, tree, root) => { for (const [key, value] of Object.entries(pairs)) { const leaf = hashNode(key, value); const proof = tree.getProof(leaf); // hex proof for solidity byte32[] input // const hexProof = tree.getHexProof(leaf); if (!tree.verify(proof, leaf, root)) { console.err("Verification failed"); return false; } } return true; }; function main(filepath, outputPath) { const rawData = readRawList(filepath); const [merkleRoot, merkleTree] = generateMerkleTree(rawData); if (checkTree(rawData, merkleTree, merkleRoot)) { fs.writeFileSync( outputPath, JSON.stringify({ root: merkleRoot, tree: merkleTree, }) ); console.log(`Successfully generate merkle tree to ${outputPath}.`); } else { console.err("Generate merkle tree failed."); } } main("./db/freeClaimList.json", "./db/freeClaimMerkle.json"); 需要通过solidity函数将根存储在合约中。然后用MerkleProof库进行链上验证。每当有人想mint时,需要在前台或者后台为用户生成证明,用tree.getHexProof函数生成bytes32[]证明。查看checkTree函数中的注释,了解更详细的实现。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.11; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; contract PrimitiveWhiteList is ERC721Enumerable, Ownable { using ECDSA for bytes32; uint256 public constant MINT_PRICE = 0.1 ether; bytes32 private _whitelistMerkleRoot; constructor() ERC721("Merkle Tree Whitelist", "MTW") {} function whitelistSale(bytes32[] memory proof, uint256 amount) external payable { // merkle tree list related require(_whitelistMerkleRoot != "", "Free Claim merkle tree not set"); require( MerkleProof.verify( proof, _whitelistMerkleRoot, keccak256(abi.encodePacked(msg.sender, amount)) ), "Free Claim validation failed" ); // start minting uint256 currentSupply = totalSupply(); for (uint256 i = 1; i <= amount; i++) { _safeMint(msg.sender, currentSupply + i); } } function setWhitelistMerkleRoot(bytes32 newMerkleRoot_) external onlyOwner { _whitelistMerkleRoot = newMerkleRoot_; } } 每当调整白名单时,必须更新_freeClaimMerkleRoot。 优点:经济效益高,易于使用 缺点:对用户来说,mint时gas的用量略大。每次希望改变白名单时都需要重置Root。 聪明的方法二 — 后台签名 最后一种方式也比第一种方式更便宜。然而,最后一种方式比前一种方式更中心化。这种方法利用了签署信息的机制。需要在后端设置一个地址,并保持它的可信度。每当一个白名单上的用户希望mint需要首先验证它。验证后,可以签署信息并将其传回给用户。然后,用户就可以使用签名的信息,并进行mint。 但你可能会问,如何确保没有人可以伪造签名信息?原因是,如果你用你的私钥签署信息,你将得到一个哈希的信息。然后,你可以用哈希的信息和哈希前的信息生成公钥。因此,如果你在合约处存储公钥,在后端用私钥签署消息,你可以确保没有人可以伪造消息。 然而,为了防止重放攻击,你可以使用一个nonce来确保签署的消息不会被恶意使用。因此,整个签名过程将如下所示: import Web3 from "web3"; import dotenv from "dotenv"; // Establish web3 provider dotenv.config(); const web3 = new Web3(process.env.MAINNET_RPC_URL); const generateNonce = () => { return crypto.randomBytes(16).toString("hex"); }; // Hash message const mintMsgHash = (recipient, amount, newNonce, contract) => { return ( web3.utils.soliditySha3( { t: "address", v: recipient }, { t: "uint256", v: amount }, { t: "string", v: newNonce }, { t: "address", v: contract } ) || "" ); }; const signMessage = (msgHash, privateKey) => { return web3.eth.accounts.sign(msgHash, privateKey); }; // Signing the message at backend. // You can store the data at database or check for Nonce conflict export const Signing = (address, amount) => { const newNonce = generateNonce(); const hash = mintMsgHash( address, amount, newNonce, config.ContractAddress ); const signner = signMessage(hash, config.PrivateKey); return { amount: amount, nonce: newNonce, hash: signner.message, signature: signner.signature, }; } 因此在生成哈希信息后,即在返回数据处的哈希和签名,可以将其传递给前端,供用户mint NFT。因此必须在链上设置验证。 //SPDX-License-Identifier: MIT pragma solidity ^0.8.11; import "@openzeppelin/contracts/token/ERC721/ERC721"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract SignatureWhitelist is ERC721, Ownable { using ECDSA for bytes32; address private _systemAddress; mapping(string => bool) public _usedNonces; function publicSale( uint256 amount, string memory nonce, bytes32 hash, bytes memory signature ) external payable nonReentrant { // signature realted require(matchSigner(hash, signature), "Plz mint through website"); require(!_usedNonces[nonce], "Hash reused"); require( hashTransaction(msg.sender, amount, nonce) == hash, "Hash failed" ); _usedNonces[nonce] = true; // start minting uint256 currentSupply = totalSupply(); for (uint256 i = 1; i <= amount; i++) { _safeMint(msg.sender, currentSupply + i); } } function matchSigner(bytes32 hash, bytes memory signature) public view returns (bool) { return _systemAddress == hash.toEthSignedMessageHash().recover(signature); } function hashTransaction( address sender, uint256 amount, string memory nonce ) public view returns (bytes32) { bytes32 hash = keccak256( abi.encodePacked(sender, amount, nonce, address(this)) ); return hash; } } 优点:对开发者来说更便宜,在后端更容易管理白名单。 缺点:造币需要更多的gas,不那么去中心化。 总结 有很多方法来实现白名单机制。每种方式都有其优点和缺点。因此,开发者应该仔细考虑需求,并在每种方式之间找到平衡。 原文:https://coinsbench.com/smart-contract-whitelist-mechanism-fbe3464159ed ## Publication Information - [BlockGames](https://paragraph.com/@981225/): Publication homepage - [All Posts](https://paragraph.com/@981225/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@981225): Subscribe to updates