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