Timestamp Dependency Remediation
How to fix dangerous dependencies on block timestamps in security-critical logic.
Timestamp Dependency Remediation
Overview
Related Detector: Timestamp Dependency
Solana block timestamps are derived from validator votes and can be manipulated within bounds. Programs that use timestamps for exact equality checks, access control, or randomness generation are vulnerable. The fix depends on the specific pattern: replace equality checks with range comparisons, replace timestamp-based randomness with verifiable random functions, and add tolerance windows to time-gated logic.
Recommended Fix
Before (Vulnerable)
pub fn process_lottery(ctx: Context<Lottery>) -> Result<()> {
let clock = Clock::get()?;
// VULNERABLE: exact equality check
if clock.unix_timestamp == ctx.accounts.lottery.draw_time {
// VULNERABLE: timestamp as randomness
let winner = (clock.unix_timestamp as u64) % ctx.accounts.lottery.num_entries;
distribute_prize(ctx, winner)?;
}
Ok(())
}
After (Fixed)
pub fn process_lottery(ctx: Context<Lottery>) -> Result<()> {
let clock = Clock::get()?;
let lottery = &ctx.accounts.lottery;
// FIXED: range-based comparison with tolerance
let draw_window = 300i64; // 5-minute window
require!(
clock.unix_timestamp >= lottery.draw_time
&& clock.unix_timestamp <= lottery.draw_time + draw_window,
ErrorCode::OutsideDrawWindow
);
// FIXED: use SlotHashes for entropy
let recent_slothashes = &ctx.accounts.recent_slothashes;
let data = recent_slothashes.try_borrow_data()?;
let hash = solana_program::hash::hash(&data[..32]);
let winner = u64::from_le_bytes(
hash.to_bytes()[..8].try_into().unwrap()
) % lottery.num_entries;
distribute_prize(ctx, winner)?;
Ok(())
}
Alternative Mitigations
1. Use slot numbers instead of timestamps
Slot numbers are monotonically increasing and less susceptible to manipulation:
pub fn check_access(clock: &Clock, required_slot: u64) -> Result<()> {
// Slots advance predictably at ~400ms intervals
require!(clock.slot >= required_slot, ErrorCode::TooEarly);
Ok(())
}
2. Commit-reveal scheme for randomness
For applications requiring verifiable randomness, use a two-phase commit-reveal:
// Phase 1: Commit hash of secret
pub fn commit(ctx: Context<Commit>, hash: [u8; 32]) -> Result<()> {
ctx.accounts.game.commitment = hash;
ctx.accounts.game.commit_slot = Clock::get()?.slot;
Ok(())
}
// Phase 2: Reveal secret after N slots
pub fn reveal(ctx: Context<Reveal>, secret: Vec<u8>) -> Result<()> {
let clock = Clock::get()?;
let game = &ctx.accounts.game;
// Ensure enough slots have passed
require!(clock.slot >= game.commit_slot + 10, ErrorCode::TooEarly);
// Verify commitment
let hash = solana_program::hash::hash(&secret);
require!(hash.to_bytes() == game.commitment, ErrorCode::InvalidReveal);
// Use revealed secret as entropy
let random_value = u64::from_le_bytes(hash.to_bytes()[..8].try_into().unwrap());
Ok(())
}
3. Switchboard VRF for on-chain randomness
use switchboard_solana::prelude::*;
pub fn consume_randomness(ctx: Context<ConsumeRandomness>) -> Result<()> {
let vrf = ctx.accounts.vrf.load()?;
let result_buffer = vrf.get_result()?;
require!(!result_buffer.iter().all(|&x| x == 0), ErrorCode::VrfNotReady);
let random_value = u64::from_le_bytes(result_buffer[..8].try_into().unwrap());
// Use verifiable random value...
Ok(())
}
Common Mistakes
Mistake 1: Using unix_timestamp for strict equality
// WRONG: may never match due to validator timestamp variance
if clock.unix_timestamp == 1700000000 { ... }
Always use range comparisons: timestamp >= target && timestamp <= target + tolerance.
Mistake 2: XOR-ing timestamp with a “secret” constant
// WRONG: constant XOR does not add entropy
let random = clock.unix_timestamp ^ 0xDEADBEEF;
The constant is public knowledge and the timestamp is predictable. This provides no meaningful randomness.
Mistake 3: Combining multiple predictable values
// WRONG: all values are known to validators before block finalization
let seed = clock.unix_timestamp ^ clock.slot ^ clock.epoch;
Combining multiple predictable values does not create unpredictable output. Use a VRF or commit-reveal scheme.