Ethereum线下签名实现

步骤

  • 创建并打包原始消息

    原始消息:”Hello, Web3.”

    消息哈希:0xb0a90a95a2e95f53bc8a27629ce3d3b8c32507b1889e418e308b4ecb0d01554c

  • 生成以太坊签名哈希

    签名哈希:0x8eea87c68554dc0b647493d38a4b53aeea63c6841b10290714ff5f65ff08f2d5

  • Metamask签名消息

    打开浏览器前端console,输入

    ethereum.send('eth_requestAccounts') account = "以太坊签名账户地址" hash = "消息哈希" ethereum.request({method: "personal_sign", params: [account, hash]})

    得到签名:

    0x7ac68849793e559e457718a22f0b122ec3f9c94ed2a5163283d6a55d3c3c32c05e2eaeaa81766fe6c6ce82e971f66a943a7dfa34a9f3d40ede2fb66ea4d698b01c

  • 验签

    使用签名地址、以太坊签名哈希、签名,验签

代码

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.2 <0.9.0;

contract test {
    function getMessageHash(string memory message) public pure returns(bytes32) {
    return keccak256(abi.encodePacked(message));
    }
    
    function getEthSignedMessageHash(bytes32 _messageHash) public pure returns(bytes32) {
        return keccak256(
            // 这是标准字符串: \x19Ethereum Signed Message:\n
            // 32 表示后面的哈希内容长度
            abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
        );
     }

     function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        // 返回解析出来的签名地址
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    // 分割签名
    function splitSignature(bytes memory sig) public pure returns(bytes32 r, bytes32 s, uint8 v) {
        require(sig.length == 65, "invalid signature length");

        // 通过读取内存数据 根据规则进行截取 返回 r, s, v 数据
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
    }

    function verify(bytes32 _ethSignedMessageHash, bytes memory _signature, address _signer) public pure returns(bool) {
        return recoverSigner(_ethSignedMessageHash, _signature) == _signer;
    }

}