1use chie_crypto::{
9 Hash, decrypt, derive_chunk_nonce as crypto_derive_nonce, derive_content_key, encrypt, hash,
10};
11use std::io::{self, Read, Write};
12use thiserror::Error;
13
14pub const ENCRYPTED_CHUNK_SIZE: usize = 262_144;
16
17pub const NONCE_SIZE: usize = 12;
19
20#[derive(Debug, Error)]
22pub enum ChunkEncryptionError {
23 #[error("Encryption failed: {0}")]
24 EncryptionFailed(String),
25
26 #[error("Decryption failed: {0}")]
27 DecryptionFailed(String),
28
29 #[error("Invalid chunk index")]
30 InvalidChunkIndex,
31
32 #[error("Key derivation failed: {0}")]
33 KeyDerivationFailed(String),
34
35 #[error("Invalid nonce")]
36 InvalidNonce,
37
38 #[error("IO error: {0}")]
39 IoError(#[from] io::Error),
40}
41
42#[inline]
44pub fn derive_chunk_nonce(
45 master_key: &[u8; 32],
46 content_id: &str,
47 chunk_index: u64,
48) -> Result<[u8; NONCE_SIZE], ChunkEncryptionError> {
49 crypto_derive_nonce(master_key, content_id, chunk_index)
50 .map_err(|e| ChunkEncryptionError::KeyDerivationFailed(e.to_string()))
51}
52
53#[inline]
55pub fn derive_chunk_key(
56 master_key: &[u8; 32],
57 content_id: &str,
58 chunk_index: u64,
59) -> Result<[u8; 32], ChunkEncryptionError> {
60 derive_content_key(master_key, content_id, chunk_index)
61 .map_err(|e| ChunkEncryptionError::KeyDerivationFailed(e.to_string()))
62}
63
64#[derive(Debug, Clone)]
66pub struct EncryptedChunk {
67 pub index: u64,
69 pub ciphertext: Vec<u8>,
71 pub nonce: [u8; NONCE_SIZE],
73 pub plaintext_hash: Hash,
75}
76
77impl EncryptedChunk {
78 #[must_use]
80 #[inline]
81 pub fn size(&self) -> usize {
82 self.ciphertext.len()
83 }
84
85 #[must_use]
87 #[inline]
88 pub fn to_bytes(&self) -> Vec<u8> {
89 let mut bytes = Vec::new();
90 bytes.extend_from_slice(&self.index.to_le_bytes());
91 bytes.extend_from_slice(&self.nonce);
92 bytes.extend_from_slice(&self.plaintext_hash);
93 bytes.extend_from_slice(&(self.ciphertext.len() as u32).to_le_bytes());
94 bytes.extend_from_slice(&self.ciphertext);
95 bytes
96 }
97
98 pub fn from_bytes(bytes: &[u8]) -> Result<Self, ChunkEncryptionError> {
100 if bytes.len() < 8 + NONCE_SIZE + 32 + 4 {
101 return Err(ChunkEncryptionError::InvalidNonce);
102 }
103
104 let index = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
105 let mut nonce = [0u8; NONCE_SIZE];
106 nonce.copy_from_slice(&bytes[8..8 + NONCE_SIZE]);
107 let mut plaintext_hash = [0u8; 32];
108 plaintext_hash.copy_from_slice(&bytes[8 + NONCE_SIZE..8 + NONCE_SIZE + 32]);
109 let ciphertext_len = u32::from_le_bytes(
110 bytes[8 + NONCE_SIZE + 32..8 + NONCE_SIZE + 36]
111 .try_into()
112 .unwrap(),
113 ) as usize;
114
115 if bytes.len() < 8 + NONCE_SIZE + 36 + ciphertext_len {
116 return Err(ChunkEncryptionError::InvalidNonce);
117 }
118
119 let ciphertext = bytes[8 + NONCE_SIZE + 36..8 + NONCE_SIZE + 36 + ciphertext_len].to_vec();
120
121 Ok(Self {
122 index,
123 ciphertext,
124 nonce,
125 plaintext_hash,
126 })
127 }
128}
129
130pub struct ChunkEncryptor {
132 master_key: [u8; 32],
133 content_id: String,
134 chunk_size: usize,
135 current_index: u64,
136}
137
138impl ChunkEncryptor {
139 #[must_use]
141 #[inline]
142 pub fn new(master_key: [u8; 32], content_id: impl Into<String>, chunk_size: usize) -> Self {
143 Self {
144 master_key,
145 content_id: content_id.into(),
146 chunk_size,
147 current_index: 0,
148 }
149 }
150
151 pub fn encrypt_chunk(
153 &mut self,
154 plaintext: &[u8],
155 ) -> Result<EncryptedChunk, ChunkEncryptionError> {
156 let index = self.current_index;
157 self.current_index += 1;
158
159 encrypt_chunk_with_index(&self.master_key, &self.content_id, index, plaintext)
160 }
161
162 #[must_use]
164 #[inline]
165 pub const fn current_index(&self) -> u64 {
166 self.current_index
167 }
168
169 #[must_use]
171 #[inline]
172 pub const fn chunk_size(&self) -> usize {
173 self.chunk_size
174 }
175
176 #[inline]
178 pub fn reset(&mut self) {
179 self.current_index = 0;
180 }
181}
182
183pub fn encrypt_chunk_with_index(
185 master_key: &[u8; 32],
186 content_id: &str,
187 chunk_index: u64,
188 plaintext: &[u8],
189) -> Result<EncryptedChunk, ChunkEncryptionError> {
190 let chunk_key = derive_chunk_key(master_key, content_id, chunk_index)?;
192
193 let nonce = derive_chunk_nonce(master_key, content_id, chunk_index)?;
195
196 let plaintext_hash = hash(plaintext);
198
199 let ciphertext = encrypt(plaintext, &chunk_key, &nonce)
201 .map_err(|e| ChunkEncryptionError::EncryptionFailed(e.to_string()))?;
202
203 Ok(EncryptedChunk {
204 index: chunk_index,
205 ciphertext,
206 nonce,
207 plaintext_hash,
208 })
209}
210
211pub fn decrypt_chunk(
213 master_key: &[u8; 32],
214 content_id: &str,
215 chunk: &EncryptedChunk,
216) -> Result<Vec<u8>, ChunkEncryptionError> {
217 let chunk_key = derive_chunk_key(master_key, content_id, chunk.index)?;
219
220 let expected_nonce = derive_chunk_nonce(master_key, content_id, chunk.index)?;
222 if chunk.nonce != expected_nonce {
223 return Err(ChunkEncryptionError::InvalidNonce);
224 }
225
226 let plaintext = decrypt(&chunk.ciphertext, &chunk_key, &chunk.nonce)
228 .map_err(|e| ChunkEncryptionError::DecryptionFailed(e.to_string()))?;
229
230 let computed_hash = hash(&plaintext);
232 if computed_hash != chunk.plaintext_hash {
233 return Err(ChunkEncryptionError::DecryptionFailed(
234 "Plaintext hash mismatch".to_string(),
235 ));
236 }
237
238 Ok(plaintext)
239}
240
241pub struct ChunkDecryptor {
243 master_key: [u8; 32],
244 content_id: String,
245}
246
247impl ChunkDecryptor {
248 #[must_use]
250 #[inline]
251 pub fn new(master_key: [u8; 32], content_id: impl Into<String>) -> Self {
252 Self {
253 master_key,
254 content_id: content_id.into(),
255 }
256 }
257
258 pub fn decrypt_chunk(&self, chunk: &EncryptedChunk) -> Result<Vec<u8>, ChunkEncryptionError> {
260 decrypt_chunk(&self.master_key, &self.content_id, chunk)
261 }
262}
263
264pub fn encrypt_content<R: Read>(
266 master_key: &[u8; 32],
267 content_id: &str,
268 reader: &mut R,
269 chunk_size: usize,
270) -> Result<Vec<EncryptedChunk>, ChunkEncryptionError> {
271 let mut encryptor = ChunkEncryptor::new(*master_key, content_id, chunk_size);
272 let mut chunks = Vec::new();
273 let mut buffer = vec![0u8; chunk_size];
274
275 loop {
276 let mut total_read = 0;
277
278 while total_read < chunk_size {
279 let bytes_read = reader.read(&mut buffer[total_read..])?;
280 if bytes_read == 0 {
281 break;
282 }
283 total_read += bytes_read;
284 }
285
286 if total_read == 0 {
287 break;
288 }
289
290 let chunk = encryptor.encrypt_chunk(&buffer[..total_read])?;
291 chunks.push(chunk);
292
293 if total_read < chunk_size {
294 break;
295 }
296 }
297
298 Ok(chunks)
299}
300
301pub fn decrypt_content<W: Write>(
303 master_key: &[u8; 32],
304 content_id: &str,
305 chunks: &[EncryptedChunk],
306 writer: &mut W,
307) -> Result<u64, ChunkEncryptionError> {
308 let decryptor = ChunkDecryptor::new(*master_key, content_id);
309 let mut total_written = 0u64;
310
311 for chunk in chunks {
312 let plaintext = decryptor.decrypt_chunk(chunk)?;
313 writer.write_all(&plaintext)?;
314 total_written += plaintext.len() as u64;
315 }
316
317 Ok(total_written)
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use std::io::Cursor;
324
325 #[test]
326 fn test_derive_chunk_nonce() {
327 let master_key = [0u8; 32];
328 let content_id = "QmTest123";
329
330 let nonce1 = derive_chunk_nonce(&master_key, content_id, 0).unwrap();
331 let nonce2 = derive_chunk_nonce(&master_key, content_id, 1).unwrap();
332 let nonce3 = derive_chunk_nonce(&master_key, content_id, 0).unwrap();
333
334 assert_ne!(nonce1, nonce2); assert_eq!(nonce1, nonce3); }
337
338 #[test]
339 fn test_derive_chunk_key() {
340 let master_key = [0u8; 32];
341 let content_id = "QmTest123";
342
343 let key1 = derive_chunk_key(&master_key, content_id, 0).unwrap();
344 let key2 = derive_chunk_key(&master_key, content_id, 1).unwrap();
345
346 assert_ne!(key1, key2); }
348
349 #[test]
350 fn test_encrypt_decrypt_chunk() {
351 let master_key = [1u8; 32];
352 let content_id = "QmTest123";
353 let plaintext = b"Hello, CHIE Protocol!";
354
355 let chunk = encrypt_chunk_with_index(&master_key, content_id, 0, plaintext).unwrap();
356 let decrypted = decrypt_chunk(&master_key, content_id, &chunk).unwrap();
357
358 assert_eq!(decrypted, plaintext);
359 }
360
361 #[test]
362 fn test_chunk_encryptor() {
363 let master_key = [2u8; 32];
364 let content_id = "QmTest456";
365
366 let mut encryptor = ChunkEncryptor::new(master_key, content_id, 1024);
367
368 let chunk1 = encryptor.encrypt_chunk(b"Chunk 1").unwrap();
369 let chunk2 = encryptor.encrypt_chunk(b"Chunk 2").unwrap();
370
371 assert_eq!(chunk1.index, 0);
372 assert_eq!(chunk2.index, 1);
373 assert_ne!(chunk1.nonce, chunk2.nonce);
374 }
375
376 #[test]
377 fn test_encrypt_content() {
378 let master_key = [3u8; 32];
379 let content_id = "QmContent";
380 let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
381 let mut cursor = Cursor::new(data);
382
383 let chunks = encrypt_content(&master_key, content_id, &mut cursor, 10).unwrap();
384
385 assert_eq!(chunks.len(), 3); let mut output = Vec::new();
389 decrypt_content(&master_key, content_id, &chunks, &mut output).unwrap();
390
391 assert_eq!(output, data);
392 }
393
394 #[test]
395 fn test_encrypted_chunk_serialization() {
396 let master_key = [4u8; 32];
397 let content_id = "QmSerial";
398
399 let chunk = encrypt_chunk_with_index(&master_key, content_id, 42, b"Test data").unwrap();
400
401 let bytes = chunk.to_bytes();
402 let deserialized = EncryptedChunk::from_bytes(&bytes).unwrap();
403
404 assert_eq!(chunk.index, deserialized.index);
405 assert_eq!(chunk.nonce, deserialized.nonce);
406 assert_eq!(chunk.plaintext_hash, deserialized.plaintext_hash);
407 assert_eq!(chunk.ciphertext, deserialized.ciphertext);
408 }
409
410 #[test]
411 fn test_different_chunks_different_keys() {
412 let master_key = [5u8; 32];
413 let content_id = "QmDiffKeys";
414
415 let chunk1 = encrypt_chunk_with_index(&master_key, content_id, 0, b"Same data").unwrap();
416 let chunk2 = encrypt_chunk_with_index(&master_key, content_id, 1, b"Same data").unwrap();
417
418 assert_ne!(chunk1.ciphertext, chunk2.ciphertext);
420 }
421}