web3.eth.personal.sign签名后ecrecover无法获取正确签名者地址
问题原因
web3.eth.personal.sign遵循EIP-191签名标准,签名时会自动为传入的消息添加前缀\x19Ethereum Signed Message:\n+ 消息长度,再做一次哈希运算。你当前合约直接使用原始消息哈希作为ecrecover的输入,两边哈希值不匹配,导致签名地址恢复错误。- 部分节点返回的签名v值为0或1,而Solidity的
ecrecover要求v值为27或28,未做转换也会导致恢复失败。
解决方案1:修改Solidity合约(推荐,兼容EIP-191标准)
// SPDX-License-Identifier: MIT pragma solidity ^0.6.2; contract Example { function signature(uint256 tokenId, uint8 v, bytes32 r, bytes32 s) public view returns(address) { // 计算原始消息哈希 bytes32 messageHash = keccak256(abi.encodePacked(this, tokenId)); // 拼接EIP-191标准签名前缀,32对应原始消息哈希的字节长度 bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)); // 兼容v值为0/1的情况 if (v < 27) { v += 27; } return ecrecover(ethSignedMessageHash, v, r, s); } }
解决方案2:修改JavaScript代码(无需修改合约,兼容旧逻辑)
如果你不想调整合约代码,可以改用无自动前缀的签名方式,和之前废弃的web3.eth.sign逻辑保持一致:
async function sign(){ const message = 1001; const orgmessage = web3.eth.abi.encodeParameter('uint256',message); const orginal = `0xf8e81D47203A594245E36C48e151709F0C19fBe8${orgmessage.slice(2)}`; const hash = web3.utils.keccak256(orginal); const accounts = await getCurrentAccount(); // 调用eth_sign方法,和旧逻辑完全一致,大部分钱包仍支持该方法 const signature = await web3.eth.sign(hash, accounts); const sig1 = signature.slice(2); const r = `0x${sig1.slice(0, 64)}`; const s = `0x${sig1.slice(64, 128)}`; let v = web3.utils.toDecimal(`0x${sig1.slice(128, 130)}`); // 统一转换v值为27/28 if (v < 27) v += 27; console.log("h",hash); console.log("v",v); console.log("r",r); console.log("s",s); }
内容的提问来源于stack exchange,提问作者Dineshkumar A
相关产品推荐
相关产品推荐

