1use crate::tools::server_id::ServerId;
22use crate::tools::time::{DurationMillis, MILLIS_IN_DAY, MILLIS_IN_MONTH, TimeMillis, TimeMillisBytes};
23use crate::tools::time_provider::time_provider::TimeProvider;
24use crate::tools::types::{Hash, Id, PQCommitmentBytes, Pow, Salt, Signature, SignatureKey, VerificationKey, VerificationKeyBytes};
25use crate::tools::{config, hashing, signing, tools};
26use crate::{anyhow_assert_eq, anyhow_assert_ge};
27use serde::{Deserialize, Serialize};
28use std::fmt;
29
30#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
44pub struct PeerPow {
45 #[serde(rename = "sp")]
46 pub sponsor_id: Id,
47 #[serde(rename = "t")]
48 pub timestamp: TimeMillis,
49 #[serde(rename = "ch")]
50 pub content_hash: Hash, #[serde(rename = "s")]
52 pub salt: Salt,
53 #[serde(rename = "z")]
54 pub pow: Pow,
55}
56
57impl PeerPow {
58 pub fn new(sponsor_id: Id, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes, timestamp: TimeMillis, content_hash: Hash, salt: Salt) -> anyhow::Result<PeerPow> {
59 let (pow, _) = Self::pow(&sponsor_id, verification_key, pq_commitment_bytes, ×tamp.encode_be(), &content_hash, &salt)?;
60 Ok(Self {
61 sponsor_id,
62 timestamp,
63 content_hash,
64 salt,
65 pow,
66 })
67 }
68
69 pub fn zero() -> Self {
70 PeerPow {
71 sponsor_id: Id::zero(),
72 timestamp: TimeMillis::zero(),
73 content_hash: Hash::zero(),
74 salt: Salt::zero(),
75 pow: Pow(0),
76 }
77 }
78
79 pub fn random() -> Self {
80 Self {
81 sponsor_id: Id::random(),
82 timestamp: TimeMillis::random(),
83 content_hash: Hash::random(),
84 salt: Salt::random(),
85 pow: Pow(tools::random_u8()),
86 }
87 }
88
89 pub fn verify(&self, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes) -> anyhow::Result<()> {
90 let (pow, _) = Self::pow(&self.sponsor_id, verification_key, pq_commitment_bytes, &self.timestamp.encode_be(), &self.content_hash, &self.salt)?;
91 match pow == self.pow {
92 true => Ok(()),
93 false => Err(anyhow::anyhow!("pow does not match inputs: {} != {}", self.pow, pow)),
94 }
95 }
96
97 pub fn pow_decayed_day(&self, current_time_millis: TimeMillis) -> f64 {
98 self.pow_decayed(current_time_millis, MILLIS_IN_DAY)
99 }
100
101 pub fn pow_decayed_month(&self, current_time_millis: TimeMillis) -> f64 {
102 self.pow_decayed(current_time_millis, MILLIS_IN_MONTH)
103 }
104
105 fn pow_decayed(&self, current_time_millis: TimeMillis, pow_halflife_millis: DurationMillis) -> f64 {
109 let elapsed_millis = (current_time_millis - self.timestamp).max(DurationMillis(0));
110 (self.pow.0 as f64) / 2.0f64.powf(elapsed_millis.0 as f64 / pow_halflife_millis.0 as f64)
111 }
112
113 pub fn pow(sponsor_id: &Id, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes, timestamp: &TimeMillisBytes, hash: &Hash, salt: &Salt) -> anyhow::Result<(Pow, Hash)> {
114 ServerId::pow_measure(sponsor_id, verification_key, pq_commitment_bytes, timestamp, hash, salt)
115 }
116
117 pub fn own_pow(&self, verification_key: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes) -> anyhow::Result<(Pow, Hash)> {
118 Self::pow(&self.sponsor_id, verification_key, pq_commitment_bytes, &self.timestamp.encode_be(), &self.content_hash, &self.salt)
119 }
120}
121
122#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
135pub struct ClientPow {
136 pub peer_verification_key: VerificationKeyBytes,
137 pub peer_pq_commitment_bytes: PQCommitmentBytes,
138 pub peer_pow: PeerPow,
139}
140
141impl ClientPow {
142 pub fn measure(peer_verification_key: &VerificationKeyBytes, peer_pq_commitment_bytes: &PQCommitmentBytes, sponsor_id: &Id, timestamp: TimeMillis, content_hash: &Hash, salt: &Salt) -> anyhow::Result<Self> {
143 let (pow, _) = ServerId::pow_measure(sponsor_id, peer_verification_key, peer_pq_commitment_bytes, ×tamp.encode_be(), content_hash, salt)?;
144 Ok(Self {
145 peer_verification_key: *peer_verification_key,
146 peer_pq_commitment_bytes: *peer_pq_commitment_bytes,
147 peer_pow: PeerPow {
148 sponsor_id: *sponsor_id,
149 timestamp,
150 content_hash: *content_hash,
151 salt: *salt,
152 pow,
153 },
154 })
155 }
156
157 pub fn verify(&self) -> anyhow::Result<()> {
158 self.peer_pow.verify(&self.peer_verification_key, &self.peer_pq_commitment_bytes)
159 }
160}
161
162impl fmt::Display for PeerPow {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "timestamp={} hash={} salt={} pow={}", self.timestamp, hex::encode(self.content_hash), hex::encode(self.salt), self.pow,)
165 }
166}
167
168#[derive(Serialize, Deserialize, PartialEq, Clone)]
182pub struct Peer {
183 pub id: Id,
184
185 #[serde(rename = "vkb")]
186 pub verification_key_bytes: VerificationKeyBytes,
187 #[serde(rename = "pqcb")]
188 pub pq_commitment_bytes: PQCommitmentBytes,
189
190 #[serde(rename = "pi")]
191 pub pow_initial: PeerPow, #[serde(rename = "pcd")]
193 pub pow_current_day: PeerPow, #[serde(rename = "pcm")]
195 pub pow_current_month: PeerPow, #[serde(rename = "ver")]
198 pub version: String, #[serde(rename = "addr")]
200 pub address: String, #[serde(rename = "ts")]
203 pub timestamp: TimeMillis, #[serde(rename = "sig")]
206 pub signature: Signature, }
208
209impl fmt::Debug for Peer {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 fmt::Display::fmt(self, f)
212 }
213}
214
215impl fmt::Display for Peer {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 write!(
218 f,
219 "[ id={} version={} address={} pows=[ {} {} {} ] timestamp={} ]",
220 self.id, self.version, self.address, self.pow_initial.pow, self.pow_current_day.pow, self.pow_current_month.pow, self.timestamp
221 )
222
223 }
236}
237
238impl Peer {
239 pub fn zero() -> Peer {
240 Peer {
241 id: Id::zero(),
242 verification_key_bytes: VerificationKeyBytes::zero(),
243 pq_commitment_bytes: PQCommitmentBytes::zero(),
244
245 pow_initial: PeerPow::zero(),
246 pow_current_day: PeerPow::zero(),
247 pow_current_month: PeerPow::zero(),
248
249 version: env!("CARGO_PKG_VERSION").to_string(),
250 address: String::new(),
251
252 timestamp: TimeMillis::zero(),
253
254 signature: Signature::zero(),
255 }
256 }
257
258 pub fn score(&self, time_millis: TimeMillis) -> f64 {
262 self.pow_current_day.pow_decayed_day(time_millis) + self.pow_current_month.pow_decayed_month(time_millis)
263 }
264
265 pub fn signature_hash_generate(&self) -> anyhow::Result<Hash> {
266 Ok(hashing::hash_multiple(&[
267 self.id.as_ref(),
268 self.verification_key_bytes.as_ref(),
269 self.pq_commitment_bytes.as_ref(),
270 self.pow_initial.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
271 self.pow_current_day.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
272 self.pow_current_month.own_pow(&self.verification_key_bytes, &self.pq_commitment_bytes)?.1.as_ref(),
273 self.address.as_bytes(),
274 self.timestamp.encode_be().as_ref(),
275 ]))
276 }
277
278 pub fn sign(&mut self, time_provider: &dyn TimeProvider, signature_key: &SignatureKey) -> anyhow::Result<()> {
279 self.timestamp = time_provider.current_time_millis();
280
281 let signature_hash = self.signature_hash_generate()?;
282 self.signature = signing::sign(signature_key, signature_hash.as_ref());
283 Ok(())
284 }
285
286 pub fn verify(&self) -> anyhow::Result<()> {
287 self.verify_signature()?;
288 self.verify_server_id()?;
289 self.verify_pows()?;
290 Ok(())
291 }
292
293 pub fn verify_timestamps(&self, current_time_millis: TimeMillis) -> anyhow::Result<()> {
297 let latest_allowed_time_millis = current_time_millis + config::PEER_MAX_FUTURE_TIMESTAMP_MILLIS;
298 let timestamps = [
299 ("timestamp", self.timestamp),
300 ("pow_initial.timestamp", self.pow_initial.timestamp),
301 ("pow_current_day.timestamp", self.pow_current_day.timestamp),
302 ("pow_current_month.timestamp", self.pow_current_month.timestamp),
303 ];
304 for (field_name, timestamp) in timestamps {
305 if timestamp > latest_allowed_time_millis {
306 anyhow::bail!("peer {} is too far in the future: {}={} > now={} + {}", field_name, field_name, timestamp, current_time_millis, config::PEER_MAX_FUTURE_TIMESTAMP_MILLIS);
307 }
308 }
309 Ok(())
310 }
311
312 fn verify_signature(&self) -> anyhow::Result<()> {
313 let signature_hash = self.signature_hash_generate()?;
314 signing::verify(&VerificationKey::from_bytes(&self.verification_key_bytes)?, &self.signature, signature_hash.as_ref())?;
315
316 Ok(())
317 }
318
319 fn verify_server_id(&self) -> anyhow::Result<()> {
320 let (pow, pow_hash) = ServerId::pow_measure(
321 &self.pow_initial.sponsor_id,
322 &self.verification_key_bytes,
323 &self.pq_commitment_bytes,
324 &self.pow_initial.timestamp.encode_be(),
325 &self.pow_initial.content_hash,
326 &self.pow_initial.salt,
327 )?;
328 anyhow_assert_ge!(&pow, &config::SERVER_KEY_POW_MIN, "insufficient server_id pow");
329
330 let id = ServerId::server_pow_hash_to_id(pow_hash)?;
331
332 anyhow_assert_eq!(&self.id, &id, "served_id mismatch");
333
334 Ok(())
335 }
336
337 fn verify_pows(&self) -> anyhow::Result<()> {
338 self.pow_initial.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
339 self.pow_current_day.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
340 self.pow_current_month.verify(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
341 Ok(())
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use crate::protocol::peer::{Peer, PeerPow};
348 use crate::tools::server_id::ServerId;
349 use crate::tools::config;
350 use crate::tools::time::{DurationMillis, TimeMillis, MILLIS_IN_DAY, MILLIS_IN_MONTH, MILLIS_IN_YEAR};
351 use crate::tools::time_provider::time_provider::{RealTimeProvider, TimeProvider};
352 use crate::tools::pow_generator::single_threaded_pow_generator::SingleThreadedPowGenerator;
353 use crate::tools::tools;
354 use crate::tools::types::{Pow, VerificationKeyBytes};
355
356 async fn get_random_ingredients() -> anyhow::Result<(ServerId, Peer)> {
357 let time_provider = RealTimeProvider;
358 let pow_generator = SingleThreadedPowGenerator::new();
359 let server_id = ServerId::new("own_pow", &time_provider, Pow(0), true, &pow_generator).await?;
360
361 let mut peer = server_id.to_peer(&time_provider)?;
362 peer.pow_current_day = PeerPow::random();
363 peer.pow_current_month = PeerPow::random();
364 peer.address = tools::random_base64(16);
365
366 peer.sign(&time_provider, &server_id.keys.signature_key)?;
368
369 Ok((server_id, peer))
370 }
371
372 #[tokio::test]
373 async fn signing_test() -> anyhow::Result<()> {
374 let (_, peer) = get_random_ingredients().await?;
375
376 peer.verify_signature()?;
378 Ok(())
379 }
380
381 #[tokio::test]
382 async fn signing_fail_test() -> anyhow::Result<()> {
383 {
384 let (_, mut peer) = get_random_ingredients().await?;
385 peer.verification_key_bytes = VerificationKeyBytes::zero();
386 peer.verify_signature().unwrap_err();
387 }
388
389 {
390 let (_, mut peer) = get_random_ingredients().await?;
391 peer.pow_initial = PeerPow::random();
392 peer.verify_signature().unwrap_err();
393 }
394
395 {
396 let (_, mut peer) = get_random_ingredients().await?;
397 peer.pow_current_day = PeerPow::random();
398 peer.verify_signature().unwrap_err();
399 }
400
401 {
402 let (_, mut peer) = get_random_ingredients().await?;
403 peer.pow_current_month = PeerPow::random();
404 peer.verify_signature().unwrap_err();
405 }
406
407 {
408 let (_, mut peer) = get_random_ingredients().await?;
409 peer.address = tools::random_base64(16);
410 peer.verify_signature().unwrap_err();
411 }
412
413 {
414 let (_, mut peer) = get_random_ingredients().await?;
415 peer.timestamp = TimeMillis::random();
416 peer.verify_signature().unwrap_err();
417 }
418
419 Ok(())
420 }
421
422 fn peer_pow_at(timestamp: TimeMillis, pow: Pow) -> PeerPow {
423 PeerPow { timestamp, pow, ..PeerPow::zero() }
424 }
425
426 #[test]
428 fn pow_decayed_is_bounded_for_future_timestamp() {
429 let now = RealTimeProvider.current_time_millis();
430 let far_future_peer_pow = peer_pow_at(now + MILLIS_IN_YEAR.const_mul(75), Pow(20));
431 assert_eq!(far_future_peer_pow.pow_decayed_day(now), 20.0);
432 assert_eq!(far_future_peer_pow.pow_decayed_month(now), 20.0);
433 }
434
435 #[test]
437 fn pow_decayed_zero_pow_future_timestamp_is_not_nan() {
438 let now = RealTimeProvider.current_time_millis();
439 let far_future_peer_pow = peer_pow_at(now + MILLIS_IN_YEAR.const_mul(75), Pow(0));
440 assert_eq!(far_future_peer_pow.pow_decayed_day(now), 0.0);
441 assert_eq!(far_future_peer_pow.pow_decayed_month(now), 0.0);
442 }
443
444 #[test]
445 fn pow_decayed_still_halves_per_halflife() {
446 let now = RealTimeProvider.current_time_millis();
447 assert_eq!(peer_pow_at(now, Pow(20)).pow_decayed_day(now), 20.0);
448 assert_eq!(peer_pow_at(now - MILLIS_IN_DAY, Pow(20)).pow_decayed_day(now), 10.0);
449 assert_eq!(peer_pow_at(now - MILLIS_IN_DAY.const_mul(2), Pow(20)).pow_decayed_day(now), 5.0);
450 assert_eq!(peer_pow_at(now - MILLIS_IN_MONTH, Pow(20)).pow_decayed_month(now), 10.0);
451 }
452
453 #[tokio::test]
454 async fn verify_timestamps_accepts_within_drift_and_rejects_beyond() -> anyhow::Result<()> {
455 let (_, honest_peer) = get_random_ingredients().await?;
456 let now = RealTimeProvider.current_time_millis();
457 let latest_allowed_time_millis = now + config::PEER_MAX_FUTURE_TIMESTAMP_MILLIS;
458 let too_far_time_millis = latest_allowed_time_millis + DurationMillis(1);
459
460 let setters: [(&str, fn(&mut Peer, TimeMillis)); 4] = [
461 ("timestamp", |peer, timestamp| peer.timestamp = timestamp),
462 ("pow_initial", |peer, timestamp| peer.pow_initial.timestamp = timestamp),
463 ("pow_current_day", |peer, timestamp| peer.pow_current_day.timestamp = timestamp),
464 ("pow_current_month", |peer, timestamp| peer.pow_current_month.timestamp = timestamp),
465 ];
466 for (field_name, set_timestamp) in setters {
467 let mut peer = honest_peer.clone();
468
469 set_timestamp(&mut peer, now - MILLIS_IN_YEAR);
470 assert!(peer.verify_timestamps(now).is_ok(), "{} in the past must be accepted", field_name);
471
472 set_timestamp(&mut peer, latest_allowed_time_millis);
473 assert!(peer.verify_timestamps(now).is_ok(), "{} at the drift limit must be accepted", field_name);
474
475 set_timestamp(&mut peer, too_far_time_millis);
476 assert!(peer.verify_timestamps(now).is_err(), "{} beyond the drift limit must be rejected", field_name);
477 }
478 Ok(())
479 }
480}