如何在链上验证Ethers生成的链下签名对应的签名者地址?
链上验证Ethers/Metamask生成的签名地址
问题核心是:Ethers的signMessage和Metamask的签名遵循EIP-191标准,会自动给原始消息添加特定前缀后再计算哈希,直接对原始字符串哈希或者用错参数格式,都会导致签名恢复失败。
为什么之前的代码无效?
- 直接用
keccak256(abi.encodePacked(message))哈希原始字符串,和Ethers签名时的哈希逻辑完全不同,自然无法恢复出正确地址。 - 错误的
ECDSA.toEthSignedMessageHash(message, signature)写法不仅参数顺序错误,而且toEthSignedMessageHash接收的是bytes类型的消息,不是string。
正确的链上验证方法
使用OpenZeppelin的ECDSA库时,必须先把字符串消息转换为bytes,再通过toEthSignedMessageHash生成符合EIP-191标准的哈希,最后用recover恢复签名者地址。
完整智能合约示例
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract SignatureVerifier { using ECDSA for bytes32; function verifySigner(string memory message, bytes memory signature) public view returns(address) { // 1. 将string消息转换为bytes bytes memory messageBytes = bytes(message); // 2. 生成符合EIP-191标准的消息哈希(对应Ethers的signMessage逻辑) bytes32 ethSignedMessageHash = messageBytes.toEthSignedMessageHash(); // 3. 从哈希和签名中恢复签名者地址 address signer = ethSignedMessageHash.recover(signature); return signer; } }
也可以用更简洁的写法:
function verifySigner(string memory message, bytes memory signature) public view returns(address) { bytes32 hash = ECDSA.toEthSignedMessageHash(bytes(message)); return ECDSA.recover(hash, signature); }
验证逻辑对应关系
链下Ethers签名代码:
let provider = new ethers.providers.Web3Provider(window.ethereum); let signer = provider.getSigner(); // 签名原始字符串消息 let signature = await signer.signMessage("Please confirm this: 12.315");
链上验证时,只要传入完全相同的字符串消息和签名,就能正确恢复出签名者地址。
关键注意点
- 消息内容必须完全一致:包括大小写、空格、标点符号,任何细微差异都会导致哈希不同,恢复失败。
- 签名格式要正确:Ethers返回的是标准65字节格式(r+s+v)的签名,直接传递给合约即可。
内容的提问来源于stack exchange,提问作者GGizmos
相关产品推荐
相关产品推荐

