@@ -10,29 +10,55 @@ import "../../Wallet.sol";
1010abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
1111 bytes32 public immutable INIT_CODE_HASH;
1212 address public immutable FACTORY;
13+ address public immutable IMMUTABLE_SIGNER_CONTRACT;
1314
14- constructor (address _factory , address _startupWalletImpl ) {
15+ constructor (address _factory , address _startupWalletImpl , address _immutableSignerContract ) {
1516 // Build init code hash of the deployed wallets using that module
1617 bytes32 initCodeHash = keccak256 (abi.encodePacked (Wallet.creationCode, uint256 (uint160 (_startupWalletImpl))));
1718
1819 INIT_CODE_HASH = initCodeHash;
1920 FACTORY = _factory;
21+ IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
22+ }
23+
24+ /// @notice Calculate imageHash for Immutable-only signer
25+ /// @dev Uses the iterative hash format matching ModuleAuth._signatureValidationWithUpdateCheck
26+ /// For a single signer with threshold=1 and weight=1:
27+ /// imageHash = keccak256(abi.encode(bytes32(threshold), weight, signerAddress))
28+ /// Uses the IMMUTABLE_SIGNER_CONTRACT address directly as the signer
29+ function imageHashOfImmutableSigner () internal view returns (bytes32 ) {
30+ // Use the signer contract address directly (threshold=1, weight=1)
31+ bytes32 imageHash = bytes32 (uint256 (1 )); // Start with threshold
32+ imageHash = keccak256 (abi.encode (imageHash, uint256 (1 ), IMMUTABLE_SIGNER_CONTRACT)); // Apply weight=1, contract address
33+ return imageHash;
2034 }
2135
2236 /**
23- * @notice Validates the signature image with the salt used to deploy the contract
24- * if there is no stored image hash. This will happen prior to the first meta
25- * transaction. Subsequently, validate the
26- * signature image with a valid image hash defined in the contract storage
27- * @param _imageHash Hash image of signature
28- * @return true if the signature image is valid, and true if the image hash needs to be updated
37+ * @notice Validates the given signature image hash against the known valid patterns,
38+ * supporting both normal and bootstrap/upgrade paths:
39+ * - If there is no stored image hash (first transaction after deployment),
40+ * allows authentication in two ways:
41+ * 1. If the image hash was used as the salt for counterfactual wallet deployment,
42+ * the image is valid and should now be stored.
43+ * 2. Alternatively, if the image hash matches the hash derived from the
44+ * Immutable Signer contract, the image is also valid and should be stored.
45+ * - In all these initial cases, the return value requests that the image hash
46+ * is recorded for future use (second return value is true).
47+ * - If a stored image hash exists, only that exact image hash is considered valid.
48+ * In this case, there is no need to update the stored image hash
49+ * (second return value is false).
50+ * @param _imageHash Hash image of the signature
51+ * @return (bool, bool) First value true if the image hash is valid,
52+ * second value true if the image hash needs to be stored/updated.
2953 */
3054 function _isValidImage (bytes32 _imageHash ) internal view override returns (bool , bool ) {
55+ // Standard validation: Check if CFA matches (for normal deployment)
3156 bytes32 storedImageHash = ModuleStorage.readBytes32 (ImageHashKey.IMAGE_HASH_KEY);
57+
3258 if (storedImageHash == 0 ) {
3359 // No image hash stored. Check that the image hash was used as the salt when
3460 // deploying the wallet proxy contract.
35- bool authenticated = address (
61+ address computedAddress = address (
3662 uint160 (uint256 (
3763 keccak256 (
3864 abi.encodePacked (
@@ -43,15 +69,31 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
4369 )
4470 )
4571 ))
46- ) == address (this );
72+ );
73+
74+ bool authenticated = computedAddress == address (this );
75+
4776 // Indicate need to update = true. This will trigger a call to store the image hash
48- return (authenticated, true );
49- }
77+ if (authenticated) {
78+ return (true , true );
79+ } else {
80+ // BOOTSTRAP MODE: Check if signed by Immutable signer only
81+ // This allows deploying a wallet with a different salt (from another chain)
82+ // and using Immutable-only signature to authorize the first transaction
83+ bytes32 immutableImageHash = imageHashOfImmutableSigner ();
84+
85+ if (_imageHash == immutableImageHash) {
86+ return (true , true ); // Bootstrap with immutable signer
87+ }
88+ }
5089
51- // Image hash has been stored.
52- return ((_imageHash != bytes32 (0 ) && _imageHash == storedImageHash), false );
90+ return (false , false ); // Invalid signature
91+ }
92+
93+ // Image hash has been stored. Compare it with the provided image hash
94+ bool isValid = _imageHash != bytes32 (0 ) && _imageHash == storedImageHash;
95+
96+ // Return the result of the comparison. No need to update the image hash.
97+ return (isValid, false );
5398 }
54- }
55-
56-
57-
99+ }
0 commit comments