Skip to main content
Sigvex

Timestamp Dependence Remediation

How to eliminate exploitable timestamp dependence by replacing block.timestamp with block numbers, commit-reveal schemes, or off-chain randomness.

Timestamp Dependence Remediation

Overview

Timestamp dependence vulnerabilities arise when contract logic relies on block.timestamp for outcomes where a validator’s ability to adjust the timestamp by approximately 15 seconds creates an exploitable window. The remediation depends on the use case: replace with block numbers for duration calculations, or use verifiable randomness (an off-chain verifiable random function service) for random outcomes.

Related Detector: Timestamp Dependence

Before (Vulnerable)

// Lottery using block.timestamp — validators can withhold blocks to bias outcome
function pickWinner() external {
    require(block.timestamp >= lotteryEndTime, "Lottery still active");
    uint256 randomIndex = uint256(keccak256(abi.encodePacked(
        block.timestamp,  // Validator-influenceable
        block.prevrandao,
        msg.sender
    ))) % participants.length;
    winner = participants[randomIndex];
}

After (Fixed)

// Option 1: an off-chain verifiable random function service for provably fair randomness
import "./interfaces/AggregatorV3Interface.sol";
import "./interfaces/AggregatorV3Interface.sol";

contract FairLottery is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;
    uint256 public randomResult;

    function requestWinner() external {
        require(block.timestamp >= lotteryEndTime);
        // Request randomness from an off-chain verifiable random function service — unpredictable and verifiable
        COORDINATOR.requestRandomWords(keyHash, subscriptionId, 3, 100000, 1);
    }

    function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override {
        winner = participants[randomWords[0] % participants.length];
    }
}

Alternative Mitigations

Block number instead of timestamp for duration tracking — block numbers increase monotonically and cannot be manipulated by validators (only delayed):

// Instead of: require(block.timestamp >= startTime + duration)
// Use: require(block.number >= startBlock + durationInBlocks)

uint256 public startBlock;
uint256 public constant DURATION_BLOCKS = 7200; // ~1 day at 12s/block

function isEnded() public view returns (bool) {
    return block.number >= startBlock + DURATION_BLOCKS;
}

15-second tolerance — for non-critical time checks where 15 seconds of variance is acceptable, document the assumption and ensure the economic value at stake does not justify the manipulation cost:

// Acceptable: loan due date with 15-second tolerance is not exploitable
// because the economic value of 15 seconds of time is negligible
require(block.timestamp >= dueDate, "Loan not yet due");
// Document: validator timestamp manipulation is bounded to ~15s;
// this tolerance is acceptable for loan timing

Common Mistakes

Using block.timestamp for lotteries or random outcomes — any timestamp-based randomness where the expected gain exceeds the cost of 15-second block withholding is exploitable by validators.

Using block.timestamp as a nonce or unique identifier — two transactions in the same block share the same block.timestamp. Never use timestamp as a unique key.

Tight deadline enforcement — enforcing a deadline within seconds using block.timestamp is unreliable because validators can bias timestamps by up to 15 seconds.

References