3分钟Solidity: 6.6 验证签名

43 阅读5分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的“验证签名(代码示例)”

消息可以在链下签名,然后通过智能合约在链上进行验证。

使用 ethers.js 的示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*签名验证

如何签名与验证

# 签名步骤
1.  创建待签名的消息
2.  对消息进行哈希处理
3.  签署哈希值(链下操作,务必保管好私钥)

# 验证步骤
1.  根据原始消息重新生成哈希值
2.  通过签名和哈希值还原签名者
3.  将还原的签名者与声明签名者进行比对
*/

contract VerifySignature {
    /* 1.  解锁MetaMask账户
    ethereum.enable()
    */

    /* 2. 获取消息哈希以进行签名
    getMessageHash(
        0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
        123,
        "coffee and donuts",
        1
    )

    hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
    */
    function getMessageHash(
        address _to,
        uint256 _amount,
        string memory _message,
        uint256 _nonce
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
    }

    /* 3. 签名消息哈希
    # 使用浏览器
    account = "在此处复制并粘贴签名人账户"
    ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)

    # 使用 web3
    web3.personal.sign(hash, web3.eth.defaultAccount, console.log)

    不同账户的签名会有所不同
    0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
    */
    function getEthSignedMessageHash(bytes32 _messageHash)
        public
        pure
        returns (bytes32)
    {
        /*
        签名是通过对以下格式的keccak256哈希进行签名生成的:
        "\x19Ethereum Signed Message\n" + len(msg) + msg
        */
        return keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
        );
    }

    /* 4. 验证签名
    signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
    to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
    amount = 123
    message = "coffee and donuts"
    nonce = 1
    signature =
        0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
    */
    function verify(
        address _signer,
        address _to,
        uint256 _amount,
        string memory _message,
        uint256 _nonce,
        bytes memory signature
    ) public pure returns (bool) {
        bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == _signer;
    }

    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");

        assembly {
            /*
            前32个字节存储签名的长度

            add(sig, 32) = pointer of sig + 32
            实际上,跳过了签名的前32个字节

            mload(p) 从内存地址 p 开始将接下来的 32 字节加载到内存中
            */

            // 前32字节,在长度前缀之后
            r := mload(add(sig, 32))
            // 第二个 32 字节
            s := mload(add(sig, 64))
            // 最终字节(接下来的32个字节的第一个字节)
            v := byte(0, mload(add(sig, 96)))
        }

        // 隐式返回 (r, s, v)
    }
}

在Remix中使用验证签名功能

重要提示:使用Remix测试签名验证时,请注意Remix处理消息哈希的方式与合约不同。Remix在创建ETH签名消息哈希之前会再次对消息哈希进行哈希运算,而合约则直接使用消息哈希。这意味着Remix和合约最终生成的ETH签名消息哈希会有所不同:

// 相同输入参数的示例:
Message hash:    0x56f00a5093efc595178316938b3e9ab51b37610ca57b1b471aa4ce801f05251d
Remix output:   0xd3445702e9995d1b351adf2606d88910d12dd95554f0bbdaa8d02061933c6363
Contract output: 0xed08430382ce60ae9e2b032b99a36b2c5c5c5a3fa1d293926ce87c723f2fce84

为了正确测试,您可能需要改用ethers.js或web3.js,如上例链接所示。

  1. 获取消息哈希值:

    • 使用以下示例参数调用 getMessageHash
address _to: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 // which is the second account on Remix
uint256 _amount: 123
string _message: "coffee and donuts"
uint256 _nonce: 1
  • 复制返回的messageHash
  1. 签名消息哈希:

    • 在“部署和运行交易”选项卡中(您应该已经在此处)

    • 选择第一个账户

    • 点击 +号后的图标来签署消息,它将弹出一个窗口

    • 粘贴messageHash

    • 点击“签名”

    • Remix 返回:

      • 哈希值 (ethSignedMessageHash)
      • 签名 (验证时需要用到)
  2. 验证签名:

    • 使用以下参数调用 verify:
address _signer: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
address _to: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
uint256 _amount: 123
string _message: "coffee and donuts"
uint256 _nonce: 1
bytes signature: [Signature from step 2]
  • 如果签名正确,应返回 true

Remix Lite 尝试一下

solidity-verifying_signature


END