1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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));
}
}
|