-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSemaphoreValidator.sol
More file actions
257 lines (223 loc) · 8.6 KB
/
Copy pathSemaphoreValidator.sol
File metadata and controls
257 lines (223 loc) · 8.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23 <=0.8.29;
// import { console } from "forge-std/Test.sol";
// Rhinestone module-kit
import { ERC7579ValidatorBase } from "modulekit/Modules.sol";
import { PackedUserOperation } from "modulekit/ModuleKit.sol";
import { LibBytes } from "solady/Milady.sol";
import { ISemaphoreExecutor } from "src/interfaces/ISemaphoreExecutor.sol";
import { Identity } from "src/utils/Identity.sol";
import {
SIGNATURE_LEN,
MIN_TARGET_CALLDATA_LEN,
SEMAPHORE_EXECUTOR,
SEMAPHORE_VALIDATOR,
MOCK_SIG_P2,
VERSION
} from "src/utils/Constants.sol";
contract SemaphoreValidator is ERC7579ValidatorBase {
/**
* Errors
*/
error InvalidTargetAddress(address target);
error InvalidSignature(address account, bytes signature);
error InvalidTargetCallData(address account, bytes callData);
error MemberNotExists(address account, bytes pubKey);
error NoSemaphoreModuleInstalled(address account);
error NotValidSemaphoreExecutor(address target);
error SemaphoreExecutorNotInitialized(address account);
/**
* Events
*/
event SemaphoreValidatorInitialized(address indexed account);
event SemaphoreValidatorUninitialized(address indexed account);
/**
* Storage
*/
ISemaphoreExecutor public immutable semaphoreExecutor;
mapping(address account => bool installed) public acctInstalled;
// Ensure the following match with the 3 function calls.
bytes4 public constant INITIATETX_SEL = ISemaphoreExecutor.initiateTx.selector;
bytes4 public constant SIGNTX_SEL = ISemaphoreExecutor.signTx.selector;
bytes4 public constant EXECUTETX_SEL = ISemaphoreExecutor.executeTx.selector;
constructor(ISemaphoreExecutor _semaphoreExecutor) {
if (
!LibBytes.eq(bytes(_semaphoreExecutor.name()), bytes(SEMAPHORE_EXECUTOR))
|| !_semaphoreExecutor.isModuleType(TYPE_EXECUTOR)
) {
revert NotValidSemaphoreExecutor(address(_semaphoreExecutor));
}
semaphoreExecutor = _semaphoreExecutor;
}
/**
* Config
*/
function isInitialized(address account) external view override returns (bool) {
return acctInstalled[account] && semaphoreExecutor.isInitialized(account);
}
function onInstall(bytes calldata) external override {
address account = msg.sender;
if (!semaphoreExecutor.isInitialized(account)) {
revert SemaphoreExecutorNotInitialized(account);
}
if (acctInstalled[account]) revert ModuleAlreadyInitialized(account);
acctInstalled[account] = true;
emit SemaphoreValidatorInitialized(account);
}
function onUninstall(bytes calldata) external override {
// remove from our data structure
address account = msg.sender;
if (!acctInstalled[account]) revert NotInitialized(account);
delete acctInstalled[account];
emit SemaphoreValidatorUninitialized(account);
}
/**
* Module logics
*/
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
)
public
virtual
override
returns (ValidationData)
{
address account = userOp.sender;
// For callData, the first 100 bytes are reserved by ERC-7579 use. Then 32 bytes of value,
bytes calldata targetCallData = userOp.callData[100:];
if (_validateSignatureWithConfig(account, userOpHash, userOp.signature, targetCallData)) {
return VALIDATION_SUCCESS;
}
return VALIDATION_FAILED;
}
/**
* Validates an ERC-1271 signature with the sender
*
* @param hash bytes32 hash of the data
* @param data bytes data containing the signatures, and target calldata
*
* @return bytes4 EIP1271_SUCCESS if the signature is valid, EIP1271_FAILED otherwise
*/
function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata data
)
external
view
virtual
override
returns (bytes4)
{
if (data.length < SIGNATURE_LEN) return EIP1271_FAILED;
bytes calldata signature = data[0:SIGNATURE_LEN];
bytes calldata targetCallData = data[SIGNATURE_LEN:];
if (_validateSignatureWithConfig(sender, hash, signature, targetCallData)) {
return EIP1271_SUCCESS;
}
return EIP1271_FAILED;
}
/**
* Validates a signature given some data
* For [ERC-7780](https://eips.ethereum.org/EIPS/eip-7780) Stateless Validator
*
* @param hash The data that was signed over
* @param signature The signature to verify
* @param data The data to validate the verified signature agains
*
* MUST validate that the signature is a valid signature of the hash
* MUST compare the validated signature against the data provided
* MUST return true if the signature is valid and false otherwise
*/
function validateSignatureWithData(
bytes32 hash,
bytes calldata signature,
bytes calldata data
)
external
view
virtual
returns (bool)
{
address account = address(bytes20(data[0:20]));
bytes calldata targetCallData = data[20:];
return _validateSignatureWithConfig(account, hash, signature, targetCallData);
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
function _validateSignatureWithConfig(
address account,
bytes32 hash,
bytes calldata signature,
bytes calldata targetCallData
)
internal
view
returns (bool)
{
// you want to exclude initiateTx, signTx, executeTx from needing tx count.
// you just need to ensure they are a valid proof from the semaphore group members
(bool found,) = semaphoreExecutor.getGroupId(account);
if (!found) revert NoSemaphoreModuleInstalled(account);
// The userOp.signature is 160 bytes containing:
// (uint256 pubX (32 bytes), uint256 pubY (32 bytes), bytes[96] signature (96 bytes))
if (signature.length != SIGNATURE_LEN) revert InvalidSignature(account, signature);
if (!_isMockSignature(signature) && !Identity.verifySignature(hash, signature)) {
revert InvalidSignature(account, signature);
}
// Verify if the identity commitment is one of the semaphore group members
bytes memory pubKey = signature[0:64];
uint256 cmt = Identity.getCommitment(pubKey);
if (!semaphoreExecutor.accountHasMember(account, cmt)) {
revert MemberNotExists(account, pubKey);
}
if (targetCallData.length < MIN_TARGET_CALLDATA_LEN) {
revert InvalidTargetCallData(account, targetCallData);
}
// We don't allow call to other contracts, other than msa-validator and msa-executor
// quick hack here
address target = address(bytes20(targetCallData[0:20]));
bytes4 funcSel = bytes4(targetCallData[52:56]);
if (target != address(semaphoreExecutor)) revert InvalidTargetAddress(target);
// We only allow calls to `initiateTx()`, `signTx()`, and `executeTx()` to pass,
// and reject the rest.
return _isAllowedSelector(funcSel);
}
function _isAllowedSelector(bytes4 sel) internal pure returns (bool) {
return sel == INITIATETX_SEL || sel == SIGNTX_SEL || sel == EXECUTETX_SEL;
}
function _isMockSignature(bytes calldata signature) internal pure returns (bool) {
return LibBytes.eq(signature[64:], MOCK_SIG_P2);
}
/*//////////////////////////////////////////////////////////////////////////
METADATA
//////////////////////////////////////////////////////////////////////////*/
/**
* The name of the module
*
* @return name The name of the module
*/
function name() external pure returns (string memory) {
return SEMAPHORE_VALIDATOR;
}
/**
* The version of the module
*
* @return version The version of the module
*/
function version() external pure returns (string memory) {
return VERSION;
}
/**
* Check if the module is of a certain type
*
* @param typeID The type ID to check
*
* @return true if the module is of the given type, false otherwise
*/
function isModuleType(uint256 typeID) external pure override returns (bool) {
return typeID == TYPE_VALIDATOR;
}
}