Skip to content

Commit fae0a67

Browse files
committed
Add ERC7913 signers and utilities
1 parent 29c48d9 commit fae0a67

File tree

7 files changed

+634
-0
lines changed

7 files changed

+634
-0
lines changed

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+
}

contracts/utils/README.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ Miscellaneous contracts and libraries containing utility functions you can use t
5050
* {AbstractSigner}: Abstract contract for internal signature validation in smart contracts.
5151
* {ERC7739}: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
5252
* {ERC7739Utils}: Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739.
53+
* {ERC7913Utils}: utilities library that implements ERC-7913 signature verification with fallback to ERC-1271 and ECDSA.
5354
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
5455
* {SignerERC7702}: Implementation of {AbstractSigner} that validates signatures using the contract's own address as the signer, useful for delegated accounts following EIP-7702.
56+
* {SignerERC7913}, {MultiSignerERC7913}, {MultiSignerERC7913Weighted}: Implementations of {AbstractSigner} that validate signatures based on ERC-7913. Including a simple and weighted multisignature scheme.
57+
* {ERC7913SignatureVerifierP256}, {ERC7913SignatureVerifierRSA}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys
5558

5659
[NOTE]
5760
====
@@ -100,6 +103,20 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable
100103

101104
{{SignerRSA}}
102105

106+
{{SignerERC7913}}
107+
108+
{{MultiSignerERC7913}}
109+
110+
{{MultiSignerERC7913Weighted}}
111+
112+
=== ERC-7913
113+
114+
{{ERC7913Utils}}
115+
116+
{{ERC7913SignatureVerifierP256}}
117+
118+
{{ERC7913SignatureVerifierRSA}}
119+
103120
== Security
104121

