1use std::{
8 collections::{HashMap, HashSet},
9 time::{Duration, SystemTime, UNIX_EPOCH},
10};
11
12use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
13use iroh::{PublicKey, SecretKey};
14use iroh_blobs::Hash;
15use iroh_gossip::{
16 TopicId,
17 api::{GossipSender, GossipTopic},
18};
19use serde::{Deserialize, Serialize};
20
21use super::{
22 IndexingDatabase,
23 resilience::FetchFailureKind,
24 wot::{ProviderTrustAction, ProviderTrustRecord, TrustPolicy},
25};
26use crate::{
27 error::{Result, SyncwebError},
28 gossip::SignedGossipMessage,
29 node::gossip_service::GossipService,
30};
31
32const REPUTATION_SIGNAL_CONTEXT: &[u8] = b"syncweb/provider-trust/v1\0";
33const TRUST_STREAM_TOPIC_SEED: &[u8] = b"syncweb/provider-trust-stream/v1";
34const DEFAULT_DECAY_HALF_LIFE: Duration = Duration::from_hours(24);
35const DEFAULT_TEMPORARY_BAN: Duration = Duration::from_hours(1);
36const DEFAULT_BACKOFF_FACTOR: f64 = 2.0;
37const DEFAULT_MAX_BAN: Duration = Duration::from_hours(24 * 30);
38const DEFAULT_SIGNAL_TTL: Duration = Duration::from_hours(168);
39const DEFAULT_SIGNAL_BATCH_SIZE: usize = 64;
40const AUTO_BAN_FAILURE_THRESHOLD: u32 = 3;
41const DEFAULT_BAN_HISTORY_RETENTION: Duration = Duration::from_hours(720);
42
43#[derive(Clone, Debug, Serialize, Deserialize)]
45#[non_exhaustive]
46pub struct ProviderReputation {
47 pub provider: PublicKey,
48 pub total_fetches: u64,
49 pub successful_fetches: u64,
50 pub failed_fetches: u64,
51 pub consecutive_failures: u32,
52 pub last_success_at: Option<u64>,
53 pub last_failure_at: Option<u64>,
54}
55
56impl ProviderReputation {
57 #[must_use]
58 pub const fn new(provider: PublicKey) -> Self {
59 Self {
60 provider,
61 total_fetches: 0,
62 successful_fetches: 0,
63 failed_fetches: 0,
64 consecutive_failures: 0,
65 last_success_at: None,
66 last_failure_at: None,
67 }
68 }
69
70 pub const fn record_success(&mut self, now: u64) {
71 self.total_fetches = self.total_fetches.saturating_add(1);
72 self.successful_fetches = self.successful_fetches.saturating_add(1);
73 self.consecutive_failures = 0;
74 self.last_success_at = Some(now);
75 }
76
77 pub const fn record_failure(&mut self, _kind: FetchFailureKind, now: u64) {
78 self.total_fetches = self.total_fetches.saturating_add(1);
79 self.failed_fetches = self.failed_fetches.saturating_add(1);
80 self.consecutive_failures = self.consecutive_failures.saturating_add(1);
81 self.last_failure_at = Some(now);
82 }
83
84 #[must_use]
89 pub fn reliability_score(&self, now: u64) -> f64 {
90 self.reliability_score_with(now, DEFAULT_DECAY_HALF_LIFE, 2.0)
91 }
92
93 #[must_use]
94 pub fn is_reliable(&self, threshold: f64) -> bool {
95 self.reliability_score(current_epoch_seconds()) > threshold
96 }
97
98 #[must_use]
99 pub fn is_reliable_at(&self, threshold: f64, now: u64) -> bool {
100 self.reliability_score(now) > threshold
101 }
102
103 #[must_use]
104 pub const fn should_auto_ban(&self, consecutive_threshold: u32) -> bool {
105 self.consecutive_failures >= consecutive_threshold
106 }
107
108 fn reliability_score_with(&self, now: u64, half_life: Duration, failure_weight: f64) -> f64 {
109 if self.total_fetches == 0 {
110 return 0.5;
111 }
112 let failure_count = u32::try_from(self.failed_fetches.min(u64::from(u32::MAX))).unwrap_or(u32::MAX);
113 let success_count = u32::try_from(self.successful_fetches.min(u64::from(u32::MAX))).unwrap_or(u32::MAX);
114 let failures = std::ops::Mul::mul(failure_weight.max(0.0), f64::from(failure_count));
115 let denominator = std::ops::Add::add(f64::from(success_count), failures);
116 let base = if denominator == 0.0 {
117 0.5
118 } else {
119 std::ops::Div::div(f64::from(success_count), denominator)
120 };
121 let latest = self.last_success_at.max(self.last_failure_at).unwrap_or(now);
122 let age_seconds = now.saturating_sub(latest).min(u64::from(u32::MAX));
123 let age = f64::from(u32::try_from(age_seconds).unwrap_or(u32::MAX));
124 let half_life_seconds = half_life.as_secs_f64().max(1.0);
125 let decay = 0.5_f64.powf(std::ops::Div::div(age, half_life_seconds));
126 std::ops::Sub::sub(base, 0.5).mul_add(decay, 0.5).clamp(0.0, 1.0)
127 }
128}
129
130#[derive(Clone, Copy, Debug)]
132#[non_exhaustive]
133pub struct ReputationConfig {
134 pub min_samples: usize,
135 pub decay_half_life: Duration,
136 pub failure_weight: f64,
137 pub temporary_ban_duration: Duration,
138 pub auto_ban_backoff_factor: f64,
139 pub max_auto_ban_duration: Duration,
140 pub ban_history_retention: Duration,
141}
142
143impl Default for ReputationConfig {
144 fn default() -> Self {
145 Self {
146 min_samples: 5,
147 decay_half_life: DEFAULT_DECAY_HALF_LIFE,
148 failure_weight: 2.0,
149 temporary_ban_duration: DEFAULT_TEMPORARY_BAN,
150 auto_ban_backoff_factor: DEFAULT_BACKOFF_FACTOR,
151 max_auto_ban_duration: DEFAULT_MAX_BAN,
152 ban_history_retention: DEFAULT_BAN_HISTORY_RETENTION,
153 }
154 }
155}
156
157#[derive(Clone, Debug)]
158struct AutoBan {
159 until: u64,
160 count: u32,
161 last_banned_at: u64,
162}
163
164#[derive(Clone, Debug)]
166pub struct ProviderReputationStore {
167 reputations: HashMap<PublicKey, ProviderReputation>,
168 config: ReputationConfig,
169 policy: TrustPolicy,
170 auto_bans: HashMap<PublicKey, AutoBan>,
171 signal_sequences: HashMap<(PublicKey, PublicKey), u64>,
172 pending_signals: Vec<ProviderTrustSignal>,
173 max_signal_batch: usize,
174 reporter: Option<PublicKey>,
175 next_signal_sequence: u64,
176 database: Option<IndexingDatabase>,
177}
178
179impl Default for ProviderReputationStore {
180 fn default() -> Self {
181 Self::new(ReputationConfig::default())
182 }
183}
184
185impl ProviderReputationStore {
186 #[must_use]
187 pub fn new(config: ReputationConfig) -> Self {
188 Self {
189 reputations: HashMap::new(),
190 config,
191 policy: TrustPolicy::new(),
192 auto_bans: HashMap::new(),
193 signal_sequences: HashMap::new(),
194 pending_signals: Vec::new(),
195 max_signal_batch: DEFAULT_SIGNAL_BATCH_SIZE,
196 reporter: None,
197 next_signal_sequence: 1,
198 database: None,
199 }
200 }
201
202 pub fn with_database(database: IndexingDatabase, config: ReputationConfig) -> Result<Self> {
211 let reputations = database.load_all_reputations().unwrap_or_default();
212 let signal_sequences = database.load_signal_sequences().unwrap_or_default();
213 let auto_bans = Self::reconstruct_auto_bans(&database);
214 Ok(Self {
215 reputations,
216 config,
217 policy: TrustPolicy::new(),
218 auto_bans,
219 signal_sequences,
220 pending_signals: Vec::new(),
221 max_signal_batch: DEFAULT_SIGNAL_BATCH_SIZE,
222 reporter: None,
223 next_signal_sequence: 1,
224 database: Some(database),
225 })
226 }
227
228 fn reconstruct_auto_bans(database: &IndexingDatabase) -> HashMap<PublicKey, AutoBan> {
229 let now = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs());
230 database
231 .load_auto_bans()
232 .unwrap_or_default()
233 .into_iter()
234 .filter_map(|(provider, (until, count))| {
235 until.filter(|u| *u > now).map(|u| {
236 (
237 provider,
238 AutoBan {
239 until: u,
240 count,
241 last_banned_at: u,
242 },
243 )
244 })
245 })
246 .collect()
247 }
248
249 #[must_use]
250 pub fn with_policy(config: ReputationConfig, policy: TrustPolicy) -> Self {
251 let mut store = Self::new(config);
252 store.policy = policy;
253 store
254 }
255
256 #[must_use]
257 pub const fn config(&self) -> &ReputationConfig {
258 &self.config
259 }
260
261 pub fn set_policy(&mut self, policy: TrustPolicy) {
262 self.policy = policy;
263 }
264
265 pub const fn set_reporter(&mut self, reporter: PublicKey) {
267 self.reporter = Some(reporter);
268 }
269
270 pub fn trust_reporter(&mut self, reporter: &PublicKey) -> Result<()> {
274 let verifying_key = VerifyingKey::from_bytes(reporter.as_bytes())
275 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid trust reporter: {error}")))?;
276 self.policy.trust_author(&verifying_key);
277 Ok(())
278 }
279
280 #[must_use]
281 pub fn reputation(&self, provider: PublicKey) -> ProviderReputation {
282 self.reputations
283 .get(&provider)
284 .cloned()
285 .unwrap_or_else(|| ProviderReputation::new(provider))
286 }
287
288 #[must_use]
289 pub fn score(&self, provider: PublicKey, now: u64) -> f64 {
290 let reputation = self.reputation(provider);
291 if usize::try_from(reputation.total_fetches).unwrap_or(usize::MAX) < self.config.min_samples {
292 0.5
293 } else {
294 reputation.reliability_score_with(now, self.config.decay_half_life, self.config.failure_weight)
295 }
296 }
297
298 pub fn record_fetch_result(&mut self, provider: PublicKey, success: bool, kind: FetchFailureKind, now: u64) {
299 let previous_score = self.score(provider, now);
300 {
301 let reputation = self
302 .reputations
303 .entry(provider)
304 .or_insert_with(|| ProviderReputation::new(provider));
305 if success {
306 reputation.record_success(now);
307 } else {
308 reputation.record_failure(kind, now);
309 if reputation.should_auto_ban(AUTO_BAN_FAILURE_THRESHOLD)
310 && self.auto_bans.get(&provider).is_none_or(|ban| ban.until <= now)
311 {
312 self.apply_auto_ban(provider, now);
313 }
314 }
315 }
316 let current_score = self.score(provider, now);
317 let crossed_to_unreliable = previous_score >= 0.5 && current_score < 0.5;
318 let crossed_to_reliable = previous_score < 0.5 && current_score >= 0.5;
319 if (crossed_to_unreliable || crossed_to_reliable)
320 && let Some(reporter) = self.reporter
321 {
322 let signal_kind = if crossed_to_reliable {
323 TrustSignalKind::ObservedSuccess
324 } else {
325 TrustSignalKind::ObservedFailure
326 };
327 let signal = ProviderTrustSignal {
328 provider,
329 signal: signal_kind,
330 hash: None,
331 reporter,
332 timestamp: now,
333 sequence: self.next_signal_sequence,
334 signature: None,
335 };
336 self.next_signal_sequence = self.next_signal_sequence.saturating_add(1);
337 self.enqueue_signal(signal);
338 }
339 if let Some(ref database) = self.database {
340 let auto_ban_until = self.auto_bans.get(&provider).map(|ban| ban.until);
341 let auto_ban_count = self.auto_bans.get(&provider).map_or(0, |ban| ban.count);
342 if let Some(rep) = self.reputations.get(&provider) {
343 let _ = database.upsert_reputation(&provider, rep, auto_ban_until, auto_ban_count);
344 }
345 }
346 }
347
348 pub fn record_success(&mut self, provider: PublicKey, now: u64) {
349 self.record_fetch_result(provider, true, FetchFailureKind::Unknown, now);
350 }
351
352 pub fn record_failure(&mut self, provider: PublicKey, kind: FetchFailureKind, now: u64) {
353 self.record_fetch_result(provider, false, kind, now);
354 }
355
356 #[must_use]
357 pub fn is_banned(&self, provider: PublicKey, now: u64) -> bool {
358 self.auto_bans.get(&provider).is_some_and(|ban| ban.until > now)
359 }
360
361 #[must_use]
362 pub fn auto_ban_until(&self, provider: PublicKey) -> Option<u64> {
363 self.auto_bans.get(&provider).map(|ban| ban.until)
364 }
365
366 #[must_use]
367 pub fn auto_ban_count(&self, provider: PublicKey) -> u32 {
368 self.auto_bans.get(&provider).map_or(0, |ban| ban.count)
369 }
370
371 pub fn rejoin(&mut self, provider: PublicKey, now: u64) -> bool {
373 self.auto_bans.get(&provider).is_some_and(|ban| ban.until <= now)
374 }
375
376 #[must_use]
377 pub fn should_skip_provider(&self, provider: PublicKey, now: u64, threshold: f64) -> bool {
378 self.is_banned(provider, now) || self.score(provider, now) < threshold
379 }
380
381 #[must_use]
383 pub fn rank_providers(&self, now: u64, hash: Hash) -> Vec<PublicKey> {
384 let providers = self.reputations.keys().copied().collect::<Vec<_>>();
385 self.rank_provider_list(now, hash, &providers)
386 }
387
388 #[must_use]
389 pub fn rank_provider_list(&self, now: u64, hash: Hash, providers: &[PublicKey]) -> Vec<PublicKey> {
390 let mut unique = HashSet::new();
391 let mut ranked = providers
392 .iter()
393 .copied()
394 .filter(|provider| unique.insert(*provider))
395 .map(|provider| (self.score(provider, now), provider))
396 .collect::<Vec<_>>();
397 ranked.sort_by(|left, right| {
398 right
399 .0
400 .total_cmp(&left.0)
401 .then_with(|| xor_distance(hash, right.1).cmp(&xor_distance(hash, left.1)))
402 .then_with(|| left.1.as_bytes().cmp(right.1.as_bytes()))
403 });
404 ranked.into_iter().map(|(_, provider)| provider).collect()
405 }
406
407 pub fn purge_stale(&mut self, now: u64, ttl: Duration) {
408 let ttl_seconds = ttl.as_secs();
409 self.reputations.retain(|_, reputation| {
410 let last = reputation.last_success_at.max(reputation.last_failure_at);
411 last.is_some_and(|timestamp| now.saturating_sub(timestamp) <= ttl_seconds)
412 });
413 let retention_secs = self.config.ban_history_retention.as_secs();
414 self.auto_bans
415 .retain(|_, ban| ban.until > now || now.saturating_sub(ban.last_banned_at) <= retention_secs);
416 if let Some(ref database) = self.database {
417 let _ = database.delete_stale_reputations(now, ttl_seconds);
418 }
419 }
420
421 pub fn queue_trust_signal(&mut self, signal: ProviderTrustSignal) -> Result<bool> {
426 signal.validate()?;
427 Ok(self.enqueue_signal(signal))
428 }
429
430 fn enqueue_signal(&mut self, signal: ProviderTrustSignal) -> bool {
431 if self.pending_signals.iter().any(|existing| {
432 existing.provider == signal.provider
433 && existing.reporter == signal.reporter
434 && existing.signal == signal.signal
435 && existing.hash == signal.hash
436 }) {
437 return false;
438 }
439 if self.pending_signals.len() >= self.max_signal_batch {
440 self.pending_signals.remove(0);
441 }
442 self.pending_signals.push(signal);
443 true
444 }
445
446 #[must_use]
447 pub fn pending_trust_signals(&self) -> &[ProviderTrustSignal] {
448 &self.pending_signals
449 }
450
451 pub fn take_pending_trust_signals(&mut self) -> Vec<ProviderTrustSignal> {
452 std::mem::take(&mut self.pending_signals)
453 }
454
455 pub fn set_max_signal_batch(&mut self, max_signal_batch: usize) {
456 self.max_signal_batch = max_signal_batch.max(1);
457 let keep_from = self.pending_signals.len().saturating_sub(self.max_signal_batch);
458 if keep_from > 0 {
459 self.pending_signals.drain(..keep_from);
460 }
461 }
462
463 pub fn ingest_trust_signal(&mut self, signal: ProviderTrustSignal) -> Result<bool> {
468 signal.verify()?;
469 let ProviderTrustSignal {
470 provider,
471 signal: kind,
472 hash,
473 reporter,
474 timestamp,
475 sequence,
476 signature,
477 } = signal;
478 drop(signature);
479 let reporter_id = hex::encode(reporter.as_bytes());
480 if !self.policy.is_trusted_for_at(&reporter_id, hash.as_ref(), timestamp) {
481 return Ok(false);
482 }
483 let sequence_key = (reporter, provider);
484 if self
485 .signal_sequences
486 .get(&sequence_key)
487 .is_some_and(|last_sequence| *last_sequence >= sequence)
488 {
489 return Ok(false);
490 }
491 self.signal_sequences.insert(sequence_key, sequence);
492 match kind {
493 TrustSignalKind::ObservedSuccess => {
494 self.record_fetch_result(provider, true, FetchFailureKind::Unknown, timestamp);
495 }
496 TrustSignalKind::ObservedFailure => {
497 self.record_fetch_result(provider, false, FetchFailureKind::Unknown, timestamp);
498 }
499 TrustSignalKind::ObservedCorruption => {
500 self.record_fetch_result(provider, false, FetchFailureKind::Corruption, timestamp);
501 }
502 }
503 Ok(true)
504 }
505
506 pub async fn publish_signal(
513 &self,
514 gossip: &GossipService,
515 sender: &GossipSender,
516 signal: &ProviderTrustSignal,
517 ) -> Result<()> {
518 signal.verify()?;
519 gossip.publish(sender, signal.to_bytes()?).await
520 }
521
522 pub async fn subscribe_trust_stream(
528 &self,
529 gossip: &GossipService,
530 bootstrap: Vec<PublicKey>,
531 ) -> Result<GossipTopic> {
532 gossip.subscribe(trust_stream_topic(), bootstrap).await
533 }
534
535 fn apply_auto_ban(&mut self, provider: PublicKey, now: u64) {
536 let previous_count = self.auto_bans.get(&provider).map_or(0, |ban| ban.count);
537 let count = previous_count.saturating_add(1);
538 let exponent = i32::try_from(count.saturating_sub(1)).unwrap_or(i32::MAX);
539 let scaled = std::ops::Mul::mul(
540 self.config.temporary_ban_duration.as_secs_f64(),
541 self.config.auto_ban_backoff_factor.max(1.0).powi(exponent),
542 );
543 let duration = Duration::from_secs_f64(scaled.min(self.config.max_auto_ban_duration.as_secs_f64()));
544 self.auto_bans.insert(
545 provider,
546 AutoBan {
547 until: now.saturating_add(duration.as_secs()),
548 count,
549 last_banned_at: now,
550 },
551 );
552 }
553}
554
555#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
557#[non_exhaustive]
558pub struct ProviderTrustSignal {
559 pub provider: PublicKey,
560 pub signal: TrustSignalKind,
561 pub hash: Option<Hash>,
562 pub reporter: PublicKey,
563 pub timestamp: u64,
564 pub sequence: u64,
565 pub signature: Option<String>,
566}
567
568#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
570#[non_exhaustive]
571pub enum TrustSignalKind {
572 ObservedSuccess,
573 ObservedFailure,
574 ObservedCorruption,
575}
576
577impl ProviderTrustSignal {
578 pub fn new(
584 provider: PublicKey,
585 kind: TrustSignalKind,
586 hash: Option<Hash>,
587 reporter: PublicKey,
588 timestamp: u64,
589 sequence: u64,
590 ) -> Result<Self> {
591 let signal = Self {
592 provider,
593 signal: kind,
594 hash,
595 reporter,
596 timestamp,
597 sequence,
598 signature: None,
599 };
600 signal.validate()?;
601 Ok(signal)
602 }
603
604 pub fn new_with_time(
610 provider: PublicKey,
611 kind: TrustSignalKind,
612 hash: Option<Hash>,
613 sequence: u64,
614 signing_key: &SigningKey,
615 ) -> Result<Self> {
616 let reporter = PublicKey::from_bytes(&signing_key.verifying_key().to_bytes())
617 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid trust reporter: {error}")))?;
618 let mut created = Self::new(provider, kind, hash, reporter, current_epoch_seconds(), sequence)?;
619 created.sign(signing_key)?;
620 Ok(created)
621 }
622
623 pub fn from_trust_record(record: &ProviderTrustRecord, signing_key: &SigningKey) -> Result<Self> {
635 Self::new_with_time(
636 record.provider,
637 match record.action {
638 ProviderTrustAction::Vouch => TrustSignalKind::ObservedSuccess,
639 ProviderTrustAction::Distrust => TrustSignalKind::ObservedFailure,
640 ProviderTrustAction::Trust | ProviderTrustAction::Warn => {
641 return Err(SyncwebError::InvalidConfig(
642 "only vouch/distrust records can be converted to trust signals".into(),
643 ));
644 }
645 },
646 record.scope,
647 record.sequence,
648 signing_key,
649 )
650 }
651
652 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
656 if self.reporter.as_bytes() != signing_key.verifying_key().as_bytes() {
657 return Err(SyncwebError::InvalidIdentity(
658 "provider trust signal signer does not match reporter".to_owned(),
659 ));
660 }
661 self.signature = Some(hex::encode(signing_key.sign(&self.unsigned_bytes()?).to_bytes()));
662 Ok(())
663 }
664
665 pub fn sign_with_secret_key(&mut self, secret_key: &SecretKey) -> Result<()> {
669 self.sign(&SigningKey::from_bytes(&secret_key.to_bytes()))
670 }
671
672 pub fn verify(&self) -> Result<()> {
676 self.verify_at(current_epoch_seconds())
677 }
678
679 pub fn verify_at(&self, now: u64) -> Result<()> {
683 const CLOCK_SKEW_TOLERANCE: u64 = 3600;
684 self.validate()?;
685 if self.timestamp > now.saturating_add(CLOCK_SKEW_TOLERANCE) {
686 return Err(SyncwebError::InvalidConfig(
687 "provider trust signal is from the future".to_owned(),
688 ));
689 }
690 if now.saturating_sub(self.timestamp) > DEFAULT_SIGNAL_TTL.as_secs() {
691 return Err(SyncwebError::InvalidConfig(
692 "provider trust signal has expired".to_owned(),
693 ));
694 }
695 let signature_text = self
696 .signature
697 .as_deref()
698 .ok_or_else(|| SyncwebError::InvalidConfig("provider trust signal must contain a signature".to_owned()))?;
699 let signature_bytes = hex::decode(signature_text)
700 .map_err(|error| SyncwebError::InvalidConfig(format!("invalid provider trust signature: {error}")))?;
701 let signature = Signature::from_slice(&signature_bytes)
702 .map_err(|error| SyncwebError::InvalidConfig(format!("invalid provider trust signature: {error}")))?;
703 let key = VerifyingKey::from_bytes(self.reporter.as_bytes())
704 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid provider trust reporter: {error}")))?;
705 key.verify(&self.unsigned_bytes()?, &signature)
706 .map_err(|error| SyncwebError::InvalidConfig(format!("provider trust signature is invalid: {error}")))
707 }
708
709 pub fn validate(&self) -> Result<()> {
713 if self.sequence == 0 {
714 return Err(SyncwebError::InvalidConfig(
715 "provider trust signal sequence must be greater than zero".to_owned(),
716 ));
717 }
718 let _provider = VerifyingKey::from_bytes(self.provider.as_bytes())
719 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid provider identity: {error}")))?;
720 let _reporter = VerifyingKey::from_bytes(self.reporter.as_bytes())
721 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid reporter identity: {error}")))?;
722 Ok(())
723 }
724
725 pub fn to_bytes(&self) -> Result<Vec<u8>> {
729 self.validate()?;
730 serde_json::to_vec(self)
731 .map_err(|error| SyncwebError::operation("failed to serialize provider trust signal", error))
732 }
733
734 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
738 let signal: Self = serde_json::from_slice(bytes.as_ref())
739 .map_err(|error| SyncwebError::operation("failed to deserialize provider trust signal", error))?;
740 signal.validate()?;
741 Ok(signal)
742 }
743
744 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
745 let mut unsigned = self.clone();
746 unsigned.signature = None;
747 let encoded = serde_json::to_vec(&unsigned)
748 .map_err(|error| SyncwebError::operation("failed to serialize unsigned provider trust signal", error))?;
749 let mut bytes = Vec::with_capacity(REPUTATION_SIGNAL_CONTEXT.len().saturating_add(encoded.len()));
750 bytes.extend_from_slice(REPUTATION_SIGNAL_CONTEXT);
751 bytes.extend_from_slice(&encoded);
752 Ok(bytes)
753 }
754}
755
756#[must_use]
757pub fn trust_stream_topic() -> TopicId {
758 TopicId::from_bytes(*blake3::hash(TRUST_STREAM_TOPIC_SEED).as_bytes())
759}
760
761fn xor_distance(hash: Hash, provider: PublicKey) -> [u8; 32] {
762 let mut distance = [0_u8; 32];
763 for ((target, hash_byte), provider_byte) in distance.iter_mut().zip(hash.as_bytes()).zip(provider.as_bytes()) {
764 *target = *hash_byte ^ *provider_byte;
765 }
766 distance
767}
768
769impl SignedGossipMessage for ProviderTrustSignal {
770 fn verify_signature(&self) -> Result<()> {
771 self.verify()
772 }
773}
774
775fn current_epoch_seconds() -> u64 {
776 SystemTime::now()
777 .duration_since(UNIX_EPOCH)
778 .map_or(0, |duration| duration.as_secs())
779}