1
1
// SPDX-License-Identifier: MIT
2
2
pragma solidity >= 0.8.23 <= 0.8.29 ;
3
3
4
+ // Rhinestone module-kit
4
5
import { ERC7579ValidatorBase } from "modulekit/Modules.sol " ;
5
- import { VALIDATION_SUCCESS } from "modulekit/accounts/common/ interfaces/IERC7579Module .sol " ;
6
+ import { IStatelessValidator } from "modulekit/module-bases/ interfaces/IStatelessValidator .sol " ;
6
7
import { PackedUserOperation } from "modulekit/external/ERC4337.sol " ;
7
- import { LibSort, LibBytes } from "solady/Milady.sol " ;
8
+
9
+ import { LibSort } from "solady/Milady.sol " ;
8
10
9
11
import { ISemaphore, ISemaphoreGroups } from "./utils/Semaphore.sol " ;
10
12
import { ValidatorLibBytes } from "./utils/ValidatorLibBytes.sol " ;
11
13
import { Identity } from "./utils/Identity.sol " ;
12
14
// import { console } from "forge-std/console.sol";
13
15
14
- contract SemaphoreMSAValidator is ERC7579ValidatorBase {
16
+ contract SemaphoreMSAValidator is ERC7579ValidatorBase , IStatelessValidator {
15
17
using LibSort for * ;
16
18
using ValidatorLibBytes for bytes ;
17
19
18
20
// Constants
19
21
uint8 public constant MAX_MEMBERS = 32 ;
20
- uint8 internal constant CMT_BYTELEN = 32 ;
22
+ uint8 public constant CMT_BYTELEN = 32 ;
21
23
22
24
// Ensure the following match with the 3 function calls.
23
- bytes4 [3 ] internal ALLOWED_SELECTORS =
25
+ bytes4 [3 ] public ALLOWED_SELECTORS =
24
26
[this .initiateTx.selector , this .signTx.selector , this .executeTx.selector ];
25
27
26
28
struct ExtCallCount {
@@ -45,7 +47,6 @@ contract SemaphoreMSAValidator is ERC7579ValidatorBase {
45
47
error InvalidSignatureLen (address account , uint256 len );
46
48
error InvalidSignature (address account , bytes signature );
47
49
error InvalidSemaphoreProof (bytes reason );
48
- error NonAllowedSelector (address account , bytes4 funcSel );
49
50
error NonValidatorCallBanned (address targetAddr , address selfAddr );
50
51
error InitiateTxWithNullAddress (address account );
51
52
error InitiateTxWithNullCallDataAndNullValue (address account , address targetAddr );
@@ -230,7 +231,7 @@ contract SemaphoreMSAValidator is ERC7579ValidatorBase {
230
231
// 1. targetAddr cannot be 0
231
232
// 2. if txCallData is blank, then msg.value must be > 0, else revert
232
233
if (targetAddr == address (0 )) revert InitiateTxWithNullAddress (account);
233
- if (LibBytes. cmp ( txCallData, "" ) == 0 && msg .value == 0 ) {
234
+ if (txCallData. length == 0 && msg .value == 0 ) {
234
235
revert InitiateTxWithNullCallDataAndNullValue (account, targetAddr);
235
236
}
236
237
@@ -322,80 +323,127 @@ contract SemaphoreMSAValidator is ERC7579ValidatorBase {
322
323
bytes32 userOpHash
323
324
)
324
325
external
325
- // view
326
+ virtual
326
327
override
327
328
returns (ValidationData)
328
329
{
329
- // you want to exclude initiateTx, signTx, executeTx from needing tx count.
330
- // you just need to ensure they are a valid proof from the semaphore group members
331
330
address account = userOp.sender;
332
- uint256 groupId = groupMapping[account];
333
-
334
- // The userOp.signature is 160 bytes containing:
335
- // (uint256 pubX (32 bytes), uint256 pubY (32 bytes), bytes[96] signature (96 bytes))
336
- if (userOp.signature.length != 160 ) {
337
- revert InvalidSignatureLen (account, userOp.signature.length );
331
+ bytes calldata targetCallData = userOp.callData[100 :];
332
+ if (_validateSignatureWithConfig (account, userOpHash, userOp.signature, targetCallData)) {
333
+ return VALIDATION_SUCCESS;
338
334
}
339
-
340
- // Verify signature using the public key
341
- if (! Identity.verifySignature (userOpHash, userOp.signature)) {
342
- revert InvalidSignature (account, userOp.signature);
343
- }
344
-
345
- // Verify if the identity commitment is one of the semaphore group members
346
- bytes memory pubKey = LibBytes.slice (userOp.signature, 0 , 66 );
347
- uint256 cmt = Identity.getCommitment (pubKey);
348
- if (! groups.hasMember (groupId, cmt)) revert MemberNotExists (account, cmt);
349
-
350
- // We don't allow call to other contracts.
351
- address targetAddr = address (bytes20 (userOp.callData[100 :120 ]));
352
- if (targetAddr != address (this )) revert NonValidatorCallBanned (targetAddr, address (this ));
353
-
354
- // For callData, the first 120 bytes are reserved by ERC-7579 use. Then 32 bytes of value,
355
- // then the remaining as the callData passed in getExecOps
356
- bytes memory valAndCallData = userOp.callData[120 :];
357
- bytes4 funcSel = bytes4 (LibBytes.slice (valAndCallData, 32 , 36 ));
358
-
359
- // We only allow calls to `initiateTx()`, `signTx()`, and `executeTx()` to pass,
360
- // and reject the rest.
361
- if (_isAllowedSelector (funcSel)) return VALIDATION_SUCCESS;
362
- revert NonAllowedSelector (account, funcSel);
335
+ return VALIDATION_FAILED;
363
336
}
364
337
338
+ /**
339
+ * Validates an ERC-1271 signature with the sender
340
+ *
341
+ * @param hash bytes32 hash of the data
342
+ * @param data bytes data containing the signatures, and target calldata
343
+ *
344
+ * @return bytes4 EIP1271_SUCCESS if the signature is valid, EIP1271_FAILED otherwise
345
+ */
365
346
function isValidSignatureWithSender (
366
347
address sender ,
367
348
bytes32 hash ,
368
- bytes calldata signature
349
+ bytes calldata data
369
350
)
370
351
external
371
352
view
372
353
virtual
373
354
override
374
- returns (bytes4 sugValidationResult )
355
+ returns (bytes4 )
375
356
{
376
- return EIP1271_SUCCESS;
357
+ bytes calldata signature = data[0 :160 ];
358
+ bytes calldata targetCallData = data[160 :];
359
+ if (_validateSignatureWithConfig (sender, hash, signature, targetCallData)) {
360
+ return EIP1271_SUCCESS;
361
+ }
362
+ return EIP1271_FAILED;
377
363
}
378
364
365
+ /**
366
+ * Validates a signature given some data
367
+ * For [ERC-7780](https://eips.ethereum.org/EIPS/eip-7780) Stateless Validator
368
+ *
369
+ * @param hash The data that was signed over
370
+ * @param signature The signature to verify
371
+ * @param data The data to validate the verified signature agains
372
+ *
373
+ * MUST validate that the signature is a valid signature of the hash
374
+ * MUST compare the validated signature against the data provided
375
+ * MUST return true if the signature is valid and false otherwise
376
+ */
379
377
function validateSignatureWithData (
380
- bytes32 ,
381
- bytes calldata ,
382
- bytes calldata
378
+ bytes32 hash ,
379
+ bytes calldata signature ,
380
+ bytes calldata data
383
381
)
384
382
external
385
383
view
386
384
virtual
387
- returns (bool validSig )
385
+ returns (bool )
388
386
{
389
- return true ;
387
+ address account = address (bytes20 (data[0 :20 ]));
388
+ bytes calldata targetCallData = data[20 :];
389
+ return _validateSignatureWithConfig (account, hash, signature, targetCallData);
390
390
}
391
391
392
+ /*//////////////////////////////////////////////////////////////////////////
393
+ INTERNAL FUNCTIONS
394
+ //////////////////////////////////////////////////////////////////////////*/
395
+
392
396
function _isAllowedSelector (bytes4 sel ) internal view returns (bool allowed ) {
393
397
for (uint256 i = 0 ; i < ALLOWED_SELECTORS.length ; ++ i) {
394
398
if (sel == ALLOWED_SELECTORS[i]) return true ;
395
399
}
396
400
return false ;
397
401
}
398
402
403
+ function _validateSignatureWithConfig (
404
+ address account ,
405
+ bytes32 hash ,
406
+ bytes calldata signature ,
407
+ bytes calldata targetCallData
408
+ )
409
+ internal
410
+ view
411
+ returns (bool )
412
+ {
413
+ // you want to exclude initiateTx, signTx, executeTx from needing tx count.
414
+ // you just need to ensure they are a valid proof from the semaphore group members
415
+ uint256 groupId = groupMapping[account];
416
+
417
+ // The userOp.signature is 160 bytes containing:
418
+ // (uint256 pubX (32 bytes), uint256 pubY (32 bytes), bytes[96] signature (96 bytes))
419
+ if (signature.length != 160 ) {
420
+ revert InvalidSignatureLen (account, signature.length );
421
+ }
422
+
423
+ // Verify signature using the public key
424
+ if (! Identity.verifySignature (hash, signature)) {
425
+ revert InvalidSignature (account, signature);
426
+ }
427
+
428
+ // Verify if the identity commitment is one of the semaphore group members
429
+ bytes memory pubKey = signature[0 :64 ];
430
+ uint256 cmt = Identity.getCommitment (pubKey);
431
+ if (! groups.hasMember (groupId, cmt)) revert MemberNotExists (account, cmt);
432
+
433
+ // We don't allow call to other contracts.
434
+ address targetAddr = address (bytes20 (targetCallData[0 :20 ]));
435
+ if (targetAddr != address (this )) revert NonValidatorCallBanned (targetAddr, address (this ));
436
+
437
+ // For callData, the first 120 bytes are reserved by ERC-7579 use. Then 32 bytes of value,
438
+ // then the remaining as the callData passed in getExecOps
439
+ bytes calldata valAndCallData = targetCallData[20 :];
440
+ bytes4 funcSel = bytes4 (valAndCallData[32 :36 ]);
441
+
442
+ // We only allow calls to `initiateTx()`, `signTx()`, and `executeTx()` to pass,
443
+ // and reject the rest.
444
+ return _isAllowedSelector (funcSel);
445
+ }
446
+
399
447
/*//////////////////////////////////////////////////////////////////////////
400
448
METADATA
401
449
//////////////////////////////////////////////////////////////////////////*/
@@ -426,6 +474,6 @@ contract SemaphoreMSAValidator is ERC7579ValidatorBase {
426
474
* @return true if the module is of the given type, false otherwise
427
475
*/
428
476
function isModuleType (uint256 typeID ) external pure override returns (bool ) {
429
- return typeID == TYPE_VALIDATOR;
477
+ return typeID == TYPE_VALIDATOR || typeID == TYPE_STATELESS_VALIDATOR ;
430
478
}
431
479
}
0 commit comments