105122
{{ReentrancyGuard}}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {SignatureChecker} from "./SignatureChecker.sol";
6+
import {Bytes} from "../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 xref:api:utils#SignatureChecker[SignatureChecker]
13+
* to support signature verification for keys that do not have an Ethereum address of their own
14+
* as with ERC-1271.
15+
*
16+
* See https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
17+
*/
18+
library ERC7913Utils {
19+
using Bytes for bytes;
20+
21+
/**
22+
* @dev Verifies a signature for a given signer and hash.
23+
*
24+
* The signer is a `bytes` object that is the concatenation of an address and optionally a key:
25+
* `verifier || key`. A signer must be at least 20 bytes long.
26+
*
27+
* Verification is done as follows:
28+
* - If `signer.length < 20`: verification fails
29+
* - If `signer.length == 20`: verification is done using {SignatureChecker}
30+
* - Otherwise: verification is done using {IERC7913SignatureVerifier}
31+
*/
32+
function isValidSignatureNow(
33+
bytes memory signer,
34+
bytes32 hash,
35+
bytes memory signature
36+
) internal view returns (bool) {
37+
if (signer.length < 20) {
38+
return false;
39+
} else if (signer.length == 20) {
40+
return SignatureChecker.isValidSignatureNow(address(bytes20(signer)), hash, signature);
41+
} else {
42+
(bool success, bytes memory result) = address(bytes20(signer)).staticcall(
43+
abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature))
44+
);
45+
return (success &&
46+
result.length >= 32 &&
47+
abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector));
48+
}
49+
}
50+
51+
/**
52+
* @dev Verifies multiple `signatures` for a given hash using a set of `signers`.
53+
*
54+
* The signers must be ordered by their `keccak256` hash to ensure no duplicates and to optimize
55+
* the verification process. The function will return `false` if the signers are not properly ordered.
56+
*
57+
* Requirements:
58+
*
59+
* * The `signatures` array must be at least the `signers` array's length.
60+
*/
61+
function areValidSignaturesNow(
62+
bytes32 hash,
63+
bytes[] memory signers,
64+
bytes[] memory signatures
65+
) internal view returns (bool) {
66+
bytes32 previousId = bytes32(0);
67+
68+
uint256 signersLength = signers.length;
69+
for (uint256 i = 0; i < signersLength; i++) {
70+
bytes memory signer = signers[i];
71+
// Signers must ordered by id to ensure no duplicates
72+
bytes32 id = keccak256(signer);
73+
if (previousId >= id || !isValidSignatureNow(signer, hash, signatures[i])) {
74+
return false;
75+
}
76+
77+
previousId = id;
78+
}
79+
80+
return true;
81+
}
82+
}
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.27;
4+
5+
import {AbstractSigner} from "./AbstractSigner.sol";
6+
import {ERC7913Utils} from "./ERC7913Utils.sol";
7+
import {EnumerableSetExtended} from "../structs/EnumerableSetExtended.sol";
8+
import {Calldata} from "../../utils/Calldata.sol";
9+
import {SafeCast} from "../../utils/math/SafeCast.sol";
10+
11+
/**
12+
* @dev Implementation of {AbstractSigner} using multiple ERC-7913 signers with a threshold-based
13+
* signature verification system.
14+
*
15+
* This contract allows managing a set of authorized signers and requires a minimum number of
16+
* signatures (threshold) to approve operations. It uses ERC-7913 formatted signers, which
17+
* concatenate a verifier address and a key: `verifier || key`.
18+
*
19+
* Example of usage:
20+
*
21+
* ```solidity
22+
* contract MyMultiSignerAccount is Account, MultiSignerERC7913, Initializable {
23+
* constructor() EIP712("MyMultiSignerAccount", "1") {}
24+
*
25+
* function initialize(bytes[] memory signers, uint256 threshold) public initializer {
26+
* _addSigners(signers);
27+
* _setThreshold(threshold);
28+
* }
29+
*
30+
* function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
31+
* _addSigners(signers);
32+
* }
33+
*
34+
* function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
35+
* _removeSigners(signers);
36+
* }
37+
*
38+
* function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
39+
* _setThreshold(threshold);
40+
* }
41+
* }
42+
* ```
43+
*
44+
* IMPORTANT: Failing to properly initialize the signers and threshold either during construction
45+
* (if used standalone) or during initialization (if used as a clone) may leave the contract
46+
* either front-runnable or unusable.
47+
*/
48+
abstract contract MultiSignerERC7913 is AbstractSigner {
49+
using EnumerableSetExtended for EnumerableSetExtended.BytesSet;
50+
using ERC7913Utils for *;
51+
using SafeCast for uint256;
52+
53+
EnumerableSetExtended.BytesSet private _signersSet;
54+
uint128 private _threshold;
55+
56+
/// @dev Emitted when signers are added.
57+
event ERC7913SignersAdded(bytes[] indexed signers);
58+
59+
/// @dev Emitted when signers are removed.
60+
event ERC7913SignersRemoved(bytes[] indexed signers);
61+
62+
/// @dev Emitted when the threshold is updated.
63+
event ERC7913ThresholdSet(uint256 threshold);
64+
65+
/// @dev The `signer` already exists.
66+
error MultiSignerERC7913AlreadyExists(bytes signer);
67+
68+
/// @dev The `signer` does not exist.
69+
error MultiSignerERC7913NonexistentSigner(bytes signer);
70+
71+
/// @dev The `signer` is less than 20 bytes long.
72+
error MultiSignerERC7913InvalidSigner(bytes signer);
73+
74+
/// @dev The `threshold` is unreachable given the number of `signers`.
75+
error MultiSignerERC7913UnreachableThreshold(uint256 signers, uint256 threshold);
76+
77+
/**
78+
* @dev Returns the set of authorized signers. Prefer {_signers} for internal use.
79+
*
80+
* WARNING: This operation copies the entire signers set to memory, which can be expensive. This is designed
81+
* for view accessors queried without gas fees. Using it in state-changing functions may become uncallable
82+
* if the signers set grows too large.
83+
*/
84+
function signers() public view virtual returns (bytes[] memory) {
85+
return _signers().values();
86+
}
87+
88+
/// @dev Returns whether the `signer` is an authorized signer.
89+
function isSigner(bytes memory signer) public view virtual returns (bool) {
90+
return _signers().contains(signer);
91+
}
92+
93+
/// @dev Returns the minimum number of signers required to approve a multisignature operation.
94+
function threshold() public view virtual returns (uint256) {
95+
return _threshold;
96+
}
97+
98+
/// @dev Returns the set of authorized signers.
99+
function _signers() internal view virtual returns (EnumerableSetExtended.BytesSet storage) {
100+
return _signersSet;
101+
}
102+
103+
/**
104+
* @dev Adds the `newSigners` to those allowed to sign on behalf of this contract.
105+
* Internal version without access control.
106+
*
107+
* Requirements:
108+
*
109+
* * Each of `newSigners` must be at least 20 bytes long. Reverts with {MultiSignerERC7913InvalidSigner} if not.
110+
* * Each of `newSigners` must not be authorized. See {isSigner}. Reverts with {MultiSignerERC7913AlreadyExists} if so.
111+
*/
112+
function _addSigners(bytes[] memory newSigners) internal virtual {
113+
uint256 newSignersLength = newSigners.length;
114+
for (uint256 i = 0; i < newSignersLength; i++) {
115+
bytes memory signer = newSigners[i];
116+
require(signer.length >= 20, MultiSignerERC7913InvalidSigner(signer));
117+
require(_signers().add(signer), MultiSignerERC7913AlreadyExists(signer));
118+
}
119+
emit ERC7913SignersAdded(newSigners);
120+
}
121+
122+
/**
123+
* @dev Removes the `oldSigners` from the authorized signers. Internal version without access control.
124+
*
125+
* Requirements:
126+
*
127+
* * Each of `oldSigners` must be authorized. See {isSigner}. Otherwise {MultiSignerERC7913NonexistentSigner} is thrown.
128+
* * See {_validateReachableThreshold} for the threshold validation.
129+
*/
130+
function _removeSigners(bytes[] memory oldSigners) internal virtual {
131+
uint256 oldSignersLength = oldSigners.length;
132+
for (uint256 i = 0; i < oldSignersLength; i++) {
133+
bytes memory signer = oldSigners[i];
134+
require(_signers().remove(signer), MultiSignerERC7913NonexistentSigner(signer));
135+
}
136+
_validateReachableThreshold();
137+
emit ERC7913SignersRemoved(oldSigners);
138+
}
139+
140+
/**
141+
* @dev Sets the signatures `threshold` required to approve a multisignature operation.
142+
* Internal version without access control.
143+
*
144+
* Requirements:
145+
*
146+
* * See {_validateReachableThreshold} for the threshold validation.
147+
*/
148+
function _setThreshold(uint256 newThreshold) internal virtual {
149+
_threshold = newThreshold.toUint128();
150+
_validateReachableThreshold();
151+
emit ERC7913ThresholdSet(newThreshold);
152+
}
153+
154+
/**
155+
* @dev Validates the current threshold is reachable.
156+
*
157+
* Requirements:
158+
*
159+
* * The {signers}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
160+
*/
161+
function _validateReachableThreshold() internal view virtual {
162+
uint256 totalSigners = _signers().length();
163+
uint256 currentThreshold = threshold();
164+
require(
165+
totalSigners >= currentThreshold,
166+
MultiSignerERC7913UnreachableThreshold(totalSigners, currentThreshold)
167+
);
168+
}
169+
170+
/**
171+
* @dev Decodes, validates the signature and checks the signers are authorized.
172+
* See {_validateSignatures} and {_validateThreshold} for more details.
173+
*
174+
* Example of signature encoding:
175+
*
176+
* ```solidity
177+
* // Encode signers (verifier || key)
178+
* bytes memory signer1 = abi.encodePacked(verifier1, key1);
179+
* bytes memory signer2 = abi.encodePacked(verifier2, key2);
180+
*
181+
* // Order signers by their id
182+
* if (keccak256(signer1) > keccak256(signer2)) {
183+
* (signer1, signer2) = (signer2, signer1);
184+
* (signature1, signature2) = (signature2, signature1);
185+
* }
186+
*
187+
* // Assign ordered signers and signatures
188+
* bytes[] memory signers = new bytes[](2);
189+
* bytes[] memory signatures = new bytes[](2);
190+
* signers[0] = signer1;
191+
* signatures[0] = signature1;
192+
* signers[1] = signer2;
193+
* signatures[1] = signature2;
194+
*
195+
* // Encode the multi signature
196+
* bytes memory signature = abi.encode(signers, signatures);
197+
* ```
198+
*
199+
* Requirements:
200+
*
201+
* * The `signature` must be encoded as `abi.encode(signers, signatures)`.
202+
*/
203+
function _rawSignatureValidation(
204+
bytes32 hash,
205+
bytes calldata signature
206+
) internal view virtual override returns (bool) {
207+
if (signature.length == 0) return false; // For ERC-7739 compatibility
208+
(bytes[] memory signingSigners, bytes[] memory signatures) = abi.decode(signature, (bytes[], bytes[]));
209+
if (signingSigners.length != signatures.length) return false;
210+
return _validateThreshold(signingSigners) && _validateSignatures(hash, signingSigners, signatures);
211+
}
212+
213+
/**
214+
* @dev Validates the signatures using the signers and their corresponding signatures.
215+
* Returns whether whether the signers are authorized and the signatures are valid for the given hash.
216+
*
217+
* IMPORTANT: For simplicity, this contract assumes that the signers are ordered by their `keccak256` hash
218+
* to avoid duplication when iterating through the signers (i.e. `keccak256(signer1) < keccak256(signer2)`).
219+
* The function will return false if the signers are not ordered.
220+
*
221+
* Requirements:
222+
*
223+
* * The `signatures` arrays must be at least as large as the `signingSigners` arrays. Panics otherwise.
224+
*/
225+
function _validateSignatures(
226+
bytes32 hash,
227+
bytes[] memory signingSigners,
228+
bytes[] memory signatures
229+
) internal view virtual returns (bool valid) {
230+
uint256 signersLength = signingSigners.length;
231+
for (uint256 i = 0; i < signersLength; i++) {
232+
if (!isSigner(signingSigners[i])) {
233+
return false;
234+
}
235+
}
236+
return hash.areValidSignaturesNow(signingSigners, signatures);
237+
}
238+
239+
/**
240+
* @dev Validates that the number of signers meets the {threshold} requirement.
241+
* Assumes the signers were already validated. See {_validateSignatures} for more details.
242+
*/
243+
function _validateThreshold(bytes[] memory validatingSigners) internal view virtual returns (bool) {
244+
return validatingSigners.length >= threshold();
245+
}
246+
}

0 commit comments

Comments
 (0)