Skip to content

Instantly share code, notes, and snippets.

@Agusx1211
Last active March 8, 2023 19:22
Show Gist options
  • Save Agusx1211/9e70e397a34f12a1ed240852284b91b5 to your computer and use it in GitHub Desktop.
Save Agusx1211/9e70e397a34f12a1ed240852284b91b5 to your computer and use it in GitHub Desktop.
pragma solidity ^0.8.18;
// As per ERC-1271
interface IERC1271Wallet {
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}
error ERC1271Error(string reason);
enum NoSideEffectsResult {
Invalid,
Valid,
Revert
}
contract UniversalSigValidator {
bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;
function isValidSigWithSideEffects2(
address _signer,
bytes32 _hash,
bytes calldata _signature
) public returns (bool, bool) {
uint contractCodeLen = address(_signer).code.length;
bool hadSideEffects = false;
// The order here is striclty defined in https://eips.ethereum.org/EIPS/eip-6492
// - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed so as to not invalidate old sigs
// - ERC-1271 verification if there's contract code
// - finally, ecrecover
bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX;
if (isCounterfactual) {
// resize the sig to remove the magic suffix
_signature = _signature[0:_signature.length-32];
address create2Factory;
bytes memory factoryCalldata;
bytes memory originalSig;
(create2Factory, factoryCalldata, originalSig) = abi.decode(_signature, (address, bytes, bytes));
if (contractCodeLen == 0) {
(bool success, ) = create2Factory.call(factoryCalldata);
require(success, 'SignatureValidator: deployment');
hadSideEffects = true;
}
}
if (isCounterfactual || contractCodeLen > 0) {
bytes4 magicValue = IERC1271Wallet(_signer).isValidSignature(_hash, _signature);
return (magicValue == ERC1271_SUCCESS, hadSideEffects);
}
// ecrecover verification
require(_signature.length == 65, 'SignatureValidator#recoverSigner: invalid signature length');
bytes32 r = bytes32(_signature[0:32]);
bytes32 s = bytes32(_signature[32:64]);
uint8 v = uint8(_signature[64]);
if (v != 27 && v != 28) {
revert('SignatureValidator#recoverSigner: invalid signature v value');
}
return (ecrecover(_hash, v, r, s) == _signer, hadSideEffects);
}
function isValidSigWithSideEffects(
address _signer,
bytes32 _hash,
bytes calldata _signature
) external returns (bool) {
(bool isValid,) = isValidSigWithSideEffects2(_signer, _hash, _signature);
return isValid;
}
function isValidSigNoSideEffects(
address _signer,
bytes32 _hash,
bytes calldata _signature
) external returns (NoSideEffectsResult, bytes memory) {
(bool success, bytes memory data) = (address(this)).call(
abi.encodeWithSelector(
this.isValidSigWithSideEffects2.selector,
_signer,
_hash,
_signature
)
);
if (!success) {
// if the call failed, we need to return the
// data, so we can bubble up the revert reason
return (NoSideEffectsResult.Revert, data);
}
(bool isValid, bool hadSideEffects) = abi.decode(data, (bool, bool));
NoSideEffectsResult result = isValid ? NoSideEffectsResult.Valid : NoSideEffectsResult.Invalid;
if (hadSideEffects) {
// if the call had side effects we need to return the
// result using a `revert` (to undo the state changes)
assembly {
// if the result is valid or invalid, the data is empty
// it's never decoded, so we can just skip it
mstore(0, result)
revert(0, 32)
}
}
return (result, bytes(""));
}
// In order to have a no side effects function, we need to call something that always reverts and then parse it's result
function isValidSig(
address _signer,
bytes32 _hash,
bytes calldata _signature
) external returns (bool) {
(,bytes memory data) = address(this).call(
abi.encodeWithSelector(
this.isValidSigNoSideEffects.selector,
_signer,
_hash,
_signature
)
);
NoSideEffectsResult result = abi.decode(data, (NoSideEffectsResult));
if (result == NoSideEffectsResult.Revert) {
// bubble up the revert
// so it behaves like a normal eth_call
(,bytes memory reason) = abi.decode(data, (NoSideEffectsResult, bytes));
assembly {
revert(add(32, reason), mload(reason))
}
}
return result == NoSideEffectsResult.Valid;
}
}
// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton
contract ValidateSig {
constructor (address _signer, bytes32 _hash, bytes memory _signature) {
UniversalSigValidator validator = new UniversalSigValidator();
bool isValidSig = validator.isValidSigWithSideEffects(_signer, _hash, _signature);
assembly {
mstore(0, isValidSig)
return(31, 1)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment