# Validator threshold can be bypassed: a single compromised validator can update minter's state to historical state

Mzero MinterGateway takes the minimum validator timestamp, so one compromised validator can roll a minter's collateral state back to a historical value and mint free M tokens.

- Payout: Contest reward
- Severity: medium
- Program: Mzero
- Credited: 00001111x0
- Disclosed: 2023-10-27
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-10-mzero-judging/issues/46
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-10-mzero%3AM-3
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:signature-replay

**Target:** MinterGateway.updateCollateral / _verifyValidatorSignatures
**Root cause:** _verifyValidatorSignatures returns the minimum timestamp across validator signatures rather than a threshold-consistent timestamp, so a single validator can supply an arbitrarily old timestamp while the monotonic check only compares against the last update.
**Steps:**
1. Set updateCollateralValidatorThreshold=2 with validators [validator2, validator1]; compromise validator1.
2. At T0+100 deposit 100e6: validator2 signs timestamp=block.timestamp, validator1 signs timestamp=T0+1; call `updateCollateral(100e6, [], 0, validators, timestamps, signatures)` -> updateTimestamp=T0+1.
3. At T0+200 proposeRetrieval(100e6), retrieve offchain; validator2 signs block.timestamp, validator1 signs T0+2; call `updateCollateral(0, [retrievalId], 0, ...)` -> collateral=0, updateTimestamp=T0+2.
4. At T0+300 reuse cached validator2 signature from step 1 (timestamp T0+100), validator1 signs collateral=100e6 at T0+3; call `updateCollateral(100e6, [], 0, ...)` -> collateral=100e6, updateTimestamp=T0+3.
5. Mint against the restored 100e6 collateral before collateralExpiryTimestamp (updateCollateralInterval=86400).
**How found:** Code review of _verifyValidatorSignatures noticing it returns the minimum timestamp and that _updateCollateral only enforces monotonicity versus the last stored timestamp, so a stale-but-newer-than-last timestamp passes.
**Exploitation:** A single compromised validator signs an old timestamp while the honest validator signs current time; min() picks the attacker's timestamp, letting the minter replay a prior collateral state after retrieving funds, then mint unbacked M tokens.
**Escalation:** Mint proposal can be placed between step 1 and step 2 to reduce mintDelay wait; 86400s updateCollateralInterval gives ample window before fake collateral expires.
**Insight:** When a threshold of signers is aggregated, check whether the aggregated value (min/max/first) can be dominated by a single signer; a monotonicity check against last state does not prevent replay of an older signed payload if the aggregator picks the attacker's value.
