pqc_binary_format/
format.rs

1//! Core binary format implementation with serialization and validation.
2
3use crate::{
4    algorithm::Algorithm, error::CryptoError, metadata::PqcMetadata, Result, PQC_BINARY_VERSION,
5    PQC_MAGIC,
6};
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10/// PQC Binary Format v1.0 specification
11///
12/// This structure represents encrypted data in a standardized, self-describing format
13/// compatible across all post-quantum cryptographic algorithms.
14///
15/// # Example
16///
17/// ```
18/// use pqc_binary_format::{PqcBinaryFormat, Algorithm, PqcMetadata, EncParameters};
19/// use std::collections::HashMap;
20///
21/// let metadata = PqcMetadata {
22///     enc_params: EncParameters {
23///         iv: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
24///         tag: vec![1; 16],
25///         params: HashMap::new(),
26///     },
27///     ..Default::default()
28/// };
29///
30/// let format = PqcBinaryFormat::new(
31///     Algorithm::Hybrid,
32///     metadata,
33///     vec![1, 2, 3, 4, 5],
34///);
35///
36/// // Serialize to bytes
37/// let bytes = format.to_bytes().unwrap();
38///
39/// // Deserialize and verify
40/// let recovered = PqcBinaryFormat::from_bytes(&bytes).unwrap();
41/// assert_eq!(format, recovered);
42/// ```
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct PqcBinaryFormat {
45    /// Magic bytes - always "PQC\x01"
46    pub magic: [u8; 4],
47    /// Format version - currently 0x01
48    pub version: u8,
49    /// Algorithm identifier
50    pub algorithm: Algorithm,
51    /// Feature flags
52    pub flags: u8,
53    /// Algorithm-specific metadata
54    pub metadata: PqcMetadata,
55    /// Encrypted data payload
56    pub data: Vec<u8>,
57    /// SHA-256 checksum of the entire structure (excluding this field)
58    pub checksum: [u8; 32],
59}
60
61/// Feature flags for PQC Binary Format
62///
63/// Indicates optional features used in the encrypted data.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct FormatFlags(u8);
66
67impl FormatFlags {
68    /// Create new flags with all features disabled
69    #[must_use]
70    pub const fn new() -> Self {
71        Self(0)
72    }
73
74    /// Enable compression flag (bit 0)
75    #[must_use]
76    pub const fn with_compression(mut self) -> Self {
77        self.0 |= 0x01;
78        self
79    }
80
81    /// Enable streaming flag (bit 1)
82    #[must_use]
83    pub const fn with_streaming(mut self) -> Self {
84        self.0 |= 0x02;
85        self
86    }
87
88    /// Enable additional authentication flag (bit 2)
89    #[must_use]
90    pub const fn with_additional_auth(mut self) -> Self {
91        self.0 |= 0x04;
92        self
93    }
94
95    /// Enable experimental features flag (bit 3)
96    #[must_use]
97    pub const fn with_experimental(mut self) -> Self {
98        self.0 |= 0x08;
99        self
100    }
101
102    /// Check if compression is enabled
103    #[must_use]
104    pub const fn has_compression(self) -> bool {
105        (self.0 & 0x01) != 0
106    }
107
108    /// Check if streaming is enabled
109    #[must_use]
110    pub const fn has_streaming(self) -> bool {
111        (self.0 & 0x02) != 0
112    }
113
114    /// Check if additional authentication is enabled
115    #[must_use]
116    pub const fn has_additional_auth(self) -> bool {
117        (self.0 & 0x04) != 0
118    }
119
120    /// Check if experimental features are enabled
121    #[must_use]
122    pub const fn has_experimental(self) -> bool {
123        (self.0 & 0x08) != 0
124    }
125
126    /// Get raw flags value
127    #[must_use]
128    pub const fn as_u8(self) -> u8 {
129        self.0
130    }
131
132    /// Create flags from u8 value
133    #[must_use]
134    pub const fn from_u8(value: u8) -> Self {
135        Self(value)
136    }
137}
138
139impl Default for FormatFlags {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl PqcBinaryFormat {
146    /// Create a new PQC binary format structure with default flags
147    ///
148    /// The checksum is automatically calculated and set.
149    ///
150    /// # Example
151    ///
152    /// ```
153    /// use pqc_binary_format::{PqcBinaryFormat, Algorithm, PqcMetadata, EncParameters};
154    /// use std::collections::HashMap;
155    ///
156    /// let metadata = PqcMetadata {
157    ///     enc_params: EncParameters {
158    ///         iv: vec![1; 12],
159    ///         tag: vec![1; 16],
160    ///         params: HashMap::new(),
161    ///     },
162    ///     ..Default::default()
163    /// };
164    ///
165    /// let format = PqcBinaryFormat::new(
166    ///     Algorithm::PostQuantum,
167    ///     metadata,
168    ///     vec![1, 2, 3],
169    /// );
170    /// ```
171    #[must_use]
172    pub fn new(algorithm: Algorithm, metadata: PqcMetadata, data: Vec<u8>) -> Self {
173        let mut format = Self {
174            magic: PQC_MAGIC,
175            version: PQC_BINARY_VERSION,
176            algorithm,
177            flags: FormatFlags::new().as_u8(),
178            metadata,
179            data,
180            checksum: [0u8; 32],
181        };
182
183        // Calculate and set checksum
184        format.checksum = format.calculate_checksum();
185        format
186    }
187
188    /// Create with specific flags
189    ///
190    /// # Example
191    ///
192    /// ```
193    /// use pqc_binary_format::{PqcBinaryFormat, Algorithm, PqcMetadata, FormatFlags, EncParameters};
194    /// use std::collections::HashMap;
195    ///
196    /// let metadata = PqcMetadata {
197    ///     enc_params: EncParameters {
198    ///         iv: vec![1; 12],
199    ///         tag: vec![1; 16],
200    ///         params: HashMap::new(),
201    ///     },
202    ///     ..Default::default()
203    /// };
204    ///
205    /// let flags = FormatFlags::new().with_compression().with_streaming();
206    ///
207    /// let format = PqcBinaryFormat::with_flags(
208    ///     Algorithm::Hybrid,
209    ///     flags,
210    ///     metadata,
211    ///     vec![1, 2, 3],
212    /// );
213    ///
214    /// assert!(format.flags().has_compression());
215    /// assert!(format.flags().has_streaming());
216    /// ```
217    #[must_use]
218    pub fn with_flags(
219        algorithm: Algorithm,
220        flags: FormatFlags,
221        metadata: PqcMetadata,
222        data: Vec<u8>,
223    ) -> Self {
224        let mut format = Self {
225            magic: PQC_MAGIC,
226            version: PQC_BINARY_VERSION,
227            algorithm,
228            flags: flags.as_u8(),
229            metadata,
230            data,
231            checksum: [0u8; 32],
232        };
233
234        format.checksum = format.calculate_checksum();
235        format
236    }
237
238    /// Serialize to binary format
239    ///
240    /// # Errors
241    ///
242    /// Returns [`CryptoError::BinaryFormatError`] if:
243    /// - Format validation fails
244    /// - Binary serialization fails
245    ///
246    /// # Example
247    ///
248    /// ```
249    /// use pqc_binary_format::{PqcBinaryFormat, Algorithm, PqcMetadata, EncParameters};
250    /// use std::collections::HashMap;
251    ///
252    /// # let metadata = PqcMetadata {
253    /// #     enc_params: EncParameters {
254    /// #         iv: vec![1; 12],
255    /// #         tag: vec![1; 16],
256    /// #         params: HashMap::new(),
257    /// #     },
258    /// #     ..Default::default()
259    /// # };
260    /// # let format = PqcBinaryFormat::new(Algorithm::Hybrid, metadata, vec![1, 2, 3]);
261    /// let bytes = format.to_bytes().unwrap();
262    /// // Send bytes over network or save to file
263    /// ```
264    pub fn to_bytes(&self) -> Result<Vec<u8>> {
265        // Validate format before serialization
266        self.validate()?;
267
268        bincode::serialize(self)
269            .map_err(|e| CryptoError::BinaryFormatError(format!("Serialization failed: {e}")))
270    }
271
272    /// Deserialize from binary format with checksum verification
273    ///
274    /// # Errors
275    ///
276    /// Returns [`CryptoError::BinaryFormatError`] if:
277    /// - Binary deserialization fails
278    /// - Format validation fails after deserialization
279    /// - Checksum verification fails
280    ///
281    /// # Example
282    ///
283    /// ```
284    /// use pqc_binary_format::PqcBinaryFormat;
285    ///
286    /// # use pqc_binary_format::{Algorithm, PqcMetadata, EncParameters};
287    /// # use std::collections::HashMap;
288    /// # let metadata = PqcMetadata {
289    /// #     enc_params: EncParameters {
290    /// #         iv: vec![1; 12],
291    /// #         tag: vec![1; 16],
292    /// #         params: HashMap::new(),
293    /// #     },
294    /// #     ..Default::default()
295    /// # };
296    /// # let format = PqcBinaryFormat::new(Algorithm::Hybrid, metadata, vec![1, 2, 3]);
297    /// # let bytes = format.to_bytes().unwrap();
298    /// let recovered = PqcBinaryFormat::from_bytes(&bytes).unwrap();
299    /// ```
300    pub fn from_bytes(data: &[u8]) -> Result<Self> {
301        let format: Self = bincode::deserialize(data)
302            .map_err(|e| CryptoError::BinaryFormatError(format!("Deserialization failed: {e}")))?;
303
304        // Validate deserialized format
305        format.validate()?;
306
307        // Verify checksum
308        let stored_checksum = format.checksum;
309        let mut format_copy = format;
310        format_copy.checksum = [0u8; 32]; // Zero out for recalculation
311        let calculated_checksum = format_copy.calculate_checksum();
312
313        // Restore checksum
314        format_copy.checksum = stored_checksum;
315
316        if stored_checksum != calculated_checksum {
317            return Err(CryptoError::ChecksumMismatch);
318        }
319
320        Ok(format_copy)
321    }
322
323    /// Validate the binary format structure
324    ///
325    /// # Errors
326    ///
327    /// Returns [`CryptoError`] if:
328    /// - Magic bytes are invalid
329    /// - Version is unsupported
330    /// - Algorithm ID is invalid
331    /// - Metadata validation fails
332    pub fn validate(&self) -> Result<()> {
333        // Check magic bytes
334        if self.magic != PQC_MAGIC {
335            return Err(CryptoError::InvalidMagic);
336        }
337
338        // Check version
339        if self.version != PQC_BINARY_VERSION {
340            return Err(CryptoError::UnsupportedVersion(self.version));
341        }
342
343        // Validate algorithm
344        if Algorithm::from_id(self.algorithm.as_id()).is_none() {
345            return Err(CryptoError::UnknownAlgorithm(format!(
346                "Invalid algorithm ID: {:#x}",
347                self.algorithm.as_id()
348            )));
349        }
350
351        // Validate metadata
352        self.metadata.validate()?;
353
354        Ok(())
355    }
356
357    /// Update the checksum field with the calculated checksum
358    ///
359    /// Call this after modifying any fields to maintain integrity.
360    pub fn update_checksum(&mut self) {
361        self.checksum = self.calculate_checksum();
362    }
363
364    /// Calculate SHA-256 checksum with deterministic field-by-field hashing
365    ///
366    /// Uses a deterministic approach to ensure consistent checksums across platforms.
367    fn calculate_checksum(&self) -> [u8; 32] {
368        let mut hasher = Sha256::new();
369
370        // Hash fixed fields with guaranteed deterministic ordering
371        hasher.update(self.magic);
372        hasher.update([self.version]);
373        hasher.update(self.algorithm.as_id().to_le_bytes());
374        hasher.update([self.flags]);
375
376        // Hash metadata deterministically
377        self.hash_metadata_deterministic(&mut hasher);
378
379        // Hash data length and content
380        hasher.update((self.data.len() as u64).to_le_bytes());
381        hasher.update(&self.data);
382
383        hasher.finalize().into()
384    }
385
386    /// Hash metadata in a deterministic way, field by field
387    ///
388    /// Ensures `HashMap` contents are sorted before hashing for consistency.
389    #[allow(clippy::cast_possible_truncation)]
390    fn hash_metadata_deterministic(&self, hasher: &mut Sha256) {
391        // Hash KEM parameters if present
392        if let Some(ref kem_params) = self.metadata.kem_params {
393            hasher.update([1u8]); // Present flag
394            hasher.update((kem_params.public_key.len() as u32).to_le_bytes());
395            hasher.update(&kem_params.public_key);
396            hasher.update((kem_params.ciphertext.len() as u32).to_le_bytes());
397            hasher.update(&kem_params.ciphertext);
398            // Hash params map deterministically by sorting keys
399            let mut sorted_params: Vec<_> = kem_params.params.iter().collect();
400            sorted_params.sort_by(|a, b| a.0.cmp(b.0));
401            hasher.update((sorted_params.len() as u32).to_le_bytes());
402            for (key, value) in sorted_params {
403                hasher.update((key.len() as u32).to_le_bytes());
404                hasher.update(key.as_bytes());
405                hasher.update((value.len() as u32).to_le_bytes());
406                hasher.update(value);
407            }
408        } else {
409            hasher.update([0u8]); // Not present flag
410        }
411
412        // Hash signature parameters if present
413        if let Some(ref sig_params) = self.metadata.sig_params {
414            hasher.update([1u8]); // Present flag
415            hasher.update((sig_params.public_key.len() as u32).to_le_bytes());
416            hasher.update(&sig_params.public_key);
417            hasher.update((sig_params.signature.len() as u32).to_le_bytes());
418            hasher.update(&sig_params.signature);
419            // Hash params map deterministically
420            let mut sorted_params: Vec<_> = sig_params.params.iter().collect();
421            sorted_params.sort_by(|a, b| a.0.cmp(b.0));
422            hasher.update((sorted_params.len() as u32).to_le_bytes());
423            for (key, value) in sorted_params {
424                hasher.update((key.len() as u32).to_le_bytes());
425                hasher.update(key.as_bytes());
426                hasher.update((value.len() as u32).to_le_bytes());
427                hasher.update(value);
428            }
429        } else {
430            hasher.update([0u8]); // Not present flag
431        }
432
433        // Hash encryption parameters
434        hasher.update((self.metadata.enc_params.iv.len() as u32).to_le_bytes());
435        hasher.update(&self.metadata.enc_params.iv);
436        hasher.update((self.metadata.enc_params.tag.len() as u32).to_le_bytes());
437        hasher.update(&self.metadata.enc_params.tag);
438        // Hash enc params map deterministically
439        let mut sorted_params: Vec<_> = self.metadata.enc_params.params.iter().collect();
440        sorted_params.sort_by(|a, b| a.0.cmp(b.0));
441        hasher.update((sorted_params.len() as u32).to_le_bytes());
442        for (key, value) in sorted_params {
443            hasher.update((key.len() as u32).to_le_bytes());
444            hasher.update(key.as_bytes());
445            hasher.update((value.len() as u32).to_le_bytes());
446            hasher.update(value);
447        }
448
449        // Hash compression params if present
450        if let Some(ref comp_params) = self.metadata.compression_params {
451            hasher.update([1u8]); // Present flag
452            hasher.update((comp_params.algorithm.len() as u32).to_le_bytes());
453            hasher.update(comp_params.algorithm.as_bytes());
454            hasher.update(comp_params.level.to_le_bytes());
455            hasher.update(comp_params.original_size.to_le_bytes());
456        } else {
457            hasher.update([0u8]); // Not present flag
458        }
459
460        // Hash custom fields deterministically by sorting keys
461        let mut sorted_custom: Vec<_> = self.metadata.custom.iter().collect();
462        sorted_custom.sort_by(|a, b| a.0.cmp(b.0));
463        hasher.update((sorted_custom.len() as u32).to_le_bytes());
464        for (key, value) in sorted_custom {
465            hasher.update((key.len() as u32).to_le_bytes());
466            hasher.update(key.as_bytes());
467            hasher.update((value.len() as u32).to_le_bytes());
468            hasher.update(value);
469        }
470    }
471
472    /// Get format flags
473    #[must_use]
474    pub fn flags(&self) -> FormatFlags {
475        FormatFlags(self.flags)
476    }
477
478    /// Get algorithm
479    #[must_use]
480    pub const fn algorithm(&self) -> Algorithm {
481        self.algorithm
482    }
483
484    /// Get encrypted data
485    #[must_use]
486    pub fn data(&self) -> &[u8] {
487        &self.data
488    }
489
490    /// Get metadata
491    #[must_use]
492    pub const fn metadata(&self) -> &PqcMetadata {
493        &self.metadata
494    }
495
496    /// Get total size of the binary format when serialized
497    #[must_use]
498    pub fn total_size(&self) -> usize {
499        self.to_bytes().map_or(0, |bytes| bytes.len())
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506    use crate::EncParameters;
507    use std::collections::HashMap;
508
509    #[test]
510    fn test_format_flags() {
511        let flags = FormatFlags::new().with_compression().with_streaming();
512
513        assert!(flags.has_compression());
514        assert!(flags.has_streaming());
515        assert!(!flags.has_additional_auth());
516        assert!(!flags.has_experimental());
517    }
518
519    #[test]
520    fn test_binary_format_roundtrip() {
521        let metadata = PqcMetadata {
522            enc_params: EncParameters {
523                iv: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
524                tag: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
525                params: HashMap::new(),
526            },
527            ..Default::default()
528        };
529
530        let original = PqcBinaryFormat::new(Algorithm::Hybrid, metadata, vec![1, 2, 3, 4, 5]);
531
532        let bytes = original.to_bytes().unwrap();
533        let deserialized = PqcBinaryFormat::from_bytes(&bytes).unwrap();
534
535        assert_eq!(original, deserialized);
536    }
537
538    #[test]
539    fn test_checksum_validation() {
540        let metadata = PqcMetadata {
541            enc_params: EncParameters {
542                iv: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
543                tag: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
544                params: HashMap::new(),
545            },
546            ..Default::default()
547        };
548
549        let format = PqcBinaryFormat::new(Algorithm::PostQuantum, metadata, vec![1, 2, 3, 4, 5]);
550
551        let mut bytes = format.to_bytes().unwrap();
552
553        // Corrupt the data
554        if let Some(byte) = bytes.last_mut() {
555            *byte = byte.wrapping_add(1);
556        }
557
558        // Should fail checksum validation
559        assert!(PqcBinaryFormat::from_bytes(&bytes).is_err());
560    }
561
562    #[test]
563    fn test_flags_roundtrip() {
564        let metadata = PqcMetadata {
565            enc_params: EncParameters {
566                iv: vec![1; 12],
567                tag: vec![1; 16],
568                params: HashMap::new(),
569            },
570            ..Default::default()
571        };
572
573        let flags = FormatFlags::new()
574            .with_compression()
575            .with_streaming()
576            .with_additional_auth();
577
578        let format =
579            PqcBinaryFormat::with_flags(Algorithm::QuadLayer, flags, metadata, vec![1, 2, 3]);
580
581        let bytes = format.to_bytes().unwrap();
582        let recovered = PqcBinaryFormat::from_bytes(&bytes).unwrap();
583
584        assert!(recovered.flags().has_compression());
585        assert!(recovered.flags().has_streaming());
586        assert!(recovered.flags().has_additional_auth());
587        assert!(!recovered.flags().has_experimental());
588    }
589}