Merkle Tree在高效验证数据的同时减少了链上计算和存储,因为非常适合基于区块链的白名单验证,空投,IDO等需要验证数据的业务。
默克尔树,在区块链出现前,曾广泛用于文件系统和P2P系统中。 在区块链中,默克尔树常用于高效验证数据,如,实现空投,白名单,IDO,混币器等。
默克尔树是一种hash树,底层叶子节点的hash变动会一层一层的传递直到树根root,所以roothash实际代表了底层所有数据的摘要,通过验证roothash来确定是否是它的叶子节点。那么只需要在链上记录树根就可以开始验证其叶子节点的归属,每当新增叶子节点,也只需更新roothash即可,而不必存储整棵树,并且roothash的计算也可放在链下进行。
Merkle Tree在高效验证数据的同时减少了链上计算和存储,因为非常适合基于区块链的白名单验证,空投,IDO等需要验证数据的业务。
为配合solidity,选择使用keccak256算法;
merkletree.getProof()
返回的是Array of objects;merkletree.getHexProof()
返回的Proof array as hex strings,更适合作为参数传入合约。
const { MerkleTree } = require('merkletreejs')
const keccak256 = require('keccak256')
let whitelist = [
"0xc11cb63bdca7627f74ec69c14612522fdb7a0c20",
"0x1499b8312e6fe58b5d1164d4eccf795367c9e1d3",
"0x55f510be6ab4c7e07ec6ee637aa83574975d6898",
"0xcc2fe3615a45fcacc3534d53be41c6543a0a312d",
"0xee226379db83cffc681495730c11fdde79ba4c0c",
"0x55f510be6ab4c7e07ec6ee637aa83574975d6898",
"0x18b2a687610328590bc8f2e5fedde3b582a49cda"
]
const leafNodes = whitelist.map(addr => keccak256(addr));
const merkletree = new MerkleTree(leafNodes, keccak256, {sortPairs: true});
const rootHash = merkletree.getRoot().toString('hex');
console.log("rootHash is: ", rootHash);
console.log(merkletree.toString());
console.log("--------verify------------");
const claimingAddr2 = leafNodes[0];
const hexProof = merkletree.getHexProof(claimingAddr2);
console.log(hexProof); // Proof array as hex strings
console.log(merkletree.verify(hexProof, claimingAddr2, rootHash));
使用openzeppelin的MerkleProof.verify
进行验证。
在合约中只需要存储roothash,验证时用户需传入默克尔树证明,新增叶子节点时,也只需要更新roothash。
验证通过后可领取空投,并将状态改为已领取,避免重复领取。
存储所有符合条件的地址,这样当用户访问站点时,可以立即查看他们是否符合条件;
如果符合条件,再调用智能合约验证,并执行接下来的操作;
设置merkle树根,并提供验证接口;
白名单地址可自行调用合约来领取空投;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract WhiteListMerkle { bytes32 public merkleRoot = 0xb63cec15d66cdcc08199c64a2105f32356c226151d1d1b43a6e9d68fb2e7684f;
mapping(address => bool) public whitelistClaimed;
// verify the provided _merkleProof
function whitelist(bytes32[] calldata _merkleProof) public{
require(!whitelistClaimed[msg.sender], "address has already claimed.");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Invaild proof.");
whitelistClaimed[msg.sender] = true;
}
function setRootHash(bytes32 roothash_) external {
merkleRoot = roothash_;
}
}
## 合约交互
调用合约API进行验证,需传入默克尔树证明。`merkletree.getHexProof()`
async function main() { const WhiteListMerkle = await ethers.getContractFactory("WhiteListMerkle"); const whiteListMerkle = await WhiteListMerkle.attach(contractAddress);
// verify
// const merkleProof = [
// '0x497de751e77af5954dbcae3eba7c203606e8c52006fb1fcc2c0df507b976363a',
// '0x71249e6e1d6f7059bd2f07ab00601bade6fbd5ffd7c0d63b983cab0bc097bfe3',
// '0xa25d73d7d8dbcf73b7b71286c8de55451b4efa13be5bac629d7b2c966588ea0c'
// ]
// const tx_verify = await whiteListMerkle.whitelist(merkleProof);
// console.log(tx_verify);
// view whitelistClaimed
const view_claimed = await whiteListMerkle.whitelistClaimed(owner.address);
console.log(view_claimed);
}
## merkle空投完整案例
contract MerkleDistributor { address public immutable token; bytes32 public immutable merkleRoot;
// This is a packed array of booleans.
mapping(uint256 => uint256) private claimedBitMap;
constructor(address token_, bytes32 merkleRoot_) {
token = token_;
merkleRoot = merkleRoot_;
}
function isClaimed(uint256 index) public view returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}
function _setClaimed(uint256 index) private {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}
function claim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) external {
require(!isClaimed(index), 'MerkleDistributor: Drop already claimed.');
// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, account, amount));
require(MerkleProof.verify(merkleProof, merkleRoot, node), 'MerkleDistributor: Invalid proof.');
// Mark it claimed and send the token.
_setClaimed(index);
require(IERC20(token).transfer(account, amount), 'MerkleDistributor: Transfer failed.');
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!