diff options
Diffstat (limited to 'timing/src/timerange.rs')
| -rw-r--r-- | timing/src/timerange.rs | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/timing/src/timerange.rs b/timing/src/timerange.rs new file mode 100644 index 0000000..8991930 --- /dev/null +++ b/timing/src/timerange.rs @@ -0,0 +1,211 @@ +use chrono::{DateTime, Duration, Utc}; +use serde::{Deserialize, Serialize}; +use blake3::Hasher; + +/// Represents a fuzzy time range for transaction timestamps +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TimeRange { + /// Earliest possible timestamp (Unix seconds) + pub earliest: i64, + /// Latest possible timestamp (Unix seconds) + pub latest: i64, +} + +impl TimeRange { + /// Minimum allowed range duration (2 hours) + pub const MIN_RANGE_SECONDS: i64 = 2 * 3600; + /// Maximum allowed range duration (6 hours) + pub const MAX_RANGE_SECONDS: i64 = 6 * 3600; + /// Maximum age for a transaction (4 hours from latest) + pub const MAX_TX_AGE_SECONDS: i64 = 4 * 3600; + + /// Create a new TimeRange around a real timestamp + pub fn new(real_time: DateTime<Utc>, range_hours: u32) -> Result<Self, TimeRangeError> { + let range_seconds = (range_hours as i64) * 3600; + + if range_seconds < Self::MIN_RANGE_SECONDS { + return Err(TimeRangeError::RangeTooSmall); + } + + if range_seconds > Self::MAX_RANGE_SECONDS { + return Err(TimeRangeError::RangeTooLarge); + } + + let half_range = Duration::seconds(range_seconds / 2); + let earliest = (real_time - half_range).timestamp(); + let latest = (real_time + half_range).timestamp(); + + Ok(Self { earliest, latest }) + } + + /// Create a fuzzy range with random padding + pub fn new_fuzzy(real_time: DateTime<Utc>) -> Self { + use rand::Rng; + let mut rng = rand::thread_rng(); + + // Random range between 2-6 hours + let range_hours = rng.gen_range(2..=6); + + Self::new(real_time, range_hours).expect("Range should be valid") + } + + /// Check if this range is valid at current time + pub fn is_valid(&self, current_time: DateTime<Utc>) -> bool { + let range_duration = self.latest - self.earliest; + + // Check range size + if range_duration < Self::MIN_RANGE_SECONDS || range_duration > Self::MAX_RANGE_SECONDS { + return false; + } + + // Check not too old + let age = current_time.timestamp() - self.latest; + if age > Self::MAX_TX_AGE_SECONDS { + return false; + } + + true + } + + /// Get the duration of this range in seconds + pub fn duration_seconds(&self) -> i64 { + self.latest - self.earliest + } + + /// Check if a timestamp falls within this range + pub fn contains(&self, timestamp: i64) -> bool { + timestamp >= self.earliest && timestamp <= self.latest + } +} + +/// Zero-knowledge proof that a transaction was created within a time range +/// Simplified version using hash chains +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TimeRangeProof { + /// Hash at earliest time point + pub earliest_hash: [u8; 32], + /// Hash at latest time point + pub latest_hash: [u8; 32], + /// Commitment to the real creation time + pub time_commitment: [u8; 32], +} + +impl TimeRangeProof { + /// Generate a proof for a real timestamp within a range + /// Uses hash chain: H^n(seed) where n = minutes since epoch + pub fn generate( + seed: &[u8; 32], + real_time: DateTime<Utc>, + time_range: &TimeRange, + ) -> Result<Self, TimeRangeError> { + if !time_range.contains(real_time.timestamp()) { + return Err(TimeRangeError::TimeOutOfRange); + } + + // Convert timestamps to "minutes since epoch" for hash chain + let earliest_minutes = time_range.earliest / 60; + let latest_minutes = time_range.latest / 60; + let real_minutes = real_time.timestamp() / 60; + + // Generate hash chain values + let earliest_hash = Self::hash_chain(seed, earliest_minutes as u64); + let latest_hash = Self::hash_chain(seed, latest_minutes as u64); + + // Commitment to real time (hash of seed + real_time) + let mut hasher = Hasher::new(); + hasher.update(seed); + hasher.update(&real_minutes.to_le_bytes()); + let time_commitment = *hasher.finalize().as_bytes(); + + Ok(Self { + earliest_hash, + latest_hash, + time_commitment, + }) + } + + /// Verify that the proof is internally consistent + /// Note: This is a simplified proof system. A production version would use + /// proper ZK-SNARKs or Bulletproofs for stronger guarantees + pub fn verify(&self, time_range: &TimeRange) -> bool { + // In a real implementation, we would verify: + // 1. earliest_hash and latest_hash are valid points in hash chain + // 2. time_commitment corresponds to a point between them + // 3. No information about real_time is leaked + + // For this prototype, we just check the hashes are non-zero + self.earliest_hash != [0u8; 32] + && self.latest_hash != [0u8; 32] + && self.time_commitment != [0u8; 32] + && time_range.duration_seconds() > 0 + } + + /// Hash chain computation: applies blake3 hash n times + fn hash_chain(seed: &[u8; 32], iterations: u64) -> [u8; 32] { + let mut current = *seed; + + for _ in 0..iterations { + let mut hasher = Hasher::new(); + hasher.update(¤t); + current = *hasher.finalize().as_bytes(); + } + + current + } +} + +#[derive(Debug, thiserror::Error)] +pub enum TimeRangeError { + #[error("Time range is too small (minimum 2 hours)")] + RangeTooSmall, + + #[error("Time range is too large (maximum 6 hours)")] + RangeTooLarge, + + #[error("Real timestamp is outside the specified range")] + TimeOutOfRange, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_timerange_creation() { + let now = Utc::now(); + let range = TimeRange::new(now, 4).unwrap(); + + assert!(range.duration_seconds() == 4 * 3600); + assert!(range.contains(now.timestamp())); + } + + #[test] + fn test_timerange_validation() { + let now = Utc::now(); + let range = TimeRange::new(now, 4).unwrap(); + + assert!(range.is_valid(now)); + assert!(range.is_valid(now + Duration::hours(1))); + } + + #[test] + fn test_timerange_proof() { + let seed = [42u8; 32]; + let now = Utc::now(); + let range = TimeRange::new(now, 4).unwrap(); + + let proof = TimeRangeProof::generate(&seed, now, &range).unwrap(); + assert!(proof.verify(&range)); + } + + #[test] + fn test_fuzzy_range() { + let now = Utc::now(); + let range1 = TimeRange::new_fuzzy(now); + let range2 = TimeRange::new_fuzzy(now); + + // Ranges should be valid but potentially different + assert!(range1.is_valid(now)); + assert!(range2.is_valid(now)); + } +} |
