1mod error;
2
3pub use error::{DecryptionError, EncryptionError, PsCypherError};
4pub use ps_buffer::Buffer;
5
6use chacha20poly1305::aead::{Aead, KeyInit};
7use chacha20poly1305::ChaCha20Poly1305;
8use ps_deflate::{compress, decompress};
9use ps_ecc::{decode, encode, Codeword, DecodeError};
10use ps_hash::{Hash, PARITY_SIZE};
11use ps_util::subarray;
12use std::ops::Deref;
13use std::sync::Arc;
14
15#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Encrypted {
17 pub bytes: Buffer,
18 pub hash: Arc<Hash>,
19 pub key: Arc<Hash>,
20}
21
22const KSIZE: usize = 32;
23const NSIZE: usize = 12;
24
25const PARITY: u8 = 12;
26
27#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
28pub struct ParsedKey {
29 key: [u8; KSIZE],
30 nonce: [u8; NSIZE],
31 length: usize,
32}
33
34impl std::fmt::Debug for ParsedKey {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("ParsedKey")
37 .field("key", &"<REDACTED>")
38 .field("nonce", &self.nonce)
39 .field("length", &self.length)
40 .finish()
41 }
42}
43
44impl From<&Hash> for ParsedKey {
45 fn from(value: &Hash) -> Self {
46 Self {
47 key: *value.digest(),
48 length: value.data_max_len().to_usize(),
49 nonce: *subarray(value.parity(), PARITY_SIZE - NSIZE),
50 }
51 }
52}
53
54pub fn encrypt(data: &[u8]) -> Result<Encrypted, EncryptionError> {
60 let compressed_data = compress(data)?;
61 let hash_of_raw_data = ps_hash::hash(data)?;
62
63 let ParsedKey {
64 key: encryption_key,
65 length: _,
66 nonce,
67 } = (&hash_of_raw_data).into();
68
69 let chacha = ChaCha20Poly1305::new(&encryption_key.into());
70 let encrypted_data = chacha
71 .encrypt(&nonce.into(), compressed_data.as_ref())
72 .map_err(|_| EncryptionError::ChaChaError)?;
73
74 let bytes = encode(&encrypted_data, PARITY)?;
75 let hash = Hash::hash(&bytes)?.into();
76
77 let encrypted = Encrypted {
78 bytes,
79 hash,
80 key: hash_of_raw_data.into(),
81 };
82
83 Ok(encrypted)
84}
85
86pub fn decrypt(data: &[u8], key: &Hash) -> Result<Buffer, DecryptionError> {
91 let ParsedKey {
92 key: encryption_key,
93 length: out_size,
94 nonce,
95 } = key.into();
96
97 let ecc_decoded = extract_encrypted(data)?;
98 let chacha = ChaCha20Poly1305::new(&encryption_key.into());
99 let compressed_data = chacha
100 .decrypt(&nonce.into(), &ecc_decoded[..])
101 .map_err(|_| DecryptionError::ChaChaError)?;
102
103 Ok(decompress(&compressed_data, out_size)?)
104}
105
106#[inline]
107pub fn extract_encrypted(data: &[u8]) -> Result<Codeword<'_>, DecodeError> {
111 decode(data, PARITY)
112}
113
114#[inline]
115#[must_use]
116pub fn validate_ecc(data: &[u8]) -> bool {
131 ps_ecc::validate(data, PARITY)
132}
133
134impl AsRef<[u8]> for Encrypted {
135 fn as_ref(&self) -> &[u8] {
136 self
137 }
138}
139
140impl Deref for Encrypted {
141 type Target = [u8];
142
143 fn deref(&self) -> &Self::Target {
144 &self.bytes
145 }
146}
147
148#[cfg(test)]
149#[allow(clippy::unwrap_used)]
150mod tests {
151 use ps_buffer::ToBuffer;
152 use ps_hash::hash;
153
154 use super::*;
155
156 #[test]
157 fn test_encrypt_and_decrypt() -> Result<(), PsCypherError> {
158 let original_data = b"Hello, World!";
159
160 let encrypted_data = encrypt(original_data)?;
161
162 let decrypted_data = decrypt(&encrypted_data.bytes, &encrypted_data.key)?;
163
164 assert_ne!(
165 original_data.to_buffer().unwrap(),
166 encrypted_data.bytes,
167 "Encryption should modify the data"
168 );
169
170 assert_eq!(
171 encrypted_data.bytes.len(),
172 31 + 2 * usize::from(PARITY),
173 "Encrypted data should be 31 bytes long"
174 );
175
176 assert_eq!(
177 original_data,
178 &decrypted_data[..],
179 "Decryption should reverse encryption"
180 );
181
182 Ok(())
183 }
184
185 fn create_test_key() -> Hash {
187 hash("Hello, world!").unwrap()
188 }
189
190 #[test]
191 fn test_parse_key() {
192 let key = &create_test_key();
193
194 let ParsedKey {
195 key: encryption_key,
196 length: _,
197 nonce,
198 } = key.into();
199
200 assert_eq!(encryption_key.len(), 32);
201 assert_eq!(nonce.len(), 12);
202 assert_eq!(&encryption_key[0..4], &[220, 186, 155, 106]); assert_eq!(&nonce[0..4], &[46, 215, 220, 44]); }
206
207 #[test]
208 fn test_encrypt_decrypt() {
209 let data = b"This is some data to encrypt";
210 let encrypted = encrypt(data).unwrap();
211 let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
212 assert_eq!(&*decrypted, data);
213 }
214
215 #[test]
216 fn test_encrypt_decrypt_empty_data() {
217 let data = b"";
218 let encrypted = encrypt(data).unwrap();
219 let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
220 assert_eq!(&*decrypted, data);
221 }
222
223 #[test]
224 fn test_encrypt_decrypt_long_data() {
225 let data = "This is a very long string to test the encryption and decryption with a large amount of data. We want to make sure that the compression and decompression work correctly, and that the encryption and decryption can handle a significant amount of data without any issues. This should be longer than any reasonable message. Let's add some more to be absolutely sure. And even more, just to be safe.".as_bytes();
226 let encrypted = encrypt(data).unwrap();
227 let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
228 assert_eq!(&*decrypted, data);
229 }
230
231 #[test]
232 fn test_encrypt_decrypt_different_key() {
233 let data = b"This is some data";
234 let encrypted = encrypt(data).unwrap();
235 let different_key = create_test_key(); let result = decrypt(&encrypted, &different_key);
238 assert!(result.is_err());
239 match result.unwrap_err() {
240 DecryptionError::ChaChaError => {} _ => panic!("Unexpected error type"),
242 }
243 }
244
245 #[test]
246 fn test_encrypt_decrypt_tampered_data() -> Result<(), PsCypherError> {
247 let data = b"This is some data";
248 let mut encrypted = encrypt(data).unwrap();
249 encrypted.bytes[0] ^= 0x01; let decrypted = decrypt(&encrypted, &encrypted.key)?;
253
254 assert_eq!(decrypted.slice(..), data);
255
256 Ok(())
257 }
258
259 #[test]
260 fn test_as_ref_encrypted() {
261 let data = b"Test data";
262 let encrypted = encrypt(data).unwrap();
263 let as_ref_data: &[u8] = encrypted.as_ref();
264 assert_eq!(as_ref_data, &*encrypted);
265 assert_eq!(as_ref_data, &encrypted.bytes[..]);
266 }
267
268 #[test]
269 fn test_deref_encrypted() {
270 let data = b"More test data";
271 let encrypted = encrypt(data).unwrap();
272 let deref_data: &[u8] = &encrypted; assert_eq!(deref_data, &encrypted.bytes[..]);
274 }
275
276 #[test]
277 fn test_key_from_hash() {
278 let data = b"Test data for key derivation";
279 let h = hash(data).unwrap();
280
281 let ParsedKey {
282 key,
283 length: _,
284 nonce: _,
285 } = (&h).into();
286
287 assert_eq!(key.len(), 32);
288 }
289
290 #[test]
291 fn test_encrypt_large_data() {
292 let data = vec![b'A'; 1024 * 1024];
294 let encrypted = encrypt(&data).unwrap();
295 let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
296 assert_eq!(&*decrypted, &data[..]);
297 }
298
299 #[test]
300 fn test_ps_cypher_error_display() {
301 let data = b"test";
302 let encrypted = encrypt(data).unwrap();
303 let bad_key = hash(b"invalid_key").unwrap();
304 let result = decrypt(&encrypted, &bad_key);
305
306 if let Err(e) = result {
307 let error_message = format!("{e}");
308 assert_eq!(
309 error_message,
310 "Encryption/Decryption failure (from chacha20poly1305)"
311 ); } else {
313 panic!("Expected an error, but got success");
314 }
315 }
316
317 #[test]
318 fn test_ps_cypher_error_source() {
319 let data = b"test";
320 let encrypted = encrypt(data).unwrap();
321 let bad_key = hash(b"invalid_key").unwrap();
322 let result = decrypt(&encrypted, &bad_key);
323
324 if let Err(e) = result {
325 let source = std::error::Error::source(&e);
326 if let Some(err) = source {
327 let _ = format!("{err}"); }
329 } else {
330 panic!("Expected an error, but got success");
331 }
332 }
333}