@@ -4,19 +4,155 @@ pragma solidity 0.8.17;
44
55import "./ModuleAuthUpgradable.sol " ;
66import "./ImageHashKey.sol " ;
7+ import "./ModuleStorage.sol " ;
8+ import "./NonceKey.sol " ;
79import "../../Wallet.sol " ;
810
11+ import "../../utils/LibBytes.sol " ;
912
1013abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
14+ using LibBytes for bytes ;
15+
1116 bytes32 public immutable INIT_CODE_HASH;
1217 address public immutable FACTORY;
18+ address public immutable IMMUTABLE_SIGNER_CONTRACT;
1319
14- constructor (address _factory , address _startupWalletImpl ) {
20+ constructor (address _factory , address _startupWalletImpl , address _immutableSignerContract ) {
1521 // Build init code hash of the deployed wallets using that module
1622 bytes32 initCodeHash = keccak256 (abi.encodePacked (Wallet.creationCode, uint256 (uint160 (_startupWalletImpl))));
1723
1824 INIT_CODE_HASH = initCodeHash;
1925 FACTORY = _factory;
26+ IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
27+ }
28+
29+ /**
30+ * @notice Verify if signer is default wallet owner
31+ * @param _hash Hashed signed message
32+ * @param _signature Array of signatures with signers ordered
33+ * like the the keys in the multisig configs
34+ *
35+ * @dev The signature must be solidity packed and contain the total number of owners,
36+ * the threshold, the weight and either the address or a signature for each owner.
37+ *
38+ * Each weight & (address or signature) pair is prefixed by a flag that signals if such pair
39+ * contains an address or a signature. The aggregated weight of the signatures must surpass the threshold.
40+ *
41+ * Flag types:
42+ * 0x00 - Signature
43+ * 0x01 - Address
44+ *
45+ * E.g:
46+ * abi.encodePacked(
47+ * uint16 threshold,
48+ * uint8 01, uint8 weight_1, address signer_1,
49+ * uint8 00, uint8 weight_2, bytes signature_2,
50+ * ...
51+ * uint8 01, uint8 weight_5, address signer_5
52+ * )
53+ */
54+ function _signatureValidation (
55+ bytes32 _hash ,
56+ bytes memory _signature
57+ )
58+ internal virtual override returns (bool )
59+ {
60+ (bool verified , bool needsUpdate , bytes32 imageHash ) = _signatureValidationWithUpdateCheck (_hash, _signature);
61+ if (needsUpdate) {
62+ updateImageHashInternal (imageHash);
63+ }
64+ return verified;
65+ }
66+
67+ /**
68+ * @notice Verify signature and determine if image hash needs updating
69+ * @param _hash Hashed signed message
70+ * @param _signature Packed signature data containing threshold, flags, weights, and addresses/signatures
71+ * @return verified True if the signature is valid and weight threshold is met
72+ * @return needsUpdate True if the image hash needs to be stored (first transaction)
73+ * @return imageHash The computed image hash from the signature
74+ *
75+ * @dev This function parses the signature, recovers/reads signer addresses, and validates them.
76+ * For defensive validation, each extracted address is compared against IMMUTABLE_SIGNER_CONTRACT.
77+ * If a match is found, it is recorded.
78+ *
79+ * Special case: If this is the first transaction (nonce == 0) and the immutable signer contract
80+ * is one of the signers, the signature is automatically validated and approved without checking
81+ * the stored image hash. This allows the immutable signer to bootstrap the wallet on first use.
82+ */
83+ function _signatureValidationWithUpdateCheck (
84+ bytes32 _hash ,
85+ bytes memory _signature
86+ )
87+ internal view override returns (bool , bool , bytes32 )
88+ {
89+ (
90+ uint16 threshold , // required threshold signature
91+ uint256 rindex // read index
92+ ) = _signature.readFirstUint16 ();
93+
94+ // Start image hash generation
95+ bytes32 imageHash = bytes32 (uint256 (threshold));
96+
97+ // Acumulated weight of signatures
98+ uint256 totalWeight;
99+
100+ // Track if immutable signer contract is one of the signers
101+ bool immutableSignerContractFound = false ;
102+
103+ // Iterate until the image is completed
104+ while (rindex < _signature.length ) {
105+ // Read next item type and addrWeight
106+ uint256 flag; uint256 addrWeight; address addr;
107+ (flag, addrWeight, rindex) = _signature.readUint8Uint8 (rindex);
108+
109+ if (flag == FLAG_ADDRESS) {
110+ // Read plain address
111+ (addr, rindex) = _signature.readAddress (rindex);
112+ } else if (flag == FLAG_SIGNATURE) {
113+ // Read single signature and recover signer
114+ bytes memory signature;
115+ (signature, rindex) = _signature.readBytes66 (rindex);
116+ addr = recoverSigner (_hash, signature);
117+
118+ // Acumulate total weight of the signature
119+ totalWeight += addrWeight;
120+ } else if (flag == FLAG_DYNAMIC_SIGNATURE) {
121+ // Read signer
122+ (addr, rindex) = _signature.readAddress (rindex);
123+
124+ // Read signature size
125+ uint256 size;
126+ (size, rindex) = _signature.readUint16 (rindex);
127+
128+ // Read dynamic size signature
129+ bytes memory signature;
130+ (signature, rindex) = _signature.readBytes (rindex, size);
131+ require (isValidSignature (_hash, addr, signature), "ModuleAuthDynamic#_signatureValidation: INVALID_SIGNATURE " );
132+
133+ // Acumulate total weight of the signature
134+ totalWeight += addrWeight;
135+ } else {
136+ revert ("ModuleAuthDynamic#_signatureValidation INVALID_FLAG " );
137+ }
138+
139+ // Defensive check: compare extracted address with target address
140+ if (IMMUTABLE_SIGNER_CONTRACT != address (0 ) && addr == IMMUTABLE_SIGNER_CONTRACT) {
141+ immutableSignerContractFound = true ;
142+ }
143+
144+ // Write weight and address to image
145+ imageHash = keccak256 (abi.encode (imageHash, addrWeight, addr));
146+ }
147+
148+ // Check if this is the first transaction (nonce == 0) and immutable signer contract is one of the signers
149+ uint256 currentNonce = uint256 (ModuleStorage.readBytes32Map (NonceKey.NONCE_KEY, bytes32 (uint256 (0 ))));
150+ if (currentNonce == 0 && immutableSignerContractFound) {
151+ return (true , true , imageHash);
152+ }
153+
154+ (bool verified , bool needsUpdate ) = _isValidImage (imageHash);
155+ return ((totalWeight >= threshold && verified), needsUpdate, imageHash);
20156 }
21157
22158 /**
0 commit comments