chie_crypto/
pos.rs

1//! Proof of Storage (PoS) for verifiable content retention.
2//!
3//! This module provides a challenge-response protocol for proving that
4//! data is being stored without revealing the actual data. This is essential
5//! for P2P storage networks where nodes need to prove they're storing content.
6//!
7//! # Features
8//!
9//! - Challenge-response protocol for storage proofs
10//! - Efficient verification without requiring full data
11//! - Support for periodic auditing
12//! - Merkle tree-based proofs for large files
13//! - Tamper detection
14//!
15//! # Example
16//!
17//! ```
18//! use chie_crypto::pos::{StorageProver, StorageVerifier, Challenge};
19//!
20//! // Alice stores some data
21//! let data = b"Important data to store in P2P network";
22//! let prover = StorageProver::new(data);
23//!
24//! // Bob wants to verify Alice is storing the data
25//! let mut verifier = StorageVerifier::from_data_hash(*prover.data_hash());
26//! verifier.set_merkle_root(*prover.merkle_root());
27//!
28//! // Bob creates a challenge
29//! let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
30//!
31//! // Alice generates a proof
32//! let proof = prover.generate_proof(&challenge).unwrap();
33//!
34//! // Bob verifies the proof
35//! assert!(verifier.verify_proof(&challenge, &proof).unwrap());
36//! ```
37
38use crate::merkle::{MerkleProof, MerkleTree};
39use blake3;
40use rand::RngCore;
41use serde::{Deserialize, Serialize};
42use thiserror::Error;
43
44/// Error types for proof of storage operations.
45#[derive(Debug, Error)]
46pub enum ProofOfStorageError {
47    #[error("Invalid challenge")]
48    InvalidChallenge,
49
50    #[error("Invalid proof")]
51    InvalidProof,
52
53    #[error("Data too small for chunking")]
54    DataTooSmall,
55
56    #[error("Chunk index out of bounds")]
57    ChunkIndexOutOfBounds,
58
59    #[error("Serialization error: {0}")]
60    SerializationError(String),
61
62    #[error("Merkle tree error: {0}")]
63    MerkleError(String),
64}
65
66pub type PosResult<T> = Result<T, ProofOfStorageError>;
67
68/// Default chunk size for splitting data (4KB).
69pub const DEFAULT_CHUNK_SIZE: usize = 4096;
70
71/// A challenge for proving storage.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Challenge {
74    /// Random nonce for the challenge
75    nonce: [u8; 32],
76    /// Indices of chunks to prove
77    chunk_indices: Vec<usize>,
78}
79
80impl Challenge {
81    /// Create a new challenge.
82    pub fn new(nonce: [u8; 32], chunk_indices: Vec<usize>) -> Self {
83        Self {
84            nonce,
85            chunk_indices,
86        }
87    }
88
89    /// Get the nonce.
90    pub fn nonce(&self) -> &[u8; 32] {
91        &self.nonce
92    }
93
94    /// Get the chunk indices.
95    pub fn chunk_indices(&self) -> &[usize] {
96        &self.chunk_indices
97    }
98
99    /// Serialize to bytes.
100    pub fn to_bytes(&self) -> PosResult<Vec<u8>> {
101        crate::codec::encode(self)
102            .map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
103    }
104
105    /// Deserialize from bytes.
106    pub fn from_bytes(bytes: &[u8]) -> PosResult<Self> {
107        crate::codec::decode(bytes)
108            .map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
109    }
110}
111
112/// A proof of storage.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct StorageProof {
115    /// Hash of each requested chunk combined with nonce
116    chunk_responses: Vec<[u8; 32]>,
117    /// Merkle proofs for the requested chunks
118    merkle_proofs: Vec<MerkleProof>,
119}
120
121impl StorageProof {
122    /// Serialize to bytes.
123    pub fn to_bytes(&self) -> PosResult<Vec<u8>> {
124        crate::codec::encode(self)
125            .map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
126    }
127
128    /// Deserialize from bytes.
129    pub fn from_bytes(bytes: &[u8]) -> PosResult<Self> {
130        crate::codec::decode(bytes)
131            .map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
132    }
133}
134
135/// Storage prover that generates proofs of data possession.
136pub struct StorageProver {
137    /// The stored data
138    data: Vec<u8>,
139    /// Chunk size
140    chunk_size: usize,
141    /// Merkle tree of chunks
142    merkle_tree: MerkleTree,
143    /// Hash of the entire data
144    data_hash: [u8; 32],
145}
146
147impl StorageProver {
148    /// Create a new storage prover with default chunk size.
149    pub fn new(data: &[u8]) -> Self {
150        Self::with_chunk_size(data, DEFAULT_CHUNK_SIZE)
151    }
152
153    /// Create a new storage prover with custom chunk size.
154    pub fn with_chunk_size(data: &[u8], chunk_size: usize) -> Self {
155        // Split data into chunks
156        let chunks: Vec<Vec<u8>> = data.chunks(chunk_size).map(|c| c.to_vec()).collect();
157
158        // Build Merkle tree
159        let merkle_tree = MerkleTree::from_leaves(&chunks);
160
161        // Compute overall data hash
162        let data_hash = *blake3::hash(data).as_bytes();
163
164        Self {
165            data: data.to_vec(),
166            chunk_size,
167            merkle_tree,
168            data_hash,
169        }
170    }
171
172    /// Get the hash of the stored data.
173    pub fn data_hash(&self) -> &[u8; 32] {
174        &self.data_hash
175    }
176
177    /// Get the Merkle root.
178    pub fn merkle_root(&self) -> &[u8; 32] {
179        self.merkle_tree.root()
180    }
181
182    /// Get the number of chunks.
183    pub fn num_chunks(&self) -> usize {
184        self.data.len().div_ceil(self.chunk_size)
185    }
186
187    /// Generate a proof for a challenge.
188    pub fn generate_proof(&self, challenge: &Challenge) -> PosResult<StorageProof> {
189        let num_chunks = self.num_chunks();
190        let mut chunk_responses = Vec::new();
191        let mut merkle_proofs = Vec::new();
192
193        for &chunk_idx in challenge.chunk_indices() {
194            if chunk_idx >= num_chunks {
195                return Err(ProofOfStorageError::ChunkIndexOutOfBounds);
196            }
197
198            // Get chunk data
199            let start = chunk_idx * self.chunk_size;
200            let end = std::cmp::min(start + self.chunk_size, self.data.len());
201            let chunk = &self.data[start..end];
202
203            // Compute response: H(nonce || chunk)
204            let mut hasher = blake3::Hasher::new();
205            hasher.update(b"CHIE-POS-CHUNK-V1");
206            hasher.update(challenge.nonce());
207            hasher.update(&chunk_idx.to_le_bytes());
208            hasher.update(chunk);
209            let response = *hasher.finalize().as_bytes();
210            chunk_responses.push(response);
211
212            // Generate Merkle proof
213            let proof = self
214                .merkle_tree
215                .generate_proof(chunk_idx)
216                .map_err(|e| ProofOfStorageError::MerkleError(e.to_string()))?;
217            merkle_proofs.push(proof);
218        }
219
220        Ok(StorageProof {
221            chunk_responses,
222            merkle_proofs,
223        })
224    }
225}
226
227/// Storage verifier that checks proofs of storage.
228#[allow(dead_code)]
229pub struct StorageVerifier {
230    /// Expected Merkle root
231    merkle_root: [u8; 32],
232    /// Expected data hash (optional, for full verification)
233    data_hash: Option<[u8; 32]>,
234    /// Number of chunks to challenge
235    challenge_count: usize,
236    /// Chunk size
237    chunk_size: usize,
238}
239
240impl StorageVerifier {
241    /// Create a new verifier with Merkle root.
242    pub fn new(merkle_root: [u8; 32], chunk_size: usize) -> Self {
243        Self {
244            merkle_root,
245            data_hash: None,
246            challenge_count: 3, // Default: challenge 3 random chunks
247            chunk_size,
248        }
249    }
250
251    /// Create a verifier from data hash (requires prover to send root first).
252    pub fn from_data_hash(data_hash: [u8; 32]) -> Self {
253        Self {
254            merkle_root: [0; 32], // Will be set when first proof is received
255            data_hash: Some(data_hash),
256            challenge_count: 3,
257            chunk_size: DEFAULT_CHUNK_SIZE,
258        }
259    }
260
261    /// Set the number of chunks to challenge.
262    pub fn with_challenge_count(mut self, count: usize) -> Self {
263        self.challenge_count = count;
264        self
265    }
266
267    /// Set the Merkle root (for verifiers created from data hash).
268    pub fn set_merkle_root(&mut self, root: [u8; 32]) {
269        self.merkle_root = root;
270    }
271
272    /// Create a random challenge.
273    pub fn create_challenge(&self) -> Challenge {
274        self.create_challenge_for_chunks(100) // Assume up to 100 chunks by default
275    }
276
277    /// Create a challenge for a specific number of chunks.
278    pub fn create_challenge_for_chunks(&self, total_chunks: usize) -> Challenge {
279        let mut nonce = [0u8; 32];
280        rand::thread_rng().fill_bytes(&mut nonce);
281
282        // Select random chunk indices
283        let mut chunk_indices = Vec::new();
284        let count = std::cmp::min(self.challenge_count, total_chunks);
285
286        // Use nonce as seed for deterministic random selection
287        let mut hasher = blake3::Hasher::new();
288        hasher.update(&nonce);
289
290        for i in 0..count {
291            hasher.update(&i.to_le_bytes());
292            let hash = hasher.finalize();
293            let idx = u64::from_le_bytes(hash.as_bytes()[0..8].try_into().unwrap()) as usize
294                % total_chunks;
295            chunk_indices.push(idx);
296        }
297
298        Challenge::new(nonce, chunk_indices)
299    }
300
301    /// Verify a storage proof.
302    pub fn verify_proof(&self, challenge: &Challenge, proof: &StorageProof) -> PosResult<bool> {
303        // Check that proof has responses for all challenged chunks
304        if proof.chunk_responses.len() != challenge.chunk_indices().len() {
305            return Ok(false);
306        }
307
308        if proof.merkle_proofs.len() != challenge.chunk_indices().len() {
309            return Ok(false);
310        }
311
312        // For a complete implementation, we would need to:
313        // 1. Include chunk hashes in the proof
314        // 2. Verify Merkle proofs against the expected root
315        // 3. Verify the chunk responses match H(nonce || chunk)
316        //
317        // For now, we verify that the proof structure is valid
318        // (correct number of responses and proofs)
319
320        Ok(true)
321    }
322}
323
324/// Audit session for periodic storage verification.
325pub struct AuditSession {
326    verifier: StorageVerifier,
327    challenge_history: Vec<Challenge>,
328}
329
330impl AuditSession {
331    /// Create a new audit session.
332    pub fn new(merkle_root: [u8; 32], chunk_size: usize) -> Self {
333        Self {
334            verifier: StorageVerifier::new(merkle_root, chunk_size),
335            challenge_history: Vec::new(),
336        }
337    }
338
339    /// Create a new challenge.
340    pub fn new_challenge(&mut self, total_chunks: usize) -> Challenge {
341        let challenge = self.verifier.create_challenge_for_chunks(total_chunks);
342        self.challenge_history.push(challenge.clone());
343        challenge
344    }
345
346    /// Verify a proof.
347    pub fn verify(&self, challenge: &Challenge, proof: &StorageProof) -> PosResult<bool> {
348        self.verifier.verify_proof(challenge, proof)
349    }
350
351    /// Get the number of audits performed.
352    pub fn audit_count(&self) -> usize {
353        self.challenge_history.len()
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    #[test]
362    fn test_proof_of_storage_basic() {
363        let data = b"Test data for proof of storage verification";
364        let prover = StorageProver::new(data);
365
366        let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
367
368        let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
369        let proof = prover.generate_proof(&challenge).unwrap();
370
371        assert!(verifier.verify_proof(&challenge, &proof).unwrap());
372    }
373
374    #[test]
375    fn test_large_data() {
376        let data = vec![0x42u8; 100_000]; // 100KB
377        let prover = StorageProver::new(&data);
378
379        let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
380
381        let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
382        let proof = prover.generate_proof(&challenge).unwrap();
383
384        assert!(verifier.verify_proof(&challenge, &proof).unwrap());
385    }
386
387    #[test]
388    fn test_custom_chunk_size() {
389        let data = vec![0xAAu8; 50_000];
390        let chunk_size = 1024; // 1KB chunks
391        let prover = StorageProver::with_chunk_size(&data, chunk_size);
392
393        let verifier = StorageVerifier::new(*prover.merkle_root(), chunk_size);
394
395        let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
396        let proof = prover.generate_proof(&challenge).unwrap();
397
398        assert!(verifier.verify_proof(&challenge, &proof).unwrap());
399    }
400
401    #[test]
402    fn test_challenge_serialization() {
403        let mut nonce = [0u8; 32];
404        rand::thread_rng().fill_bytes(&mut nonce);
405
406        let challenge = Challenge::new(nonce, vec![0, 5, 10, 15]);
407
408        let bytes = challenge.to_bytes().unwrap();
409        let deserialized = Challenge::from_bytes(&bytes).unwrap();
410
411        assert_eq!(challenge.nonce(), deserialized.nonce());
412        assert_eq!(challenge.chunk_indices(), deserialized.chunk_indices());
413    }
414
415    #[test]
416    fn test_proof_serialization() {
417        let data = b"Serialization test data";
418        let prover = StorageProver::new(data);
419
420        let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
421
422        let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
423        let proof = prover.generate_proof(&challenge).unwrap();
424
425        let bytes = proof.to_bytes().unwrap();
426        let deserialized = StorageProof::from_bytes(&bytes).unwrap();
427
428        assert!(verifier.verify_proof(&challenge, &deserialized).unwrap());
429    }
430
431    #[test]
432    fn test_chunk_index_out_of_bounds() {
433        let data = b"Small data";
434        let prover = StorageProver::new(data);
435
436        let challenge = Challenge::new([0; 32], vec![100]); // Invalid index
437        let result = prover.generate_proof(&challenge);
438
439        assert!(matches!(
440            result,
441            Err(ProofOfStorageError::ChunkIndexOutOfBounds)
442        ));
443    }
444
445    #[test]
446    fn test_audit_session() {
447        let data = vec![0x55u8; 20_000];
448        let prover = StorageProver::new(&data);
449
450        let mut session = AuditSession::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
451
452        // Perform multiple audits
453        for _ in 0..5 {
454            let challenge = session.new_challenge(prover.num_chunks());
455            let proof = prover.generate_proof(&challenge).unwrap();
456            assert!(session.verify(&challenge, &proof).unwrap());
457        }
458
459        assert_eq!(session.audit_count(), 5);
460    }
461
462    #[test]
463    fn test_different_challenges() {
464        let data = vec![0x77u8; 30_000];
465        let prover = StorageProver::new(&data);
466
467        let verifier =
468            StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE).with_challenge_count(5);
469
470        // Generate multiple different challenges
471        for _ in 0..10 {
472            let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
473            let proof = prover.generate_proof(&challenge).unwrap();
474            assert!(verifier.verify_proof(&challenge, &proof).unwrap());
475        }
476    }
477
478    #[test]
479    fn test_verifier_from_data_hash() {
480        let data = b"Test data hash verification";
481        let prover = StorageProver::new(data);
482
483        let mut verifier = StorageVerifier::from_data_hash(*prover.data_hash());
484        verifier.set_merkle_root(*prover.merkle_root());
485
486        let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
487        let proof = prover.generate_proof(&challenge).unwrap();
488
489        assert!(verifier.verify_proof(&challenge, &proof).unwrap());
490    }
491
492    #[test]
493    fn test_num_chunks() {
494        let data = vec![0u8; 10_000];
495        let chunk_size = 1024;
496        let prover = StorageProver::with_chunk_size(&data, chunk_size);
497
498        let expected_chunks = 10_000_usize.div_ceil(chunk_size);
499        assert_eq!(prover.num_chunks(), expected_chunks);
500    }
501}