qfe/lib.rs
1#![crate_name = "qfe"]
2//! # Qualitative Frame Entanglement (QFE) - Experimental Secure Communication Framework
3//!
4//! **Current Version Date:** April 7, 2025
5//!
6//! **NOTE:** This library is an experimental simulation framework. While it now incorporates
7//! standard, vetted cryptographic primitives for core security functions (PQC KEM, HKDF, AEAD),
8//! the overall integrated system has not undergone formal security audits. Use with caution
9//! and primarily for research or educational purposes.
10//!
11//! ## Overview
12//!
13//! The QFE framework provides Rust types and functions to simulate secure communication
14//! between participants represented as `Frame` instances. Originally conceived from
15//! foundational principles, the implementation has evolved significantly to prioritize robust
16//! security using modern cryptographic standards.
17//!
18//! The core security flow involves:
19//! 1. **Frame Initialization:** Participants create `Frame` instances with unique internal
20//! states derived deterministically from IDs and seeds using SHA-512.
21//! 2. **Post-Quantum Key Establishment:** A shared secret context (`Sqs`) is established
22//! using the `establish_sqs_kem` function. This function simulates key exchange
23//! using the **ML-KEM-1024 (Kyber)** algorithm (a NIST standard for Post-Quantum
24//! Cryptography), providing resistance against known quantum computer attacks for the
25//! initial shared secret.
26//! 3. **Key Derivation:** The **HKDF-SHA512** key derivation function is used with the
27//! ML-KEM shared secret (and contextual information like participant IDs) to derive
28//! robust session keys, including a specific key for authenticated encryption. These
29//! keys are stored within the `Sqs` struct shared between the participant `Frame`s.
30//! 4. **Authenticated Encryption:** Subsequent communication requiring confidentiality,
31//! integrity, and authenticity is handled by the `encode_aead` and `decode_aead`
32//! methods. These methods utilize the **ChaCha20-Poly1305 AEAD** cipher, keyed with
33//! the key derived during the HKDF step. Correct usage requires generating a **unique
34//! nonce** for each message.
35//! 5. **Zero-Knowledge Proofs (Optional):** The library includes experimental ZKP
36//! features within the `zkp` module (e.g., Schnorr's protocol) that can be bound
37//! to the established session context.
38//!
39//! ## Security Model
40//!
41//! The security of this revised QFE implementation relies directly on the standard
42//! cryptographic hardness assumptions of the underlying primitives:
43//! - **ML-KEM-1024:** Security based on the hardness of the Module-LWE problem (Post-Quantum Secure).
44//! - **HKDF-SHA512:** Security as a standard Key Derivation Function based on HMAC-SHA512.
45//! - **ChaCha20-Poly1305:** Standard AEAD security (Confidentiality, Integrity, Authenticity).
46//! - **SHA-512:** Standard hash function properties.
47//!
48//! **Important Considerations:**
49//! - **Nonce Reuse:** Nonce uniqueness *must* be maintained for AEAD security. Reusing a nonce with the same key breaks ChaCha20-Poly1305 security guarantees. The current implementation uses random nonces.
50//! - **Key Exchange MitM:** The base ML-KEM protocol requires the public key and ciphertext to be exchanged. This exchange is vulnerable to Man-in-the-Middle (MitM) attacks if performed over an insecure channel. Protection against MitM requires either an underlying authenticated channel or out-of-band verification (e.g., comparing `Sqs` fingerprints after establishment).
51//! - **Experimental Status:** As mentioned, the integrated system requires further analysis and review.
52//!
53//! ## Example Workflow
54//!
55//! ```rust,no_run
56//! use qfe::{Frame, establish_sqs_kem, setup_qfe_pair}; // Assuming setup_qfe_pair uses KEM
57//!
58//! fn main() -> Result<(), Box<dyn std::error::Error>> {
59//! // 1. Setup Frames and SQS using KEM + HKDF
60//! let alice_id = "Alice_Main".to_string();
61//! let bob_id = "Bob_Main".to_string();
62//! let context = "example_session_v1";
63//! // Use setup_qfe_pair (assuming it's updated for KEM and context)
64//! let (mut frame_a, mut frame_b) = setup_qfe_pair(
65//! alice_id.clone(), // Seed for Alice's Frame init
66//! bob_id.clone(), // Seed for Bob's Frame init
67//! context // Context string for HKDF salt
68//! )?;
69//!
70//! // (Optional but Recommended) Compare SQS fingerprints out-of-band
71//! let fp_a = frame_a.calculate_sqs_fingerprint()?;
72//! let fp_b = frame_b.calculate_sqs_fingerprint()?;
73//! assert_eq!(fp_a, fp_b, "Fingerprint mismatch indicates potential MitM!");
74//! println!("SQS Fingerprints match: {}", fp_a);
75//!
76//! // 2. Alice Encrypts a message for Bob
77//! let plaintext = b"Secret message from Alice!";
78//! let associated_data = Some(b"message_id_001" as &[u8]);
79//! let encrypted_msg = frame_a.encode_aead(plaintext, associated_data)?;
80//! println!("Alice sends encrypted message (len: {})", encrypted_msg.ciphertext.len());
81//!
82//! // 3. Bob Decrypts the message
83//! let decoded_plaintext = frame_b.decode_aead(&encrypted_msg, associated_data)?;
84//! println!("Bob received: {}", String::from_utf8_lossy(&decoded_plaintext));
85//! assert_eq!(plaintext.as_slice(), decoded_plaintext.as_slice());
86//!
87//! Ok(())
88//! }
89//! ```
90
91use std::sync::Arc; // For shared ownership of Sqs
92use std::error::Error;
93use std::fmt;
94use sha2::{Sha512, Digest};
95
96pub mod zkp;
97pub use zkp::{ZkpValidityResponse, establish_zkp_sqs};
98pub type Sha512Hash = [u8; 64];
99
100use chacha20poly1305::{
101 aead::{Aead, AeadInPlace, KeyInit, Nonce}, // Import necessary traits and types
102 ChaCha20Poly1305, // The AEAD cipher implementation
103 Key, // Type alias for the 32-byte key
104};
105use pqcrypto::kem::mlkem1024;
106use pqcrypto::traits::kem::SharedSecret;
107use hkdf::Hkdf;
108use rand::RngCore;
109
110const SQS_COMPONENTS_V2_INFO: &[u8] = b"QFE_SQS_COMPONENTS_V2";
111const SQS_AEAD_KEY_V1_INFO: &[u8] = b"QFE_AEAD_KEY_V1";
112const SQS_SALT_CONTEXT_V1: &[u8] = b"QFE_SQS_SALT_CONTEXT_V1";
113
114/// Structure to hold the result of AEAD encryption.
115#[derive(Debug, Clone)] // PartialEq, Eq, Hash might be tricky with Vec<u8>
116pub struct QfeEncryptedMessage {
117 /// Nonce used for encryption (12 bytes for ChaCha20Poly1305).
118 /// Must be unique per message per key. MUST be sent with ciphertext.
119 pub nonce: Vec<u8>, // Store as Vec<u8> for flexibility, convert to Nonce type on use
120 /// Ciphertext including the 16-byte authentication tag appended at the end.
121 pub ciphertext: Vec<u8>,
122}
123
124// // --- Constants derived from Framework Core Mathematics ---
125// Primary Scale: φ (phi)
126const PHI: f64 = 1.618033988749895;
127
128// --- Core Data Structures ---
129
130/// Custom error types for the QFE library operations.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum QfeError {
133 SqsEstablishmentFailed(String),
134 EncodingFailed(String),
135 DecodingFailed(String),
136 InvalidUtf8(std::string::FromUtf8Error),
137 FrameInvalid,
138 SqsMissing,
139 InvalidSignature,
140 InternalError(String),
141}
142
143// Display and Error impl remain the same...
144impl fmt::Display for QfeError {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 QfeError::SqsEstablishmentFailed(s) => write!(f, "SQS Establishment Failed: {}", s),
148 QfeError::EncodingFailed(s) => write!(f, "Encoding Failed: {}", s),
149 QfeError::DecodingFailed(s) => write!(f, "Decoding Failed: {}", s),
150 QfeError::InvalidUtf8(e) => write!(f, "Decoded data is not valid UTF-8: {}", e),
151 QfeError::FrameInvalid => write!(f, "Operation failed: Frame is in an invalid state"),
152 QfeError::SqsMissing => write!(f, "Operation failed: SQS component is missing"),
153 QfeError::InvalidSignature => write!(f, "Message signature verification failed"),
154 QfeError::InternalError(s) => write!(f, "Internal QFE error: {}", s),
155 }
156 }
157}
158impl Error for QfeError {}
159
160
161/// Represents the established shared state (secret key and context) between two Frames.
162#[derive(Clone, Default, PartialEq)] // SQS components (Vec<u8>) make Eq complex, PartialEq is fine.
163pub struct Sqs {
164 /// Specific 32-byte key for AEAD (ChaCha20Poly1305) derived via HKDF-SHA512.
165 pub aead_key: Key, // Key is [u8; 32]
166 pattern_type: PatternType,
167 /// The core shared secret (SHA-512 hash output) derived from the interaction.
168 // Note: components are now 64 bytes long.
169 pub components: Vec<u8>,
170 /// Internal validation status determined during creation.
171 validation: bool,
172 /// Identifier of the first participant establishing this SQS.
173 pub participant_a_id: String,
174 /// Identifier of the second participant establishing this SQS.
175 pub participant_b_id: String,
176}
177
178// Debug impl for Sqs remains the same (still uses DefaultHasher for display hash)
179impl fmt::Debug for Sqs {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 let components_prefix = self.components.get(..4) // Get first 4 bytes safely
182 .map(|slice| format!("{:02x}{:02x}{:02x}{:02x}", slice[0], slice[1], slice[2], slice[3]))
183 .unwrap_or_else(|| "[]".to_string()); // Handle empty or short components
184 let aead_key_prefix = format!("{:02x}{:02x}..", self.aead_key[0], self.aead_key[1]);
185 f.debug_struct("Sqs")
186 .field("aead_key_prefix", &aead_key_prefix)
187 .field("pattern_type", &self.pattern_type)
188 .field("components_len", &self.components.len())
189 .field("components_prefix", &components_prefix)
190 .field("validation", &self.validation)
191 .finish()
192 }
193}
194
195
196/// Represents a participant Frame (e.g., Sender A, Receiver B).
197#[derive(Debug, Clone)]
198pub struct Frame {
199 id: String,
200 sqs_component: Option<Arc<Sqs>>,
201 pub validation_status: bool,
202 pub zkp_witness: Option<Vec<u8>>,
203 pub zkp_secret_scalar: Option<curve25519_dalek::Scalar>,
204}
205
206/// Represents different types of patterns P(n) = Ω(x) × R(Ω).
207#[derive(Debug, Default, Clone, PartialEq)]
208enum PatternType {
209 #[default]
210 Sqs, // Shared Qualitative Structure
211 // Future types: Information, Transformation, etc.
212}
213
214// --- QFE Algorithm Implementation ---
215
216impl Frame {
217 /// Initializes a new `Frame` instance.
218 ///
219 /// Creates a frame with a unique internal state derived deterministically
220 /// from the provided identifier and seed. The frame starts in a valid state
221 /// without any established shared state (`SQS`).
222 ///
223 /// # Arguments
224 /// * `id`: A `String` identifier for the frame.
225 /// * `initial_seed`: A `u64` seed value used to generate the frame's unique internal state.
226 ///
227 /// # Returns
228 /// * A new `Frame` instance.
229 pub fn initialize(id: String) -> Self {
230 Frame {
231 id,
232 // internal_patterns: Vec::new(),
233 sqs_component: None,
234 validation_status: true, // Starts valid
235 zkp_witness: None,
236 zkp_secret_scalar: None,
237 }
238 }
239
240 /// Checks if a shared state (`SQS`) has been successfully established for this frame
241 /// and if the frame is currently in a valid state.
242 ///
243 /// # Returns
244 /// * `true` if an `SQS` is present and `validation_status` is true, `false` otherwise.
245 pub fn has_sqs(&self) -> bool {
246 self.sqs_component.is_some() && self.validation_status
247 }
248
249 /// Returns an immutable reference to the established shared state (`SQS`) data, if available.
250 ///
251 /// Returns `None` if no `SQS` has been established or if the frame is invalid.
252 /// The `SQS` is shared using an `Arc`, so multiple frames can hold references.
253 ///
254 /// # Returns
255 /// * `Some(&Arc<SQS>)` if an SQS exists and the frame is valid.
256 /// * `None` otherwise.
257 pub fn get_sqs(&self) -> Option<&Arc<Sqs>> {
258 if !self.validation_status { return None; }
259 self.sqs_component.as_ref()
260 }
261
262 /// Gets the frame's unique ID.
263 pub fn id(&self) -> &str {
264 &self.id
265 }
266
267
268 /// Checks if the frame is currently considered valid.
269 ///
270 /// The validation status can become `false` if operations like `decode` detect
271 /// inconsistencies or tampering.
272 ///
273 /// # Returns
274 /// * `true` if the frame's `validation_status` is true, `false` otherwise.
275 pub fn is_valid(&self) -> bool {
276 self.validation_status
277 }
278
279 /// Encrypts a message using ChaCha20-Poly1305 AEAD with the frame's SQS context.
280 ///
281 /// Generates a unique random nonce for each encryption. The nonce is included
282 /// in the returned `QfeEncryptedMessage`.
283 ///
284 /// # Arguments
285 /// * `plaintext`: The message bytes to encrypt.
286 /// * `associated_data`: Optional data to authenticate but not encrypt.
287 ///
288 /// # Returns
289 /// * `Ok(QfeEncryptedMessage)` containing the nonce and ciphertext+tag.
290 /// * `Err(QfeError)` if SQS is missing, frame is invalid, or key derivation fails.
291 pub fn encode_aead(
292 &self,
293 plaintext: &[u8],
294 associated_data: Option<&[u8]>
295 ) -> Result<QfeEncryptedMessage, QfeError> {
296 if !self.is_valid() { return Err(QfeError::FrameInvalid); }
297 let sqs = self.get_sqs().ok_or(QfeError::SqsMissing)?;
298
299 // 1. Derive AEAD key from SQS
300 let key = &sqs.aead_key;
301 let cipher = ChaCha20Poly1305::new(key);
302
303 // 2. Generate a unique Nonce (12 bytes for ChaCha20Poly1305)
304 // Using OsRngNonce requires enabling the 'std' feature potentially, or using OsRng directly
305 let mut nonce_bytes = [0u8; 12];
306 rand::rng().fill_bytes(&mut nonce_bytes);
307 let nonce = Nonce::<ChaCha20Poly1305>::from_slice(&nonce_bytes); // Create Nonce type
308
309 // 3. Encrypt the data
310 let _ciphertext = cipher.encrypt(nonce, plaintext)
311 .map_err(|e| QfeError::EncodingFailed(format!("AEAD encryption error: {}", e)))?;
312
313 // Include associated data in the encryption process if provided
314 // Note: AEAD encrypt methods often take plaintext as AsRef<[u8]>, AD separately.
315 // Re-check chacha20poly1305 docs: `encrypt` doesn't directly take AD.
316 // We need `encrypt_in_place` or construct the call differently if AD is used.
317 // Let's use `encrypt_in_place_detached` for clarity with AD.
318
319 // --- Revised Encryption Logic with AD ---
320 let key = &sqs.aead_key;
321 let cipher = ChaCha20Poly1305::new(key);
322 let mut nonce_bytes = [0u8; 12];
323 rand::rng().fill_bytes(&mut nonce_bytes);
324 let nonce = Nonce::<ChaCha20Poly1305>::from_slice(&nonce_bytes);
325
326 // Use encrypt_in_place_detached requires a mutable buffer
327 let mut buffer = Vec::with_capacity(plaintext.len() + 16); // Space for plaintext + tag
328 buffer.extend_from_slice(plaintext);
329
330 let tag = cipher.encrypt_in_place_detached(
331 nonce,
332 associated_data.unwrap_or(&[]), // Pass AD here
333 &mut buffer
334 )
335 .map_err(|e| QfeError::EncodingFailed(format!("AEAD encryption error: {}", e)))?;
336
337 // Append the tag to the ciphertext in the buffer
338 buffer.extend_from_slice(tag.as_slice());
339
340 Ok(QfeEncryptedMessage {
341 nonce: nonce_bytes.to_vec(), // Store the raw nonce bytes
342 ciphertext: buffer, // Ciphertext now includes the tag
343 })
344 }
345
346 /// Decrypts a message using ChaCha20-Poly1305 AEAD with the frame's SQS context.
347 ///
348 /// Verifies the integrity and authenticity using the tag included in the ciphertext.
349 /// Marks the frame invalid if decryption fails.
350 ///
351 /// # Arguments
352 /// * `encrypted_message`: The `QfeEncryptedMessage` containing nonce and ciphertext+tag.
353 /// * `associated_data`: Optional associated data that must match the data used during encryption.
354 ///
355 /// # Returns
356 /// * `Ok(Vec<u8>)` containing the original plaintext if decryption and verification succeed.
357 /// * `Err(QfeError)` if SQS is missing, frame is invalid, key derivation fails, or
358 /// decryption/authentication fails (`QfeError::DecodingFailed`).
359 pub fn decode_aead(
360 &mut self, // Changed to &mut self to allow setting validation_status on failure
361 encrypted_message: &QfeEncryptedMessage,
362 associated_data: Option<&[u8]>
363 ) -> Result<Vec<u8>, QfeError> {
364 if !self.is_valid() { return Err(QfeError::FrameInvalid); } // Check before attempt
365 let sqs = self.get_sqs().ok_or(QfeError::SqsMissing)?;
366
367 // 1. Derive AEAD key from SQS
368 let key = &sqs.aead_key;
369 let cipher = ChaCha20Poly1305::new(key);
370
371 // 2. Get Nonce from message
372 if encrypted_message.nonce.len() != 12 {
373 self.validation_status = false;
374 return Err(QfeError::DecodingFailed("Invalid nonce length received".to_string()));
375 }
376 let nonce = Nonce::<ChaCha20Poly1305>::from_slice(&encrypted_message.nonce);
377
378 // 3. Decrypt the data (includes authenticity check)
379 // Use decrypt_in_place_detached if using that for encryption
380 let mut buffer = encrypted_message.ciphertext.clone(); // Clone to decrypt in place
381
382 // Need to split buffer into ciphertext and tag
383 if buffer.len() < 16 { // Check if buffer is large enough for the tag
384 self.validation_status = false;
385 return Err(QfeError::DecodingFailed("Ciphertext too short to contain tag".to_string()));
386 }
387 let tag_offset = buffer.len() - 16;
388 let (ciphertext_slice_mut, tag_slice) = buffer.split_at_mut(tag_offset);
389 let tag = chacha20poly1305::Tag::from_slice(tag_slice);
390
391
392 let decrypt_result = cipher.decrypt_in_place_detached(
393 nonce,
394 associated_data.unwrap_or(&[]), // Pass AD here
395 ciphertext_slice_mut, // Provide mutable ciphertext slice
396 tag // Provide the tag separately
397 );
398
399 match decrypt_result {
400 Ok(()) => {
401 // Decryption successful, buffer now contains plaintext
402 // Truncate buffer to remove decrypted padding/tag space if necessary (inplace should handle this)
403 Ok(ciphertext_slice_mut.to_vec()) // Return the plaintext part
404 }
405 Err(e) => {
406 self.validation_status = false; // Mark frame invalid on decryption failure
407 Err(QfeError::DecodingFailed(format!("AEAD decryption/authentication failed: {}", e)))
408 }
409 }
410 }
411
412 /// Calculates a short, human-readable fingerprint for the established SQS.
413 ///
414 /// This fingerprint should be compared out-of-band with the other participant's
415 /// fingerprint after `establish_sqs` completes successfully. A match provides
416 /// strong evidence against a Man-in-the-Middle (MitM) attack during the SQS exchange.
417 ///
418 /// The fingerprint is derived by hashing the SQS components, phase lock, and
419 /// participant IDs in a deterministic order.
420 ///
421 /// # Returns
422 /// * `Ok(String)` containing a short hexadecimal fingerprint (e.g., 8 characters).
423 /// * `Err(QfeError::SqsMissing)` if no SQS has been established for this frame.
424 /// * `Err(QfeError::FrameInvalid)` if the frame is in an invalid state.
425 pub fn calculate_sqs_fingerprint(&self) -> Result<String, QfeError> {
426 if !self.is_valid() { return Err(QfeError::FrameInvalid); }
427 let sqs = self.get_sqs().ok_or(QfeError::SqsMissing)?;
428
429 let mut hasher = Sha512::new(); // Use strong hash for fingerprint base
430 hasher.update(b"QFE_SQS_FINGERPRINT_V1"); // Domain separation
431
432 // Include participant IDs, sorted for consistency
433 let mut ids = [sqs.participant_a_id.as_str(), sqs.participant_b_id.as_str()];
434 ids.sort_unstable();
435 hasher.update(ids[0].as_bytes());
436 hasher.update(ids[1].as_bytes());
437
438 // Include core SQS data
439 hasher.update(&sqs.components);
440
441 let full_hash: [u8; 64] = hasher.finalize().into();
442
443 // Take the first 4 bytes (8 hex chars) for a short fingerprint
444 // Use data encoding crate for hex? No, keep deps minimal. Format manually.
445 let fingerprint = format!(
446 "{:02x}{:02x}{:02x}{:02x}",
447 full_hash[0], full_hash[1], full_hash[2], full_hash[3]
448 );
449
450 Ok(fingerprint)
451 }
452}
453
454// --- Public API Functions ---
455
456/// Sets up two Frames (A and B) and establishes a shared state (`SQS`) between them.
457///
458/// This function simplifies the common initialization workflow. It initializes two frames
459/// based on the provided IDs and seeds, then performs the key exchange process
460/// to establish the `SQS`.
461///
462/// # Arguments
463/// * `id_a`: A `String` identifier for the first frame (Frame A).
464/// * `seed_a`: A `u64` seed used for generating Frame A's internal state.
465/// * `id_b`: A `String` identifier for the second frame (Frame B).
466/// * `seed_b`: A `u64` seed used for generating Frame B's internal state.
467///
468/// # Returns
469/// * `Ok((Frame, Frame))` containing the two initialized `Frame` instances, each holding
470/// a reference to the same successfully established `SQS`.
471/// * `Err(QfeError)` if frame initialization fails or if the `SQS` establishment
472/// process fails (e.g., due to internal validation checks or simulated interaction errors).
473/// Specific error reasons can be found in the `QfeError::SqsEstablishmentFailed` variant.
474/// Sets up two Frames (A and B) and establishes a shared state (`SQS`) using ML-KEM.
475pub fn setup_qfe_pair(
476 id_a: String,
477 id_b: String,
478 context_string: &str, // Add context string needed for KDF salt
479) -> Result<(Frame, Frame), QfeError> {
480 let mut frame_a = Frame::initialize(id_a);
481 let mut frame_b = Frame::initialize(id_b);
482 // Use the new KEM-based establishment function
483 establish_sqs_kem(&mut frame_a, &mut frame_b, context_string)?;
484 Ok((frame_a, frame_b))
485}
486
487/// Performs the interactive shared state (`SQS`) establishment process between two Frames.
488///
489/// This function simulates the key exchange. It should typically be called after
490/// `Frame::initialize` for two frames that intend to communicate securely.
491/// Use [`setup_qfe_pair`] for a simpler setup.
492///
493/// # Arguments
494/// * `frame_a`: A mutable reference to the first participating `Frame`.
495/// * `frame_b`: A mutable reference to the second participating `Frame`.
496///
497/// # Returns
498/// * `Ok(())` if the `SQS` is successfully established and validated between the two frames.
499/// Both input frames will be updated internally with a reference to the shared `SQS`.
500/// * `Err(QfeError)` if the process fails. This can happen if:
501/// - Either frame is already invalid (`QfeError::FrameInvalid`).
502/// - An SQS is already established for one or both frames (`QfeError::SqsEstablishmentFailed`).
503/// - The simulated interaction fails internal checks (`QfeError::SqsEstablishmentFailed`).
504///
505/// Performs the interactive shared state (`SQS`) establishment process between two Frames.
506/// This simulates the key exchange. After calling this successfully, it is **highly recommended**
507/// that both participants call [`Frame::calculate_sqs_fingerprint`] and compare the results
508/// via an independent, authenticated channel (e.g., voice call, in person) to verify
509/// the absence of a Man-in-the-Middle attack before using the SQS for sensitive communication.
510///
511/// (Rest of documentation comment remains the same)
512#[deprecated(since="0.3.0", note="Use establish_sqs_kem for secure key establishment.")]
513pub fn establish_sqs(_frame_a: &mut Frame, _frame_b: &mut Frame) -> Result<(), QfeError> {
514 // Keep the old implementation here, or just return an error/panic
515 Err(QfeError::InternalError("establish_sqs is deprecated, use establish_sqs_kem".to_string()))
516 // Or copy the old implementation if needed for compatibility during transition
517}
518
519/// Establishes a shared state (SQS) using ML-KEM-1024 key exchange and HKDF.
520/// This replaces the previous custom SQS establishment logic.
521///
522/// Simulates the KEM process:
523/// 1. Alice generates a keypair (pk_A, sk_A).
524/// 2. Bob uses pk_A to encapsulate -> (ciphertext_C, shared_secret_ss_B).
525/// 3. Alice uses sk_A to decapsulate ciphertext_C -> shared_secret_ss_A.
526/// 4. Both parties use the shared secret (ss_A == ss_B) as IKM for HKDF-SHA512.
527/// 5. HKDF derives the final `Sqs.components`.
528///
529/// # Arguments
530/// * `frame_a` (Alice): Mutable reference to the initiating frame.
531/// * `frame_b` (Bob): Mutable reference to the responding frame.
532/// * `context_string`: A string for domain separation used in HKDF salt.
533///
534/// # Returns
535/// * `Ok(())` on success, updating both frames with the derived `Arc<Sqs>`.
536/// * `Err(QfeError)` on failure (e.g., frame invalid, KEM error, HKDF error).
537pub fn establish_sqs_kem(
538 frame_a: &mut Frame, // Alice
539 frame_b: &mut Frame, // Bob
540 context_string: &str // Context for HKDF salt
541) -> Result<(), QfeError> {
542 if !frame_a.validation_status || !frame_b.validation_status {
543 return Err(QfeError::FrameInvalid);
544 }
545 if frame_a.sqs_component.is_some() || frame_b.sqs_component.is_some() {
546 return Err(QfeError::SqsEstablishmentFailed(
547 "SQS already established".to_string(),
548 ));
549 }
550
551 // --- Simulate ML-KEM Exchange ---
552 // 1. Alice generates keypair
553 // In a real protocol, sk_a is kept secret, pk_a is sent to Bob.
554 let (pk_a, sk_a) = mlkem1024::keypair();
555
556 // 2. Bob receives pk_a, encapsulates -> (shared_secret_b, ciphertext)
557 // In a real protocol, Bob receives pk_a over the network.
558 // Encapsulate returns Result, handle potential errors.
559 let (shared_secret_ss_b, ciphertext_c) = mlkem1024::encapsulate(&pk_a);
560
561 // 3. Alice receives ciphertext_c, decapsulates -> shared_secret_a
562 // In a real protocol, Alice receives ciphertext_c over the network.
563 // Decapsulate returns Result, handle potential errors.
564 let shared_secret_ss_a = mlkem1024::decapsulate(&ciphertext_c, &sk_a);
565
566 // --- Verification (Crucial!) ---
567 // Check that the shared secrets match. In theory they always should if KEM is correct.
568 // The pqcrypto shared secret types should implement PartialEq.
569 if shared_secret_ss_a != shared_secret_ss_b {
570 // This indicates a critical failure in the KEM library or logic.
571 frame_a.validation_status = false;
572 frame_b.validation_status = false;
573 return Err(QfeError::SqsEstablishmentFailed(
574 "CRITICAL: ML-KEM shared secrets mismatch!".to_string()
575 ));
576 }
577 // Use ss_a (or ss_b) as the Input Keying Material (IKM) for HKDF
578 let ikm = shared_secret_ss_a.as_bytes();
579
580 // --- HKDF for SQS Components Derivation ---
581 // 1. Derive Salt for HKDF
582 let mut salt_hasher = Sha512::new();
583 salt_hasher.update(SQS_SALT_CONTEXT_V1);
584 let mut ids = [frame_a.id().as_bytes(), frame_b.id().as_bytes()];
585 ids.sort_unstable();
586 salt_hasher.update(ids[0]);
587 salt_hasher.update(ids[1]);
588 salt_hasher.update(context_string.as_bytes());
589 let salt = salt_hasher.finalize();
590
591 // 2. HKDF-Extract using SHA-512 -> Pseudorandom Key (PRK)
592 let hk = Hkdf::<Sha512>::new(Some(salt.as_slice()), ikm);
593
594 // 3. HKDF-Expand for SQS components (64 bytes)
595 let mut sqs_components_okm = [0u8; 64];
596 hk.expand(SQS_COMPONENTS_V2_INFO, &mut sqs_components_okm)
597 .map_err(|e| QfeError::SqsEstablishmentFailed(format!("HKDF-Expand error for components: {}", e)))?;
598
599 // 4. HKDF-Expand for AEAD key (32 bytes) using DIFFERENT info
600 let mut aead_key_okm = Key::default(); // Key is [u8; 32]
601 hk.expand(SQS_AEAD_KEY_V1_INFO, &mut aead_key_okm)
602 .map_err(|e| QfeError::SqsEstablishmentFailed(format!("HKDF-Expand error for AEAD key: {}", e)))?;
603
604
605 // --- Create and Store SQS ---
606 let sqs = Arc::new(Sqs {
607 pattern_type: PatternType::Sqs,
608 components: sqs_components_okm.to_vec(),
609 aead_key: aead_key_okm, // Store the derived AEAD key
610 validation: true,
611 participant_a_id: frame_a.id.clone(),
612 participant_b_id: frame_b.id.clone(),
613 });
614
615 frame_a.sqs_component = Some(Arc::clone(&sqs));
616 frame_b.sqs_component = Some(sqs);
617
618 Ok(())
619}
620
621//
622// --- Unit Tests ---
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 // Helper to setup two frames with established & conceptually authenticated SQS
628 // Re-using helper from encoding tests is fine if it exists, otherwise define here.
629 fn setup_frames_for_signing(id_a: &str, id_b: &str) -> (Frame, Frame) {
630 // Initialize frames using the derived seeds (this now uses the updated initialize function)
631 let mut frame_a = Frame::initialize(id_a.to_string());
632 let mut frame_b = Frame::initialize(id_b.to_string());
633
634 // Establish SQS (this uses the updated establish_sqs logic internally)
635 establish_sqs_kem(&mut frame_a, &mut frame_b, "test_context")
636 .expect("SQS setup failed during test helper execution");
637
638 (frame_a, frame_b)
639 }
640
641 mod aead_tests {
642 use super::*; // Import from parent `tests` module (and thus lib root)
643
644 // Helper function specific to AEAD tests
645 fn setup_frames_for_aead() -> (Frame, Frame) {
646 // Use the existing updated helper or redefine if needed
647 setup_frames_for_signing("AEAD_A", "AEAD_B") // Re-use helper is fine
648 }
649
650 #[test]
651 fn test_aead_encode_decode_success_no_ad() {
652 let (frame_a, mut frame_b) = setup_frames_for_aead();
653 let plaintext = b"This message is secret and authentic (no AD).";
654 let associated_data = None; // No associated data
655
656 // Encode
657 let encrypted_msg_res = frame_a.encode_aead(plaintext, associated_data);
658 assert!(encrypted_msg_res.is_ok());
659 let encrypted_msg = encrypted_msg_res.unwrap();
660
661 // Nonce should be 12 bytes, Ciphertext > plaintext + 16 bytes (tag)
662 assert_eq!(encrypted_msg.nonce.len(), 12);
663 assert!(encrypted_msg.ciphertext.len() >= plaintext.len() + 16);
664
665 // Decode
666 let decoded_res = frame_b.decode_aead(&encrypted_msg, associated_data);
667 assert!(decoded_res.is_ok());
668 let decoded_plaintext = decoded_res.unwrap();
669
670 // Verify correctness
671 assert_eq!(decoded_plaintext, plaintext);
672 assert!(frame_b.is_valid()); // Frame B should remain valid
673 }
674
675 #[test]
676 fn test_aead_encode_decode_success_with_ad() {
677 let (frame_a, mut frame_b) = setup_frames_for_aead();
678 let plaintext = b"This message is secret and authentic (with AD).";
679 let associated_data = Some(b"Important Context" as &[u8]);
680
681 // Encode
682 let encrypted_msg = frame_a.encode_aead(plaintext, associated_data)
683 .expect("Encoding with AD failed");
684
685 // Decode
686 let decoded_plaintext = frame_b.decode_aead(&encrypted_msg, associated_data)
687 .expect("Decoding with AD failed");
688
689 // Verify correctness
690 assert_eq!(decoded_plaintext, plaintext);
691 assert!(frame_b.is_valid());
692 }
693
694 #[test]
695 fn test_aead_encode_decode_empty_message() {
696 let (frame_a, mut frame_b) = setup_frames_for_aead();
697 let plaintext = b""; // Empty plaintext
698 let associated_data = Some(b"Context for empty message" as &[u8]);
699
700 // Encode
701 let encrypted_msg = frame_a.encode_aead(plaintext, associated_data)
702 .expect("Encoding empty message failed");
703 assert!(encrypted_msg.ciphertext.len() == 16); // Empty plaintext -> only tag remains
704
705 // Decode
706 let decoded_plaintext = frame_b.decode_aead(&encrypted_msg, associated_data)
707 .expect("Decoding empty message failed");
708
709 // Verify correctness
710 assert_eq!(decoded_plaintext, plaintext);
711 assert!(decoded_plaintext.is_empty());
712 assert!(frame_b.is_valid());
713 }
714
715
716 #[test]
717 fn test_aead_decode_fails_tampered_ciphertext() {
718 let (frame_a, mut frame_b) = setup_frames_for_aead();
719 let plaintext = b"Do not tamper!";
720 let mut encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap();
721
722 // Tamper ciphertext (avoiding the tag at the end)
723 if encrypted_msg.ciphertext.len() > 16 { // Ensure there's ciphertext before tag
724 encrypted_msg.ciphertext[0] ^= 0xAA;
725 } else if !encrypted_msg.ciphertext.is_empty() {
726 encrypted_msg.ciphertext[0] ^= 0xAA; // Tamper tag if only tag exists
727 }
728
729
730 // Attempt Decode
731 let decoded_res = frame_b.decode_aead(&encrypted_msg, None);
732 assert!(decoded_res.is_err());
733 let err = decoded_res.unwrap_err();
734 assert!(matches!(err, QfeError::DecodingFailed(_)));
735 assert!(err.to_string().contains("AEAD decryption/authentication failed"));
736 assert!(!frame_b.is_valid()); // Frame should be invalidated
737 }
738
739 #[test]
740 fn test_aead_decode_fails_tampered_tag() {
741 let (frame_a, mut frame_b) = setup_frames_for_aead();
742 let plaintext = b"Do not tamper tag!";
743 let mut encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap();
744
745 // Tamper tag (last 16 bytes)
746 let ct_len = encrypted_msg.ciphertext.len();
747 if ct_len >= 16 {
748 encrypted_msg.ciphertext[ct_len - 1] ^= 0xAA; // Flip last byte of tag
749 }
750
751 // Attempt Decode
752 let decoded_res = frame_b.decode_aead(&encrypted_msg, None);
753 assert!(decoded_res.is_err());
754 assert!(matches!(decoded_res.unwrap_err(), QfeError::DecodingFailed(_)));
755 assert!(!frame_b.is_valid());
756 }
757
758
759 #[test]
760 fn test_aead_decode_fails_tampered_nonce() {
761 let (frame_a, mut frame_b) = setup_frames_for_aead();
762 let plaintext = b"Cannot reuse nonces";
763 let mut encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap();
764
765 // Tamper nonce
766 if !encrypted_msg.nonce.is_empty() {
767 encrypted_msg.nonce[0] ^= 0xAA;
768 }
769
770 // Attempt Decode
771 let decoded_res = frame_b.decode_aead(&encrypted_msg, None);
772 // Decryption will proceed but likely produce garbage or fail tag check
773 assert!(decoded_res.is_err());
774 assert!(matches!(decoded_res.unwrap_err(), QfeError::DecodingFailed(_)));
775 assert!(!frame_b.is_valid());
776 }
777
778 // In mod aead_tests
779
780 // Test AD mismatch (string vs string)
781 #[test]
782 fn test_aead_decode_fails_wrong_ad_string_mismatch() {
783 let (frame_a, mut frame_b) = setup_frames_for_aead();
784 let plaintext = b"AD must match";
785 let associated_data = Some(b"Correct AD" as &[u8]);
786 let wrong_associated_data = Some(b"Wrong AD" as &[u8]);
787
788 let encrypted_msg = frame_a.encode_aead(plaintext, associated_data).unwrap();
789
790 // Attempt decode with WRONG AD string
791 let decoded_res = frame_b.decode_aead(&encrypted_msg, wrong_associated_data);
792 assert!(decoded_res.is_err());
793 let err = decoded_res.unwrap_err();
794 println!("Wrong AD (String Mismatch) Decode Error: {:?}", err); // Optional debug
795 assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed for AD string mismatch");
796 assert!(!frame_b.is_valid());
797 }
798
799 // Test AD mismatch (None vs Some)
800 #[test]
801 fn test_aead_decode_fails_wrong_ad_none_vs_some() {
802 let (frame_a, mut frame_b) = setup_frames_for_aead();
803 let plaintext = b"AD must match";
804 let associated_data = Some(b"Correct AD" as &[u8]);
805
806 let encrypted_msg = frame_a.encode_aead(plaintext, associated_data).unwrap();
807
808 // Attempt decode with NO AD (when encoded with AD)
809 let decoded_res_no_ad = frame_b.decode_aead(&encrypted_msg, None);
810 assert!(decoded_res_no_ad.is_err());
811 let err = decoded_res_no_ad.unwrap_err();
812 println!("Wrong AD (None vs Some) Decode Error: {:?}", err); // Optional debug
813 assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed for AD mismatch (None vs Some)");
814 assert!(!frame_b.is_valid());
815 }
816
817 // Test Nonce length too short
818 #[test]
819 fn test_aead_decode_fails_nonce_too_short() {
820 let (frame_a, mut frame_b) = setup_frames_for_aead();
821 let plaintext = b"Nonce length matters";
822 let encrypted_msg_ok = frame_a.encode_aead(plaintext, None).unwrap();
823
824 let mut bad_nonce_msg = encrypted_msg_ok.clone();
825 bad_nonce_msg.nonce = vec![0u8; 11]; // 11 bytes
826
827 let decoded_res = frame_b.decode_aead(&bad_nonce_msg, None);
828 assert!(decoded_res.is_err());
829 let err = decoded_res.unwrap_err();
830 println!("Wrong Nonce Length (11) Decode Error: {:?}", err); // Optional debug
831 assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed for Nonce Length 11");
832 // Check the specific error message associated with the nonce length check
833 assert!(err.to_string().contains("Invalid nonce length received"), "Incorrect error message for short nonce");
834 assert!(!frame_b.is_valid());
835 }
836
837 // Test Nonce length too long
838 #[test]
839 fn test_aead_decode_fails_nonce_too_long() {
840 let (frame_a, mut frame_b) = setup_frames_for_aead();
841 let plaintext = b"Nonce length matters";
842 let encrypted_msg_ok = frame_a.encode_aead(plaintext, None).unwrap();
843
844 let mut bad_nonce_msg = encrypted_msg_ok.clone();
845 bad_nonce_msg.nonce = vec![0u8; 13]; // 13 bytes
846
847 let decoded_res_13 = frame_b.decode_aead(&bad_nonce_msg, None);
848 assert!(decoded_res_13.is_err());
849 let err = decoded_res_13.unwrap_err();
850 println!("Wrong Nonce Length (13) Decode Error: {:?}", err); // Optional debug
851 assert!(matches!(err, QfeError::DecodingFailed(_)), "Expected DecodingFailed for Nonce Length 13");
852 assert!(err.to_string().contains("Invalid nonce length received"), "Incorrect error message for long nonce");
853 assert!(!frame_b.is_valid());
854 }
855
856 #[test]
857 fn test_aead_decode_fails_ad_mismatch_encode_none_decode_some() {
858 let (frame_a, mut frame_b) = setup_frames_for_aead();
859 let plaintext = b"AD mismatch 2";
860 let associated_data = Some(b"Some AD" as &[u8]);
861
862 // Encode with NO AD
863 let encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap();
864
865 // Attempt decode with SOME AD (when encoded with None)
866 let decoded_res = frame_b.decode_aead(&encrypted_msg, associated_data);
867 assert!(decoded_res.is_err());
868 assert!(matches!(decoded_res.unwrap_err(), QfeError::DecodingFailed(_)));
869 assert!(!frame_b.is_valid());
870 }
871
872
873 #[test]
874 fn test_aead_decode_fails_wrong_sqs() {
875 // Setup A-B with SQS1
876 let (frame_a, _frame_b) = setup_frames_for_aead();
877 // Setup C-D with SQS2
878 let (mut frame_c, _frame_d) = setup_frames_for_signing("AEAD_C", "AEAD_D"); // Use different IDs
879
880 // Ensure SQS differ
881 assert_ne!(
882 frame_a.get_sqs().unwrap().components,
883 frame_c.get_sqs().unwrap().components
884 );
885
886 let plaintext = b"Message from A";
887 // A encodes with SQS1
888 let encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap();
889
890 // C tries to decode with SQS2 (wrong key)
891 let decoded_res = frame_c.decode_aead(&encrypted_msg, None);
892 assert!(decoded_res.is_err());
893 assert!(matches!(decoded_res.unwrap_err(), QfeError::DecodingFailed(_)));
894 assert!(!frame_c.is_valid()); // Frame C should be invalidated
895 }
896
897 #[test]
898 fn test_aead_encode_decode_no_sqs() {
899 let frame_a_no_sqs = Frame::initialize("NoSQS_AEAD_A".to_string());
900 let mut frame_b_no_sqs = Frame::initialize("NoSQS_AEAD_B".to_string());
901 let plaintext = b"Cannot encrypt";
902 let dummy_encrypted = QfeEncryptedMessage { nonce: vec![0;12], ciphertext: vec![0;32]};
903
904 let enc_res = frame_a_no_sqs.encode_aead(plaintext, None);
905 assert!(enc_res.is_err());
906 assert!(matches!(enc_res.unwrap_err(), QfeError::SqsMissing));
907
908 let dec_res = frame_b_no_sqs.decode_aead(&dummy_encrypted, None);
909 assert!(dec_res.is_err());
910 assert!(matches!(dec_res.unwrap_err(), QfeError::SqsMissing));
911 }
912
913 #[test]
914 fn test_aead_encode_decode_invalid_frame() {
915 let (mut frame_a, mut frame_b) = setup_frames_for_aead();
916 let plaintext = b"Invalid frame test";
917 let encrypted_msg = frame_a.encode_aead(plaintext, None).unwrap(); // Encode while valid
918
919 // Invalidate frames
920 frame_a.validation_status = false;
921 frame_b.validation_status = false;
922
923 // Try encoding with invalid frame
924 let enc_res = frame_a.encode_aead(plaintext, None);
925 assert!(enc_res.is_err());
926 assert!(matches!(enc_res.unwrap_err(), QfeError::FrameInvalid));
927
928 // Try decoding with invalid frame
929 let dec_res = frame_b.decode_aead(&encrypted_msg, None);
930 assert!(dec_res.is_err());
931 assert!(matches!(dec_res.unwrap_err(), QfeError::FrameInvalid));
932 }
933
934 }
935}