# Slot and block number proofs not required for verification of withdrawal (multiple withdrawals possible)

EigenPod's BeaconChainProofs accepted empty slot/blockNumber Merkle proofs, letting a valid withdrawal proof be replayed with altered slot/block number to withdraw the same funds twice.

- Payout: Contest reward
- Severity: high
- Program: EigenLayer
- Credited: 0xTheC0der
- Disclosed: 2023-07-06
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-04-eigenlayer-findings/issues/388
- Library page: https://bugbountylibrary.com/r/c4%3A2023-04-eigenlayer%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:signature-replay

**Target:** EigenPod withdrawal proof verification (BeaconChainProofs/Merkle libraries)
**Root cause:** Merkle.verifyInclusionSha256 returns true when proof.length < 32 and leaf == root; BeaconChainProofs.verifyWithdrawalProofs never requires non-empty slotProof/blockNumberProof, so setting slotRoot=blockHeaderRoot and blockNumberRoot=executionPayloadRoot with empty proofs passes.
**Steps:**
1. Take a valid withdrawal proof (beaconStateRoot, proofs, withdrawalFields).
2. Set `proofs.slotProof = bytes("")` and `proofs.slotRoot = proofs.blockHeaderRoot`.
3. Set `proofs.blockNumberProof = bytes("")` and `proofs.blockNumberRoot = proofs.executionPayloadRoot`.
4. Call `EigenPod.verifyAndProcessWithdrawal` with modified proofs; verifyWithdrawalProofs still succeeds.
5. proofIsForValidBlockNumber modifier is bypassed since blockNumberRoot=executionPayloadRoot.
6. Replay same withdrawal with different slotRoot to bypass the per-slot partial-withdrawal check and withdraw twice.
**How found:** Code review of Merkle library noticed verifyInclusion returns true for empty proof when leaf==root; traced callers in BeaconChainProofs and found no minimum-length require on slotProof/blockNumberProof, then reasoned that leaf==root substitution lets a valid proof be re-created for arbitrary slot/block number.
**Exploitation:** Attacker with a legitimate withdrawal proof re-submits it with empty slot/blockNumber proofs and leaf==root substitution, bypassing the mostRecentWithdrawalBlockNumber check and the per-slot uniqueness check, enabling repeated partial withdrawals of the same funds (theft).
**Escalation:** Chaining the missing length check into bypass of both the block-number freshness modifier and the slot-reuse guard, converting a validation gap into double-withdrawal fund theft.
**Insight:** When a Merkle verifier short-circuits on empty proof with leaf==root, audit every caller for missing proof-length requires; leaf==root substitution lets you forge the proven index/value (slot, block number) and bypass freshness/uniqueness guards keyed on those fields.
