Skip to main content

qualia_core_db/crypto/
fiduciary_crypto.rs

1//! Fiduciary Cryptography (ML-DSA / FIPS-204) Implementation
2//!
3//! Post-quantum digital signatures using **real ML-DSA-65** (FIPS-204, NIST security
4//! category 3) via the pure-Rust `fips204` crate. Produced signatures are interoperable
5//! with any conformant FIPS-204 implementation. Pure Rust, WASM-compatible (uses
6//! `getrandom` for entropy).
7//!
8//! NOTE: revisions before 0.0.12 contained a SHA3-based *simulation* of ML-DSA for
9//! demonstration only. That fake lattice path has been removed and replaced with the
10//! standardized algorithm. The serialized key/signature byte layouts therefore changed.
11
12use fips204::ml_dsa_65;
13use fips204::traits::{SerDes, Signer, Verifier};
14use serde::{Deserialize, Serialize};
15use serde_bytes;
16use sha3::{Digest, Sha3_512};
17use std::collections::HashMap;
18use std::sync::{Arc, Mutex};
19
20/// ML-DSA-65 parameters (FIPS-204, NIST security category 3).
21pub const ML_DSA_SECURITY_LEVEL: usize = 192; // approximate classical security bits
22pub const ML_DSA_PRIVATE_KEY_SIZE: usize = ml_dsa_65::SK_LEN; // 4032 bytes
23pub const ML_DSA_PUBLIC_KEY_SIZE: usize = ml_dsa_65::PK_LEN; // 1952 bytes
24pub const ML_DSA_SIGNATURE_SIZE: usize = ml_dsa_65::SIG_LEN; // 3309 bytes
25
26/// ML-DSA cryptographic signer
27pub struct MlDsaSigner {
28    private_key: MlDsaPrivateKey,
29    public_key: MlDsaPublicKey,
30    key_id: Option<String>,
31}
32
33/// ML-DSA-65 private (secret) key — FIPS-204 serialized form (`SK_LEN` = 4032 bytes).
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct MlDsaPrivateKey {
36    #[serde(with = "serde_bytes")]
37    pub sk_bytes: Vec<u8>,
38}
39
40/// ML-DSA-65 public key — FIPS-204 serialized form (`PK_LEN` = 1952 bytes).
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct MlDsaPublicKey {
43    #[serde(with = "serde_bytes")]
44    pub pk_bytes: Vec<u8>,
45}
46
47/// ML-DSA-65 signature — FIPS-204 serialized form (`SIG_LEN` = 3309 bytes).
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct MlDsaSignature {
50    #[serde(with = "serde_bytes")]
51    pub sig_bytes: Vec<u8>,
52}
53
54/// Key management for ML-DSA
55pub struct MlDsaKeyManager {
56    keys: HashMap<String, Arc<Mutex<MlDsaSigner>>>,
57    default_key: Option<String>,
58    key_rotation_policy: KeyRotationPolicy,
59}
60
61/// Key rotation policy
62#[derive(Debug, Clone)]
63pub struct KeyRotationPolicy {
64    pub rotation_interval: u64, // seconds
65    pub max_signatures: u64,
66    pub quantum_resistance_threshold: f64,
67}
68
69/// Cryptographic context for signatures
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct CryptoContext {
72    pub domain: String,
73    pub purpose: String,
74    pub timestamp: u64,
75    pub nonce: [u8; 32],
76}
77
78/// Fiduciary cryptographic operations
79pub struct FiduciaryCrypto {
80    key_manager: Arc<Mutex<MlDsaKeyManager>>,
81    context_manager: ContextManager,
82    compliance_checker: ComplianceChecker,
83}
84
85/// Context manager for cryptographic operations
86pub struct ContextManager {
87    active_contexts: HashMap<String, CryptoContext>,
88    context_cache: Vec<CryptoContext>,
89    max_cache_size: usize,
90}
91
92/// Compliance checker for cryptographic operations
93pub struct ComplianceChecker {
94    quantum_resistance_threshold: f64,
95    fiduciary_standards: FiduciaryStandards,
96    audit_log: Vec<AuditEntry>,
97}
98
99/// Fiduciary standards compliance
100#[derive(Debug, Clone)]
101pub struct FiduciaryStandards {
102    pub min_security_level: usize,
103    pub quantum_resistance_required: bool,
104    pub audit_trail_required: bool,
105    pub key_escrow_required: bool,
106}
107
108/// Audit entry for cryptographic operations
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct AuditEntry {
111    pub timestamp: u64,
112    pub operation: String,
113    pub key_id: Option<String>,
114    pub context: Option<String>,
115    pub success: bool,
116    pub details: String,
117}
118
119impl MlDsaSigner {
120    /// Generate a new real ML-DSA-65 (FIPS-204) key pair.
121    pub fn generate_keypair() -> Result<(MlDsaPrivateKey, MlDsaPublicKey), MlDsaError> {
122        let (pk, sk) =
123            ml_dsa_65::try_keygen().map_err(|e| MlDsaError::KeyGenerationFailed(e.to_string()))?;
124        let private_key = MlDsaPrivateKey {
125            sk_bytes: sk.into_bytes().to_vec(),
126        };
127        let public_key = MlDsaPublicKey {
128            pk_bytes: pk.into_bytes().to_vec(),
129        };
130        Ok((private_key, public_key))
131    }
132
133    /// Create signer from key pair
134    pub fn from_keypair(private_key: MlDsaPrivateKey, public_key: MlDsaPublicKey) -> Self {
135        Self {
136            private_key,
137            public_key,
138            key_id: None,
139        }
140    }
141
142    /// Sign `message` with this signer's ML-DSA-65 secret key.
143    ///
144    /// The `context` fields are bound to the signature via the FIPS-204 context string
145    /// (derived deterministically by `derive_ctx`). Sign and verify must use an equal
146    /// `CryptoContext`.
147    pub fn sign(
148        &self,
149        message: &[u8],
150        context: &CryptoContext,
151    ) -> Result<MlDsaSignature, MlDsaError> {
152        Self::sign_with_secret(&self.private_key.sk_bytes, message, context)
153    }
154
155    /// Verify an ML-DSA-65 signature over `message` against this signer's public key.
156    pub fn verify(
157        &self,
158        message: &[u8],
159        signature: &MlDsaSignature,
160        context: &CryptoContext,
161    ) -> Result<bool, MlDsaError> {
162        Self::verify_with_public(&self.public_key.pk_bytes, message, signature, context)
163    }
164
165    /// Get public key
166    pub fn public_key(&self) -> &MlDsaPublicKey {
167        &self.public_key
168    }
169
170    /// Get key ID
171    pub fn key_id(&self) -> Option<&str> {
172        self.key_id.as_deref()
173    }
174
175    /// Set key ID
176    pub fn set_key_id(&mut self, key_id: String) {
177        self.key_id = Some(key_id);
178    }
179
180    /// Derive a FIPS-204 context string (<= 255 bytes) from a `CryptoContext`.
181    ///
182    /// ML-DSA accepts an application context that is bound into both signing and
183    /// verification. We compress domain/purpose/timestamp/nonce into a 64-byte SHA3-512
184    /// digest so whatever context the application supplies is deterministically bound to
185    /// the signature. Sign and verify must pass an equal `CryptoContext`.
186    fn derive_ctx(context: &CryptoContext) -> Vec<u8> {
187        let mut hasher = Sha3_512::new();
188        hasher.update(context.domain.as_bytes());
189        hasher.update(context.purpose.as_bytes());
190        hasher.update(&context.timestamp.to_be_bytes());
191        hasher.update(&context.nonce);
192        hasher.finalize().to_vec() // 64 bytes, within the 255-byte ML-DSA ctx limit
193    }
194
195    /// Sign `message` with a serialized ML-DSA-65 secret key (`SK_LEN` bytes).
196    pub fn sign_with_secret(
197        sk_bytes: &[u8],
198        message: &[u8],
199        context: &CryptoContext,
200    ) -> Result<MlDsaSignature, MlDsaError> {
201        let sk_arr: [u8; ml_dsa_65::SK_LEN] = sk_bytes.try_into().map_err(|_| {
202            MlDsaError::SignatureGenerationFailed(format!(
203                "secret key must be {} bytes",
204                ml_dsa_65::SK_LEN
205            ))
206        })?;
207        let sk = ml_dsa_65::PrivateKey::try_from_bytes(sk_arr)
208            .map_err(|e| MlDsaError::SignatureGenerationFailed(e.to_string()))?;
209        let ctx = Self::derive_ctx(context);
210        let sig = sk
211            .try_sign(message, &ctx)
212            .map_err(|e| MlDsaError::SignatureGenerationFailed(e.to_string()))?;
213        Ok(MlDsaSignature {
214            sig_bytes: sig.to_vec(),
215        })
216    }
217
218    /// Verify an ML-DSA-65 signature using a serialized public key (`PK_LEN` bytes).
219    pub fn verify_with_public(
220        pk_bytes: &[u8],
221        message: &[u8],
222        signature: &MlDsaSignature,
223        context: &CryptoContext,
224    ) -> Result<bool, MlDsaError> {
225        let pk_arr: [u8; ml_dsa_65::PK_LEN] = pk_bytes.try_into().map_err(|_| {
226            MlDsaError::SignatureVerificationFailed(format!(
227                "public key must be {} bytes",
228                ml_dsa_65::PK_LEN
229            ))
230        })?;
231        let pk = ml_dsa_65::PublicKey::try_from_bytes(pk_arr)
232            .map_err(|e| MlDsaError::SignatureVerificationFailed(e.to_string()))?;
233        let sig_arr: [u8; ml_dsa_65::SIG_LEN] =
234            signature.sig_bytes.as_slice().try_into().map_err(|_| {
235                MlDsaError::SignatureVerificationFailed(format!(
236                    "signature must be {} bytes",
237                    ml_dsa_65::SIG_LEN
238                ))
239            })?;
240        let ctx = Self::derive_ctx(context);
241        Ok(pk.verify(message, &sig_arr, &ctx))
242    }
243
244    // Generate cryptographically secure random bytes using OS entropy (rand 0.10)
245    fn secure_random(buf: &mut [u8]) -> Result<(), MlDsaError> {
246        let mut offset = 0;
247        while offset + 32 <= buf.len() {
248            let chunk: [u8; 32] = rand::random();
249            buf[offset..offset + 32].copy_from_slice(&chunk);
250            offset += 32;
251        }
252        if offset < buf.len() {
253            let remaining = buf.len() - offset;
254            let tail: [u8; 32] = rand::random();
255            buf[offset..].copy_from_slice(&tail[..remaining]);
256        }
257        Ok(())
258    }
259}
260
261impl MlDsaKeyManager {
262    /// Create new key manager
263    pub fn new() -> Self {
264        Self {
265            keys: HashMap::new(),
266            default_key: None,
267            key_rotation_policy: KeyRotationPolicy {
268                rotation_interval: 86400 * 30, // 30 days
269                max_signatures: 1000000,
270                quantum_resistance_threshold: 0.95,
271            },
272        }
273    }
274
275    /// Generate and store new key
276    pub fn generate_key(&mut self, key_id: String) -> Result<(), MlDsaError> {
277        let (private_key, public_key) = MlDsaSigner::generate_keypair()?;
278        let mut signer = MlDsaSigner::from_keypair(private_key, public_key);
279        signer.set_key_id(key_id.clone());
280
281        let signer_arc = Arc::new(Mutex::new(signer));
282        self.keys.insert(key_id.clone(), signer_arc);
283
284        // Set as default if no default exists
285        if self.default_key.is_none() {
286            self.default_key = Some(key_id);
287        }
288
289        Ok(())
290    }
291
292    /// Get signer by key ID
293    pub fn get_signer(&self, key_id: &str) -> Option<Arc<Mutex<MlDsaSigner>>> {
294        self.keys.get(key_id).cloned()
295    }
296
297    /// Get default signer
298    pub fn get_default_signer(&self) -> Option<Arc<Mutex<MlDsaSigner>>> {
299        self.default_key
300            .as_ref()
301            .and_then(|key_id| self.get_signer(key_id))
302    }
303
304    /// List all key IDs
305    pub fn list_keys(&self) -> Vec<String> {
306        self.keys.keys().cloned().collect()
307    }
308
309    /// Remove key
310    pub fn remove_key(&mut self, key_id: &str) -> Result<(), MlDsaError> {
311        self.keys.remove(key_id);
312
313        // Update default key if necessary
314        if self.default_key.as_ref() == Some(&key_id.to_string()) {
315            self.default_key = self.keys.keys().next().cloned();
316        }
317
318        Ok(())
319    }
320
321    /// Check if a key should be rotated based on the rotation policy.
322    /// Returns true if the key has exceeded its maximum signature count
323    /// or the rotation interval has elapsed.
324    pub fn should_rotate_key(
325        &self,
326        _key_id: &str,
327        signature_count: u64,
328        key_age_seconds: u64,
329    ) -> bool {
330        signature_count >= self.key_rotation_policy.max_signatures
331            || key_age_seconds >= self.key_rotation_policy.rotation_interval
332    }
333
334    /// Get the current key rotation policy
335    pub fn rotation_policy(&self) -> &KeyRotationPolicy {
336        &self.key_rotation_policy
337    }
338}
339
340impl ContextManager {
341    /// Create new context manager
342    pub fn new() -> Self {
343        Self {
344            active_contexts: HashMap::new(),
345            context_cache: Vec::new(),
346            max_cache_size: 1000,
347        }
348    }
349
350    /// Create new cryptographic context
351    pub fn create_context(
352        &mut self,
353        domain: String,
354        purpose: String,
355    ) -> Result<CryptoContext, MlDsaError> {
356        let context = CryptoContext {
357            domain,
358            purpose,
359            timestamp: std::time::SystemTime::now()
360                .duration_since(std::time::UNIX_EPOCH)
361                .unwrap()
362                .as_secs(),
363            nonce: Self::generate_nonce(),
364        };
365
366        // Add to cache
367        self.context_cache.push(context.clone());
368
369        // Limit cache size
370        if self.context_cache.len() > self.max_cache_size {
371            self.context_cache.remove(0);
372        }
373
374        Ok(context)
375    }
376
377    /// Get context by ID
378    pub fn get_context(&self, context_id: &str) -> Option<&CryptoContext> {
379        self.active_contexts.get(context_id)
380    }
381
382    /// Generate nonce
383    fn generate_nonce() -> [u8; 32] {
384        let mut nonce = [0u8; 32];
385        MlDsaSigner::secure_random(&mut nonce).unwrap_or(());
386        nonce
387    }
388}
389
390impl ComplianceChecker {
391    /// Create new compliance checker
392    pub fn new() -> Self {
393        Self {
394            quantum_resistance_threshold: 0.95,
395            fiduciary_standards: FiduciaryStandards {
396                min_security_level: 128,
397                quantum_resistance_required: true,
398                audit_trail_required: true,
399                key_escrow_required: false,
400            },
401            audit_log: Vec::new(),
402        }
403    }
404
405    /// Check cryptographic operation compliance
406    pub fn check_compliance(
407        &mut self,
408        operation: &str,
409        key_id: Option<&str>,
410    ) -> Result<bool, MlDsaError> {
411        let timestamp = std::time::SystemTime::now()
412            .duration_since(std::time::UNIX_EPOCH)
413            .unwrap()
414            .as_secs();
415
416        let entry = AuditEntry {
417            timestamp,
418            operation: operation.to_string(),
419            key_id: key_id.map(|s| s.to_string()),
420            context: None,
421            success: true,
422            details: "Compliance check passed".to_string(),
423        };
424
425        self.audit_log.push(entry);
426
427        Ok(true)
428    }
429
430    /// Get audit log
431    pub fn get_audit_log(&self) -> &[AuditEntry] {
432        &self.audit_log
433    }
434
435    /// Clear audit log
436    pub fn clear_audit_log(&mut self) {
437        self.audit_log.clear();
438    }
439
440    /// Check if the current configuration meets quantum resistance requirements.
441    /// Evaluates the ML-DSA security level against the configured threshold.
442    pub fn check_quantum_readiness(&self) -> bool {
443        let ml_dsa_security = ML_DSA_SECURITY_LEVEL as f64 / 256.0;
444        ml_dsa_security >= self.quantum_resistance_threshold
445            && self.fiduciary_standards.quantum_resistance_required
446    }
447
448    /// Get the current fiduciary standards configuration
449    pub fn fiduciary_standards(&self) -> &FiduciaryStandards {
450        &self.fiduciary_standards
451    }
452
453    /// Get quantum resistance threshold
454    pub fn quantum_resistance_threshold(&self) -> f64 {
455        self.quantum_resistance_threshold
456    }
457}
458
459impl FiduciaryCrypto {
460    /// Create new fiduciary crypto system
461    pub fn new() -> Self {
462        Self {
463            key_manager: Arc::new(Mutex::new(MlDsaKeyManager::new())),
464            context_manager: ContextManager::new(),
465            compliance_checker: ComplianceChecker::new(),
466        }
467    }
468
469    /// Generate new key
470    pub fn generate_key(&mut self, key_id: String) -> Result<(), MlDsaError> {
471        let mut key_manager = self.key_manager.lock().unwrap();
472        key_manager.generate_key(key_id)
473    }
474
475    /// Sign message using the internal MlDsaSigner for the given key.
476    ///
477    /// NOTE: The signing context uses timestamp=0 and nonce=[0] so that a matching
478    /// `verify()` call (which reconstructs the same deterministic context) will succeed.
479    /// A future upgrade to FIPS-204 ML-DSA should embed the context in the signature.
480    pub fn sign(
481        &self,
482        message: &[u8],
483        key_id: Option<&str>,
484        domain: String,
485        purpose: String,
486    ) -> Result<MlDsaSignature, MlDsaError> {
487        let key_manager = self.key_manager.lock().unwrap();
488        let signer_arc = if let Some(kid) = key_id {
489            key_manager
490                .get_signer(kid)
491                .ok_or_else(|| MlDsaError::KeyNotFound(kid.to_string()))?
492        } else {
493            key_manager
494                .get_default_signer()
495                .ok_or_else(|| MlDsaError::NoDefaultKey)?
496        };
497        let signer = signer_arc.lock().unwrap();
498
499        let context = CryptoContext {
500            domain,
501            purpose,
502            timestamp: 0,
503            nonce: [0u8; 32],
504        };
505
506        signer.sign(message, &context)
507    }
508
509    /// Verify a signature produced by `sign()` using the internal MlDsaSigner.
510    pub fn verify(
511        &self,
512        message: &[u8],
513        signature: &MlDsaSignature,
514        key_id: Option<&str>,
515        domain: String,
516        purpose: String,
517    ) -> Result<bool, MlDsaError> {
518        let key_manager = self.key_manager.lock().unwrap();
519        let signer_arc = if let Some(kid) = key_id {
520            key_manager
521                .get_signer(kid)
522                .ok_or_else(|| MlDsaError::KeyNotFound(kid.to_string()))?
523        } else {
524            key_manager
525                .get_default_signer()
526                .ok_or_else(|| MlDsaError::NoDefaultKey)?
527        };
528        let signer = signer_arc.lock().unwrap();
529
530        let context = CryptoContext {
531            domain,
532            purpose,
533            timestamp: 0,
534            nonce: [0u8; 32],
535        };
536
537        signer.verify(message, signature, &context)
538    }
539
540    /// Hash a token into a 32-byte digest using SHA3-512 (first 32 bytes).
541    pub fn hash_token(&self, token: &[u8]) -> Result<[u8; 32], MlDsaError> {
542        let mut hasher = Sha3_512::new();
543        hasher.update(token);
544        let digest = hasher.finalize();
545        let mut out = [0u8; 32];
546        out.copy_from_slice(&digest[..32]);
547        Ok(out)
548    }
549
550    /// List all keys
551    pub fn list_keys(&self) -> Vec<String> {
552        let key_manager = self.key_manager.lock().unwrap();
553        key_manager.list_keys()
554    }
555
556    /// Get audit log
557    pub fn get_audit_log(&self) -> Vec<AuditEntry> {
558        let compliance_checker = &self.compliance_checker;
559        compliance_checker.get_audit_log().to_vec()
560    }
561
562    /// Sign a message using a context managed by the internal ContextManager.
563    /// Creates a fresh cryptographic context for the given domain/purpose and
564    /// uses it to bind the signature.
565    pub fn sign_with_managed_context(
566        &mut self,
567        message: &[u8],
568        key_id: Option<&str>,
569        domain: String,
570        purpose: String,
571    ) -> Result<(MlDsaSignature, CryptoContext), MlDsaError> {
572        let context = self.context_manager.create_context(domain, purpose)?;
573        let key_manager = self.key_manager.lock().unwrap();
574        let signer_arc = if let Some(kid) = key_id {
575            key_manager
576                .get_signer(kid)
577                .ok_or_else(|| MlDsaError::KeyNotFound(kid.to_string()))?
578        } else {
579            key_manager
580                .get_default_signer()
581                .ok_or_else(|| MlDsaError::NoDefaultKey)?
582        };
583        let signer = signer_arc.lock().unwrap();
584        let sig = signer.sign(message, &context)?;
585        Ok((sig, context))
586    }
587
588    /// Check if a key should be rotated according to the key manager's policy.
589    pub fn should_rotate_key(
590        &self,
591        key_id: &str,
592        signature_count: u64,
593        key_age_seconds: u64,
594    ) -> bool {
595        let key_manager = self.key_manager.lock().unwrap();
596        key_manager.should_rotate_key(key_id, signature_count, key_age_seconds)
597    }
598
599    /// Check quantum readiness of the compliance checker
600    pub fn check_quantum_readiness(&self) -> bool {
601        self.compliance_checker.check_quantum_readiness()
602    }
603
604    /// Get context manager reference for inspection
605    pub fn context_manager(&self) -> &ContextManager {
606        &self.context_manager
607    }
608}
609
610/// ML-DSA error types
611#[derive(Debug, Clone)]
612pub enum MlDsaError {
613    KeyGenerationFailed(String),
614    KeyNotFound(String),
615    NoDefaultKey,
616    SignatureGenerationFailed(String),
617    SignatureVerificationFailed(String),
618    InvalidContext(String),
619    ComplianceError(String),
620    RandomGenerationError(String),
621}
622
623impl std::fmt::Display for MlDsaError {
624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625        match self {
626            MlDsaError::KeyGenerationFailed(msg) => write!(f, "Key generation failed: {}", msg),
627            MlDsaError::KeyNotFound(msg) => write!(f, "Key not found: {}", msg),
628            MlDsaError::NoDefaultKey => write!(f, "No default key available"),
629            MlDsaError::SignatureGenerationFailed(msg) => {
630                write!(f, "Signature generation failed: {}", msg)
631            }
632            MlDsaError::SignatureVerificationFailed(msg) => {
633                write!(f, "Signature verification failed: {}", msg)
634            }
635            MlDsaError::InvalidContext(msg) => write!(f, "Invalid context: {}", msg),
636            MlDsaError::ComplianceError(msg) => write!(f, "Compliance error: {}", msg),
637            MlDsaError::RandomGenerationError(msg) => write!(f, "Random generation error: {}", msg),
638        }
639    }
640}
641
642impl std::error::Error for MlDsaError {}
643
644// ── ML-DSA Verifiable Credential Issuance ─────────────────────────────────────
645
646/// Predicate hash for VC ML-DSA proof head
647const P_VC_PROOF_MLDSA: u64 = crate::q_hash("vc:proof/mldsa");
648/// Predicate hash for VC ML-DSA proof fragment
649const P_VC_PROOF_MLDSA_FRAG: u64 = crate::q_hash("vc:proof/mldsa/frag");
650
651/// ML-DSA VC proof fragment layout for multi-Quin storage
652/// A 3309-byte ML-DSA signature spans ~414 NQuins (8 bytes per object field)
653#[derive(Debug, Clone)]
654pub struct MlDsaVcProof {
655    pub head_quin: crate::NQuin,
656    pub fragment_quins: Vec<crate::NQuin>,
657}
658
659impl MlDsaVcProof {
660    /// Issue an ML-DSA-signed Verifiable Credential by fragmenting the signature
661    /// across multiple NQuins following the Merkle-DAG pattern.
662    pub fn issue_vc_mldsa(
663        claim_quins: &[crate::NQuin],
664        issuer_sk: &[u8],
665        issuer_did_hash: u64,
666        context: &CryptoContext,
667    ) -> Result<Self, MlDsaError> {
668        // 1. Serialize the claim graph to canonical bytes for signing
669        let claim_bytes = Self::serialize_claims(claim_quins);
670
671        // 2. Sign with ML-DSA
672        let signature = MlDsaSigner::sign_with_secret(issuer_sk, &claim_bytes, context)?;
673
674        // 3. Fragment the signature into NQuin-sized chunks (8 bytes per object field)
675        let sig_bytes = signature.sig_bytes;
676        let total_len = sig_bytes.len();
677        let fragment_count = (total_len + 7) / 8; // Ceiling division by 8
678
679        let mut fragment_quins = Vec::with_capacity(fragment_count);
680
681        for i in 0..fragment_count {
682            let start = i * 8;
683            let end = (start + 8).min(total_len);
684            let chunk = &sig_bytes[start..end];
685
686            // Pack 8 bytes into a u64
687            let mut object: u64 = 0;
688            for (j, &byte) in chunk.iter().enumerate() {
689                object |= (byte as u64) << (j * 8);
690            }
691
692            let metadata = (i as u64) << 32 | (fragment_count as u64);
693            let parity = crate::NQuin::calculate_parity(
694                issuer_did_hash,
695                P_VC_PROOF_MLDSA_FRAG,
696                object,
697                issuer_did_hash,
698                metadata,
699            );
700            let fragment = crate::NQuin {
701                subject: issuer_did_hash,
702                predicate: P_VC_PROOF_MLDSA_FRAG,
703                object,
704                context: issuer_did_hash,
705                metadata,
706                parity,
707            };
708
709            fragment_quins.push(fragment);
710        }
711
712        let head_object = ((total_len as u64) << 32) | (fragment_count as u64);
713        let head_metadata = std::time::SystemTime::now()
714            .duration_since(std::time::UNIX_EPOCH)
715            .unwrap()
716            .as_secs();
717        let head_parity = crate::NQuin::calculate_parity(
718            issuer_did_hash,
719            P_VC_PROOF_MLDSA,
720            head_object,
721            issuer_did_hash,
722            head_metadata,
723        );
724        let head = crate::NQuin {
725            subject: issuer_did_hash,
726            predicate: P_VC_PROOF_MLDSA,
727            object: head_object,
728            context: issuer_did_hash,
729            metadata: head_metadata,
730            parity: head_parity,
731        };
732
733        Ok(Self {
734            head_quin: head,
735            fragment_quins,
736        })
737    }
738
739    /// Verify an ML-DSA-signed VC by reassembling the signature fragments
740    pub fn verify_vc_mldsa(
741        &self,
742        claim_quins: &[crate::NQuin],
743        issuer_pk: &[u8],
744        context: &CryptoContext,
745    ) -> Result<bool, MlDsaError> {
746        let total_len = (self.head_quin.object >> 32) as usize;
747        let expected_fragments = (self.head_quin.object & 0xFFFF_FFFF) as usize;
748        if expected_fragments != self.fragment_quins.len() {
749            return Ok(false);
750        }
751
752        let mut ordered = self.fragment_quins.clone();
753        ordered.sort_by_key(|fragment| fragment.metadata >> 32);
754
755        let mut signature_bytes = Vec::with_capacity(total_len);
756        for fragment in &ordered {
757            let fragment_index = (fragment.metadata >> 32) as usize;
758            let fragment_count = (fragment.metadata & 0xFFFF_FFFF) as usize;
759            if fragment_count != expected_fragments || fragment_index >= expected_fragments {
760                return Ok(false);
761            }
762
763            let start = fragment_index * 8;
764            let chunk_len = 8.min(total_len.saturating_sub(start));
765            for j in 0..chunk_len {
766                let byte = ((fragment.object >> (j * 8)) & 0xFF) as u8;
767                signature_bytes.push(byte);
768            }
769        }
770
771        if signature_bytes.len() != total_len {
772            return Ok(false);
773        }
774
775        // 2. Verify the signature
776        let signature = MlDsaSignature {
777            sig_bytes: signature_bytes,
778        };
779
780        // 3. Serialize the claim graph for verification
781        let claim_bytes = Self::serialize_claims(claim_quins);
782
783        // 4. Verify with ML-DSA
784        MlDsaSigner::verify_with_public(issuer_pk, &claim_bytes, &signature, context)
785    }
786
787    /// Serialize claim Quins to canonical bytes for signing
788    fn serialize_claims(claims: &[crate::NQuin]) -> Vec<u8> {
789        // Simple serialization: concatenate all NQuin bytes
790        let mut bytes = Vec::new();
791        for quin in claims {
792            bytes.extend_from_slice(unsafe {
793                std::slice::from_raw_parts(
794                    quin as *const _ as *const u8,
795                    std::mem::size_of::<crate::NQuin>(),
796                )
797            });
798        }
799        bytes
800    }
801}
802
803// ── Interoperability Cryptographic Algorithms (W3C DID Compatibility) ────
804#[cfg(feature = "interop-crypto")]
805use secp256k1::{ecdsa, Message, PublicKey, Secp256k1, SecretKey};
806
807/// Interoperability ECDSA secp256k1 signer for W3C DID compatibility
808#[cfg(feature = "interop-crypto")]
809#[derive(Debug, Clone)]
810pub struct InteropEcdsaSigner {
811    secret_key: Option<Vec<u8>>,
812    public_key: Option<Vec<u8>>,
813    key_id: Option<String>,
814}
815
816#[cfg(feature = "interop-crypto")]
817#[derive(Debug, Clone, Serialize, Deserialize)]
818pub struct InteropEcdsaSignature {
819    #[serde(with = "serde_bytes")]
820    pub sig_bytes: Vec<u8>,
821}
822
823#[cfg(feature = "interop-crypto")]
824impl InteropEcdsaSigner {
825    /// Generate a new ECDSA keypair
826    pub fn generate() -> Result<Self, MlDsaError> {
827        // secp256k1 0.31: OsRng is now fallible (TryRng); use the rand 0.9 thread
828        // CSPRNG (auto-seeded from OS entropy) which impls the infallible Rng bound.
829        let secp = Secp256k1::new();
830        let mut rng = secp256k1::rand::rng();
831        let (secret_key, public_key) = secp.generate_keypair(&mut rng);
832
833        Ok(Self {
834            secret_key: Some(secret_key.secret_bytes().to_vec()),
835            public_key: Some(public_key.serialize().to_vec()),
836            key_id: None,
837        })
838    }
839
840    /// Create signer from existing secret key
841    pub fn from_secret_key(sk_bytes: &[u8]) -> Result<Self, MlDsaError> {
842        let secp = Secp256k1::new();
843        let secret_key = SecretKey::from_slice(sk_bytes)
844            .map_err(|e| MlDsaError::SignatureGenerationFailed(e.to_string()))?;
845        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
846
847        Ok(Self {
848            secret_key: Some(sk_bytes.to_vec()),
849            public_key: Some(public_key.serialize().to_vec()),
850            key_id: None,
851        })
852    }
853
854    /// Create a verify-only signer from a serialized secp256k1 public key.
855    pub fn from_public_key(pk_bytes: &[u8]) -> Result<Self, MlDsaError> {
856        let secp = Secp256k1::new();
857        let public_key = PublicKey::from_slice(pk_bytes)
858            .map_err(|e| MlDsaError::SignatureVerificationFailed(e.to_string()))?;
859        Ok(Self {
860            secret_key: None,
861            public_key: Some(public_key.serialize().to_vec()),
862            key_id: None,
863        })
864    }
865
866    /// Export the serialized secret key bytes when this signer holds a private key.
867    pub fn export_secret_key(&self) -> Result<Vec<u8>, MlDsaError> {
868        self.secret_key
869            .clone()
870            .ok_or_else(|| MlDsaError::KeyGenerationFailed("No secret key available".to_string()))
871    }
872
873    /// Sign a message using ECDSA
874    pub fn sign(&self, message: &[u8]) -> Result<InteropEcdsaSignature, MlDsaError> {
875        let secp = Secp256k1::new();
876        let secret_key = self
877            .secret_key
878            .as_ref()
879            .ok_or_else(|| MlDsaError::SignatureGenerationFailed("No secret key".to_string()))?;
880        let sk = SecretKey::from_slice(secret_key)
881            .map_err(|e| MlDsaError::SignatureGenerationFailed(e.to_string()))?;
882
883        let msg = Message::from_digest_slice(message)
884            .map_err(|e| MlDsaError::SignatureGenerationFailed(e.to_string()))?;
885
886        let sig = secp.sign_ecdsa(msg, &sk);
887
888        Ok(InteropEcdsaSignature {
889            sig_bytes: sig.serialize_compact().to_vec(),
890        })
891    }
892
893    /// Verify an ECDSA signature
894    pub fn verify(
895        &self,
896        message: &[u8],
897        signature: &InteropEcdsaSignature,
898    ) -> Result<bool, MlDsaError> {
899        let secp = Secp256k1::new();
900        let public_key = self
901            .public_key
902            .as_ref()
903            .ok_or_else(|| MlDsaError::SignatureVerificationFailed("No public key".to_string()))?;
904        let pk = PublicKey::from_slice(public_key)
905            .map_err(|e| MlDsaError::SignatureVerificationFailed(e.to_string()))?;
906
907        let msg = Message::from_digest_slice(message)
908            .map_err(|e| MlDsaError::SignatureVerificationFailed(e.to_string()))?;
909
910        let sig = ecdsa::Signature::from_compact(&signature.sig_bytes)
911            .map_err(|e| MlDsaError::SignatureVerificationFailed(e.to_string()))?;
912
913        Ok(secp.verify_ecdsa(msg, &sig, &pk).is_ok())
914    }
915
916    /// Get the public key
917    pub fn public_key(&self) -> Option<&[u8]> {
918        self.public_key.as_deref()
919    }
920}
921
922#[cfg(test)]
923mod tests {
924    use super::*;
925
926    #[test]
927    fn test_key_generation() {
928        let (private_key, public_key) = MlDsaSigner::generate_keypair().unwrap();
929
930        // Real FIPS-204 ML-DSA-65 serialized key sizes.
931        assert_eq!(private_key.sk_bytes.len(), ML_DSA_PRIVATE_KEY_SIZE);
932        assert_eq!(public_key.pk_bytes.len(), ML_DSA_PUBLIC_KEY_SIZE);
933    }
934
935    #[test]
936    fn test_sign_verify_rejects_tampered_message() {
937        let (private_key, public_key) = MlDsaSigner::generate_keypair().unwrap();
938        let signer = MlDsaSigner::from_keypair(private_key, public_key);
939        let context = CryptoContext {
940            domain: "test".to_string(),
941            purpose: "auth".to_string(),
942            timestamp: 42,
943            nonce: [7u8; 32],
944        };
945        let sig = signer.sign(b"genuine message", &context).unwrap();
946        // A different message must fail verification.
947        assert!(!signer.verify(b"forged message", &sig, &context).unwrap());
948        // A different context must also fail verification.
949        let other_ctx = CryptoContext {
950            purpose: "other".to_string(),
951            ..context.clone()
952        };
953        assert!(!signer.verify(b"genuine message", &sig, &other_ctx).unwrap());
954    }
955
956    #[test]
957    fn test_sign_verify() {
958        let (private_key, public_key) = MlDsaSigner::generate_keypair().unwrap();
959        let signer = MlDsaSigner::from_keypair(private_key, public_key);
960
961        let message = b"Hello, QualiaDB!";
962        let context = CryptoContext {
963            domain: "test".to_string(),
964            purpose: "authentication".to_string(),
965            timestamp: 1234567890,
966            nonce: [
967                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
968                24, 25, 26, 27, 28, 29, 30, 31, 32,
969            ],
970        };
971
972        let signature = signer.sign(message, &context).unwrap();
973        let is_valid = signer.verify(message, &signature, &context).unwrap();
974
975        assert!(is_valid);
976    }
977
978    #[test]
979    fn test_key_manager() {
980        let mut key_manager = MlDsaKeyManager::new();
981
982        key_manager.generate_key("test_key".to_string()).unwrap();
983
984        let keys = key_manager.list_keys();
985        assert!(keys.contains(&"test_key".to_string()));
986
987        let signer = key_manager.get_signer("test_key").unwrap();
988        assert!(signer.lock().unwrap().key_id() == Some("test_key"));
989    }
990
991    #[test]
992    fn test_fiduciary_crypto() {
993        let mut crypto = FiduciaryCrypto::new();
994
995        crypto.generate_key("test_key".to_string()).unwrap();
996
997        let message = b"Test message";
998        let signature = crypto
999            .sign(
1000                message,
1001                Some("test_key"),
1002                "test".to_string(),
1003                "auth".to_string(),
1004            )
1005            .unwrap();
1006
1007        let is_valid = crypto
1008            .verify(
1009                message,
1010                &signature,
1011                Some("test_key"),
1012                "test".to_string(),
1013                "auth".to_string(),
1014            )
1015            .unwrap();
1016
1017        assert!(is_valid);
1018    }
1019
1020    #[test]
1021    fn test_vc_issuance_roundtrip() {
1022        // Generate ML-DSA keypair
1023        let (private_key, public_key) = MlDsaSigner::generate_keypair().unwrap();
1024        let issuer_did_hash = 12345u64;
1025
1026        // Create a simple claim graph (one Quin)
1027        let claim_quins = vec![crate::NQuin {
1028            subject: issuer_did_hash,
1029            predicate: crate::q_hash("test:hasRole"),
1030            object: crate::q_hash("test:Admin"),
1031            context: issuer_did_hash,
1032            metadata: 0,
1033            parity: 0,
1034        }];
1035
1036        // Create context for signing
1037        let context = CryptoContext {
1038            domain: "test".to_string(),
1039            purpose: "vc-issuance".to_string(),
1040            timestamp: 0,
1041            nonce: [0u8; 32],
1042        };
1043
1044        // Issue VC
1045        let proof = MlDsaVcProof::issue_vc_mldsa(
1046            &claim_quins,
1047            &private_key.sk_bytes,
1048            issuer_did_hash,
1049            &context,
1050        )
1051        .unwrap();
1052
1053        // Verify VC
1054        let is_valid = proof
1055            .verify_vc_mldsa(&claim_quins, &public_key.pk_bytes, &context)
1056            .unwrap();
1057
1058        assert!(is_valid, "VC verification should succeed");
1059    }
1060
1061    #[test]
1062    fn test_vc_tampered_fragment_fails() {
1063        let (private_key, public_key) = MlDsaSigner::generate_keypair().unwrap();
1064        let issuer_did_hash = 12345u64;
1065
1066        let claim_quins = vec![crate::NQuin {
1067            subject: issuer_did_hash,
1068            predicate: crate::q_hash("test:hasRole"),
1069            object: crate::q_hash("test:Admin"),
1070            context: issuer_did_hash,
1071            metadata: 0,
1072            parity: 0,
1073        }];
1074
1075        let context = CryptoContext {
1076            domain: "test".to_string(),
1077            purpose: "vc-issuance".to_string(),
1078            timestamp: 0,
1079            nonce: [0u8; 32],
1080        };
1081
1082        let mut proof = MlDsaVcProof::issue_vc_mldsa(
1083            &claim_quins,
1084            &private_key.sk_bytes,
1085            issuer_did_hash,
1086            &context,
1087        )
1088        .unwrap();
1089
1090        // Tamper with a fragment
1091        if !proof.fragment_quins.is_empty() {
1092            proof.fragment_quins[0].object ^= 0xFF; // Flip bits
1093        }
1094
1095        let is_valid = proof
1096            .verify_vc_mldsa(&claim_quins, &public_key.pk_bytes, &context)
1097            .unwrap();
1098
1099        assert!(!is_valid, "Tampered fragment should fail verification");
1100    }
1101
1102    #[test]
1103    fn test_vc_wrong_key_fails() {
1104        let (private_key, _public_key) = MlDsaSigner::generate_keypair().unwrap();
1105        let (_wrong_private, wrong_public) = MlDsaSigner::generate_keypair().unwrap();
1106        let issuer_did_hash = 12345u64;
1107
1108        let claim_quins = vec![crate::NQuin {
1109            subject: issuer_did_hash,
1110            predicate: crate::q_hash("test:hasRole"),
1111            object: crate::q_hash("test:Admin"),
1112            context: issuer_did_hash,
1113            metadata: 0,
1114            parity: 0,
1115        }];
1116
1117        let context = CryptoContext {
1118            domain: "test".to_string(),
1119            purpose: "vc-issuance".to_string(),
1120            timestamp: 0,
1121            nonce: [0u8; 32],
1122        };
1123
1124        let proof = MlDsaVcProof::issue_vc_mldsa(
1125            &claim_quins,
1126            &private_key.sk_bytes,
1127            issuer_did_hash,
1128            &context,
1129        )
1130        .unwrap();
1131
1132        // Try to verify with wrong public key
1133        let is_valid = proof
1134            .verify_vc_mldsa(&claim_quins, &wrong_public.pk_bytes, &context)
1135            .unwrap();
1136
1137        assert!(!is_valid, "Wrong public key should fail verification");
1138    }
1139}