Add tooling to verify signatures with support for ERC1271 (#2532)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
@ -6,6 +6,7 @@
|
|||||||
* `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552))
|
* `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552))
|
||||||
* `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565))
|
* `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565))
|
||||||
* `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543))
|
* `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543))
|
||||||
|
* `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532))
|
||||||
|
|
||||||
## 4.0.0 (2021-03-23)
|
## 4.0.0 (2021-03-23)
|
||||||
|
|
||||||
|
|||||||
16
contracts/interfaces/IERC1271.sol
Normal file
16
contracts/interfaces/IERC1271.sol
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Interface of the ERC1271 standard signature validation method for
|
||||||
|
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
|
||||||
|
*/
|
||||||
|
interface IERC1271 {
|
||||||
|
/**
|
||||||
|
* @dev Should return whether the signature provided is valid for the provided data
|
||||||
|
* @param hash Hash of the data to be signed
|
||||||
|
* @param signature Signature byte array associated with _data
|
||||||
|
*/
|
||||||
|
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
|
||||||
|
}
|
||||||
17
contracts/mocks/ERC1271WalletMock.sol
Normal file
17
contracts/mocks/ERC1271WalletMock.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../access/Ownable.sol";
|
||||||
|
import "../interfaces/IERC1271.sol";
|
||||||
|
import "../utils/cryptography/ECDSA.sol";
|
||||||
|
|
||||||
|
contract ERC1271WalletMock is Ownable, IERC1271 {
|
||||||
|
constructor(address originalOwner) {
|
||||||
|
transferOwnership(originalOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) {
|
||||||
|
return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
contracts/mocks/SignatureCheckerMock.sol
Normal file
13
contracts/mocks/SignatureCheckerMock.sol
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../utils/cryptography/SignatureChecker.sol";
|
||||||
|
|
||||||
|
contract SignatureCheckerMock {
|
||||||
|
using SignatureChecker for address;
|
||||||
|
|
||||||
|
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) public view returns (bool) {
|
||||||
|
return signer.isValidSignatureNow(hash, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,6 +36,8 @@ Finally, {Create2} contains all necessary utilities to safely use the https://bl
|
|||||||
|
|
||||||
{{ECDSA}}
|
{{ECDSA}}
|
||||||
|
|
||||||
|
{{SignatureChecker}}
|
||||||
|
|
||||||
{{MerkleProof}}
|
{{MerkleProof}}
|
||||||
|
|
||||||
{{EIP712}}
|
{{EIP712}}
|
||||||
|
|||||||
29
contracts/utils/cryptography/SignatureChecker.sol
Normal file
29
contracts/utils/cryptography/SignatureChecker.sol
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "./ECDSA.sol";
|
||||||
|
import "../Address.sol";
|
||||||
|
import "../../interfaces/IERC1271.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Signature verification helper: Provide a single mechanism to verify both private-key (EOA) ECDSA signature and
|
||||||
|
* ERC1271 contract sigantures. Using this instead of ECDSA.recover in your contract will make them compatible with
|
||||||
|
* smart contract wallets such as Argent and Gnosis.
|
||||||
|
*
|
||||||
|
* Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change
|
||||||
|
* through time. It could return true at block N and false at block N+1 (or the opposite).
|
||||||
|
*/
|
||||||
|
library SignatureChecker {
|
||||||
|
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
|
||||||
|
if (Address.isContract(signer)) {
|
||||||
|
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magicValue) {
|
||||||
|
return magicValue == IERC1271(signer).isValidSignature.selector;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ECDSA.recover(hash, signature) == signer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
test/utils/cryptography/SignatureChecker.test.js
Normal file
71
test/utils/cryptography/SignatureChecker.test.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const { toEthSignedMessageHash, fixSignature } = require('../../helpers/sign');
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const SignatureCheckerMock = artifacts.require('SignatureCheckerMock');
|
||||||
|
const ERC1271WalletMock = artifacts.require('ERC1271WalletMock');
|
||||||
|
|
||||||
|
const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin');
|
||||||
|
const WRONG_MESSAGE = web3.utils.sha3('Nope');
|
||||||
|
|
||||||
|
contract('SignatureChecker (ERC1271)', function (accounts) {
|
||||||
|
const [signer, other] = accounts;
|
||||||
|
|
||||||
|
before('deploying', async function () {
|
||||||
|
this.signaturechecker = await SignatureCheckerMock.new();
|
||||||
|
this.wallet = await ERC1271WalletMock.new(signer);
|
||||||
|
this.signature = fixSignature(await web3.eth.sign(TEST_MESSAGE, signer));
|
||||||
|
});
|
||||||
|
|
||||||
|
context('EOA account', function () {
|
||||||
|
it('with matching signer and signature', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
signer,
|
||||||
|
toEthSignedMessageHash(TEST_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with invalid signer', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
other,
|
||||||
|
toEthSignedMessageHash(TEST_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with invalid signature', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
signer,
|
||||||
|
toEthSignedMessageHash(WRONG_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('ERC1271 wallet', function () {
|
||||||
|
it('with matching signer and signature', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
this.wallet.address,
|
||||||
|
toEthSignedMessageHash(TEST_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with invalid signer', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
this.signaturechecker.address,
|
||||||
|
toEthSignedMessageHash(TEST_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with invalid signature', async function () {
|
||||||
|
expect(await this.signaturechecker.isValidSignatureNow(
|
||||||
|
this.wallet.address,
|
||||||
|
toEthSignedMessageHash(WRONG_MESSAGE),
|
||||||
|
this.signature,
|
||||||
|
)).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user