智能合约与JavaScript测试中MerkleProof生成哈希不一致问题
问题原因
你的验证失败核心是链下叶子节点的编码逻辑和链上不匹配,其次存在默克尔树配置参数错误的问题:
- 链上生成叶子用的是
keccak256(abi.encodePacked(msg.sender, tokenId)),是直接将20字节的地址和32字节的uint256数值做二进制拼接后哈希,没有任何额外字符。而你链下是把地址和tokenId转成字符串、中间加空格后再做哈希,两者输入完全不同,哈希结果必然不一致。 - 你使用merkletreejs时配置的排序参数错误,OpenZeppelin的
MerkleProof默认会对两个子节点排序后再计算父哈希,对应merkletreejs的配置项是sortPairs: true,而非你写的sort: true,参数错误会导致默克尔树结构和链上计算逻辑不匹配。 - 测试代码中部署合约的逻辑有误,你声明的合约实例是
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
相关产品推荐
相关产品推荐

