Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 12-04-2025

- `SignerERC7913`: Abstract signer that verifies signatures using the ERC-7913 workflow.
- `ERC7913SignatureVerifierP256` and `ERC7913SignatureVerifierRSA`: Ready to use ERC-7913 verifiers that implement key verification for P256 (secp256r1) and RSA keys.
- `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers.

## 11-04-2025
Expand Down
25 changes: 25 additions & 0 deletions contracts/mocks/account/AccountERC7913Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Account} from "../../account/Account.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "../../utils/cryptography/ERC7739.sol";
import {ERC7821} from "../../account/extensions/ERC7821.sol";
import {SignerERC7913} from "../../utils/cryptography/SignerERC7913.sol";

abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
constructor(bytes memory _signer) {
_setSigner(_signer);
}

/// @inheritdoc ERC7821
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}
30 changes: 30 additions & 0 deletions contracts/mocks/docs/account/MyAccountERC7913.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// contracts/MyAccount.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Account} from "../../../account/Account.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "../../../utils/cryptography/ERC7739.sol";
import {ERC7821} from "../../../account/extensions/ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {SignerERC7913} from "../../../utils/cryptography/SignerERC7913.sol";

contract MyAccount7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
constructor() EIP712("MyAccount7913", "1") {}

function initialize(bytes memory signer) public initializer {
_setSigner(signer);
}

/// @dev Allows the entry point as an authorized executor.
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}
19 changes: 17 additions & 2 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,41 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {AbstractSigner}: Abstract contract for internal signature validation in smart contracts.
* {ERC7739}: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
* {ERC7739Utils}: Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739.
* {ERC7913Utils}: utilities library that implements ERC-7913 signature verification with fallback to ERC-1271 and ECDSA.
* {SignerECDSA}, {SignerERC7913}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
* {ERC7913SignatureVerifierP256}, {ERC7913SignatureVerifierRSA}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
* {Masks}: Library to handle `bytes32` masks.

== Cryptography

{{AbstractSigner}}

{{ERC7739}}

{{ERC7739Utils}}

=== Abstract Signers

{{AbstractSigner}}

{{SignerECDSA}}

{{SignerERC7913}}

{{SignerP256}}

{{SignerERC7702}}

{{SignerRSA}}

=== ERC-7913

{{ERC7913Utils}}

{{ERC7913SignatureVerifierP256}}

{{ERC7913SignatureVerifierRSA}}

== Structs

{{EnumerableSetExtended}}
Expand Down
26 changes: 26 additions & 0 deletions contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol";
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";

/**
* @dev ERC-7913 signature verifier that support P256 (secp256r1) keys.
*/
contract ERC7913SignatureVerifierP256 is IERC7913SignatureVerifier {
/// @inheritdoc IERC7913SignatureVerifier
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
// Signature length may be 0x40 or 0x41.
if (key.length == 0x40 && signature.length >= 0x40) {
bytes32 qx = bytes32(key[0x00:0x20]);
bytes32 qy = bytes32(key[0x20:0x40]);
bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
if (P256.verify(hash, r, s, qx, qy)) {
return IERC7913SignatureVerifier.verify.selector;
}
}
return 0xFFFFFFFF;
}
}
20 changes: 20 additions & 0 deletions contracts/utils/cryptography/ERC7913SignatureVerifierRSA.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {RSA} from "@openzeppelin/contracts/utils/cryptography/RSA.sol";
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";

/**
* @dev ERC-7913 signature verifier that support RSA keys.
*/
contract ERC7913SignatureVerifierRSA is IERC7913SignatureVerifier {
/// @inheritdoc IERC7913SignatureVerifier
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
(bytes memory e, bytes memory n) = abi.decode(key, (bytes, bytes));
return
RSA.pkcs1Sha256(abi.encodePacked(hash), signature, e, n)
? IERC7913SignatureVerifier.verify.selector
: bytes4(0xFFFFFFFF);
}
}
9 changes: 6 additions & 3 deletions contracts/utils/cryptography/ERC7913Utils.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
pragma solidity ^0.8.24;

import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";

/**
Expand All @@ -16,6 +17,8 @@ import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
* See https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
*/
library ERC7913Utils {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer having just one implementation of the isValidSignatureNow function. The rationale is that signature always ends up in memory for any valid verification, and a portion of signer will be copied too. So I doubt there's actual benefits in having two versions. I'll simplify for now following #109

Copy link
Collaborator Author

@Amxx Amxx Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory version uses a slice, that does a memory copy.

Overall, calldata versions are often cheaper because they don't copy to memory until the very last point (when the Abi call is encoded) contrary to the memory version that copy to memory at least once more (in this case 2 times)

Overall not a big deal. Having a single "memory" version is probably good enough. Just explaining the initial idea

Copy link
Member

@ernestognw ernestognw Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I saw the memory version is required, so I'd agree with just one "memory" version for now unless there are significant savings measured.

Thanks for answering though, now please you go 👨‍🍼

using Bytes for bytes;

/**
* @dev Verifies a signature for a given signer and hash.
*
Expand All @@ -28,7 +31,7 @@ library ERC7913Utils {
* - Otherwise: verification is done using {IERC7913SignatureVerifier}
*/
function isValidSignatureNow(
bytes calldata signer,
bytes memory signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
Expand All @@ -37,7 +40,7 @@ library ERC7913Utils {
} else if (signer.length == 20) {
return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature);
} else {
try IERC7913SignatureVerifier(address(bytes20(signer[0:20]))).verify(signer[20:], hash, signature) returns (
try IERC7913SignatureVerifier(address(bytes20(signer))).verify(signer.slice(20), hash, signature) returns (
bytes4 magic
) {
return magic == IERC7913SignatureVerifier.verify.selector;
Expand Down
53 changes: 53 additions & 0 deletions contracts/utils/cryptography/SignerERC7913.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {AbstractSigner} from "./AbstractSigner.sol";
import {ERC7913Utils} from "./ERC7913Utils.sol";

/**
* @dev Implementation of {AbstractSigner} using
* https://eips.ethereum.org/EIPS/eip-7913[ERC-7913] signature verification.
*
* For {Account} usage, a {_setSigner} function is provided to set the ERC-7913 formatted {signer}.
* Doing so is easier for a factory, who is likely to use initializable clones of this contract.
*
* The signer is a `bytes` object that concatenates a verifier address and a key: `verifier || key`.
*
* Example of usage:
*
* ```solidity
* contract MyAccountERC7913 is Account, SignerERC7913, Initializable {
* constructor() EIP712("MyAccountERC7913", "1") {}
*
* function initialize(bytes memory signer_) public initializer {
* _setSigner(signer_);
* }
* }
* ```
*
* IMPORTANT: Failing to call {_setSigner} either during construction (if used standalone)
* or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
*/

abstract contract SignerERC7913 is AbstractSigner {
bytes private _signer;

/// @dev Sets the signer (i.e. `verifier || key`) with an ERC-7913 formatted signer.
function _setSigner(bytes memory signer_) internal {
_signer = signer_;
}

/// @dev Return the ERC-7913 signer (i.e. `verifier || key`).
function signer() public view virtual returns (bytes memory) {
return _signer;
}

/// @dev Verifies a signature using {ERC7913Utils.isValidSignatureNow} with {signer}, `hash` and `signature`.
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
return ERC7913Utils.isValidSignatureNow(signer(), hash, signature);
}
}
9 changes: 8 additions & 1 deletion docs/modules/ROOT/pages/account-abstraction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ To setup an account, you can either bring your own validation logic and start wi

=== Selecting a signer

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`].
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`].

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.

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

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.

[source,solidity]
----
include::api:example$account/MyAccountERC7913.sol[]
----

== Account Factory

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.
Expand Down
25 changes: 25 additions & 0 deletions docs/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,28 @@ In case your smart contract validates signatures, using xref:api:utils.adoc#ERC7
----
include::api:example$utils/cryptography/ERC7739SignerECDSA.sol[]
----

==== ERC-7913 Signature Verifiers

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.

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`.

xref:api:utils.adoc#ERC7913Utils[`ERC7913Utils`] provides functions for verifying signatures using ERC-7913 compatible verifiers:

[source,solidity]
----
using ERC7913Utils for bytes;

function _verify(bytes memory signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
return signer.isValidSignatureNow(hash, signature);
}
----

The verification process works as follows:

* If `signer.length < 20`: verification fails
* If `signer.length == 20`: verification is done using https://docs.openzeppelin.com/contracts/5.x/api/utils#SignatureChecker[SignatureChecker]
* Otherwise: verification is done using an ERC-7913 verifier.

This allows for backward compatibility with EOAs and ERC-1271 contracts while supporting new types of keys.
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 2 files
+0 −68 src/Vm.sol
+2 −2 test/Vm.t.sol
Loading
Loading