use crate::{TimeRange, TimeRangeProof, DelayStrategy, MixingPool}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::time::Duration; /// Orchestrates all timing obfuscation strategies #[derive(Debug)] pub struct TimingObfuscator { /// Mixing pool for batch releases mixing_pool: MixingPool, /// Strategy for adding delays delay_strategy: DelayStrategy, /// Configuration config: ObfuscationConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ObfuscationConfig { /// Enable mixing pool batching pub use_mixing_pool: bool, /// Enable random delays pub use_delays: bool, /// Enable fuzzy timestamps pub use_fuzzy_timestamps: bool, /// Ratio of fake items to inject (0.0 - 1.0) pub fake_item_ratio: f32, } impl Default for ObfuscationConfig { fn default() -> Self { Self { use_mixing_pool: true, use_delays: true, use_fuzzy_timestamps: true, fake_item_ratio: 0.3, // 30% fake traffic } } } impl TimingObfuscator { pub fn new(config: ObfuscationConfig) -> Self { Self { mixing_pool: MixingPool::default(), delay_strategy: DelayStrategy::node_rebroadcast(), config, } } /// Add item to mixing pool pub fn add_to_pool(&mut self, item: T) { if self.config.use_mixing_pool { self.mixing_pool.add(item); } } /// Check if should release batch pub fn should_release_batch(&self) -> bool { if !self.config.use_mixing_pool { return false; } self.mixing_pool.should_release() } /// Release shuffled batch pub fn release_batch(&mut self) -> Vec { self.mixing_pool.release_batch() } /// Apply delay strategy pub async fn apply_delay(&self) { if self.config.use_delays { self.delay_strategy.sleep().await; } } /// Get current pool size pub fn pool_size(&self) -> usize { self.mixing_pool.len() } } /// Transaction timing metadata #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimingMetadata { /// Fuzzy time range pub time_range: TimeRange, /// Zero-knowledge proof of creation time pub time_proof: TimeRangeProof, /// Optional: observed network delay (for adaptive strategies) pub network_delay_hint: Option, } impl TimingMetadata { /// Create timing metadata for a transaction pub fn new(real_time: DateTime, seed: &[u8; 32]) -> Result { let time_range = TimeRange::new_fuzzy(real_time); let time_proof = TimeRangeProof::generate(seed, real_time, &time_range)?; Ok(Self { time_range, time_proof, network_delay_hint: None, }) } /// Validate timing metadata pub fn validate(&self, current_time: DateTime) -> bool { // Check time range is valid if !self.time_range.is_valid(current_time) { return false; } // Verify time proof if !self.time_proof.verify(&self.time_range) { return false; } true } /// Set network delay hint for adaptive strategies pub fn with_network_delay(mut self, delay: Duration) -> Self { self.network_delay_hint = Some(delay); self } } /// Strategies for transaction propagation #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum PropagationPhase { /// Dandelion STEM phase (anonymity) Stem, /// Dandelion FLUFF phase (broadcast) Fluff, } /// Transaction wrapper with timing information #[derive(Debug, Clone)] pub struct TimedItem { pub item: T, pub timing: TimingMetadata, pub phase: PropagationPhase, } impl TimedItem { pub fn new(item: T, timing: TimingMetadata) -> Self { Self { item, timing, phase: PropagationPhase::Stem, } } pub fn transition_to_fluff(mut self) -> Self { self.phase = PropagationPhase::Fluff; self } pub fn is_stem(&self) -> bool { self.phase == PropagationPhase::Stem } pub fn is_fluff(&self) -> bool { self.phase == PropagationPhase::Fluff } } #[cfg(test)] mod tests { use super::*; #[test] fn test_timing_metadata_creation() { let now = Utc::now(); let seed = [42u8; 32]; let metadata = TimingMetadata::new(now, &seed).unwrap(); assert!(metadata.validate(now)); } #[test] fn test_timing_metadata_validation() { let now = Utc::now(); let seed = [42u8; 32]; let metadata = TimingMetadata::new(now, &seed).unwrap(); // Should validate at current time assert!(metadata.validate(now)); // Should validate slightly in the future let future = now + chrono::Duration::hours(1); assert!(metadata.validate(future)); } #[test] fn test_obfuscator_pool() { let config = ObfuscationConfig::default(); let mut obfuscator = TimingObfuscator::::new(config); obfuscator.add_to_pool(1); obfuscator.add_to_pool(2); obfuscator.add_to_pool(3); assert_eq!(obfuscator.pool_size(), 3); } #[test] fn test_timed_item_phases() { let now = Utc::now(); let seed = [42u8; 32]; let timing = TimingMetadata::new(now, &seed).unwrap(); let item = TimedItem::new(42u32, timing); assert!(item.is_stem()); assert!(!item.is_fluff()); let item = item.transition_to_fluff(); assert!(!item.is_stem()); assert!(item.is_fluff()); } #[tokio::test] async fn test_apply_delay() { let config = ObfuscationConfig::default(); let obfuscator = TimingObfuscator::::new(config); let start = std::time::Instant::now(); obfuscator.apply_delay().await; let elapsed = start.elapsed(); // Should have some delay (at least 10s from node_rebroadcast strategy) assert!(elapsed >= Duration::from_secs(10)); } }