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

智能合约_hashTypedDataV4哈希与web3.js生成哈希不一致问题排查

问题:Web3.js生成的EIP-712哈希与Solidity合约不一致

我有一段Solidity智能合约代码,通过以下_hash函数生成EIP-712哈希:

function _hash(string memory name) internal view returns (bytes32) {
    return
        _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256("Web3Struct(string name)"),
                    keccak256(bytes(name))
                )
            )
        );
}

__EIP712_init(SIGNING_DOMAIN, SIGNATURE_VERSION);

我编写了对应的Web3.js函数hashTypedDataV4,但无法生成与合约完全一致的哈希:

function hashTypedDataV4(web3, domain) {
  const typeHash = web3.utils.keccak256('Web3Struct(string name)');
  const nameByte = web3.utils.utf8ToHex(name);
  const nameHash = web3.utils.keccak256(nameByte);
  const domainSeparator = web3.utils.keccak256(
    web3.eth.abi.encodeParameters(
      ['bytes32', 'bytes32', 'bytes32', 'string', 'address'],
      [
        web3.utils.keccak256('EIP712Domain(string name,string version,string chainId,address verifyingContract)'),
        web3.utils.keccak256(domain.name),
        web3.utils.keccak256(domain.version),
        domain.chainId.toString(), // chainId
        domain.verifyingContract // verifyingContract
      ]
    )
  );

  const messageHash = web3.utils.keccak256(
    web3.eth.abi.encodeParameters(
      ['bytes32', 'bytes32'],
      [typeHash, nameHash]
    )
  );

  const hash = web3.utils.soliditySha3(
    { t: 'bytes2', v: '0x1901' }, // EIP-191 version and transfer type
    { t: 'bytes32', v: domainSeparator },
    { t: 'bytes32', v: messageHash }
  );
  return hash;
}

另外,我有一个Ethers.js签名函数,能生成与合约_hashTypedDataV4匹配的签名:

async function signTypedData(
  verifyingContract: any,
  name: string,
  chainId: any,
  signer: any
) {
  return signer._signTypedData(
    {
      name: SIGNING_DOMAIN_NAME,
      version: chainId.toString(), // SIGNING_DOMAIN_VERSION
      verifyingContract,
      chainId,
    },
    {
      Web3Struct: [{ name: "name", type: "string" }],
    },
    { name }
  );
}

需要修正Web3.js函数,使其生成与合约_hash完全一致的哈希。


解决方案

问题分析

  1. ChainId类型错误:EIP-712 Domain中的chainId是uint256类型,但Web3.js代码错误地将其当作string处理,导致Domain Separator计算不一致。
  2. 参数缺失:Web3.js函数未接收name参数,内部name变量未定义。
  3. EIP712Domain类型字符串错误:类型字符串中的chainId类型应为uint256,而非string。

修正后的Web3.js函数

function hashTypedDataV4(web3, domain, name) {
  // 计算Web3Struct的类型哈希
  const structTypeHash = web3.utils.keccak256('Web3Struct(string name)');
  // 计算name的哈希
  const nameHash = web3.utils.keccak256(web3.utils.utf8ToHex(name));
  // 计算EIP712Domain的类型哈希(chainId为uint256类型)
  const domainTypeHash = web3.utils.keccak256(
    'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
  );
  // 计算Domain Separator
  const domainSeparator = web3.utils.keccak256(
    web3.eth.abi.encodeParameters(
      ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
      [
        domainTypeHash,
        web3.utils.keccak256(web3.utils.utf8ToHex(domain.name)),
        web3.utils.keccak256(web3.utils.utf8ToHex(domain.version)),
        domain.chainId, // 直接传入数字类型的chainId
        domain.verifyingContract
      ]
    )
  );
  // 计算消息哈希
  const messageHash = web3.utils.keccak256(
    web3.eth.abi.encodeParameters(
      ['bytes32', 'bytes32'],
      [structTypeHash, nameHash]
    )
  );
  // 计算最终的EIP-712哈希
  const finalHash = web3.utils.soliditySha3(
    { t: 'bytes1', v: '0x19' },
    { t: 'bytes1', v: '0x01' },
    { t: 'bytes32', v: domainSeparator },
    { t: 'bytes32', v: messageHash }
  );

  return finalHash;
}

关键修正点说明

  • 将chainId的类型从string改为uint256,对应encodeParameters的类型数组和传入值(直接用数字而非字符串)。
  • 给函数添加name参数,确保内部能正确获取待哈希的字符串。
  • 修正EIP712Domain的类型字符串,匹配Solidity合约中使用的标准结构。
  • 拆分0x1901为两个bytes1类型参数,与Solidity中_hashTypedDataV4的实现逻辑对齐(OpenZeppelin的实现会拼接\x19\x01 + domainSeparator + messageHash后哈希)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:10:02