@@ -18,7 +18,7 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
1818 uint256 rindex;
1919 bytes32 imageHash;
2020 uint256 totalWeight;
21- bool immutableSignerContractFound ;
21+ bool immutableSignerContractSigned ;
2222 }
2323
2424 bytes32 public immutable INIT_CODE_HASH;
@@ -43,13 +43,17 @@ constructor(address _factory, address _startupWalletImpl, address _immutableSign
4343 * @return needsUpdate True if the image hash needs to be stored (first transaction)
4444 * @return imageHash The computed image hash from the signature
4545 *
46- * @dev This function parses the signature, recovers/reads signer addresses, and validates them.
47- * For defensive validation, each extracted address is compared against IMMUTABLE_SIGNER_CONTRACT.
48- * If a match is found, it is recorded .
46+ * @dev This function parses the signature, recovers signer addresses from verified signatures , and validates them.
47+ * Only FLAG_SIGNATURE and FLAG_DYNAMIC_SIGNATURE are supported - FLAG_ADDRESS is intentionally not
48+ * supported to prevent attackers from including addresses without providing valid signatures .
4949 *
50- * Special case: If this is the first transaction (nonce was 0, now 1 after increment) and the immutable
51- * signer contract is one of the signers, the signature is automatically validated and approved without
52- * checking the stored image hash. This allows the immutable signer to bootstrap the wallet on first use.
50+ * For each verified signature, the extracted address is compared against IMMUTABLE_SIGNER_CONTRACT.
51+ * If a match is found after signature verification, it is recorded.
52+ *
53+ * Special case: If this is the first transaction (nonce was 0, now 1 after increment), the immutable
54+ * signer contract has provided a valid signature, AND the weight threshold is met, the signature is
55+ * automatically validated and approved without checking the stored image hash. This allows the immutable
56+ * signer to bootstrap the wallet on first use while preventing unauthorized bootstrap attacks.
5357 */
5458 function _signatureValidationWithUpdateCheck (
5559 bytes32 _hash ,
@@ -66,7 +70,7 @@ constructor(address _factory, address _startupWalletImpl, address _immutableSign
6670 rindex: rindex,
6771 imageHash: bytes32 (uint256 (threshold)),
6872 totalWeight: 0 ,
69- immutableSignerContractFound : false
73+ immutableSignerContractSigned : false
7074 });
7175
7276 // Iterate until the image is completed
@@ -75,17 +79,22 @@ constructor(address _factory, address _startupWalletImpl, address _immutableSign
7579 uint256 flag; uint256 addrWeight; address addr;
7680 (flag, addrWeight, state.rindex) = _signature.readUint8Uint8 (state.rindex);
7781
78- if (flag == FLAG_ADDRESS) {
79- // Read plain address
80- (addr, state.rindex) = _signature. readAddress (state.rindex);
81- } else if (flag == FLAG_SIGNATURE) {
82+ // Note: FLAG_ADDRESS is intentionally not supported in this module to prevent
83+ // attackers from including the immutable signer address without providing a valid signature.
84+ // Only FLAG_SIGNATURE and FLAG_DYNAMIC_SIGNATURE are allowed.
85+ if (flag == FLAG_SIGNATURE) {
8286 // Read single signature and recover signer
8387 bytes memory signature;
8488 (signature, state.rindex) = _signature.readBytes66 (state.rindex);
8589 addr = recoverSigner (_hash, signature);
8690
87- // Acumulate total weight of the signature
91+ // Accumulate total weight of the signature
8892 state.totalWeight += addrWeight;
93+
94+ // Check if this signer is the immutable signer contract (only after signature verification)
95+ if (addr == IMMUTABLE_SIGNER_CONTRACT) {
96+ state.immutableSignerContractSigned = true ;
97+ }
8998 } else if (flag == FLAG_DYNAMIC_SIGNATURE) {
9099 // Read signer
91100 (addr, state.rindex) = _signature.readAddress (state.rindex);
@@ -99,25 +108,27 @@ constructor(address _factory, address _startupWalletImpl, address _immutableSign
99108 (signature, state.rindex) = _signature.readBytes (state.rindex, size);
100109 require (isValidSignature (_hash, addr, signature), "ModuleAuthDynamic#_signatureValidation: INVALID_SIGNATURE " );
101110
102- // Acumulate total weight of the signature
111+ // Accumulate total weight of the signature
103112 state.totalWeight += addrWeight;
113+
114+ // Check if this signer is the immutable signer contract (only after signature verification)
115+ if (addr == IMMUTABLE_SIGNER_CONTRACT) {
116+ state.immutableSignerContractSigned = true ;
117+ }
104118 } else {
105119 revert ("ModuleAuthDynamic#_signatureValidation INVALID_FLAG " );
106120 }
107121
108- // Check if this signer is the immutable signer contract
109- if (addr == IMMUTABLE_SIGNER_CONTRACT) {
110- state.immutableSignerContractFound = true ;
111- }
112-
113122 // Write weight and address to image
114123 state.imageHash = keccak256 (abi.encode (state.imageHash, addrWeight, addr));
115124 }
116125
117- // Check if this is the first transaction (nonce was 0 before increment) and immutable signer contract is one of the signers
126+ // Check if this is the first transaction (nonce was 0 before increment) and immutable signer contract
127+ // has provided a valid signature. The immutable signer must have actually signed (not just be listed
128+ // as an address) and the total weight must meet the threshold to prevent unauthorized bootstrap attacks.
118129 // Note: _validateNonce increments the nonce before _signatureValidation is called, so we check for 1, not 0
119130 uint256 currentNonce = uint256 (ModuleStorage.readBytes32Map (NonceKey.NONCE_KEY, bytes32 (uint256 (0 ))));
120- if (currentNonce == 1 && state.immutableSignerContractFound ) {
131+ if (currentNonce == 1 && state.immutableSignerContractSigned && state.totalWeight >= threshold ) {
121132 return (true , true , state.imageHash);
122133 }
123134
0 commit comments