chie_shared/types/
bandwidth.rs

1//! Bandwidth proof protocol types and validation.
2
3#[cfg(feature = "schema")]
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use super::core::{
8    Bytes, ContentCid, MAX_CONTENT_SIZE, MAX_LATENCY_MS, MIN_LATENCY_MS, PeerIdString,
9    TIMESTAMP_TOLERANCE_MS,
10};
11
12// Used in tests
13#[cfg(test)]
14use super::core::CHUNK_SIZE;
15use super::validation::ValidationError;
16
17/// Chunk request for bandwidth proof protocol.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[cfg_attr(feature = "schema", derive(JsonSchema))]
20pub struct ChunkRequest {
21    /// IPFS CID of the content.
22    pub content_cid: ContentCid,
23    /// Index of the requested chunk.
24    pub chunk_index: u64,
25    /// Random nonce to prevent replay attacks.
26    pub challenge_nonce: [u8; 32],
27    /// Requester's peer ID.
28    pub requester_peer_id: PeerIdString,
29    /// Requester's public key for verification.
30    pub requester_public_key: [u8; 32],
31    /// Request timestamp (Unix timestamp milliseconds).
32    pub timestamp_ms: i64,
33}
34
35impl ChunkRequest {
36    /// Create a new chunk request with current timestamp.
37    ///
38    /// # Example
39    ///
40    /// ```
41    /// use chie_shared::types::bandwidth::ChunkRequest;
42    ///
43    /// let nonce = [42u8; 32];
44    /// let pubkey = [1u8; 32];
45    ///
46    /// let request = ChunkRequest::new(
47    ///     "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
48    ///     0,
49    ///     nonce,
50    ///     "12D3KooWRequesterPeerID",
51    ///     pubkey,
52    /// );
53    ///
54    /// assert_eq!(request.content_cid, "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
55    /// assert_eq!(request.chunk_index, 0);
56    /// assert!(request.is_timestamp_valid());
57    /// ```
58    #[must_use]
59    pub fn new(
60        content_cid: impl Into<String>,
61        chunk_index: u64,
62        challenge_nonce: [u8; 32],
63        requester_peer_id: impl Into<String>,
64        requester_public_key: [u8; 32],
65    ) -> Self {
66        Self {
67            content_cid: content_cid.into(),
68            chunk_index,
69            challenge_nonce,
70            requester_peer_id: requester_peer_id.into(),
71            requester_public_key,
72            timestamp_ms: chrono::Utc::now().timestamp_millis(),
73        }
74    }
75
76    /// Check if the request timestamp is still valid.
77    #[must_use]
78    pub fn is_timestamp_valid(&self) -> bool {
79        let now_ms = chrono::Utc::now().timestamp_millis();
80        let age_ms = now_ms - self.timestamp_ms;
81        (0..=TIMESTAMP_TOLERANCE_MS).contains(&age_ms)
82    }
83}
84
85/// Chunk response for bandwidth proof protocol.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[cfg_attr(feature = "schema", derive(JsonSchema))]
88pub struct ChunkResponse {
89    /// Encrypted chunk data.
90    pub encrypted_chunk: Vec<u8>,
91    /// BLAKE3 hash of the chunk (before encryption).
92    pub chunk_hash: [u8; 32],
93    /// Provider's Ed25519 signature over (`challenge_nonce` || `chunk_hash`).
94    pub provider_signature: Vec<u8>,
95    /// Provider's public key for verification.
96    pub provider_public_key: [u8; 32],
97    /// Echo of the challenge nonce.
98    pub challenge_echo: [u8; 32],
99    /// Response timestamp.
100    pub timestamp_ms: i64,
101}
102
103impl ChunkResponse {
104    /// Create a new chunk response with current timestamp.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// use chie_shared::types::bandwidth::ChunkResponse;
110    ///
111    /// let encrypted_data = vec![1, 2, 3, 4, 5];
112    /// let chunk_hash = [7u8; 32];
113    /// let signature = vec![9u8; 64];
114    /// let provider_pubkey = [10u8; 32];
115    /// let challenge_echo = [42u8; 32];
116    ///
117    /// let response = ChunkResponse::new(
118    ///     encrypted_data.clone(),
119    ///     chunk_hash,
120    ///     signature,
121    ///     provider_pubkey,
122    ///     challenge_echo,
123    /// );
124    ///
125    /// assert_eq!(response.chunk_size(), 5);
126    /// assert!(response.verify_challenge_echo(&challenge_echo));
127    /// ```
128    #[must_use]
129    pub fn new(
130        encrypted_chunk: Vec<u8>,
131        chunk_hash: [u8; 32],
132        provider_signature: Vec<u8>,
133        provider_public_key: [u8; 32],
134        challenge_echo: [u8; 32],
135    ) -> Self {
136        Self {
137            encrypted_chunk,
138            chunk_hash,
139            provider_signature,
140            provider_public_key,
141            challenge_echo,
142            timestamp_ms: chrono::Utc::now().timestamp_millis(),
143        }
144    }
145
146    /// Get the size of the encrypted chunk in bytes.
147    #[must_use]
148    pub fn chunk_size(&self) -> usize {
149        self.encrypted_chunk.len()
150    }
151
152    /// Verify that the challenge nonce matches the expected value.
153    #[must_use]
154    pub fn verify_challenge_echo(&self, expected_nonce: &[u8; 32]) -> bool {
155        &self.challenge_echo == expected_nonce
156    }
157}
158
159/// Bandwidth proof submitted to the coordinator.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[cfg_attr(feature = "schema", derive(JsonSchema))]
162pub struct BandwidthProof {
163    /// Unique session identifier.
164    pub session_id: uuid::Uuid,
165    /// Content CID.
166    pub content_cid: ContentCid,
167    /// Chunk index transferred.
168    pub chunk_index: u64,
169    /// Bytes transferred.
170    pub bytes_transferred: Bytes,
171    /// Provider's peer ID.
172    pub provider_peer_id: PeerIdString,
173    /// Requester's peer ID.
174    pub requester_peer_id: PeerIdString,
175    /// Provider's public key.
176    pub provider_public_key: Vec<u8>,
177    /// Requester's public key.
178    pub requester_public_key: Vec<u8>,
179    /// Provider's signature over the transfer data.
180    pub provider_signature: Vec<u8>,
181    /// Requester's signature confirming receipt.
182    pub requester_signature: Vec<u8>,
183    /// Challenge nonce used.
184    pub challenge_nonce: Vec<u8>,
185    /// Chunk hash for verification.
186    pub chunk_hash: Vec<u8>,
187    /// Transfer start timestamp (ms).
188    pub start_timestamp_ms: i64,
189    /// Transfer end timestamp (ms).
190    pub end_timestamp_ms: i64,
191    /// Transfer latency in milliseconds.
192    pub latency_ms: u32,
193}
194
195impl BandwidthProof {
196    /// Get the message that was signed by the provider.
197    #[must_use]
198    pub fn provider_sign_message(&self) -> Vec<u8> {
199        let mut msg = Vec::new();
200        msg.extend_from_slice(&self.challenge_nonce);
201        msg.extend_from_slice(&self.chunk_hash);
202        msg.extend_from_slice(&self.requester_public_key);
203        msg
204    }
205
206    /// Get the message that was signed by the requester.
207    #[must_use]
208    pub fn requester_sign_message(&self) -> Vec<u8> {
209        let mut msg = Vec::new();
210        msg.extend_from_slice(&self.challenge_nonce);
211        msg.extend_from_slice(&self.chunk_hash);
212        msg.extend_from_slice(&self.provider_public_key);
213        msg.extend_from_slice(&self.provider_signature);
214        msg
215    }
216
217    /// Validate the proof structure (not cryptographic verification).
218    ///
219    /// # Errors
220    ///
221    /// Returns validation errors if:
222    /// - Public key lengths are not 32 bytes
223    /// - Signature lengths are not 64 bytes
224    /// - Nonce or hash lengths are not 32 bytes
225    /// - Timestamps are invalid or out of range
226    ///
227    /// # Example
228    ///
229    /// ```
230    /// use chie_shared::types::bandwidth::BandwidthProofBuilder;
231    ///
232    /// // Valid proof
233    /// let valid_proof = BandwidthProofBuilder::new()
234    ///     .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
235    ///     .provider_peer_id("12D3KooWProvider")
236    ///     .requester_peer_id("12D3KooWRequester")
237    ///     .provider_public_key(vec![1u8; 32])
238    ///     .requester_public_key(vec![2u8; 32])
239    ///     .provider_signature(vec![3u8; 64])
240    ///     .requester_signature(vec![4u8; 64])
241    ///     .challenge_nonce(vec![5u8; 32])
242    ///     .chunk_hash(vec![6u8; 32])
243    ///     .timestamps(1000, 1100)
244    ///     .build()
245    ///     .unwrap();
246    ///
247    /// assert!(valid_proof.validate().is_ok());
248    ///
249    /// // Invalid proof (wrong signature length)
250    /// let invalid_proof = BandwidthProofBuilder::new()
251    ///     .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
252    ///     .provider_peer_id("12D3KooWProvider")
253    ///     .requester_peer_id("12D3KooWRequester")
254    ///     .provider_public_key(vec![1u8; 32])
255    ///     .requester_public_key(vec![2u8; 32])
256    ///     .provider_signature(vec![3u8; 32])  // Wrong length!
257    ///     .requester_signature(vec![4u8; 64])
258    ///     .challenge_nonce(vec![5u8; 32])
259    ///     .chunk_hash(vec![6u8; 32])
260    ///     .timestamps(1000, 1100)
261    ///     .build()
262    ///     .unwrap();
263    ///
264    /// assert!(invalid_proof.validate().is_err());
265    /// ```
266    pub fn validate(&self) -> Result<(), Vec<ValidationError>> {
267        let mut errors = Vec::new();
268
269        // Validate public key lengths
270        if self.provider_public_key.len() != 32 {
271            errors.push(ValidationError::InvalidPublicKeyLength {
272                expected: 32,
273                actual: self.provider_public_key.len(),
274            });
275        }
276        if self.requester_public_key.len() != 32 {
277            errors.push(ValidationError::InvalidPublicKeyLength {
278                expected: 32,
279                actual: self.requester_public_key.len(),
280            });
281        }
282
283        // Validate signature lengths
284        if self.provider_signature.len() != 64 {
285            errors.push(ValidationError::InvalidSignatureLength {
286                expected: 64,
287                actual: self.provider_signature.len(),
288            });
289        }
290        if self.requester_signature.len() != 64 {
291            errors.push(ValidationError::InvalidSignatureLength {
292                expected: 64,
293                actual: self.requester_signature.len(),
294            });
295        }
296
297        // Validate nonce length
298        if self.challenge_nonce.len() != 32 {
299            errors.push(ValidationError::InvalidNonceLength {
300                expected: 32,
301                actual: self.challenge_nonce.len(),
302            });
303        }
304
305        // Validate hash length
306        if self.chunk_hash.len() != 32 {
307            errors.push(ValidationError::InvalidHashLength {
308                expected: 32,
309                actual: self.chunk_hash.len(),
310            });
311        }
312
313        // Validate CID is not empty
314        if self.content_cid.is_empty() {
315            errors.push(ValidationError::EmptyCid);
316        }
317
318        // Validate self-transfer
319        if self.provider_peer_id == self.requester_peer_id {
320            errors.push(ValidationError::SelfTransfer);
321        }
322
323        // Validate timestamp order
324        if self.start_timestamp_ms > self.end_timestamp_ms {
325            errors.push(ValidationError::InvalidTimestampOrder {
326                start_ms: self.start_timestamp_ms,
327                end_ms: self.end_timestamp_ms,
328            });
329        }
330
331        // Validate latency bounds
332        if self.latency_ms < MIN_LATENCY_MS {
333            errors.push(ValidationError::LatencyTooLow {
334                latency_ms: self.latency_ms,
335                min_ms: MIN_LATENCY_MS,
336            });
337        }
338        if self.latency_ms > MAX_LATENCY_MS {
339            errors.push(ValidationError::LatencyTooHigh {
340                latency_ms: self.latency_ms,
341                max_ms: MAX_LATENCY_MS,
342            });
343        }
344
345        // Validate latency matches timestamps (with tolerance)
346        let calculated_latency = self.end_timestamp_ms - self.start_timestamp_ms;
347        let latency_diff = (calculated_latency - i64::from(self.latency_ms)).abs();
348        if latency_diff > 100 {
349            // 100ms tolerance for clock skew
350            errors.push(ValidationError::LatencyMismatch {
351                calculated_ms: calculated_latency,
352                reported_ms: self.latency_ms,
353            });
354        }
355
356        // Validate bytes transferred
357        if self.bytes_transferred > MAX_CONTENT_SIZE {
358            errors.push(ValidationError::BytesExceedMax {
359                bytes: self.bytes_transferred,
360                max: MAX_CONTENT_SIZE,
361            });
362        }
363
364        if errors.is_empty() {
365            Ok(())
366        } else {
367            Err(errors)
368        }
369    }
370
371    /// Validate timestamp against current time.
372    ///
373    /// # Errors
374    ///
375    /// Returns `ValidationError` if:
376    /// - Timestamp is in the future
377    /// - Timestamp is too old (beyond tolerance window)
378    pub fn validate_timestamp(&self, now_ms: i64) -> Result<(), ValidationError> {
379        if self.end_timestamp_ms > now_ms {
380            return Err(ValidationError::TimestampInFuture {
381                timestamp_ms: self.end_timestamp_ms,
382                now_ms,
383            });
384        }
385        if now_ms - self.end_timestamp_ms > TIMESTAMP_TOLERANCE_MS {
386            return Err(ValidationError::TimestampTooOld {
387                timestamp_ms: self.end_timestamp_ms,
388                now_ms,
389                tolerance_ms: TIMESTAMP_TOLERANCE_MS,
390            });
391        }
392        Ok(())
393    }
394
395    /// Check if this proof is valid (basic structural validation).
396    #[must_use]
397    pub fn is_valid(&self) -> bool {
398        self.validate().is_ok()
399    }
400
401    /// Calculate the effective bandwidth in bytes per second.
402    #[must_use]
403    #[allow(clippy::cast_precision_loss)]
404    pub fn bandwidth_bps(&self) -> f64 {
405        if self.latency_ms == 0 {
406            return 0.0;
407        }
408        (self.bytes_transferred as f64 * 1000.0) / f64::from(self.latency_ms)
409    }
410
411    /// Check if this transfer meets the minimum quality threshold.
412    #[must_use]
413    /// Latency should be under 500ms for full reward.
414    pub fn meets_quality_threshold(&self) -> bool {
415        self.latency_ms <= 500
416    }
417
418    /// Get a penalty multiplier based on latency (0.5x for high latency).
419    ///
420    /// # Example
421    ///
422    /// ```
423    /// use chie_shared::types::bandwidth::BandwidthProofBuilder;
424    ///
425    /// // Good quality transfer (low latency)
426    /// let fast_proof = BandwidthProofBuilder::new()
427    ///     .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
428    ///     .provider_peer_id("12D3KooWProvider")
429    ///     .requester_peer_id("12D3KooWRequester")
430    ///     .provider_public_key(vec![1u8; 32])
431    ///     .requester_public_key(vec![2u8; 32])
432    ///     .provider_signature(vec![3u8; 64])
433    ///     .requester_signature(vec![4u8; 64])
434    ///     .challenge_nonce(vec![5u8; 32])
435    ///     .chunk_hash(vec![6u8; 32])
436    ///     .timestamps(1000, 1200)  // 200ms latency
437    ///     .build()
438    ///     .unwrap();
439    ///
440    /// assert_eq!(fast_proof.quality_multiplier(), 1.0);
441    /// assert!(fast_proof.meets_quality_threshold());
442    ///
443    /// // Poor quality transfer (high latency)
444    /// let slow_proof = BandwidthProofBuilder::new()
445    ///     .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
446    ///     .provider_peer_id("12D3KooWProvider")
447    ///     .requester_peer_id("12D3KooWRequester")
448    ///     .provider_public_key(vec![1u8; 32])
449    ///     .requester_public_key(vec![2u8; 32])
450    ///     .provider_signature(vec![3u8; 64])
451    ///     .requester_signature(vec![4u8; 64])
452    ///     .challenge_nonce(vec![5u8; 32])
453    ///     .chunk_hash(vec![6u8; 32])
454    ///     .timestamps(1000, 1800)  // 800ms latency
455    ///     .build()
456    ///     .unwrap();
457    ///
458    /// assert_eq!(slow_proof.quality_multiplier(), 0.5);
459    /// assert!(!slow_proof.meets_quality_threshold());
460    /// ```
461    #[must_use]
462    pub fn quality_multiplier(&self) -> f64 {
463        if self.latency_ms <= 500 { 1.0 } else { 0.5 }
464    }
465}
466
467/// Builder for `BandwidthProof`.
468#[derive(Debug, Default)]
469pub struct BandwidthProofBuilder {
470    session_id: Option<uuid::Uuid>,
471    content_cid: Option<ContentCid>,
472    chunk_index: u64,
473    bytes_transferred: Bytes,
474    provider_peer_id: Option<PeerIdString>,
475    requester_peer_id: Option<PeerIdString>,
476    provider_public_key: Option<Vec<u8>>,
477    requester_public_key: Option<Vec<u8>>,
478    provider_signature: Option<Vec<u8>>,
479    requester_signature: Option<Vec<u8>>,
480    challenge_nonce: Option<Vec<u8>>,
481    chunk_hash: Option<Vec<u8>>,
482    start_timestamp_ms: i64,
483    end_timestamp_ms: i64,
484    latency_ms: u32,
485}
486
487impl BandwidthProofBuilder {
488    /// Create a new builder.
489    ///
490    /// # Example
491    ///
492    /// ```
493    /// use chie_shared::types::bandwidth::BandwidthProofBuilder;
494    ///
495    /// let proof = BandwidthProofBuilder::new()
496    ///     .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
497    ///     .chunk_index(0)
498    ///     .bytes_transferred(262_144)
499    ///     .provider_peer_id("12D3KooWProviderPeerID")
500    ///     .requester_peer_id("12D3KooWRequesterPeerID")
501    ///     .provider_public_key(vec![1u8; 32])
502    ///     .requester_public_key(vec![2u8; 32])
503    ///     .provider_signature(vec![3u8; 64])
504    ///     .requester_signature(vec![4u8; 64])
505    ///     .challenge_nonce(vec![5u8; 32])
506    ///     .chunk_hash(vec![6u8; 32])
507    ///     .timestamps(1000, 1100)
508    ///     .build()
509    ///     .expect("Failed to build bandwidth proof");
510    ///
511    /// assert_eq!(proof.bytes_transferred, 262_144);
512    /// assert_eq!(proof.latency_ms, 100);
513    /// assert!(proof.is_valid());
514    /// assert_eq!(proof.bandwidth_bps(), 2_621_440.0);
515    /// ```
516    #[must_use]
517    pub fn new() -> Self {
518        Self::default()
519    }
520
521    /// Set the session ID (auto-generated if not set).
522    #[must_use]
523    pub fn session_id(mut self, id: uuid::Uuid) -> Self {
524        self.session_id = Some(id);
525        self
526    }
527
528    /// Set the content CID.
529    #[must_use]
530    pub fn content_cid(mut self, cid: impl Into<String>) -> Self {
531        self.content_cid = Some(cid.into());
532        self
533    }
534
535    /// Set the chunk index.
536    #[must_use]
537    pub fn chunk_index(mut self, index: u64) -> Self {
538        self.chunk_index = index;
539        self
540    }
541
542    /// Set bytes transferred.
543    #[must_use]
544    pub fn bytes_transferred(mut self, bytes: Bytes) -> Self {
545        self.bytes_transferred = bytes;
546        self
547    }
548
549    /// Set provider peer ID.
550    #[must_use]
551    pub fn provider_peer_id(mut self, peer_id: impl Into<String>) -> Self {
552        self.provider_peer_id = Some(peer_id.into());
553        self
554    }
555
556    /// Set requester peer ID.
557    #[must_use]
558    pub fn requester_peer_id(mut self, peer_id: impl Into<String>) -> Self {
559        self.requester_peer_id = Some(peer_id.into());
560        self
561    }
562
563    /// Set provider public key.
564    #[must_use]
565    pub fn provider_public_key(mut self, key: impl Into<Vec<u8>>) -> Self {
566        self.provider_public_key = Some(key.into());
567        self
568    }
569
570    /// Set requester public key.
571    #[must_use]
572    pub fn requester_public_key(mut self, key: impl Into<Vec<u8>>) -> Self {
573        self.requester_public_key = Some(key.into());
574        self
575    }
576
577    /// Set provider signature.
578    #[must_use]
579    pub fn provider_signature(mut self, sig: impl Into<Vec<u8>>) -> Self {
580        self.provider_signature = Some(sig.into());
581        self
582    }
583
584    /// Set requester signature.
585    #[must_use]
586    pub fn requester_signature(mut self, sig: impl Into<Vec<u8>>) -> Self {
587        self.requester_signature = Some(sig.into());
588        self
589    }
590
591    /// Set challenge nonce.
592    #[must_use]
593    pub fn challenge_nonce(mut self, nonce: impl Into<Vec<u8>>) -> Self {
594        self.challenge_nonce = Some(nonce.into());
595        self
596    }
597
598    /// Set chunk hash.
599    #[must_use]
600    pub fn chunk_hash(mut self, hash: impl Into<Vec<u8>>) -> Self {
601        self.chunk_hash = Some(hash.into());
602        self
603    }
604
605    /// Set timestamps.
606    #[must_use]
607    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
608    pub fn timestamps(mut self, start_ms: i64, end_ms: i64) -> Self {
609        self.start_timestamp_ms = start_ms;
610        self.end_timestamp_ms = end_ms;
611        self.latency_ms = (end_ms - start_ms).max(0) as u32;
612        self
613    }
614
615    /// Set latency directly (overrides calculated from timestamps).
616    #[must_use]
617    pub fn latency_ms(mut self, latency: u32) -> Self {
618        self.latency_ms = latency;
619        self
620    }
621
622    /// Build the `BandwidthProof`.
623    ///
624    /// # Errors
625    ///
626    /// Returns an error if required fields are missing:
627    /// - `content_cid`
628    /// - `provider_peer_id`
629    /// - `requester_peer_id`
630    /// - `provider_public_key`
631    /// - `requester_public_key`
632    pub fn build(self) -> Result<BandwidthProof, &'static str> {
633        Ok(BandwidthProof {
634            session_id: self.session_id.unwrap_or_else(uuid::Uuid::new_v4),
635            content_cid: self.content_cid.ok_or("content_cid is required")?,
636            chunk_index: self.chunk_index,
637            bytes_transferred: self.bytes_transferred,
638            provider_peer_id: self
639                .provider_peer_id
640                .ok_or("provider_peer_id is required")?,
641            requester_peer_id: self
642                .requester_peer_id
643                .ok_or("requester_peer_id is required")?,
644            provider_public_key: self
645                .provider_public_key
646                .ok_or("provider_public_key is required")?,
647            requester_public_key: self
648                .requester_public_key
649                .ok_or("requester_public_key is required")?,
650            provider_signature: self
651                .provider_signature
652                .ok_or("provider_signature is required")?,
653            requester_signature: self
654                .requester_signature
655                .ok_or("requester_signature is required")?,
656            challenge_nonce: self.challenge_nonce.ok_or("challenge_nonce is required")?,
657            chunk_hash: self.chunk_hash.ok_or("chunk_hash is required")?,
658            start_timestamp_ms: self.start_timestamp_ms,
659            end_timestamp_ms: self.end_timestamp_ms,
660            latency_ms: self.latency_ms,
661        })
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668
669    // BandwidthProof tests
670    #[test]
671    fn test_bandwidth_proof_builder() {
672        let proof = BandwidthProofBuilder::new()
673            .content_cid("QmTest123")
674            .chunk_index(0)
675            .bytes_transferred(CHUNK_SIZE as u64)
676            .provider_peer_id("12D3KooProvider")
677            .requester_peer_id("12D3KooRequester")
678            .provider_public_key(vec![1u8; 32])
679            .requester_public_key(vec![2u8; 32])
680            .provider_signature(vec![3u8; 64])
681            .requester_signature(vec![4u8; 64])
682            .challenge_nonce(vec![5u8; 32])
683            .chunk_hash(vec![6u8; 32])
684            .timestamps(1000, 1100)
685            .build()
686            .unwrap();
687
688        assert_eq!(proof.content_cid, "QmTest123");
689        assert_eq!(proof.chunk_index, 0);
690        assert_eq!(proof.bytes_transferred, CHUNK_SIZE as u64);
691        assert_eq!(proof.latency_ms, 100);
692        assert!(proof.is_valid());
693    }
694
695    #[test]
696    fn test_bandwidth_proof_validation_invalid_key_length() {
697        let proof = BandwidthProofBuilder::new()
698            .content_cid("QmTest")
699            .provider_peer_id("provider")
700            .requester_peer_id("requester")
701            .provider_public_key(vec![1u8; 16]) // Invalid length
702            .requester_public_key(vec![2u8; 32])
703            .provider_signature(vec![3u8; 64])
704            .requester_signature(vec![4u8; 64])
705            .challenge_nonce(vec![5u8; 32])
706            .chunk_hash(vec![6u8; 32])
707            .timestamps(1000, 1100)
708            .build()
709            .unwrap();
710
711        let result = proof.validate();
712        assert!(result.is_err());
713        let errors = result.unwrap_err();
714        assert!(errors.iter().any(|e| matches!(
715            e,
716            ValidationError::InvalidPublicKeyLength {
717                expected: 32,
718                actual: 16
719            }
720        )));
721    }
722
723    #[test]
724    fn test_bandwidth_proof_validation_self_transfer() {
725        let proof = BandwidthProofBuilder::new()
726            .content_cid("QmTest")
727            .provider_peer_id("same_peer")
728            .requester_peer_id("same_peer")
729            .provider_public_key(vec![1u8; 32])
730            .requester_public_key(vec![2u8; 32])
731            .provider_signature(vec![3u8; 64])
732            .requester_signature(vec![4u8; 64])
733            .challenge_nonce(vec![5u8; 32])
734            .chunk_hash(vec![6u8; 32])
735            .timestamps(1000, 1100)
736            .build()
737            .unwrap();
738
739        let result = proof.validate();
740        assert!(result.is_err());
741        let errors = result.unwrap_err();
742        assert!(
743            errors
744                .iter()
745                .any(|e| matches!(e, ValidationError::SelfTransfer))
746        );
747    }
748
749    #[test]
750    fn test_bandwidth_proof_validation_invalid_timestamp_order() {
751        let proof = BandwidthProofBuilder::new()
752            .content_cid("QmTest")
753            .provider_peer_id("provider")
754            .requester_peer_id("requester")
755            .provider_public_key(vec![1u8; 32])
756            .requester_public_key(vec![2u8; 32])
757            .provider_signature(vec![3u8; 64])
758            .requester_signature(vec![4u8; 64])
759            .challenge_nonce(vec![5u8; 32])
760            .chunk_hash(vec![6u8; 32])
761            .timestamps(2000, 1000) // End before start
762            .build()
763            .unwrap();
764
765        let result = proof.validate();
766        assert!(result.is_err());
767        let errors = result.unwrap_err();
768        assert!(
769            errors
770                .iter()
771                .any(|e| matches!(e, ValidationError::InvalidTimestampOrder { .. }))
772        );
773    }
774
775    #[test]
776    fn test_bandwidth_proof_validation_latency_too_low() {
777        let proof = BandwidthProofBuilder::new()
778            .content_cid("QmTest")
779            .provider_peer_id("provider")
780            .requester_peer_id("requester")
781            .provider_public_key(vec![1u8; 32])
782            .requester_public_key(vec![2u8; 32])
783            .provider_signature(vec![3u8; 64])
784            .requester_signature(vec![4u8; 64])
785            .challenge_nonce(vec![5u8; 32])
786            .chunk_hash(vec![6u8; 32])
787            .timestamps(1000, 1001)
788            .latency_ms(0) // Too low
789            .build()
790            .unwrap();
791
792        let result = proof.validate();
793        assert!(result.is_err());
794        let errors = result.unwrap_err();
795        assert!(
796            errors
797                .iter()
798                .any(|e| matches!(e, ValidationError::LatencyTooLow { .. }))
799        );
800    }
801
802    #[test]
803    fn test_bandwidth_proof_validation_latency_too_high() {
804        let proof = BandwidthProofBuilder::new()
805            .content_cid("QmTest")
806            .provider_peer_id("provider")
807            .requester_peer_id("requester")
808            .provider_public_key(vec![1u8; 32])
809            .requester_public_key(vec![2u8; 32])
810            .provider_signature(vec![3u8; 64])
811            .requester_signature(vec![4u8; 64])
812            .challenge_nonce(vec![5u8; 32])
813            .chunk_hash(vec![6u8; 32])
814            .timestamps(1000, 1100)
815            .latency_ms(40_000) // Too high
816            .build()
817            .unwrap();
818
819        let result = proof.validate();
820        assert!(result.is_err());
821        let errors = result.unwrap_err();
822        assert!(
823            errors
824                .iter()
825                .any(|e| matches!(e, ValidationError::LatencyTooHigh { .. }))
826        );
827    }
828
829    #[test]
830    fn test_bandwidth_proof_timestamp_validation() {
831        let now_ms = chrono::Utc::now().timestamp_millis();
832
833        let proof = BandwidthProofBuilder::new()
834            .content_cid("QmTest")
835            .provider_peer_id("provider")
836            .requester_peer_id("requester")
837            .provider_public_key(vec![1u8; 32])
838            .requester_public_key(vec![2u8; 32])
839            .provider_signature(vec![3u8; 64])
840            .requester_signature(vec![4u8; 64])
841            .challenge_nonce(vec![5u8; 32])
842            .chunk_hash(vec![6u8; 32])
843            .timestamps(now_ms - 1000, now_ms - 900)
844            .build()
845            .unwrap();
846
847        // Should pass - timestamp is recent
848        assert!(proof.validate_timestamp(now_ms).is_ok());
849
850        // Should fail - timestamp in future
851        assert!(proof.validate_timestamp(now_ms - 2000).is_err());
852
853        // Should fail - timestamp too old
854        assert!(
855            proof
856                .validate_timestamp(now_ms + TIMESTAMP_TOLERANCE_MS + 1000)
857                .is_err()
858        );
859    }
860
861    #[test]
862    fn test_bandwidth_proof_sign_messages() {
863        let proof = BandwidthProofBuilder::new()
864            .content_cid("QmTest")
865            .provider_peer_id("provider")
866            .requester_peer_id("requester")
867            .provider_public_key(vec![1u8; 32])
868            .requester_public_key(vec![2u8; 32])
869            .provider_signature(vec![3u8; 64])
870            .requester_signature(vec![4u8; 64])
871            .challenge_nonce(vec![5u8; 32])
872            .chunk_hash(vec![6u8; 32])
873            .timestamps(1000, 1100)
874            .build()
875            .unwrap();
876
877        let provider_msg = proof.provider_sign_message();
878        assert_eq!(provider_msg.len(), 32 + 32 + 32); // nonce + hash + requester_key
879
880        let requester_msg = proof.requester_sign_message();
881        assert_eq!(requester_msg.len(), 32 + 32 + 32 + 64); // nonce + hash + provider_key + provider_sig
882    }
883
884    #[test]
885    fn test_bandwidth_proof_serialization() {
886        let proof = BandwidthProofBuilder::new()
887            .content_cid("QmTest")
888            .provider_peer_id("provider")
889            .requester_peer_id("requester")
890            .provider_public_key(vec![1u8; 32])
891            .requester_public_key(vec![2u8; 32])
892            .provider_signature(vec![3u8; 64])
893            .requester_signature(vec![4u8; 64])
894            .challenge_nonce(vec![5u8; 32])
895            .chunk_hash(vec![6u8; 32])
896            .timestamps(1000, 1100)
897            .build()
898            .unwrap();
899
900        let json = serde_json::to_string(&proof).unwrap();
901        let deserialized: BandwidthProof = serde_json::from_str(&json).unwrap();
902        assert_eq!(proof.content_cid, deserialized.content_cid);
903        assert_eq!(proof.chunk_index, deserialized.chunk_index);
904    }
905
906    #[test]
907    fn test_chunk_request_serialization() {
908        let request = ChunkRequest {
909            content_cid: "QmTest".to_string(),
910            chunk_index: 0,
911            challenge_nonce: [1u8; 32],
912            requester_peer_id: "12D3Koo".to_string(),
913            requester_public_key: [2u8; 32],
914            timestamp_ms: 1000,
915        };
916
917        let json = serde_json::to_string(&request).unwrap();
918        let deserialized: ChunkRequest = serde_json::from_str(&json).unwrap();
919        assert_eq!(request.content_cid, deserialized.content_cid);
920        assert_eq!(request.chunk_index, deserialized.chunk_index);
921    }
922
923    #[test]
924    fn test_chunk_response_serialization() {
925        let response = ChunkResponse {
926            encrypted_chunk: vec![1, 2, 3],
927            chunk_hash: [4u8; 32],
928            provider_signature: vec![5u8; 64],
929            provider_public_key: [6u8; 32],
930            challenge_echo: [7u8; 32],
931            timestamp_ms: 2000,
932        };
933
934        let json = serde_json::to_string(&response).unwrap();
935        let deserialized: ChunkResponse = serde_json::from_str(&json).unwrap();
936        assert_eq!(response.encrypted_chunk, deserialized.encrypted_chunk);
937    }
938
939    #[test]
940    fn test_chunk_request_timestamp_validation() {
941        let request = ChunkRequest::new("QmTest", 0, [1u8; 32], "12D3Koo", [2u8; 32]);
942
943        // Should be valid immediately after creation
944        assert!(request.is_timestamp_valid());
945    }
946
947    #[test]
948    fn test_chunk_response_verify_challenge_echo() {
949        let nonce = [42u8; 32];
950        let response =
951            ChunkResponse::new(vec![1, 2, 3], [4u8; 32], vec![5u8; 64], [6u8; 32], nonce);
952
953        assert!(response.verify_challenge_echo(&nonce));
954        assert!(!response.verify_challenge_echo(&[0u8; 32]));
955    }
956
957    #[test]
958    fn test_chunk_response_chunk_size() {
959        let data = vec![1u8; CHUNK_SIZE];
960        let response =
961            ChunkResponse::new(data.clone(), [4u8; 32], vec![5u8; 64], [6u8; 32], [7u8; 32]);
962
963        assert_eq!(response.chunk_size(), CHUNK_SIZE);
964    }
965
966    #[test]
967    fn test_bandwidth_proof_bandwidth_bps() {
968        let proof = BandwidthProofBuilder::new()
969            .content_cid("QmTest")
970            .provider_peer_id("provider")
971            .requester_peer_id("requester")
972            .provider_public_key(vec![1u8; 32])
973            .requester_public_key(vec![2u8; 32])
974            .provider_signature(vec![3u8; 64])
975            .requester_signature(vec![4u8; 64])
976            .challenge_nonce(vec![5u8; 32])
977            .chunk_hash(vec![6u8; 32])
978            .bytes_transferred(1000)
979            .timestamps(1000, 1100) // 100ms
980            .build()
981            .unwrap();
982
983        // 1000 bytes in 100ms = 10,000 bytes/second
984        assert_eq!(proof.bandwidth_bps(), 10_000.0);
985    }
986
987    #[test]
988    fn test_bandwidth_proof_quality_threshold() {
989        let proof_good = BandwidthProofBuilder::new()
990            .content_cid("QmTest")
991            .provider_peer_id("provider")
992            .requester_peer_id("requester")
993            .provider_public_key(vec![1u8; 32])
994            .requester_public_key(vec![2u8; 32])
995            .provider_signature(vec![3u8; 64])
996            .requester_signature(vec![4u8; 64])
997            .challenge_nonce(vec![5u8; 32])
998            .chunk_hash(vec![6u8; 32])
999            .timestamps(1000, 1400) // 400ms
1000            .build()
1001            .unwrap();
1002
1003        assert!(proof_good.meets_quality_threshold());
1004        assert_eq!(proof_good.quality_multiplier(), 1.0);
1005
1006        let proof_bad = BandwidthProofBuilder::new()
1007            .content_cid("QmTest")
1008            .provider_peer_id("provider")
1009            .requester_peer_id("requester")
1010            .provider_public_key(vec![1u8; 32])
1011            .requester_public_key(vec![2u8; 32])
1012            .provider_signature(vec![3u8; 64])
1013            .requester_signature(vec![4u8; 64])
1014            .challenge_nonce(vec![5u8; 32])
1015            .chunk_hash(vec![6u8; 32])
1016            .timestamps(1000, 1600) // 600ms
1017            .build()
1018            .unwrap();
1019
1020        assert!(!proof_bad.meets_quality_threshold());
1021        assert_eq!(proof_bad.quality_multiplier(), 0.5);
1022    }
1023}
1024
1025/// Helper functions for creating test/mock data.
1026#[cfg(test)]
1027pub mod test_helpers {
1028    use super::*;
1029    use crate::types::content::ContentMetadataBuilder;
1030    use crate::types::enums::ContentCategory;
1031
1032    /// Create a valid test BandwidthProof.
1033    #[must_use]
1034    pub fn create_test_proof() -> BandwidthProof {
1035        BandwidthProofBuilder::new()
1036            .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
1037            .chunk_index(0)
1038            .bytes_transferred(CHUNK_SIZE as u64)
1039            .provider_peer_id("12D3KooWProviderPeerTestID")
1040            .requester_peer_id("12D3KooWRequesterPeerTestID")
1041            .provider_public_key(vec![1u8; 32])
1042            .requester_public_key(vec![2u8; 32])
1043            .provider_signature(vec![3u8; 64])
1044            .requester_signature(vec![4u8; 64])
1045            .challenge_nonce(vec![5u8; 32])
1046            .chunk_hash(vec![6u8; 32])
1047            .timestamps(1000, 1100)
1048            .build()
1049            .unwrap()
1050    }
1051
1052    /// Create a valid test ContentMetadata.
1053    #[must_use]
1054    pub fn create_test_content() -> crate::types::content::ContentMetadata {
1055        ContentMetadataBuilder::new()
1056            .cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
1057            .title("Test Content")
1058            .description("A test content item")
1059            .category(ContentCategory::ThreeDModels)
1060            .size_bytes(5 * 1024 * 1024)
1061            .price(1000)
1062            .creator_id(uuid::Uuid::new_v4())
1063            .build()
1064            .unwrap()
1065    }
1066
1067    /// Create a valid test ChunkRequest.
1068    #[must_use]
1069    pub fn create_test_chunk_request() -> ChunkRequest {
1070        ChunkRequest::new(
1071            "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
1072            0,
1073            [1u8; 32],
1074            "12D3KooWRequesterPeerTestID",
1075            [2u8; 32],
1076        )
1077    }
1078
1079    /// Create a valid test ChunkResponse.
1080    #[must_use]
1081    pub fn create_test_chunk_response() -> ChunkResponse {
1082        ChunkResponse::new(
1083            vec![0u8; CHUNK_SIZE],
1084            [3u8; 32],
1085            vec![4u8; 64],
1086            [5u8; 32],
1087            [1u8; 32],
1088        )
1089    }
1090}