From 8825e22d80c1f3a9fdbc9869c4d3eb455b5e18de Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:15:11 +0100 Subject: [PATCH 01/17] Add an ERC-7913 based abstract signer and an ERC-7913 signature verifier for P256 keys --- contracts/interfaces/IERC7913.sol | 17 ++++++ .../mocks/account/AccountERC7913Mock.sol | 22 +++++++ .../ERC7913SignatureVerifierP256.sol | 22 +++++++ contracts/utils/cryptography/ERC7913Utils.sol | 49 +++++++++++++++ .../utils/cryptography/SignerERC7913.sol | 26 ++++++++ test/account/AccountERC7913.test.js | 61 +++++++++++++++++++ 6 files changed, 197 insertions(+) create mode 100644 contracts/interfaces/IERC7913.sol create mode 100644 contracts/mocks/account/AccountERC7913Mock.sol create mode 100644 contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol create mode 100644 contracts/utils/cryptography/ERC7913Utils.sol create mode 100644 contracts/utils/cryptography/SignerERC7913.sol create mode 100644 test/account/AccountERC7913.test.js diff --git a/contracts/interfaces/IERC7913.sol b/contracts/interfaces/IERC7913.sol new file mode 100644 index 00000000..58e33409 --- /dev/null +++ b/contracts/interfaces/IERC7913.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev Signature verifier interface. + */ +interface IERC7913SignatureVerifier { + /** + * @dev Verifies `signature` as a valid signature of `hash` by `key`. + * + * MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. + * SHOULD return 0xffffffff or revert if the signature is not valid. + * SHOULD return 0xffffffff or revert if the key is empty + */ + function verify(bytes calldata key, bytes32 hash, bytes calldata signature) external view returns (bytes4); +} diff --git a/contracts/mocks/account/AccountERC7913Mock.sol b/contracts/mocks/account/AccountERC7913Mock.sol new file mode 100644 index 00000000..fbd84451 --- /dev/null +++ b/contracts/mocks/account/AccountERC7913Mock.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Account} from "../../account/Account.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; +import {SignerERC7913} from "../../utils/cryptography/SignerERC7913.sol"; + +abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7821 { + 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); + } +} diff --git a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol new file mode 100644 index 00000000..6309198f --- /dev/null +++ b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol"; +import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; + +contract ERC7913SignatureVerifierP256 is IERC7913SignatureVerifier { + function verify(bytes calldata key, bytes32 hash, bytes calldata signature) external view returns (bytes4) { + // Signature length may be 0x40 or 0x41. + if (key.length == 0x40 && signature.length > 0x3f && signature.length < 0x42) { + 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; + } +} diff --git a/contracts/utils/cryptography/ERC7913Utils.sol b/contracts/utils/cryptography/ERC7913Utils.sol new file mode 100644 index 00000000..856cdc3a --- /dev/null +++ b/contracts/utils/cryptography/ERC7913Utils.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol"; +import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; + +library ERC7913Utils { + function isValidSignatureNow( + bytes memory signer, + bytes32 hash, + bytes calldata signature + ) internal view returns (bool) { + if (signer.length < 20) { + return false; + } else if (signer.length == 20) { + return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature); + } else { + try + IERC7913SignatureVerifier(address(bytes20(signer))).verify(Bytes.slice(signer, 20), hash, signature) + returns (bytes4 magic) { + return magic == IERC7913SignatureVerifier.verify.selector; + } catch { + return false; + } + } + } + + function isValidSignatureNowCalldata( + bytes calldata signer, + bytes32 hash, + bytes calldata signature + ) internal view returns (bool) { + if (signer.length < 20) { + return false; + } 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 ( + bytes4 magic + ) { + return magic == IERC7913SignatureVerifier.verify.selector; + } catch { + return false; + } + } + } +} diff --git a/contracts/utils/cryptography/SignerERC7913.sol b/contracts/utils/cryptography/SignerERC7913.sol new file mode 100644 index 00000000..185a9b38 --- /dev/null +++ b/contracts/utils/cryptography/SignerERC7913.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {AbstractSigner} from "./AbstractSigner.sol"; +import {ERC7913Utils} from "./ERC7913Utils.sol"; + +abstract contract SignerERC7913 is AbstractSigner { + bytes private _signer; + + function _setSigner(bytes memory signer_) internal { + _signer = signer_; + } + + function signer() public view virtual returns (bytes memory) { + return _signer; + } + + /// @inheritdoc AbstractSigner + function _rawSignatureValidation( + bytes32 hash, + bytes calldata signature + ) internal view virtual override returns (bool) { + return ERC7913Utils.isValidSignatureNow(signer(), hash, signature); + } +} diff --git a/test/account/AccountERC7913.test.js b/test/account/AccountERC7913.test.js new file mode 100644 index 00000000..c5d4ef15 --- /dev/null +++ b/test/account/AccountERC7913.test.js @@ -0,0 +1,61 @@ +const { ethers, entrypoint } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { getDomain } = require('@openzeppelin/contracts/test/helpers/eip712'); +const { ERC4337Helper } = require('../helpers/erc4337'); +const { NonNativeSigner, P256SigningKey } = require('../helpers/signers'); +const { PackedUserOperation } = require('../helpers/eip712-types'); + +const { shouldBehaveLikeAccountCore, shouldBehaveLikeAccountHolder } = require('./Account.behavior'); +const { shouldBehaveLikeERC1271 } = require('../utils/cryptography/ERC1271.behavior'); +const { shouldBehaveLikeERC7821 } = require('./extensions/ERC7821.behavior'); + +async function fixture() { + // EOAs and environment + const [beneficiary, other] = await ethers.getSigners(); + const target = await ethers.deployContract('CallReceiverMockExtended'); + + // P256 signer + const signer = new NonNativeSigner(P256SigningKey.random()); + const verifier = await ethers.deployContract('ERC7913SignatureVerifierP256'); + + // ERC-4337 account + const helper = new ERC4337Helper(); + const mock = await helper.newAccount('$AccountERC7913Mock', [ + 'AccountERC7913', + '1', + ethers.solidityPacked( + ['address', 'bytes32', 'bytes32'], + [verifier.target, signer.signingKey.publicKey.qx, signer.signingKey.publicKey.qy], + ), + ]); + + // ERC-4337 Entrypoint domain + const entrypointDomain = await getDomain(entrypoint.v08); + + // domain cannot be fetched using getDomain(mock) before the mock is deployed + const domain = { + name: 'AccountERC7913', + version: '1', + chainId: entrypointDomain.chainId, + verifyingContract: mock.address, + }; + + const signUserOp = userOp => + signer + .signTypedData(entrypointDomain, { PackedUserOperation }, userOp.packed) + .then(signature => Object.assign(userOp, { signature })); + + return { helper, mock, domain, signer, target, beneficiary, other, signUserOp }; +} + +describe('AccountERC7913', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeAccountCore(); + shouldBehaveLikeAccountHolder(); + shouldBehaveLikeERC1271({ erc7739: true }); + shouldBehaveLikeERC7821(); +}); From bd1184f2ea2b3655ca2f75e5f86f3f69094bd131 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:26:54 +0100 Subject: [PATCH 02/17] documentation --- .../ERC7913SignatureVerifierP256.sol | 4 +++ contracts/utils/cryptography/ERC7913Utils.sol | 31 +++++++++++++++++-- .../utils/cryptography/SignerERC7913.sol | 22 +++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol index 6309198f..ccc7d45e 100644 --- a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol +++ b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol @@ -5,7 +5,11 @@ 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) external view returns (bytes4) { // Signature length may be 0x40 or 0x41. if (key.length == 0x40 && signature.length > 0x3f && signature.length < 0x42) { diff --git a/contracts/utils/cryptography/ERC7913Utils.sol b/contracts/utils/cryptography/ERC7913Utils.sol index 856cdc3a..4ebedb12 100644 --- a/contracts/utils/cryptography/ERC7913Utils.sol +++ b/contracts/utils/cryptography/ERC7913Utils.sol @@ -6,7 +6,22 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol"; import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; +/** + * @dev Helper library to verify key signatures following the ERC-7913 standard, with fallback to ECDSA and ERC-1271 + * when the signer's key is empty (as specified in ERC-7913) + */ library ERC7913Utils { + using Bytes for bytes; + /** + * @dev Checks if a signature is valid for a given signer and data hash. The signer is interpreted following + * ERC-7913: + * * If the signer's key is not empty the signature is verified using the signer's verifier ERC-7913 interface. + * * Otherwise, the signature is verified using the `SignatureChecker` library, which supports both ECDSA and + * ERC-1271 signature verification + * + * NOTE: Unlike ECDSA signatures, contract signatures 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). + */ function isValidSignatureNow( bytes memory signer, bytes32 hash, @@ -17,9 +32,9 @@ library ERC7913Utils { } else if (signer.length == 20) { return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature); } else { - try - IERC7913SignatureVerifier(address(bytes20(signer))).verify(Bytes.slice(signer, 20), hash, signature) - returns (bytes4 magic) { + try IERC7913SignatureVerifier(address(bytes20(signer))).verify(signer.slice(20), hash, signature) returns ( + bytes4 magic + ) { return magic == IERC7913SignatureVerifier.verify.selector; } catch { return false; @@ -27,6 +42,16 @@ library ERC7913Utils { } } + /** + * @dev Checks if a signature is valid for a given signer and data hash. The signer is interpreted following + * ERC-7913: + * * If the signer's key is not empty the signature is verified using the signer's verifier ERC-7913 interface. + * * Otherwise, the signature is verified using the `SignatureChecker` library, which supports both ECDSA and + * ERC-1271 signature verification + * + * NOTE: Unlike ECDSA signatures, contract signatures 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). + */ function isValidSignatureNowCalldata( bytes calldata signer, bytes32 hash, diff --git a/contracts/utils/cryptography/SignerERC7913.sol b/contracts/utils/cryptography/SignerERC7913.sol index 185a9b38..4005ab49 100644 --- a/contracts/utils/cryptography/SignerERC7913.sol +++ b/contracts/utils/cryptography/SignerERC7913.sol @@ -5,6 +5,28 @@ pragma solidity ^0.8.20; import {AbstractSigner} from "./AbstractSigner.sol"; import {ERC7913Utils} from "./ERC7913Utils.sol"; +/** + * @dev Implementation of {AbstractSigner} that supports ERC-7913 signers. + * + * For {Account} usage, an {_setSigner} function is provided to set the ERC-7913 formated {signer}. + * Doing so it's easier for a factory, whose likely to use initializable clones of this contract. + * + * Example of usage: + * + * ```solidity + * contract MyAccountERC7913 is Account, SignerERC7913, Initializable { + * constructor() EIP712("MyAccountERC7913", "1") {} + * + * function initialize(bytes memory signer) public initializer { + * _setSigner(signer); + * } + * } + * ``` + * + * IMPORTANT: Avoiding 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; From acf80ac46dbc2e1bbfdd559cd817ee2b2346f514 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:27:35 +0100 Subject: [PATCH 03/17] pragma --- contracts/utils/cryptography/ERC7913Utils.sol | 2 +- contracts/utils/cryptography/SignerERC7913.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/ERC7913Utils.sol b/contracts/utils/cryptography/ERC7913Utils.sol index 4ebedb12..7ee1a0b5 100644 --- a/contracts/utils/cryptography/ERC7913Utils.sol +++ b/contracts/utils/cryptography/ERC7913Utils.sol @@ -1,6 +1,6 @@ // 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"; diff --git a/contracts/utils/cryptography/SignerERC7913.sol b/contracts/utils/cryptography/SignerERC7913.sol index 4005ab49..a99d8269 100644 --- a/contracts/utils/cryptography/SignerERC7913.sol +++ b/contracts/utils/cryptography/SignerERC7913.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {AbstractSigner} from "./AbstractSigner.sol"; import {ERC7913Utils} from "./ERC7913Utils.sol"; From 722571c15ad2400addf6789f1fc469f63d1982ae Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:28:20 +0100 Subject: [PATCH 04/17] spelling --- contracts/utils/cryptography/SignerERC7913.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/SignerERC7913.sol b/contracts/utils/cryptography/SignerERC7913.sol index a99d8269..3fa85928 100644 --- a/contracts/utils/cryptography/SignerERC7913.sol +++ b/contracts/utils/cryptography/SignerERC7913.sol @@ -8,7 +8,7 @@ import {ERC7913Utils} from "./ERC7913Utils.sol"; /** * @dev Implementation of {AbstractSigner} that supports ERC-7913 signers. * - * For {Account} usage, an {_setSigner} function is provided to set the ERC-7913 formated {signer}. + * For {Account} usage, an {_setSigner} function is provided to set the ERC-7913 formatted {signer}. * Doing so it's easier for a factory, whose likely to use initializable clones of this contract. * * Example of usage: From 6a88076be1bde0dbb91685a2bbcec9db24f21470 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:46:57 +0100 Subject: [PATCH 05/17] pragma --- contracts/mocks/account/AccountERC7913Mock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mocks/account/AccountERC7913Mock.sol b/contracts/mocks/account/AccountERC7913Mock.sol index fbd84451..153cb0e3 100644 --- a/contracts/mocks/account/AccountERC7913Mock.sol +++ b/contracts/mocks/account/AccountERC7913Mock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {Account} from "../../account/Account.sol"; import {ERC7821} from "../../account/extensions/ERC7821.sol"; From 687871098b289c29f852418721eea9d6454d16a1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Mar 2025 21:49:06 +0100 Subject: [PATCH 06/17] Update contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol --- contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol index ccc7d45e..075d29f8 100644 --- a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol +++ b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol @@ -10,7 +10,7 @@ import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; */ contract ERC7913SignatureVerifierP256 is IERC7913SignatureVerifier { /// @inheritdoc IERC7913SignatureVerifier - function verify(bytes calldata key, bytes32 hash, bytes calldata signature) external view returns (bytes4) { + 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 > 0x3f && signature.length < 0x42) { bytes32 qx = bytes32(key[0x00:0x20]); From 7c3bef20cbbc7d2ce86f939efb0248e5498ee064 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 30 Mar 2025 12:09:25 +0200 Subject: [PATCH 07/17] add RSA signature verifier --- .../ERC7913SignatureVerifierRSA.sol | 20 ++++ test/account/AccountERC7913.test.js | 103 +++++++++++++----- 2 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 contracts/utils/cryptography/ERC7913SignatureVerifierRSA.sol diff --git a/contracts/utils/cryptography/ERC7913SignatureVerifierRSA.sol b/contracts/utils/cryptography/ERC7913SignatureVerifierRSA.sol new file mode 100644 index 00000000..597224e9 --- /dev/null +++ b/contracts/utils/cryptography/ERC7913SignatureVerifierRSA.sol @@ -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); + } +} diff --git a/test/account/AccountERC7913.test.js b/test/account/AccountERC7913.test.js index c5d4ef15..72004254 100644 --- a/test/account/AccountERC7913.test.js +++ b/test/account/AccountERC7913.test.js @@ -3,50 +3,47 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { getDomain } = require('@openzeppelin/contracts/test/helpers/eip712'); const { ERC4337Helper } = require('../helpers/erc4337'); -const { NonNativeSigner, P256SigningKey } = require('../helpers/signers'); +const { NonNativeSigner, P256SigningKey, RSASHA256SigningKey } = require('../helpers/signers'); const { PackedUserOperation } = require('../helpers/eip712-types'); const { shouldBehaveLikeAccountCore, shouldBehaveLikeAccountHolder } = require('./Account.behavior'); const { shouldBehaveLikeERC1271 } = require('../utils/cryptography/ERC1271.behavior'); const { shouldBehaveLikeERC7821 } = require('./extensions/ERC7821.behavior'); +// Prepare signer in advance (RSA are long to initialize) +const signerECDSA = ethers.Wallet.createRandom(); +const signerP256 = new NonNativeSigner(P256SigningKey.random()); +const signerRSA = new NonNativeSigner(RSASHA256SigningKey.random()); + +// Minimal fixture common to the different signer verifiers async function fixture() { // EOAs and environment const [beneficiary, other] = await ethers.getSigners(); const target = await ethers.deployContract('CallReceiverMockExtended'); - // P256 signer - const signer = new NonNativeSigner(P256SigningKey.random()); - const verifier = await ethers.deployContract('ERC7913SignatureVerifierP256'); + // ERC-7913 verifiers + const verifierP256 = await ethers.deployContract('ERC7913SignatureVerifierP256'); + const verifierRSA = await ethers.deployContract('ERC7913SignatureVerifierRSA'); - // ERC-4337 account + // ERC-4337 env const helper = new ERC4337Helper(); - const mock = await helper.newAccount('$AccountERC7913Mock', [ - 'AccountERC7913', - '1', - ethers.solidityPacked( - ['address', 'bytes32', 'bytes32'], - [verifier.target, signer.signingKey.publicKey.qx, signer.signingKey.publicKey.qy], - ), - ]); - - // ERC-4337 Entrypoint domain + await helper.wait(); const entrypointDomain = await getDomain(entrypoint.v08); + const domain = { name: 'AccountERC7913', version: '1', chainId: entrypointDomain.chainId }; // Missing verifyingContract, - // domain cannot be fetched using getDomain(mock) before the mock is deployed - const domain = { - name: 'AccountERC7913', - version: '1', - chainId: entrypointDomain.chainId, - verifyingContract: mock.address, - }; + const makeMock = signer => + helper.newAccount('$AccountERC7913Mock', ['AccountERC7913', '1', signer]).then(mock => { + domain.verifyingContract = mock.address; + return mock; + }); - const signUserOp = userOp => - signer + const signUserOp = function (userOp) { + return this.signer .signTypedData(entrypointDomain, { PackedUserOperation }, userOp.packed) .then(signature => Object.assign(userOp, { signature })); + }; - return { helper, mock, domain, signer, target, beneficiary, other, signUserOp }; + return { helper, verifierP256, verifierRSA, domain, target, beneficiary, other, makeMock, signUserOp }; } describe('AccountERC7913', function () { @@ -54,8 +51,56 @@ describe('AccountERC7913', function () { Object.assign(this, await loadFixture(fixture)); }); - shouldBehaveLikeAccountCore(); - shouldBehaveLikeAccountHolder(); - shouldBehaveLikeERC1271({ erc7739: true }); - shouldBehaveLikeERC7821(); + // Using ECDSA key as verifier + describe('ECDSA key', function () { + beforeEach(async function () { + this.signer = signerECDSA; + this.mock = await this.makeMock(this.signer.address); + }); + + shouldBehaveLikeAccountCore(); + shouldBehaveLikeAccountHolder(); + shouldBehaveLikeERC1271({ erc7739: true }); + shouldBehaveLikeERC7821(); + }); + + // Using P256 key with an ERC-7913 verifier + describe('P256 key', function () { + beforeEach(async function () { + this.signer = signerP256; + this.mock = await this.makeMock( + ethers.concat([ + this.verifierP256.target, + this.signer.signingKey.publicKey.qx, + this.signer.signingKey.publicKey.qy, + ]), + ); + }); + + shouldBehaveLikeAccountCore(); + shouldBehaveLikeAccountHolder(); + shouldBehaveLikeERC1271({ erc7739: true }); + shouldBehaveLikeERC7821(); + }); + + // Using RSA key with an ERC-7913 verifier + describe('RSA key', function () { + beforeEach(async function () { + this.signer = signerRSA; + this.mock = await this.makeMock( + ethers.concat([ + this.verifierRSA.target, + ethers.AbiCoder.defaultAbiCoder().encode( + ['bytes', 'bytes'], + [this.signer.signingKey.publicKey.e, this.signer.signingKey.publicKey.n], + ), + ]), + ); + }); + + shouldBehaveLikeAccountCore(); + shouldBehaveLikeAccountHolder(); + shouldBehaveLikeERC1271({ erc7739: true }); + shouldBehaveLikeERC7821(); + }); }); From a086759ce17c9a8265ad7d2f071c3a7783dfdbfc Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 30 Mar 2025 12:12:40 +0200 Subject: [PATCH 08/17] doc --- contracts/utils/README.adoc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index bb4c33f3..1d1dd975 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -8,23 +8,35 @@ 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}, {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 * {Masks}: Library to handle `bytes32` masks. == Cryptography -{{AbstractSigner}} - {{ERC7739}} {{ERC7739Utils}} +=== Abstract signers + +{{AbstractSigner}} + {{SignerECDSA}} {{SignerP256}} {{SignerRSA}} +=== ERC-7913 + +{{ERC7913Utils}} + +{{ERC7913SignatureVerifierP256}} + +{{ERC7913SignatureVerifierRSA}} + == Libraries {{Masks}} From fb0e6494eae72e4758fb98f06e519424ac152547 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 30 Mar 2025 12:21:47 +0200 Subject: [PATCH 09/17] add changelog entry --- CHANGELOG.md | 6 ++++++ contracts/utils/README.adoc | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df1d60f3..f818954e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## XX-XX-XXXX + +- `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers. +- `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. + ## 07-03-2025 - `ERC7786Aggregator`: Add an aggregator that implements a meta gateway on top of multiple ERC-7786 gateways. diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 1d1dd975..d0d7b2cb 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -9,7 +9,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {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}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms. + * {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 * {Masks}: Library to handle `bytes32` masks. @@ -25,6 +25,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t {{SignerECDSA}} +{{SignerERC7913}} + {{SignerP256}} {{SignerRSA}} From 18d70b658215105bdbe26c0c51ddb426873579f3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 3 Apr 2025 13:43:02 +0200 Subject: [PATCH 10/17] Update contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol --- contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol index 075d29f8..fc822a8a 100644 --- a/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol +++ b/contracts/utils/cryptography/ERC7913SignatureVerifierP256.sol @@ -12,7 +12,7 @@ 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 > 0x3f && signature.length < 0x42) { + 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]); From 87119a00b0e1a1e7e3aaf2454256086fce8b67ce Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 4 Apr 2025 22:37:49 +0200 Subject: [PATCH 11/17] fix test --- contracts/mocks/account/AccountERC7913Mock.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/mocks/account/AccountERC7913Mock.sol b/contracts/mocks/account/AccountERC7913Mock.sol index 153cb0e3..e25961d2 100644 --- a/contracts/mocks/account/AccountERC7913Mock.sol +++ b/contracts/mocks/account/AccountERC7913Mock.sol @@ -3,10 +3,13 @@ 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, ERC7821 { +abstract contract AccountERC7913Mock is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder { constructor(bytes memory _signer) { _setSigner(_signer); } From 67d5c39b88b277b37521d84ea199a0a4fbeb2b93 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 13:25:32 -0600 Subject: [PATCH 12/17] Nit --- CHANGELOG.md | 1 + contracts/utils/README.adoc | 2 +- contracts/utils/cryptography/ERC7913Utils.sol | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 713ef9e5..e4d932f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - `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. + ## 12-04-2025 - `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers. diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index bd0487da..fd0c7d75 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -21,7 +21,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t {{ERC7739Utils}} -=== Abstract signers +=== Abstract Signers {{AbstractSigner}} diff --git a/contracts/utils/cryptography/ERC7913Utils.sol b/contracts/utils/cryptography/ERC7913Utils.sol index 157dcbfb..75b15b3f 100644 --- a/contracts/utils/cryptography/ERC7913Utils.sol +++ b/contracts/utils/cryptography/ERC7913Utils.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol"; import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; /** @@ -16,6 +17,8 @@ import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; * See https://eips.ethereum.org/EIPS/eip-7913[ERC-7913]. */ library ERC7913Utils { + using Bytes for bytes; + /** * @dev Verifies a signature for a given signer and hash. * @@ -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) { @@ -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; From 1e3f120ad25c09cc8ffc0d999936b461ab1fab22 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 13:27:37 -0600 Subject: [PATCH 13/17] Pragma --- contracts/utils/cryptography/ERC7913Utils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/ERC7913Utils.sol b/contracts/utils/cryptography/ERC7913Utils.sol index 75b15b3f..efe7df09 100644 --- a/contracts/utils/cryptography/ERC7913Utils.sol +++ b/contracts/utils/cryptography/ERC7913Utils.sol @@ -1,6 +1,6 @@ // 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"; From c33ca987faab8afb09ba2a04aa6089f37c243988 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 13:42:38 -0600 Subject: [PATCH 14/17] Add docs --- docs/modules/ROOT/pages/utilities.adoc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 9dcf744e..42726a2a 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -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. From a1d07fe6e3880e39f9a492502cb8517c7e384add Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 13:56:51 -0600 Subject: [PATCH 15/17] Improve docs --- .../mocks/docs/account/MyAccountERC7913.sol | 30 +++++++++++++++++++ .../utils/cryptography/SignerERC7913.sol | 19 +++++++----- .../ROOT/pages/account-abstraction.adoc | 9 +++++- 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 contracts/mocks/docs/account/MyAccountERC7913.sol diff --git a/contracts/mocks/docs/account/MyAccountERC7913.sol b/contracts/mocks/docs/account/MyAccountERC7913.sol new file mode 100644 index 00000000..7ba8d1b1 --- /dev/null +++ b/contracts/mocks/docs/account/MyAccountERC7913.sol @@ -0,0 +1,30 @@ +// contracts/MyAccount.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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); + } +} diff --git a/contracts/utils/cryptography/SignerERC7913.sol b/contracts/utils/cryptography/SignerERC7913.sol index 3fa85928..3ba43981 100644 --- a/contracts/utils/cryptography/SignerERC7913.sol +++ b/contracts/utils/cryptography/SignerERC7913.sol @@ -6,10 +6,13 @@ import {AbstractSigner} from "./AbstractSigner.sol"; import {ERC7913Utils} from "./ERC7913Utils.sol"; /** - * @dev Implementation of {AbstractSigner} that supports ERC-7913 signers. + * @dev Implementation of {AbstractSigner} using + * https://eips.ethereum.org/EIPS/eip-7913[ERC-7913] signature verification. * - * For {Account} usage, an {_setSigner} function is provided to set the ERC-7913 formatted {signer}. - * Doing so it's easier for a factory, whose likely to use initializable clones of this contract. + * 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: * @@ -17,28 +20,30 @@ import {ERC7913Utils} from "./ERC7913Utils.sol"; * contract MyAccountERC7913 is Account, SignerERC7913, Initializable { * constructor() EIP712("MyAccountERC7913", "1") {} * - * function initialize(bytes memory signer) public initializer { - * _setSigner(signer); + * function initialize(bytes memory signer_) public initializer { + * _setSigner(signer_); * } * } * ``` * - * IMPORTANT: Avoiding to call {_setSigner} either during construction (if used standalone) + * 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; } - /// @inheritdoc AbstractSigner + /// @dev Verifies a signature using {ERC7913Utils.isValidSignatureNow} with {signer}, `hash` and `signature`. function _rawSignatureValidation( bytes32 hash, bytes calldata signature diff --git a/docs/modules/ROOT/pages/account-abstraction.adoc b/docs/modules/ROOT/pages/account-abstraction.adoc index 55f7337f..eddfa58c 100644 --- a/docs/modules/ROOT/pages/account-abstraction.adoc +++ b/docs/modules/ROOT/pages/account-abstraction.adoc @@ -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. @@ -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. From d470aef81d98224494402186be57eff4c2f69e60 Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 13:59:47 -0600 Subject: [PATCH 16/17] Pragma --- contracts/mocks/docs/account/MyAccountERC7913.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mocks/docs/account/MyAccountERC7913.sol b/contracts/mocks/docs/account/MyAccountERC7913.sol index 7ba8d1b1..165d77a2 100644 --- a/contracts/mocks/docs/account/MyAccountERC7913.sol +++ b/contracts/mocks/docs/account/MyAccountERC7913.sol @@ -1,7 +1,7 @@ // contracts/MyAccount.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {Account} from "../../../account/Account.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; From fa2c352a2bda5dabda38e5b7cda7e102c0dcc81f Mon Sep 17 00:00:00 2001 From: ernestognw Date: Sat, 12 Apr 2025 14:07:03 -0600 Subject: [PATCH 17/17] Update CHANGELOG --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d932f3..c4c9b73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,7 @@ -## XX-XX-XXXX +## 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. - -## 12-04-2025 - - `ERC7913Utils`: Utilities library for verifying signatures by ERC-7913 formatted signers. ## 11-04-2025