智能合约_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完全一致的哈希。
解决方案
问题分析
- ChainId类型错误:EIP-712 Domain中的
chainId是uint256类型,但Web3.js代码错误地将其当作string处理,导致Domain Separator计算不一致。 - 参数缺失:Web3.js函数未接收
name参数,内部name变量未定义。 - 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
相关产品推荐
相关产品推荐

