You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在链上验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 08:22:18