# 白名單機制

目前有三種做法，以下先談到搭配後端與合約的簽名驗證做法

## 1. 後端簽名，合約驗證

1.生成私鑰和對應地址：首先，你需要生成一對以太坊私鑰和公鑰，然後從公鑰導出對應的以太坊地址。這個地址將被用於識別簽名者。重要的是，私鑰必須保密，只能由授權的個人或系統持有。

2.將地址設置為合約中的root：在部署或初始化智能合約時，你需要將這個生成的以太坊地址設置為合約中的root變量。這個地址代表了被授權進行特定操作（如白名單鑄幣）的實體。

3.在後端使用私鑰進行簽名：在後端，當用戶請求鑄造代幣時，你需要使用私鑰對用戶的地址或其他相關數據進行簽名。這個簽名後續會被用戶提交給智能合約。

4.在智能合約中驗證簽名：當智能合約接收到用戶提交的簽名時，它會使用hash.recover(signature)方法從簽名中恢覆出地址，並檢查這個地址是否與存儲在root中的地址相匹配。如果匹配，則驗證成功，表明簽名是由持有對應私鑰的實體生成的，用戶便被授權進行鑄幣操作。

後端

> 生成簽名

```javascript
function backendSign(address, contractName) {
  try {
    const messageHash = web3.utils.soliditySha3(
      { type: "address", value: address }, // 發出交易的人的地址
      { type: "string", value: contractName } // 合約的名稱
    );
    const signature = EthCrypto.sign(
      identity.privateKey, // privateKey
      messageHash // hash of message
    );
    return signature; // 填入 mint 交易的簽名
  } catch (err) {
    return "address not valid"
  }
}
```

合約端

> 呼叫 mint 時傳入後端生成的簽名

<pre class="language-solidity"><code class="lang-solidity">function initialize(address root_, uint256 reserveAmount, uint256 price_, string calldata unrevealedURI) external onlyOwner{
  require(!initialized, "only initialized once");
  initialized = true;
  root = root_;
}

function mint(bytes memory _signature) external nonReentrant payable{
  require(msg.value == price, "Price not correct");
  require(totalSupply() + devReserve &#x3C; collectionSize, "Reach max supply");
  _verifyAndMint(_signature, msg.sender);
}

function _verifyAndMint(bytes memory _signature, address account) internal{
  bytes32 msgHash = keccak256(abi.encodePacked(account, name()));
  require(isValidSignature(msgHash, _signature), "Not authorized to mint");
  ...
}
<strong>
</strong><strong>function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bool isValid) {
</strong>  return hash.recover(signature) == root;
}
</code></pre>

## Merkle Tree 方式

產生 root

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

// List of whitelisted addresses
const whitelistAddresses = [
  '0xAddress1...',
  '0xAddress2...',
  '0xAddress3...',
  // ... more addresses
];

// Generate leaf nodes
const leaves = whitelistAddresses.map(addr => keccak256(addr));
const tree = new MerkleTree(leaves, keccak256, { sortPairs: true });

// Get the root hash of the Merkle tree (to be used in the smart contract)
const rootHash = tree.getRoot().toString('hex');
```

產生 proof

> 用戶發送 mint 前，瀏覽器用個別地址跟 API Server 獲取 proof

```javascript
const address = '0xAddress1...'; // Address to generate proof for
const leaf = keccak256(address);
const proof = tree.getHexProof(leaf);

console.log(bytes32Proof); // This will be used in the smart contract for verification
```

contract&#x20;

```javascript
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract WhitelistedNFT {
  bytes32 public merkleRoot;

  constructor(bytes32 _merkleRoot) {
    merkleRoot = _merkleRoot;
  }

  function mint(bytes32[] calldata _merkleProof) public {
    // Verify the provided address against the Merkle root
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Not in whitelist");

    // Mint NFT logic here...
  }
}
```

可用以下 Node.js 腳本測試

> 記得 leaves 至少要一個以上，不然 getProof 會回傳空 array
>
> 在 Node 端 verify 時要用 getHexProof 只用 getProof，但如果是要給合約的 proof 要用 byte32 格式，所以要用 getHexProof

```javascript
const { MerkleTree } = require("merkletreejs");
const keccak256 = require("keccak256");
const leaves = ['0xaa2eAbb245944168705e3Ad21C9D266131E296E7', '0xaaA0ea4E952C2a9bB6FDaDf7cBa1a08eb20EE157'].map(x => keccak256(x))
const tree = new MerkleTree(leaves, keccak256)
const root = tree.getRoot().toString('hex')
const leaf = keccak256('0xaa2eAbb245944168705e3Ad21C9D266131E296E7')
const proof = tree.getProof(leaf)
const rootHash = tree.getRoot().toString("hex");
console.log(tree.verify(proof, leaf, rootHash)) // true
```

#### 實際合約與測試

Hardhat 測試

whitelist.sol

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract Whitelist {
    bytes32 public merkleRoot;

    constructor(bytes32 _merkleRoot) {
        merkleRoot = _merkleRoot;
    }

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

test.js

```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MerkleTree } = require("merkletreejs");
const keccak256 = require("keccak256");

describe("Whitelist Contract", function () {
  let Whitelist;
  let whitelistContract;
  let accounts;
  let whitelistAddresses;
  let tree;
  let rootHash;

  before(async function () {
    Whitelist = await ethers.getContractFactory("Whitelist");
    accounts = await ethers.getSigners();

    // Set up whitelist addresses
    whitelistAddresses = [accounts[0].address, accounts[1].address];
    const leaves = whitelistAddresses.map((addr) => keccak256(addr));
    tree = new MerkleTree(leaves, keccak256, { sortPairs: true });
    rootHash = tree.getHexRoot();
    whitelistContract = await Whitelist.deploy(rootHash);
  });

  it("should allow whitelisted address", async function () {
    const address = whitelistAddresses[0];
    const leaf = keccak256(address);
    const proof = tree.getHexProof(leaf);

    expect(await whitelistContract.isWhitelisted(proof, address)).to.be.true;
  });

  it("should not allow non-whitelisted address", async function () {
    const nonWhitelistedAddress = accounts[2].address;
    const leaf = keccak256(nonWhitelistedAddress);
    const proof = tree.getHexProof(leaf);

    expect(await whitelistContract.isWhitelisted(proof, nonWhitelistedAddress))
      .to.be.false;
  });
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://easonwang.gitbook.io/blockchain/smart/erc721-fan-li/bai-ming-dan-ji-zhi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
