Viem生成的EIP712签名合约验证ecrecover返回address(0),Ethers正常
Viem与Ethers EIP712签名的格式差异及解决办法
你的问题核心是Viem返回的签名v值采用了EIP-155标准的偏移处理,而合约中的ecrecover只接受原始的27/28值,导致验证失败,具体差异和解决办法如下:
差异点
- Ethers v5生成的EIP712签名,默认返回的v值是原始的27或28(即使启用EIP-155,部分场景下也会自动处理后返回原始值),可以直接被合约的ecrecover识别。
- Viem的
signTypedData返回的签名v值是EIP-155兼容格式,即v = 27/28 + 2 * chainId + 35,这个值直接传给ecrecover会导致计算出的地址为address(0),因为ecrecover仅接受27或28作为合法的v值。 - 前端
verifyTypedData能验证通过,是因为它内部自动处理了EIP-155的v值偏移,无需手动调整。
解决方案
方案一:前端处理v值后传给合约
在解析签名时,将Viem返回的v值转换为原始的27/28:
const sig = await walletClient.signTypedData({ domain, types, primaryType: 'Mint', message }); const r = sig.slice(0, 66) as `0x${string}`; const s = `0x${sig.slice(66, 130)}` as `0x${string}`; let v = parseInt(sig.slice(130, 132), 16); // 计算EIP-155偏移量并转换v值 const eip155Offset = 2 * chainId + 35; if (v >= eip155Offset) { v -= eip155Offset; } // 确保v值合法 if (v !== 27 && v !== 28) { throw new Error('无效的签名v值'); }
方案二:修改合约验证函数,兼容EIP-155格式
在合约中自动处理偏移后的v值:
function verify(address signer, address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { bytes32 structHash = keccak256(abi.encode(TYPEHASH, to, amount)); bytes32 hash = _hashTypedDataV4(structHash); uint8 adjustedV = v; // 处理EIP-155的v值偏移 if (adjustedV > 28) { adjustedV = adjustedV - 2 * block.chainid - 35; } return ecrecover(hash, adjustedV, r, s) == signer; }
两种方案任选其一即可解决验证失败的问题。
内容的提问来源于stack exchange,提问作者Muhammad Abdullah
相关产品推荐
相关产品推荐

