You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity与Firebase函数Merkle树叶子计算不一致问题求助

Merkle树叶子节点计算不一致导致合约验证失败的排查与修复

问题背景

我正在开发一个集成Merkle树白名单功能的NFT智能合约,流程为:Firebase函数接收用户地址与预留代币数量,生成Merkle Proof返回前端,前端再调用合约的mint函数。当前核心问题是Firebase侧与Solidity合约侧的叶子节点计算逻辑不匹配,导致合约始终验证Proof失败:

  • 合约侧:通过keccak256(abi.encodePacked(msg.sender, _addressReservedTokens))将地址(address类型)和预留数量(uint256类型)打包后哈希生成叶子
  • Firebase侧:实际代码仅对地址单独做哈希,完全未结合预留数量,且地址格式存在错误

双方代码对比

Solidity合约代码

// 1. 地址校验:验证proof是否有效
function isPrivateListed(
        uint256 _addressReservedTokens,
        bytes32[] memory _proof
    ) public view returns (bool) {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _addressReservedTokens));
        return MerkleProof.verify(_proof, privateListRoot, leaf);
    }


// 2. 前端调用的Mint函数
function privateMint(
        uint256 _mintAmount,
        uint256 _addressReservedTokens,
        bytes32[] calldata proof
    ) public payable {
        require(privateOpen, "The private mint is not opened");
        require(
            isPrivateListed(_addressReservedTokens, proof),
            "you are not in the private list"
        );
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

        uint256 owned = addressMintedBalance[msg.sender];
        require(
            owned + _mintAmount <= _addressReservedTokens,
            "You have less nft reserved"
        );

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
            addressMintedBalance[msg.sender]++;
        }
    }

Firebase函数代码(原始错误版本)

// 1. 从客户端调用的函数中获取参数
const walletAddress = data.walletAddress;

let whitelistAddresses = [
  '0x38207d0e84edb04a57482c3769fe1926175203733',
  '0x943926a8ff0000350d0b879a658fa52bcd4fca186',
];

// 2. 通过keccak256哈希`whitelistAddresses`的所有元素,创建`leafNodes`数组;再以keccak256为算法创建Merkle树对象
const leafNodes = whitelistAddresses.map((addr) => keccak256(addr));
const merkleTree = new MerkleTree(leafNodes, keccak256, {
  sortPairs: true,
});

// 3. 获取Merkle树的根哈希,转换为十六进制格式(0x开头)
const rootHash = merkleTree.getRoot();
const rootHashHex = merkleTree.getHexRoot();
// ***** ***** ***** 客户端侧 ***** ***** ***** //

const claimingAddress = keccak256(walletAddress);
const hexProof = merkleTree.getHexProof(claimingAddress);

const verifyProof = merkleTree.verify(hexProof, claimingAddress, rootHash);

核心差异分析

  1. 数据组合缺失:Firebase代码仅对地址单独哈希,完全未结合预留代币数量,但合约侧是将地址+预留数量一起打包哈希,这是最关键的不匹配点
  2. 数据编码规则不一致:Solidity的abi.encodePacked会按原始字节长度拼接address(20字节)和uint256(32字节),而直接拼接字符串的字节流与该规则完全不同
  3. 地址格式错误:Firebase中的测试地址长度不符合标准(正常以太坊地址为20字节,示例中多了1位),会直接导致哈希结果错误

修复方案(修正Firebase代码)

需要完全对齐Solidity侧的叶子节点计算逻辑,使用ethers.js模拟abi.encodePacked的行为:

const { ethers } = require("ethers");

// 1. 从客户端获取参数:需同时获取地址和对应预留数量
const walletAddress = data.walletAddress;
const userReservedAmount = data.reservedAmount;

// 2. 修正白名单数据:使用标准地址,每个地址绑定对应预留数量
const whitelistEntries = [
  { address: '0x38207d0e84edb04a57482c3769fe192617520373', reserved: 5 },
  { address: '0x943926a8ff0000350d0b879a658fa52bcd4fca18', reserved: 3 },
];

// 3. 按照Solidity abi.encodePacked规则生成叶子节点
const leafNodes = whitelistEntries.map(entry => {
  // 模拟合约侧的打包逻辑
  const packedData = ethers.utils.solidityPack(
    ["address", "uint256"],
    [entry.address, entry.reserved]
  );
  return ethers.utils.keccak256(packedData);
});

// 4. 创建Merkle树(保持sortPairs参数与合约侧一致)
const merkleTree = new MerkleTree(leafNodes, ethers.utils.keccak256, {
  sortPairs: true,
});

// 5. 生成当前用户的叶子节点并获取Proof
const userLeaf = ethers.utils.keccak256(
  ethers.utils.solidityPack(["address", "uint256"], [walletAddress, userReservedAmount])
);
const hexProof = merkleTree.getHexProof(userLeaf);

// 本地验证Proof(可选)
const rootHashHex = merkleTree.getHexRoot();
const verifyResult = merkleTree.verify(hexProof, userLeaf, rootHashHex);

额外注意事项

  • 确保合约部署时,privateListRoot设置为Firebase生成的rootHashHex
  • 前端调用privateMint时,传入的_addressReservedTokens必须与Firebase生成叶子时使用的预留数量完全一致
  • 保持Merkle树的sortPairs参数在Firebase与合约侧(若MerkleProof库有配置)完全相同

内容的提问来源于stack exchange,提问作者Pietro Ciattaglia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 15:15:35