Skip to content

Commit 450cf0c

Browse files
committed
feat: add signature validation hooks
1 parent 13beea1 commit 450cf0c

File tree

11 files changed

+154
-26
lines changed

11 files changed

+154
-26
lines changed

src/account/UpgradeableModularAccount.sol

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,6 @@ contract UpgradeableModularAccount is
236236
}
237237

238238
/// @notice Initializes the account with a validation function added to the global pool.
239-
/// TODO: remove and merge with regular initialization, after we figure out a better install/uninstall workflow
240-
/// with user install configs.
241239
/// @dev This function is only callable once.
242240
function initializeWithValidation(
243241
ValidationConfig validationConfig,
@@ -301,14 +299,35 @@ contract UpgradeableModularAccount is
301299
AccountStorage storage _storage = getAccountStorage();
302300

303301
ModuleEntity sigValidation = ModuleEntity.wrap(bytes24(signature));
302+
signature = signature[24:];
304303

305-
(address module, uint32 entityId) = sigValidation.unpack();
306304
if (!_storage.validationData[sigValidation].isSignatureValidation) {
307-
revert SignatureValidationInvalid(module, entityId);
305+
(address _module, uint32 _entityId) = sigValidation.unpack();
306+
revert SignatureValidationInvalid(_module, _entityId);
307+
}
308+
309+
ModuleEntity[] memory preSignatureValidationHooks =
310+
getAccountStorage().validationData[sigValidation].preValidationHooks;
311+
312+
for (uint256 i = 0; i < preSignatureValidationHooks.length; ++i) {
313+
(address hookModule, uint32 hookEntityId) = preSignatureValidationHooks[i].unpack();
314+
315+
bytes memory currentSignatureSegment;
316+
317+
(currentSignatureSegment, signature) = signature.advanceSegmentIfAtIndex(uint8(i));
318+
319+
// If this reverts, bubble up revert reason.
320+
IValidationHookModule(hookModule).preSignatureValidationHook(
321+
hookEntityId, msg.sender, hash, currentSignatureSegment
322+
);
308323
}
309324

325+
signature = signature.getFinalSegment();
326+
327+
(address module, uint32 entityId) = sigValidation.unpack();
328+
310329
if (
311-
IValidationModule(module).validateSignature(address(this), entityId, msg.sender, hash, signature[24:])
330+
IValidationModule(module).validateSignature(address(this), entityId, msg.sender, hash, signature)
312331
== _1271_MAGIC_VALUE
313332
) {
314333
return _1271_MAGIC_VALUE;

src/interfaces/IModuleManager.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ interface IModuleManager {
3030
/// path.
3131
/// Installs a validation function across a set of execution selectors, and optionally mark it as a global
3232
/// validation.
33-
/// TODO: remove or update.
3433
/// @dev This does not validate anything against the manifest - the caller must ensure validity.
3534
/// @param validationConfig The validation function to install, along with configuration flags.
3635
/// @param selectors The selectors to install the validation function for.
@@ -46,7 +45,6 @@ interface IModuleManager {
4645
) external;
4746

4847
/// @notice Uninstall a validation function from a set of execution selectors.
49-
/// TODO: remove or update.
5048
/// @param validationFunction The validation function to uninstall.
5149
/// @param uninstallData Optional data to be decoded and used by the module to clear module data for the
5250
/// account.

src/interfaces/IValidationHookModule.sol

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,14 @@ interface IValidationHookModule is IModule {
3232
bytes calldata authorization
3333
) external;
3434

35-
// TODO: support this hook type within the account & in the manifest
36-
3735
/// @notice Run the pre signature validation hook specified by the `entityId`.
3836
/// @dev To indicate the call should revert, the function MUST revert.
3937
/// @param entityId An identifier that routes the call to different internal implementations, should there
4038
/// be more than one.
4139
/// @param sender The caller address.
4240
/// @param hash The hash of the message being signed.
4341
/// @param signature The signature of the message.
44-
// function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata
45-
// signature)
46-
// external
47-
// view
48-
// returns (bytes4);
42+
function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature)
43+
external
44+
view;
4945
}

src/modules/NativeTokenLimitModule.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ contract NativeTokenLimitModule is BaseModule, IExecutionHookModule, IValidation
116116
override
117117
{} // solhint-disable-line no-empty-blocks
118118

119+
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {
120+
return;
121+
}
122+
119123
/// @inheritdoc IModule
120124
function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) {
121125
ModuleMetadata memory metadata;

src/modules/permissionhooks/AllowlistModule.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ contract AllowlistModule is IValidationHookModule, BaseModule {
8585
return;
8686
}
8787

88+
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {
89+
return;
90+
}
91+
8892
function moduleMetadata() external pure override returns (ModuleMetadata memory) {
8993
ModuleMetadata memory metadata;
9094
metadata.name = "Allowlist Module";

test/account/PerHookData.t.sol

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,53 @@ contract PerHookDataTest is CustomValidationTestBase {
352352
);
353353
}
354354

355+
function test_pass1271AccessControl() public {
356+
string memory message = "Hello, world!";
357+
358+
bytes32 messageHash = keccak256(abi.encodePacked(message));
359+
360+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, messageHash);
361+
362+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1);
363+
preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(message)});
364+
365+
bytes4 result = account1.isValidSignature(
366+
messageHash, _encode1271Signature(_signerValidation, preValidationHookData, abi.encodePacked(r, s, v))
367+
);
368+
369+
assertEq(result, bytes4(0x1626ba7e));
370+
}
371+
372+
function test_fail1271AccessControl_badSigData() public {
373+
string memory message = "Hello, world!";
374+
375+
bytes32 messageHash = keccak256(abi.encodePacked(message));
376+
377+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, messageHash);
378+
379+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1);
380+
preValidationHookData[0] = PreValidationHookData({
381+
index: 0,
382+
validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234))
383+
});
384+
385+
vm.expectRevert("Preimage not provided");
386+
account1.isValidSignature(
387+
messageHash, _encode1271Signature(_signerValidation, preValidationHookData, abi.encodePacked(r, s, v))
388+
);
389+
}
390+
391+
function test_fail1271AccessControl_noSigData() public {
392+
string memory message = "Hello, world!";
393+
394+
bytes32 messageHash = keccak256(abi.encodePacked(message));
395+
396+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, messageHash);
397+
398+
vm.expectRevert("Preimage not provided");
399+
account1.isValidSignature(messageHash, _encode1271Signature(_signerValidation, abi.encodePacked(r, s, v)));
400+
}
401+
355402
function _getCounterUserOP() internal view returns (PackedUserOperation memory, bytes32) {
356403
PackedUserOperation memory userOp = PackedUserOperation({
357404
sender: address(account1),

test/account/UpgradeableModularAccount.t.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa
1212
import {ModuleManagerInternals} from "../../src/account/ModuleManagerInternals.sol";
1313
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
1414

15+
import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol";
16+
1517
import {ExecutionDataView, IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol";
1618
import {ExecutionManifest} from "../../src/interfaces/IExecutionModule.sol";
1719
import {IModuleManager} from "../../src/interfaces/IModuleManager.sol";
@@ -381,8 +383,10 @@ contract UpgradeableModularAccountTest is AccountTestBase {
381383

382384
// singleSignerValidationModule.ownerOf(address(account1));
383385

384-
bytes memory signature =
385-
abi.encodePacked(address(singleSignerValidationModule), TEST_DEFAULT_VALIDATION_ENTITY_ID, r, s, v);
386+
bytes memory signature = _encode1271Signature(
387+
ModuleEntityLib.pack(address(singleSignerValidationModule), TEST_DEFAULT_VALIDATION_ENTITY_ID),
388+
abi.encodePacked(r, s, v)
389+
);
386390

387391
bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature);
388392

test/mocks/modules/ComprehensiveModule.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ contract ComprehensiveModule is
104104
revert NotImplemented();
105105
}
106106

107+
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {
108+
return;
109+
}
110+
107111
function validateSignature(address, uint32 entityId, address, bytes32, bytes calldata)
108112
external
109113
pure

test/mocks/modules/MockAccessControlHookModule.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,17 @@ contract MockAccessControlHookModule is IValidationHookModule, BaseModule {
7272
revert NotImplemented();
7373
}
7474

75+
function preSignatureValidationHook(uint32, address, bytes32 hash, bytes calldata signature)
76+
external
77+
pure
78+
override
79+
{
80+
// Simulates some signature checking by requiring a preimage of the hash.
81+
82+
require(keccak256(signature) == hash, "Preimage not provided");
83+
84+
return;
85+
}
86+
7587
function moduleMetadata() external pure override returns (ModuleMetadata memory) {}
7688
}

test/mocks/modules/ValidationModuleMocks.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ abstract contract MockBaseUserOpValidationModule is
6464
revert NotImplemented();
6565
}
6666

67+
function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {
68+
return;
69+
}
70+
6771
function validateSignature(address, uint32, address, bytes32, bytes calldata)
6872
external
6973
pure

0 commit comments

Comments
 (0)