summaryrefslogtreecommitdiffstats
path: root/timing/src/obfuscation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'timing/src/obfuscation.rs')
-rw-r--r--timing/src/obfuscation.rs243
1 files changed, 243 insertions, 0 deletions
diff --git a/timing/src/obfuscation.rs b/timing/src/obfuscation.rs
new file mode 100644
index 0000000..b48ea82
--- /dev/null
+++ b/timing/src/obfuscation.rs
@@ -0,0 +1,243 @@
+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<T> {
+ /// Mixing pool for batch releases
+ mixing_pool: MixingPool<T>,
+
+ /// 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<T> TimingObfuscator<T> {
+ 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<T> {
+ 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<Duration>,
+}
+
+impl TimingMetadata {
+ /// Create timing metadata for a transaction
+ pub fn new(real_time: DateTime<Utc>, seed: &[u8; 32]) -> Result<Self, crate::timerange::TimeRangeError> {
+ 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<Utc>) -> 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<T> {
+ pub item: T,
+ pub timing: TimingMetadata,
+ pub phase: PropagationPhase,
+}
+
+impl<T> TimedItem<T> {
+ 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::<u32>::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::<u32>::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));
+ }
+}