#  validateCallback() is vulnerable to a birthday attack

Panoptic's validateCallback truncates keccak256 to 160 bits, so a birthday-attack collision could spoof a Uni-V3 pool and drain approved tokens.

- Payout: Contest reward
- Severity: medium
- Program: Panoptic
- Credited: LokiThe5th
- Disclosed: 2024-06-24
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-11-panoptic-findings/issues/247
- Library page: https://bugbountylibrary.com/r/c4%3A2023-11-panoptic%3AM-05
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:signature-replay

**Target:** SemiFungiblePositionManager uniswapV3MintCallback / CallbackLib.validateCallback
**Root cause:** validateCallback derives the expected pool address by truncating keccak256 to uint160 and comparing to msg.sender, instead of querying the factory's getPool; truncation leaves 2^96 preimages per address, enabling birthday collisions.
**Steps:**
1. Attacker varies token1 and fee in PoolFeatures to generate many candidate pool addresses via the CREATE2 derivation.
2. Attacker generates many EOA/deterministic addresses (varying seeds) to find one colliding with any candidate pool address (~2^80 each set).
3. Deploy the colliding EOA and call `uniswapV3MintCallback` directly with the seed PoolFeatures in `data`.
4. validateCallback passes; attacker specifies a victim token0 (e.g. USDC/WETH) and transfers the victim's approved amount to the attacker.
**How found:** Code reading of the CREATE2-style address derivation in CallbackLib.validateCallback; noticed the keccak256 result is cast to uint160, losing 96 bits, and reasoned via the birthday paradox that collisions become feasible rather than preimage-resistant.
**Exploitation:** Precondition: SemiFungiblePositionManager holds user token approvals. Attacker spends ~$4.6M capex/energy over ~2 years on ~300 Antminers to find a collision between a derived pool address and an attacker-controlled address, then calls uniswapV3MintCallback with crafted data to pass validation and pull approved token0 to the attacker.
**Insight:** When a contract validates msg.sender by recomputing a CREATE2 address and truncating a hash to 160 bits, the security margin drops to ~2^80 birthday collision; prefer querying the factory's getPool mapping. Also note callbacks that only check address derivation, not pool existence.
