Skip to content

Commit 53f590e

Browse files
Amxxernestognw
andauthored
Add SignerERC7913 and verifiers for P256 and RSA (#98)
Co-authored-by: ernestognw <[email protected]>
1 parent 10c435a commit 53f590e

File tree

12 files changed

+319
-7
lines changed

12 files changed

+319
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 12-04-2025
22

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.
35
- `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers.
46

57
## 11-04-2025
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: 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/utils/README.adoc

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,41 @@ 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
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
1316
* {Masks}: Library to handle `bytes32` masks.
1417

1518
== Cryptography
1619

17-
{{AbstractSigner}}
18-
1920
{{ERC7739}}
2021

2122
{{ERC7739Utils}}
2223

24+
=== Abstract Signers
25+
26+
{{AbstractSigner}}
27+
2328
{{SignerECDSA}}
2429

30+
{{SignerERC7913}}
31+
2532
{{SignerP256}}
2633

2734
{{SignerERC7702}}
2835

2936
{{SignerRSA}}
3037

38+
=== ERC-7913
39+
40+
{{ERC7913Utils}}
41+
42+
{{ERC7913SignatureVerifierP256}}
43+
44+
{{ERC7913SignatureVerifierRSA}}
45+
3146
== Structs
3247

3348
{{EnumerableSetExtended}}
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+
}

contracts/utils/cryptography/ERC7913Utils.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.8.20;
3+
pragma solidity ^0.8.24;
44

55
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
6+
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
67
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
78

89
/**
@@ -16,6 +17,8 @@ import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
1617
* See https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
1718
*/
1819
library ERC7913Utils {
20+
using Bytes for bytes;
21+
1922
/**
2023
* @dev Verifies a signature for a given signer and hash.
2124
*
@@ -28,7 +31,7 @@ library ERC7913Utils {
2831
* - Otherwise: verification is done using {IERC7913SignatureVerifier}
2932
*/
3033
function isValidSignatureNow(
31-
bytes calldata signer,
34+
bytes memory signer,
3235
bytes32 hash,
3336
bytes memory signature
3437
) internal view returns (bool) {
@@ -37,7 +40,7 @@ library ERC7913Utils {
3740
} else if (signer.length == 20) {
3841
return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature);
3942
} else {
40-
try IERC7913SignatureVerifier(address(bytes20(signer[0:20]))).verify(signer[20:], hash, signature) returns (
43+
try IERC7913SignatureVerifier(address(bytes20(signer))).verify(signer.slice(20), hash, signature) returns (
4144
bytes4 magic
4245
) {
4346
return magic == IERC7913SignatureVerifier.verify.selector;
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+
}

docs/modules/ROOT/pages/account-abstraction.adoc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ To setup an account, you can either bring your own validation logic and start wi
2929

3030
=== Selecting a signer
3131

32-
The library includes specializations of the `AbstractSigner` contract that use custom digital signature verification algorithms. These are xref:api:utils.adoc#SignerECDSA[`SignerECDSA`], xref:api:utils.adoc#SignerP256[`SignerP256`] and xref:api:utils.adoc#SignerRSA[`SignerRSA`].
32+
The library includes specializations of the `AbstractSigner` contract that use custom digital signature verification algorithms. These are xref:api:utils.adoc#SignerECDSA[`SignerECDSA`], xref:api:utils.adoc#SignerP256[`SignerP256`], xref:api:utils.adoc#SignerRSA[`SignerRSA`], xref:api:utils.adoc#SignerERC7702[`SignerERC7702`], and xref:api:utils.adoc#SignerERC7913[`SignerERC7913`].
3333

3434
Since smart accounts are deployed by a factory, the best practice is to create https://docs.openzeppelin.com/contracts/5.x/api/proxy#minimal_clones[minimal clones] of initializable contracts. These signer implementations provide an initializable design by default so that the factory can interact with the account to set it up after deployment in a single transaction.
3535

@@ -56,6 +56,13 @@ Similarly, some government and corporate public key infrastructures use RSA for
5656
include::api:example$account/MyAccountRSA.sol[]
5757
----
5858

59+
For more advanced use cases where you need to support keys that don't have their own Ethereum address (like hardware devices or non-Ethereum cryptographic curves), you can use xref:api:utils.adoc#SignerERC7913[`SignerERC7913`]. This implementation allows for signature verification using ERC-7913 compatible verifiers.
60+
61+
[source,solidity]
62+
----
63+
include::api:example$account/MyAccountERC7913.sol[]
64+
----
65+
5966
== Account Factory
6067

6168
The first time a user sends an user operation, the account will be created deterministically (i.e. its code and address can be predicted) using the the `initCode` field in the UserOperation. This field contains both the address of a smart contract (the factory) and the data required to call it and deploy the smart account.

docs/modules/ROOT/pages/utilities.adoc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,28 @@ In case your smart contract validates signatures, using xref:api:utils.adoc#ERC7
3434
----
3535
include::api:example$utils/cryptography/ERC7739SignerECDSA.sol[]
3636
----
37+
38+
==== ERC-7913 Signature Verifiers
39+
40+
ERC-7913 extends the concept of signature verification to support keys that don't have their own Ethereum address. This is particularly useful for integrating non-Ethereum cryptographic curves, hardware devices, or other identity systems into smart accounts.
41+
42+
The standard defines a verifier interface that can be implemented to support different types of keys. A signer is represented as a `bytes` object that concatenates a verifier address and a key: `verifier || key`.
43+
44+
xref:api:utils.adoc#ERC7913Utils[`ERC7913Utils`] provides functions for verifying signatures using ERC-7913 compatible verifiers:
45+
46+
[source,solidity]
47+
----
48+
using ERC7913Utils for bytes;
49+
50+
function _verify(bytes memory signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
51+
return signer.isValidSignatureNow(hash, signature);
52+
}
53+
----
54+
55+
The verification process works as follows:
56+
57+
* If `signer.length < 20`: verification fails
58+
* If `signer.length == 20`: verification is done using https://docs.openzeppelin.com/contracts/5.x/api/utils#SignatureChecker[SignatureChecker]
59+
* Otherwise: verification is done using an ERC-7913 verifier.
60+
61+
This allows for backward compatibility with EOAs and ERC-1271 contracts while supporting new types of keys.

0 commit comments

Comments
 (0)