summaryrefslogtreecommitdiffstats
path: root/timing/src/timerange.rs
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
commit9f5d864d533ce86459e654f5d78212933c0269ea (patch)
tree4b71e8c7ab4e78da93e5f3164803da4de68c7031 /timing/src/timerange.rs
downloadonioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.tar.gz
onioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.tar.xz
onioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.zip
Initial commit: OnionCoin prototype with Proof-of-Relay consensusHEADv0.1.0main
OnionCoin is a privacy cryptocurrency that rewards Tor relay operators through a unique Proof-of-Contribution consensus mechanism. Core Features: - Proof-of-Relay: 30% of block rewards go to Tor operators - Native .onion node identity (no IP exposure) - Temporal obfuscation protocols - Dandelion++ over Tor propagation - Native inheritance system with dead man's switch Technical Stack: - Rust workspace with 8 crates - Ed25519/X25519 cryptography - arti (Rust Tor client) integration planned - 10 minute block time, 5-10 TPS design Status: Prototype - Consensus logic complete with passing tests (30/33) - Network layer conceptual design complete - Tor integration pending - Testnet launch planned Q3 2026 License: MIT Author: Gabriele Salati (virebent) Contact: g48rix@gmail.com Website: https://www.gabrielesalati.eu Repository: https://git.virebent.art/virebent/onioncoin
Diffstat (limited to 'timing/src/timerange.rs')
-rw-r--r--timing/src/timerange.rs211
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(&current);
+ 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));
+ }
+}