# Signatures can be replayed in withdraw() to withdraw more tokens than the user originally intended.

TimelockTokenPool.withdraw() recovers signer from a hash lacking a nonce, letting anyone replay one signature to drain more unlocked tokens than intended.

- Payout: Contest reward
- Severity: high
- Program: Taiko
- Credited: lightoasis
- Disclosed: 2024-04-26
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-03-taiko-findings/issues/60
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-taiko%3AH-05
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:signature-replay

**Target:** TimelockTokenPool.withdraw() signature authorization
**Root cause:** The signed message hash is keccak256("Withdraw unlocked Taiko token to: " + _to) with no nonce, deadline, or per-signer state, so a single signature stays valid for unlimited calls.
**Steps:**
1. User signs hash of `"Withdraw unlocked Taiko token to: " + _to` and hands sig to a delegate.
2. Delegate calls `withdraw(_to, _sig)`; contract recovers recipient via `ECDSA.recover(hash, _sig)` and calls `_withdraw(recipient, _to)`.
3. Attacker reuses the same `_sig` in repeated `withdraw()` calls as more tokens unlock, each time pulling newly withdrawable balance to `_to`.
**How found:** Code review of withdraw() noting the signed hash omits a nonce, compared against the known Arbitrum castVoteBySig replay finding.
**Exploitation:** Anyone holding or observing a user's withdraw signature replays it across multiple calls; each call passes ECDSA.recover since the hash is unchanged, moving additional unlocked tokens beyond the user's one-time intent.
**Insight:** When a contract authorizes an action purely by recovering a signer from a hash, check whether that hash binds a nonce, deadline, and the specific amount/state — otherwise any state change between calls makes the same signature reusable.
