@@ -36,6 +36,15 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
3636 keccak256 ("Proposal(address account,bytes32 id,bytes callData,uint256 nonce) " );
3737
3838 error ZeroWeightSigner ();
39+ error LengthMismatch ();
40+ error EmptyGuardians ();
41+ error ZeroThreshold ();
42+ error GuardianCannotBeSelf ();
43+ error ZeroAddressGuardian ();
44+ error ZeroWeight ();
45+ error GuardianAlreadyEnabled ();
46+ error SignersNotSorted ();
47+ error ThresholdExceedsTotalWeight ();
3948
4049 mapping (bytes32 id = > mapping (address kernel = > WeightedECDSASignerStorage)) public weightedStorage;
4150 mapping (address guardian = > mapping (bytes32 id = > mapping (address kernel = > GuardianStorage))) public guardian;
@@ -53,22 +62,23 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
5362
5463 (address [] memory _guardians , uint24 [] memory _weights , uint24 _threshold ) =
5564 abi.decode (_data, (address [], uint24 [], uint24 ));
56- require (_guardians.length == _weights.length , " Length mismatch " );
57- require (_guardians.length > 0 , " No guardians " );
58- require (_threshold > 0 , " Zero threshold " );
65+ require (_guardians.length == _weights.length , LengthMismatch () );
66+ require (_guardians.length > 0 , EmptyGuardians () );
67+ require (_threshold > 0 , ZeroThreshold () );
5968
6069 weightedStorage[id][msg .sender ].firstGuardian = msg .sender ;
6170 for (uint256 i = 0 ; i < _guardians.length ; i++ ) {
62- require (_guardians[i] != msg .sender , " Guardian cannot be self " );
63- require (_guardians[i] != address (0 ), " Guardian cannot be 0 " );
64- require (_weights[i] != 0 , " Weight cannot be 0 " );
65- require (guardian[_guardians[i]][id][msg .sender ].weight == 0 , " Guardian already enabled " );
71+ require (_guardians[i] != msg .sender , GuardianCannotBeSelf () );
72+ require (_guardians[i] != address (0 ), ZeroAddressGuardian () );
73+ require (_weights[i] != 0 , ZeroWeight () );
74+ require (guardian[_guardians[i]][id][msg .sender ].weight == 0 , GuardianAlreadyEnabled () );
6675 guardian[_guardians[i]][id][msg .sender ] =
6776 GuardianStorage ({weight: _weights[i], nextGuardian: weightedStorage[id][msg .sender ].firstGuardian});
6877 weightedStorage[id][msg .sender ].firstGuardian = _guardians[i];
6978 weightedStorage[id][msg .sender ].totalWeight += _weights[i];
7079 emit GuardianAdded (_guardians[i], msg .sender , _weights[i]);
7180 }
81+ require (_threshold <= weightedStorage[id][msg .sender ].totalWeight, ThresholdExceedsTotalWeight ());
7282 weightedStorage[id][msg .sender ].threshold = _threshold;
7383 }
7484
@@ -102,6 +112,12 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
102112 return _validateUserOpSignature (id, userOp, userOpHash, userOp.signature, msg .sender );
103113 }
104114
115+ /// @notice Validate an ERC-1271 signature
116+ /// @dev The `sender` parameter (requesting protocol) is intentionally unused.
117+ /// This signer authenticates the SIGNERS (guardians), not the requesting protocol.
118+ /// WARNING: Because sender is ignored, any protocol can request signature
119+ /// validation. If you need to restrict which protocols can request signatures,
120+ /// pair this signer with a CallerPolicy.
105121 function checkSignature (bytes32 id , address , bytes32 hash , bytes calldata sig )
106122 external
107123 view
@@ -138,6 +154,15 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
138154 /**
139155 * @notice Internal function to validate user operation signatures
140156 * @dev Shared logic for both installed and stateless validator modes
157+ *
158+ * SECURITY: Split Signature Scheme
159+ * The first N-1 signatures verify a proposalHash (EIP-712 typed data covering
160+ * account, id, callData, and nonce). The last signature MUST verify the full
161+ * userOpHash to bind the complete UserOp (including gas fields).
162+ * This prevents a scenario where guardians approve a proposal but an attacker
163+ * manipulates gas parameters in the final UserOp.
164+ * A double-counting check ensures a guardian who signed both the proposalHash
165+ * and userOpHash only has their weight counted once.
141166 */
142167 function _validateUserOpSignature (
143168 bytes32 id ,
@@ -188,7 +213,7 @@ contract WeightedECDSASigner is EIP712, SignerBase, IStatelessValidator, IStatel
188213 signer = ECDSA.tryRecoverCalldata (proposalHash, sig[i * 65 :(i + 1 ) * 65 ]);
189214
190215 // Enforce sorted order to prevent signature reuse
191- require (signer > lastSigner, " Signers not sorted " );
216+ require (signer > lastSigner, SignersNotSorted () );
192217 lastSigner = signer;
193218 proposalSigners[i] = signer;
194219
0 commit comments