SignatureChecker库是一个用于链上签名验证的helper库。该库提供的验签函数既支持EOA账户地址的签名验证也支持IERC1271标准合约地址的签名验证。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
SignatureChecker库是一个用于链上签名验证的helper库。该库提供的验签函数既支持EOA账户地址的签名验证也支持IERC1271标准合约地址的签名验证。
IERC1271标准往往用于智能合约钱包的签名,例如Argent和Gnosis Safe。
注:关于IERC1271标准的详细解读参见:https://www.learnblockchain.cn/article/6443
封装SignatureChecker library成为一个可调用合约:
// 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测试合约:
凭借签名地址、签名内容以及签名本身来验证签名有效性。
需要注意的是:与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神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!