Michael.W基于Foundry精读Openzeppelin第32期——SignatureChecker.sol

  • Michael.W
  • 更新于 2023-08-28 16:00
  • 阅读 1443

SignatureChecker库是一个用于链上签名验证的helper库。该库提供的验签函数既支持EOA账户地址的签名验证也支持IERC1271标准合约地址的签名验证。

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 SignatureChecker.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/cryptography/SignatureChecker.sol

SignatureChecker库是一个用于链上签名验证的helper库。该库提供的验签函数既支持EOA账户地址的签名验证也支持IERC1271标准合约地址的签名验证。

IERC1271标准往往用于智能合约钱包的签名,例如Argent和Gnosis Safe。

注:关于IERC1271标准的详细解读参见:https://www.learnblockchain.cn/article/6443

1. 目标合约

封装SignatureChecker library成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/cryptography/MockSignatureChecker.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol";

contract MockSignatureChecker {
    using SignatureChecker for address;

    function isValidSignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) external view returns (bool){
        return signer.isValidSignatureNow(hash, signature);
    }
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/cryptography/SignatureChecker.t.sol

2. 代码精读

2.1 isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)

凭借签名地址、签名内容以及签名本身来验证签名有效性。

需要注意的是:与ECDSA的EOA地址验签不同,基于IERC1271标准的合约地址验签结果是可以改变的。可能在区块高度N和N+1上,IERC1271的验签结果是相反的。

    function isValidSignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal view returns (bool) {
        // 将输入的signer当做EOA账户地址,使用ECDSA.tryRecover()以及签名内容和签名还原出signer地址
        (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);
        if (error == ECDSA.RecoverError.NoError && recovered == signer) {
        // 如果ECDSA.tryRecover返回的错误类型为ECDSA.RecoverError.NoError且还原出的signer地址与输入signer地址相同,说明signer地址确实为EOA地址,并且通过验签。返回true
            return true;
        }

    // 如果没有通过EOA地址验签,那么再假定signer为IERC1271标准的合约地址。使用IERC1271标准的isValidSignature()函数进行验签
    // 本库使用直接staticcall的方式来调用signer合约的isValidSignature()方法
        (bool success, bytes memory result) = signer.staticcall(
            abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)
        );

        // 如果staticcall调用成功且返回值只占32字节,同时返回值为IERC1271.isValidSignature的selector,表明通过IERC1271标准的验签——即返回true
        // 如果不满足以上条件,说明本次既不通过EOA地址验签也不通过IERC1271合约地址验签。可认作是无效的签名,返回false
        return (success &&
            result.length == 32 &&
            abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
    }

foundry代码验证

contract SignatureCheckerTest is Test {
    using ECDSA for bytes;

    MockSignatureChecker msc = new MockSignatureChecker();
    uint eoaSignerPrivateKeyInERC1271 = 1024;
    MockERC1271 me = new MockERC1271(vm.addr(eoaSignerPrivateKeyInERC1271));
    uint signerPrivateKey = 2048;
    address signerAddress = vm.addr(signerPrivateKey);

    function test_IsValidSignatureNow_AsEOAAddress() external {
        // case 1: return true with correct eoa signature
        bytes32 digestHash = bytes("Michael.W").toEthSignedMessageHash();
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, digestHash);
        bytes memory signature = bytes.concat(r, s, bytes1(v));

        assertTrue(msc.isValidSignatureNow(
                signerAddress,
                digestHash,
                signature
            ));

        // case 2: return false with incorrect eoa signature
        bytes memory incorrectSignature = bytes.concat(r, s, bytes1(v + 1));
        assertFalse(msc.isValidSignatureNow(
                signerAddress,
                digestHash,
                incorrectSignature
            ));
    }

    function test_IsValidSignatureNow_AsIERC1271Address() external {
        // case 1: return true with valid signature of ERC1271
        bytes32 digestHash = bytes("Michael.W").toEthSignedMessageHash();
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(eoaSignerPrivateKeyInERC1271, digestHash);
        bytes memory signature = bytes.concat(r, s, bytes1(v));

        assertTrue(msc.isValidSignatureNow(
                address(me),
                digestHash,
                signature
            ));

        // case 2: return true with invalid signature of ERC1271
        bytes memory incorrectSignature = bytes.concat(r, s, bytes1(v + 1));

        assertFalse(msc.isValidSignatureNow(
                address(me),
                digestHash,
                incorrectSignature
            ));

        // case 3: return false when the signer contract address is not the implementor of IERC1271
        (v, r, s) = vm.sign(eoaSignerPrivateKeyInERC1271, digestHash);
        signature = bytes.concat(r, s, bytes1(v));

        assertFalse(msc.isValidSignatureNow(
                address(new NotImplementIERC1271()),
                digestHash,
                signature
            ));
    }
}

contract MockERC1271 is IERC1271 {
    using ECDSA for bytes32;

    address _signerEOA;

    constructor(address signerEOA){
        _signerEOA = signerEOA;
    }

    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue){
        return hash.recover(signature) == _signerEOA ? IERC1271.isValidSignature.selector : bytes4(0);
    }
}

contract NotImplementIERC1271 {}

ps:\ 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Michael.W
Michael.W
0x93E7...0000
狂热的区块链爱好者