Skip to content

Commit

Permalink
Update EIP-6492: add a verification method that doesn't require pre-d…
Browse files Browse the repository at this point in the history
…eploying a singleton (ethereum#6644)

* Add EIP: Standard Signature Validation Method for Counterfactually Deployed Contracts (ethereum#6492)

* Update EIP-6492: Standard Signature Validation Method for Counterfactually Deployed Contracts

* Error fixes
* Readability improvements
* Better definitions

* Update EIP-6492: Signature Validation for Predeploy Contracts

* Moved counterfactual check to the beginning

* Update EIP-6492: Signature Validation for Predeploy Contracts

* Add a note about key invalidation

* Update EIP-6492: Signature Validation for Predeploy Contracts

* Propose a validation implementation that does not require pre-deploying singletons
  • Loading branch information
Ivshti committed Mar 8, 2023
1 parent b79f564 commit 101cfb5
Showing 1 changed file with 60 additions and 26 deletions.
86 changes: 60 additions & 26 deletions EIPS/eip-6492.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,55 +83,87 @@ This ERC is backward compatible with previous work on signature validation, incl

## Reference Implementation

Example implementation of a verification contract:
Example implementation of a universal verification contract:

```solidity
contract ERC6492Verifier {
/**
* @notice Verifies that the signature is valid for that signer and hash
*/
function isValidUniversalSignature(
// As per ERC-1271
interface IERC1271Wallet {
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}
contract UniversalSigValidator {
bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;
function isValidUniversalSig(
address _signer,
bytes32 _hash,
bytes calldata _signature
) external override returns (bool) {
uint32 contractSize;
assembly {
size := extcodesize(_signer)
}
) public returns (bool) {
bytes memory contractCode = address(_signer).code;
// 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
if (bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX) {
// resize the sig to remove the magic suffix
_signature = _signature[0:_signature.length-32];
if (bytes32(_signature[_signature.length-32:signature.length]) == 0x6492649264926492649264926492649264926492649264926492649264926492) {
_signature.trimToSize(_signature.length - 4);
address create2Factory;
bytes memory factoryCalldata;
(create2Factory, factoryCalldata, sig) = abi.decode(sig, (address, bytes, bytes));
if (contractSize == 0) {
bytes memory originalSig;
(create2Factory, factoryCalldata, originalSig) = abi.decode(_signature, (address, bytes, bytes));
if (contractCode.length == 0) {
(bool success, ) = create2Factory.call(factoryCalldata);
if (!success) return false;
require(success, 'SignatureValidator: deployment');
}
return IERC1271Wallet(_signer).isValidSignature(_hash, sig) == 0x1626ba7e;
return IERC1271Wallet(_signer).isValidSignature(_hash, originalSig) == ERC1271_SUCCESS;
}
if (contractSize > 0) {
return IERC1271Wallet(_signer).isValidSignature(_hash, _signature) == 0x1626ba7e;
if (contractCode.length > 0) {
return IERC1271Wallet(_signer).isValidSignature(_hash, _signature) == ERC1271_SUCCESS;
}
// ecrecover verification
require(_signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length");
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]);
bytes32 r = _signature.readBytes32(0);
bytes32 s = _signature.readBytes32(32);
if (v != 27 && v != 28) {
revert("SignatureValidator#recoverSigner: invalid signature 'v' value");
revert('SignatureValidator#recoverSigner: invalid signature v value');
}
return ecrecover(_hash, v, r, s) == _signer;
}
}
// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton
contract VerifySig {
constructor (address _signer, bytes32 _hash, bytes memory _signature) {
// We deploy a new contract solely to take advantage of calldata array slices; otherwise we could implement the verification here too
UniversalSigValidator validator = new UniversalSigValidator();
bool isValidSig = validator.isValidUniversalSig(_signer, _hash, _signature);
assembly {
mstore(0, isValidSig)
return(31, 1)
}
}
}
```

This verification contract SHOULD be deployed as a singleton on Ethereum mainnet or any other EVM chain and it SHOULD be invoked off-chain via `eth_call` or equivalent RPC methods by the verifier. Note that even though this function does not have a `view` modifier, it can still be invoked off-chain via an `eth_call`.
This verification contract does not need to be deployed on-chain: on the contrary, it's designed to perform universal signature verification with support for this ERC, [ERC-1271](./eip-1271.md) and traditional `ecrecover` just by calling `eth_call` in a front-end verifier library.

Here's example of how to do this with the `ethers` library:

```javascript
const isValidSignature = '0x01' === await provider.call({
data: ethers.utils.concat([
verifySigBytecode,
(new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
])
})
```

Alternatively, the verifier may perform the same logic using a singleton contract like MakerDAO's multicall, by performing the aforementioned checks off-chain.

## Security Considerations

Expand All @@ -141,6 +173,8 @@ However, deploying a contract requires a `CALL` rather than a `STATICCALL`, whic

Another out-of-scope security consideration worth mentioning is whether the contract is going to be set-up with the correct permissions at deploy time, in order to allow for meaningful signature verification. By design, this is up to the implementation, but it's worth noting that thanks to how CREATE2 works, changing the bytecode or contructor callcode in the signature will not allow you to escalate permissions as it will change the deploy address and therefore make verification fail.

It must be noted that contract accounts can dynamically change their methods of authentication. This issue is mitigated by design in this EIP - even when validating counterfactual signatures, if the contract is already deployed, we will still call it.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

0 comments on commit 101cfb5

Please sign in to comment.