EIP712问题:ecrecover返回错误地址,前端签名验证正常
问题:MetaMask eth_signTypedData_v4签名后,Solidity ecrecover恢复地址错误
我使用MetaMask的eth_signTypedData_v4对类型化数据进行签名,用recoverTypedSignature_v4可以正确恢复出签名时使用的地址,但智能合约中的ecrecover每次都返回错误地址,不知道代码哪里出了问题。
原JavaScript代码
const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner();// 签名 const signerAddress = (await signer.getAddress()).toLowerCase(); const originalMessage = { types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Greeting: [ { name: 'contents', type: 'string' }, { name: "sender", type: "address" }, { name: "x", type: "uint256" } ], }, primaryType: 'Greeting', domain: { name: 'SignatureVerifyTest', version: '1', chainId: 31337, verifyingContract: "0x5fbdb2315678afecb367f032d93f642f64180aa3" }, message: { contents: 'Hello', sender: signerAddress, x: 123 } }; const signedMessage = await signer.provider.send("eth_signTypedData_v4", [signerAddress, JSON.stringify(originalMessage)]); const { v, r, s } = ethers.utils.splitSignature(signedMessage); console.log('signerAddress:', signerAddress) console.log("r:", r); console.log("s:", s); console.log("v:", v);
原Solidity代码
bytes32 eip712DomainHash = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("SignatureVerifyTest")), keccak256(bytes("1")), block.chainid, address(this) ) ); bytes32 hashStruct = keccak256( abi.encode(keccak256("Greeting(string contents,address sender,uint256 x)"), contents, sender, x) ); bytes32 hash = ECDSA.toTypedDataHash(eip712DomainHash, hashStruct); address signer = ECDSA.recover(hash, v, r, s); console.log("sender", sender); console.log("signer", signer);
问题原因及修正方案
1. 前端地址大小写处理错误
EIP-712要求签名时使用地址的校验和格式(原始大小写),你把签名地址转成小写的操作会导致前端和合约端计算的哈希值不一致,直接影响签名验证结果。
修正后的JavaScript代码:
const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner();// 签名 const signerAddress = await signer.getAddress(); // 保留原始校验和地址,不要转小写 const originalMessage = { types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Greeting: [ { name: 'contents', type: 'string' }, { name: "sender", type: "address" }, { name: "x", type: "uint256" } ], }, primaryType: 'Greeting', domain: { name: 'SignatureVerifyTest', version: '1', chainId: 31337, verifyingContract: "0x5fbdb2315678afecb367f032d93f642f64180aa3" }, message: { contents: 'Hello', sender: signerAddress, // 使用原始校验和地址 x: 123 } }; const signedMessage = await signer.provider.send("eth_signTypedData_v4", [signerAddress, JSON.stringify(originalMessage)]); const { v, r, s } = ethers.utils.splitSignature(signedMessage); console.log('signerAddress:', signerAddress) console.log("r:", r); console.log("s:", s); console.log("v:", v);
2. Solidity中结构体哈希计算错误
EIP-712规定,结构体中的引用类型(如string)需要先单独计算哈希,再参与结构体的整体哈希。另外,ECDSA.toTypedDataHash的使用方式有误,更直观的是直接按照EIP-712规范构建签名哈希。
修正后的Solidity代码:
// 正确计算Domain Separator bytes32 domainSeparator = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("SignatureVerifyTest")), keccak256(bytes("1")), block.chainid, address(this) ) ); // 结构体哈希:string类型需先单独哈希 bytes32 hashStruct = keccak256( abi.encode( keccak256("Greeting(string contents,address sender,uint256 x)"), keccak256(bytes(contents)), // 对string内容做哈希 sender, x ) ); // 按照EIP-712规范构建最终签名哈希 bytes32 hash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, hashStruct)); address signer = ECDSA.recover(hash, v, r, s); console.log("sender", sender); console.log("signer", signer);
内容的提问来源于stack exchange,提问作者cheng xu
相关产品推荐
相关产品推荐

