Skip to content

Commit c924e97

Browse files
committed
Merge branch 'master' into feature/zk-email
2 parents e1d6297 + 53f590e commit c924e97

30 files changed

+2010
-4
lines changed

.github/workflows/checks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
run: npm run test:inheritance
4646
- name: Check pragma consistency between files
4747
run: npm run test:pragma
48+
- name: Check procedurally generated contracts are up-to-date
49+
run: npm run test:generation
4850

4951
coverage:
5052
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 12-04-2025
2+
3+
- `SignerERC7913`: Abstract signer that verifies signatures using the ERC-7913 workflow.
4+
- `ERC7913SignatureVerifierP256` and `ERC7913SignatureVerifierRSA`: Ready to use ERC-7913 verifiers that implement key verification for P256 (secp256r1) and RSA keys.
5+
- `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers.
6+
7+
## 11-04-2025
8+
9+
- `EnumerableSetExtended` and `EnumerableMapExtended`: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
10+
111
## 03-04-2025
212

313
- `PaymasterERC20`: Extension of `PaymasterCore` that sponsors user operations against payment in ERC-20 tokens.

audits/2025-04-18db32c.pdf

202 KB
Binary file not shown.

audits/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Audits
2+
3+
| Date | Commit | Auditor | Scope | Links |
4+
| ------------- | ------------------------------------------------------------------------------------------ | ------------ | -------------------- | ----------------------------------------------------------- |
5+
| April 2025 | [`18db32c`](https://github.com/openzeppelin/openzeppelin-community-contracts/tree/18db32c) | OpenZeppelin | (see report) | [🔗](./2025-04-18db32c.pdf) |

contracts/interfaces/IERC7913.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
/**
6+
* @dev Signature verifier interface.
7+
*/
8+
interface IERC7913SignatureVerifier {
9+
/**
10+
* @dev Verifies `signature` as a valid signature of `hash` by `key`.
11+
*
12+
* MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid.
13+
* SHOULD return 0xffffffff or revert if the signature is not valid.
14+
* SHOULD return 0xffffffff or revert if the key is empty
15+
*/
16+
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) external view returns (bytes4);
17+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC7913SignatureVerifier} from "../../contracts/interfaces/IERC7913.sol";
6+
7+
contract ERC7913VerifierMock is IERC7913SignatureVerifier {
8+
// Store valid keys and their corresponding signatures
9+
mapping(bytes32 => bool) private _validKeys;
10+
mapping(bytes32 => mapping(bytes32 => bool)) private _validSignatures;
11+
12+
constructor() {
13+
// For testing purposes, we'll consider a specific key as valid
14+
bytes32 validKeyHash = keccak256(abi.encodePacked("valid_key"));
15+
_validKeys[validKeyHash] = true;
16+
}
17+
18+
function verify(bytes calldata key, bytes32 /* hash */, bytes calldata signature) external pure returns (bytes4) {
19+
// For testing purposes, we'll only accept a specific key and signature combination
20+
if (
21+
keccak256(key) == keccak256(abi.encodePacked("valid_key")) &&
22+
keccak256(signature) == keccak256(abi.encodePacked("valid_signature"))
23+
) {
24+
return IERC7913SignatureVerifier.verify.selector;
25+
}
26+
return 0xffffffff;
27+
}
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {Account} from "../../account/Account.sol";
6+
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
7+
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
8+
import {ERC7739} from "../../utils/cryptography/ERC7739.sol";
9+
import {ERC7821} from "../../account/extensions/ERC7821.sol";
10+
import {SignerERC7913} from "../../utils/cryptography/SignerERC7913.sol";
11+
12+
abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
13+
constructor(bytes memory _signer) {
14+
_setSigner(_signer);
15+
}
16+
17+
/// @inheritdoc ERC7821
18+
function _erc7821AuthorizedExecutor(
19+
address caller,
20+
bytes32 mode,
21+
bytes calldata executionData
22+
) internal view virtual override returns (bool) {
23+
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
24+
}
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// contracts/MyAccountERC7702.sol
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity ^0.8.20;
4+
5+
import {Account} from "../../../account/Account.sol";
6+
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
7+
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
8+
import {ERC7821} from "../../../account/extensions/ERC7821.sol";
9+
import {SignerERC7702} from "../../../utils/cryptography/SignerERC7702.sol";
10+
11+
contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder {
12+
/// @dev Allows the entry point as an authorized executor.
13+
function _erc7821AuthorizedExecutor(
14+
address caller,
15+
bytes32 mode,
16+
bytes calldata executionData
17+
) internal view virtual override returns (bool) {
18+
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
19+
}
20+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// contracts/MyAccount.sol
2+
// SPDX-License-Identifier: MIT
3+
4+
pragma solidity ^0.8.24;
5+
6+
import {Account} from "../../../account/Account.sol";
7+
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8+
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
9+
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
10+
import {ERC7739} from "../../../utils/cryptography/ERC7739.sol";
11+
import {ERC7821} from "../../../account/extensions/ERC7821.sol";
12+
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
13+
import {SignerERC7913} from "../../../utils/cryptography/SignerERC7913.sol";
14+
15+
contract MyAccount7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
16+
constructor() EIP712("MyAccount7913", "1") {}
17+
18+
function initialize(bytes memory signer) public initializer {
19+
_setSigner(signer);
20+
}
21+
22+
/// @dev Allows the entry point as an authorized executor.
23+
function _erc7821AuthorizedExecutor(
24+
address caller,
25+
bytes32 mode,
26+
bytes calldata executionData
27+
) internal view virtual override returns (bool) {
28+
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
29+
}
30+
}

contracts/mocks/import.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pragma solidity ^0.8.20;
44

55
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
66
import {ECDSAOwnedDKIMRegistry} from "@zk-email/email-tx-builder/utils/ECDSAOwnedDKIMRegistry.sol";
7+
import {ERC1271WalletMock} from "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";

contracts/utils/README.adoc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,53 @@ Miscellaneous contracts and libraries containing utility functions you can use t
88
* {AbstractSigner}: Abstract contract for internal signature validation in smart contracts.
99
* {ERC7739}: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
1010
* {ERC7739Utils}: Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739.
11+
* {ERC7913Utils}: utilities library that implements ERC-7913 signature verification with fallback to ERC-1271 and ECDSA.
12+
* {SignerECDSA}, {SignerERC7913}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
13+
* {ERC7913SignatureVerifierP256}, {ERC7913SignatureVerifierRSA}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys
1114
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
1215
* {SignerZKEmail}: Implementation of an {AbstractSigner} that enables email-based authentication through zero-knowledge proofs.
16+
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
1317
* {Masks}: Library to handle `bytes32` masks.
1418
* {ZKEmailUtils}: Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs.
1519

1620
== Cryptography
1721

18-
{{AbstractSigner}}
19-
2022
{{ERC7739}}
2123

2224
{{ERC7739Utils}}
2325

26+
=== Abstract Signers
27+
28+
{{AbstractSigner}}
29+
2430
{{SignerECDSA}}
2531

32+
{{SignerERC7913}}
33+
2634
{{SignerP256}}
2735

36+
{{SignerERC7702}}
37+
2838
{{SignerRSA}}
2939

3040
{{SignerZKEmail}}
3141

3242
{{ZKEmailUtils}}
3343

44+
=== ERC-7913
45+
46+
{{ERC7913Utils}}
47+
48+
{{ERC7913SignatureVerifierP256}}
49+
50+
{{ERC7913SignatureVerifierRSA}}
51+
52+
== Structs
53+
54+
{{EnumerableSetExtended}}
55+
56+
{{EnumerableMapExtended}}
57+
3458
== Libraries
3559

3660
{{Masks}}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol";
6+
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
7+
8+
/**
9+
* @dev ERC-7913 signature verifier that support P256 (secp256r1) keys.
10+
*/
11+
contract ERC7913SignatureVerifierP256 is IERC7913SignatureVerifier {
12+
/// @inheritdoc IERC7913SignatureVerifier
13+
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
14+
// Signature length may be 0x40 or 0x41.
15+
if (key.length == 0x40 && signature.length >= 0x40) {
16+
bytes32 qx = bytes32(key[0x00:0x20]);
17+
bytes32 qy = bytes32(key[0x20:0x40]);
18+
bytes32 r = bytes32(signature[0x00:0x20]);
19+
bytes32 s = bytes32(signature[0x20:0x40]);
20+
if (P256.verify(hash, r, s, qx, qy)) {
21+
return IERC7913SignatureVerifier.verify.selector;
22+
}
23+
}
24+
return 0xFFFFFFFF;
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {RSA} from "@openzeppelin/contracts/utils/cryptography/RSA.sol";
6+
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
7+
8+
/**
9+
* @dev ERC-7913 signature verifier that support RSA keys.
10+
*/
11+
contract ERC7913SignatureVerifierRSA is IERC7913SignatureVerifier {
12+
/// @inheritdoc IERC7913SignatureVerifier
13+
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
14+
(bytes memory e, bytes memory n) = abi.decode(key, (bytes, bytes));
15+
return
16+
RSA.pkcs1Sha256(abi.encodePacked(hash), signature, e, n)
17+
? IERC7913SignatureVerifier.verify.selector
18+
: bytes4(0xFFFFFFFF);
19+
}
20+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
6+
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
7+
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
8+
9+
/**
10+
* @dev Library that provides common ERC-7913 utility functions.
11+
*
12+
* This library extends the functionality of
13+
* https://docs.openzeppelin.com/contracts/5.x/api/utils#SignatureChecker[SignatureChecker]
14+
* to support signature verification for keys that do not have an Ethereum address of their own
15+
* as with ERC-1271.
16+
*
17+
* See https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
18+
*/
19+
library ERC7913Utils {
20+
using Bytes for bytes;
21+
22+
/**
23+
* @dev Verifies a signature for a given signer and hash.
24+
*
25+
* The signer is a `bytes` object that is the concatenation of an address and optionally a key:
26+
* `verifier || key`. A signer must be at least 20 bytes long.
27+
*
28+
* Verification is done as follows:
29+
* - If `signer.length < 20`: verification fails
30+
* - If `signer.length == 20`: verification is done using {SignatureChecker}
31+
* - Otherwise: verification is done using {IERC7913SignatureVerifier}
32+
*/
33+
function isValidSignatureNow(
34+
bytes memory signer,
35+
bytes32 hash,
36+
bytes memory signature
37+
) internal view returns (bool) {
38+
if (signer.length < 20) {
39+
return false;
40+
} else if (signer.length == 20) {
41+
return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature);
42+
} else {
43+
try IERC7913SignatureVerifier(address(bytes20(signer))).verify(signer.slice(20), hash, signature) returns (
44+
bytes4 magic
45+
) {
46+
return magic == IERC7913SignatureVerifier.verify.selector;
47+
} catch {
48+
return false;
49+
}
50+
}
51+
}
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {AbstractSigner} from "./AbstractSigner.sol";
6+
import {ERC7913Utils} from "./ERC7913Utils.sol";
7+
8+
/**
9+
* @dev Implementation of {AbstractSigner} using
10+
* https://eips.ethereum.org/EIPS/eip-7913[ERC-7913] signature verification.
11+
*
12+
* For {Account} usage, a {_setSigner} function is provided to set the ERC-7913 formatted {signer}.
13+
* Doing so is easier for a factory, who is likely to use initializable clones of this contract.
14+
*
15+
* The signer is a `bytes` object that concatenates a verifier address and a key: `verifier || key`.
16+
*
17+
* Example of usage:
18+
*
19+
* ```solidity
20+
* contract MyAccountERC7913 is Account, SignerERC7913, Initializable {
21+
* constructor() EIP712("MyAccountERC7913", "1") {}
22+
*
23+
* function initialize(bytes memory signer_) public initializer {
24+
* _setSigner(signer_);
25+
* }
26+
* }
27+
* ```
28+
*
29+
* IMPORTANT: Failing to call {_setSigner} either during construction (if used standalone)
30+
* or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
31+
*/
32+
33+
abstract contract SignerERC7913 is AbstractSigner {
34+
bytes private _signer;
35+
36+
/// @dev Sets the signer (i.e. `verifier || key`) with an ERC-7913 formatted signer.
37+
function _setSigner(bytes memory signer_) internal {
38+
_signer = signer_;
39+
}
40+
41+
/// @dev Return the ERC-7913 signer (i.e. `verifier || key`).
42+
function signer() public view virtual returns (bytes memory) {
43+
return _signer;
44+
}
45+
46+
/// @dev Verifies a signature using {ERC7913Utils.isValidSignatureNow} with {signer}, `hash` and `signature`.
47+
function _rawSignatureValidation(
48+
bytes32 hash,
49+
bytes calldata signature
50+
) internal view virtual override returns (bool) {
51+
return ERC7913Utils.isValidSignatureNow(signer(), hash, signature);
52+
}
53+
}

0 commit comments

Comments
 (0)