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

智能合约与JavaScript测试中MerkleProof生成哈希不一致问题

问题原因

你的验证失败核心是链下叶子节点的编码逻辑和链上不匹配,其次存在默克尔树配置参数错误的问题:

  1. 链上生成叶子用的是keccak256(abi.encodePacked(msg.sender, tokenId)),是直接将20字节的地址和32字节的uint256数值做二进制拼接后哈希,没有任何额外字符。而你链下是把地址和tokenId转成字符串、中间加空格后再做哈希,两者输入完全不同,哈希结果必然不一致。
  2. 你使用merkletreejs时配置的排序参数错误,OpenZeppelin的MerkleProof默认会对两个子节点排序后再计算父哈希,对应merkletreejs的配置项是sortPairs: true,而非你写的sort: true,参数错误会导致默克尔树结构和链上计算逻辑不匹配。
  3. 测试代码中部署合约的逻辑有误,你声明的合约实例是token,但部署时写的是await F24.new(),会导致合约部署失败。
解决步骤

1. 调整链下叶子节点生成逻辑

使用和Solidity abi.encodePacked 逻辑完全对齐的哈希计算方式,推荐用ethers.js提供的solidityKeccak256方法,无需手动处理数据补位规则。

2. 修正默克尔树配置参数

将merkletree初始化时的排序参数改为sortPairs: true。

3. 修正测试代码的合约部署逻辑

修改后测试代码示例
require('@openzeppelin/test-helpers');
const { ethers } = require("hardhat");
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');

const Token = artifacts.require('Token');

// 改为结构化存储地址和tokenId,避免字符串拼接
const testLeaves = [
  {addr: '0x627306090abaB3A6e1400e9345bC60c78a8BEf57', tokenId: 258831},
  {addr: '0x627306090abaB3A6e1400e9345bC60c78a8BEf57', tokenId: 166699},
  {addr: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', tokenId: 889988},
  {addr: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', tokenId: 707070},
  {addr: '0x821aEa9a577a9b44299B9c15c88cf3087F3b5544', tokenId: 566396},
  {addr: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2', tokenId: 888888},
  {addr: '0x2932b7A2355D6fecc4b5c0B6BD44cC31df247a2e', tokenId: 333666},
  {addr: '0x2191eF87E392377ec08E7c08Eb105Ef5448eCED5', tokenId: 658658},
  {addr: '0x0F4F2Ac550A1b4e2280d04c21cEa7EBD822934b5', tokenId: 130779},
  {addr: '0x6330A553Fc93768F612722BB8c2eC78aC90B3bbc', tokenId: 123456},
  {addr: '0x5AEDA56215b167893e80B4fE645BA6d5Bab767DE', tokenId: 9998888},
];

contract('Token', function (accounts) {
  beforeEach(async function () {
    // 修正合约部署逻辑
    this.token = await Token.new();
  });

  it('should return true for a valid leaf', async function () {
    // 生成和链上逻辑一致的叶子节点
    const leaves = testLeaves.map(item => 
      ethers.utils.solidityKeccak256(["address", "uint256"], [item.addr, item.tokenId])
    );
    // 修正排序参数
    const merkleTree = new MerkleTree(leaves, keccak256, {sortPairs: true});
    const root = merkleTree.getHexRoot();
    const proof = merkleTree.getHexProof(leaves[0]);
    
    // 验证链下逻辑正确性
    console.log("链下计算叶子哈希:", leaves[0]);
    console.log("默克尔根:", root);
    console.log("默克尔证明:", proof);
    console.log("链下验证结果:", merkleTree.verify(proof, leaves[0], root));

    await this.token.setMerkleRoot(root, {from: accounts[0]});
    // 调用合约验证
    expect(await this.token.claimToken(258831, proof, {from: accounts[0]})).to.equal(true);
  });
});
验证方法

如果修改后还是报错,可以对比合约claimToken方法中emit出来的leaf值和链下计算的leaves[0]值:

  • 如果两个值不一样:说明编码逻辑还有问题,检查地址大小写、tokenId类型是否匹配
  • 如果两个值一样:说明默克尔树生成或者证明导出逻辑有问题,检查merkletreejs版本和配置参数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 16:45:03