Skip to main content

qurox_pq/
simple.rs

1// Copyright 2025 Philippe Lecrosnier
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Simplified Qurox Crypto API
16//!
17//! This module provides a clean, simple API that uses the CryptographyBridge pattern
18//! internally while exposing an ergonomic interface for post-quantum cryptography.
19
20use crate::algorithms::{HybridCrypto, MlDsa44, MlKem768};
21use crate::bridge::{CryptographyBridge, KeyEncapsulationBridge};
22use crate::errors::{CryptoError, Result};
23use crate::types::{
24    Algorithm, ClassicalAlgorithm, HybridKeyPair, HybridPolicy, HybridPublicBundle,
25    PostQuantumAlgorithm, PublicKey, SecurityLevel, TransitionMode,
26};
27use zeroize::Zeroizing;
28
29/// Quantum-safe signer using ML-DSA-44
30pub struct QuantumSigner {
31    bridge: MlDsa44,
32    public_key: <MlDsa44 as CryptographyBridge>::PublicKey,
33    secret_key: <MlDsa44 as CryptographyBridge>::SecretKey,
34}
35
36impl QuantumSigner {
37    /// Create a new quantum-safe signer
38    pub fn new() -> Result<Self> {
39        let bridge = MlDsa44;
40        let (public_key, secret_key) = bridge.key_generator()?;
41        Ok(Self {
42            bridge,
43            public_key,
44            secret_key,
45        })
46    }
47
48    /// Sign a message using post-quantum cryptography
49    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
50        let signature = self.bridge.sign(&self.secret_key, message)?;
51        Ok(self.bridge.signature_to_bytes(&signature))
52    }
53
54    /// Verify a signature
55    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
56        // For ML-DSA, signature bytes are stored directly as Vec<u8>
57        let sig_vec = signature.to_vec();
58        self.bridge.verify(&self.public_key, message, &sig_vec)
59    }
60
61    /// Get public key bytes
62    pub fn public_key_bytes(&self) -> Vec<u8> {
63        self.bridge.public_key_to_bytes(&self.public_key)
64    }
65}
66
67/// Hybrid signer combining classical and post-quantum cryptography
68pub struct HybridSigner {
69    hybrid_crypto: HybridCrypto,
70    hybrid_keypair: HybridKeyPair,
71}
72
73impl HybridSigner {
74    /// Create a new hybrid signer with default policy
75    pub fn new() -> Result<Self> {
76        let hybrid_crypto = HybridCrypto::new_default();
77        let hybrid_keypair = hybrid_crypto.generate_hybrid_keypair()?;
78        Ok(Self {
79            hybrid_crypto,
80            hybrid_keypair,
81        })
82    }
83
84    /// Create a hybrid signer with custom policy
85    pub fn with_policy(policy: HybridPolicy) -> Result<Self> {
86        let hybrid_crypto = HybridCrypto::new(policy);
87        let hybrid_keypair = hybrid_crypto.generate_hybrid_keypair()?;
88        Ok(Self {
89            hybrid_crypto,
90            hybrid_keypair,
91        })
92    }
93
94    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
95        let signature = self
96            .hybrid_crypto
97            .sign_hybrid(&self.hybrid_keypair, message)?;
98        serde_json::to_vec(&signature).map_err(|_| {
99            CryptoError::SerializationError("Failed to serialize signature".to_string())
100        })
101    }
102
103    /// Sign and compress. Useful when bandwidth matters — hybrid signatures
104    /// combine ECDSA and ML-DSA, which adds up to ~2.4 KB before compression.
105    pub fn sign_compact(&self, message: &[u8]) -> Result<Vec<u8>> {
106        let compressed_sig = self
107            .hybrid_crypto
108            .sign_hybrid_compressed(&self.hybrid_keypair, message)?;
109        serde_json::to_vec(&compressed_sig).map_err(|_| {
110            CryptoError::SerializationError("Failed to serialize compressed signature".to_string())
111        })
112    }
113
114    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
115        let sig = serde_json::from_slice(signature).map_err(|_| {
116            CryptoError::SerializationError("Failed to deserialize signature".to_string())
117        })?;
118        self.hybrid_crypto
119            .verify_hybrid(&self.hybrid_keypair, message, &sig)
120    }
121
122    pub fn verify_compact(&self, message: &[u8], compressed_signature: &[u8]) -> Result<bool> {
123        let compressed_sig = serde_json::from_slice(compressed_signature).map_err(|_| {
124            CryptoError::SerializationError(
125                "Failed to deserialize compressed signature".to_string(),
126            )
127        })?;
128        self.hybrid_crypto
129            .verify_hybrid_compressed(&self.hybrid_keypair, message, &compressed_sig)
130    }
131
132    /// Sign with only the classical (ECDSA secp256k1) half of the stored keypair.
133    pub fn classical_signature(&self, message: &[u8]) -> Result<Vec<u8>> {
134        use crate::algorithms::EcdsaCrypto;
135        let sig = EcdsaCrypto::sign(&self.hybrid_keypair.classical_keypair.private_key, message)?;
136        Ok(sig.bytes)
137    }
138
139    /// The ECDSA public key corresponding to `classical_signature`.
140    /// Needed by the verifier if they only check the classical half.
141    pub fn classical_public_key(&self) -> Vec<u8> {
142        self.hybrid_keypair
143            .classical_keypair
144            .public_key
145            .bytes
146            .clone()
147    }
148
149    /// Both public keys — classical and post-quantum.
150    /// Use this when sharing your hybrid public key with a peer.
151    pub fn public_keys(&self) -> (&[u8], &[u8]) {
152        (
153            &self.hybrid_keypair.classical_keypair.public_key.bytes,
154            &self.hybrid_keypair.post_quantum_keypair.public_key.bytes,
155        )
156    }
157
158    /// Export a public-key-only bundle for cross-party verification.
159    /// Share this with a verifier — it contains no private material.
160    pub fn public_key_bundle(&self) -> HybridPublicBundle {
161        HybridPublicBundle {
162            classical_public_key: self.hybrid_keypair.classical_keypair.public_key.clone(),
163            post_quantum_public_key: self
164                .hybrid_keypair
165                .post_quantum_keypair
166                .public_key
167                .clone(),
168            security_level: self.hybrid_keypair.security_level,
169            transition_mode: self.hybrid_crypto.get_policy().transition_mode,
170        }
171    }
172
173    /// Build a verifier from this signer's public keys.
174    pub fn verifier(&self) -> HybridVerifier {
175        HybridVerifier::from_bundle(self.public_key_bundle())
176    }
177}
178
179/// Key encapsulation via ML-KEM-768 (FIPS 203).
180/// Used to establish a shared secret over an untrusted channel.
181pub struct QuantumEncryptor {
182    bridge: MlKem768,
183    public_key: <MlKem768 as KeyEncapsulationBridge>::PublicKey,
184    secret_key: <MlKem768 as KeyEncapsulationBridge>::SecretKey,
185}
186
187impl QuantumEncryptor {
188    pub fn new() -> Result<Self> {
189        let bridge = MlKem768;
190        let (public_key, secret_key) = bridge.kem_keygen()?;
191        Ok(Self {
192            bridge,
193            public_key,
194            secret_key,
195        })
196    }
197
198    /// Returns `(ciphertext, shared_secret)`. Send the ciphertext to the other party;
199    /// they call `decapsulate` to recover the same shared secret.
200    pub fn encapsulate(&self) -> Result<(Vec<u8>, Vec<u8>)> {
201        let (ciphertext, shared_secret) = self.bridge.encapsulate(&self.public_key)?;
202        use fips203::traits::SerDes;
203        Ok((ciphertext.into_bytes().to_vec(), shared_secret))
204    }
205
206    pub fn decapsulate(&self, ciphertext_bytes: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
207        use fips203::ml_kem_768::{CipherText, CT_LEN};
208        use fips203::traits::SerDes;
209
210        let ct_array: [u8; CT_LEN] = ciphertext_bytes
211            .try_into()
212            .map_err(|_| CryptoError::Generic("Invalid ciphertext size".to_string()))?;
213        let ciphertext = CipherText::try_from_bytes(ct_array)
214            .map_err(|_| CryptoError::Generic("Invalid ciphertext".to_string()))?;
215
216        let shared_secret = self.bridge.decapsulate(&self.secret_key, &ciphertext)?;
217        Ok(Zeroizing::new(shared_secret))
218    }
219
220    pub fn public_key_bytes(&self) -> Vec<u8> {
221        self.bridge.kem_public_key_to_bytes(&self.public_key)
222    }
223}
224
225/// Verifier for ML-DSA-44 signatures. Holds only the public key — no private material.
226/// Construct from `QuantumSigner::public_key_bytes()` to verify signatures from a remote party.
227pub struct QuantumVerifier {
228    public_key: PublicKey,
229}
230
231impl QuantumVerifier {
232    /// Build a verifier from raw public key bytes produced by `QuantumSigner::public_key_bytes()`.
233    pub fn from_bytes(public_key_bytes: &[u8]) -> Result<Self> {
234        Ok(Self {
235            public_key: PublicKey {
236                bytes: public_key_bytes.to_vec(),
237                algorithm: Algorithm::MlDsa44,
238            },
239        })
240    }
241
242    /// Verify a signature produced by `QuantumSigner::sign()`.
243    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
244        use crate::algorithms::MlDsaCrypto;
245        use crate::types::Signature;
246        let sig = Signature { bytes: signature.to_vec(), algorithm: Algorithm::MlDsa44 };
247        MlDsaCrypto::verify(&self.public_key, message, &sig)
248    }
249}
250
251/// Verifier for hybrid (classical + post-quantum) signatures.
252/// Holds only public keys — no private material.
253/// Construct from `HybridSigner::public_key_bundle()`.
254pub struct HybridVerifier {
255    bundle: HybridPublicBundle,
256    hybrid_crypto: HybridCrypto,
257}
258
259impl HybridVerifier {
260    /// Build a verifier from a public key bundle produced by `HybridSigner::public_key_bundle()`.
261    pub fn from_bundle(bundle: HybridPublicBundle) -> Self {
262        let classical_algorithm = match bundle.classical_public_key.algorithm {
263            Algorithm::EcdsaK256 => ClassicalAlgorithm::EcdsaK256,
264            Algorithm::EcdsaP256 => ClassicalAlgorithm::EcdsaP256,
265            Algorithm::Schnorr => ClassicalAlgorithm::Schnorr,
266            _ => ClassicalAlgorithm::EcdsaK256,
267        };
268        let post_quantum_algorithm = match bundle.post_quantum_public_key.algorithm {
269            Algorithm::MlDsa44 => PostQuantumAlgorithm::MlDsa44,
270            Algorithm::SlhDsaSha2128f => PostQuantumAlgorithm::SlhDsaSha2128f,
271            _ => PostQuantumAlgorithm::MlDsa44,
272        };
273        let policy = HybridPolicy {
274            security_level: bundle.security_level,
275            transition_mode: bundle.transition_mode,
276            classical_algorithm,
277            post_quantum_algorithm,
278            compression_enabled: false,
279            compression_config: None,
280        };
281        Self {
282            bundle,
283            hybrid_crypto: HybridCrypto::new(policy),
284        }
285    }
286
287    /// Verify a hybrid signature produced by `HybridSigner::sign()`.
288    pub fn verify(&self, message: &[u8], signature: &[u8]) -> crate::errors::Result<bool> {
289        let sig = serde_json::from_slice(signature).map_err(|_| {
290            crate::errors::CryptoError::SerializationError(
291                "Failed to deserialize signature".to_string(),
292            )
293        })?;
294        self.hybrid_crypto.verify_hybrid_bundle(&self.bundle, message, &sig)
295    }
296
297    /// Verify a compact (compressed) hybrid signature produced by `HybridSigner::sign_compact()`.
298    pub fn verify_compact(
299        &self,
300        message: &[u8],
301        compressed_signature: &[u8],
302    ) -> crate::errors::Result<bool> {
303        let compressed_sig = serde_json::from_slice(compressed_signature).map_err(|_| {
304            crate::errors::CryptoError::SerializationError(
305                "Failed to deserialize compressed signature".to_string(),
306            )
307        })?;
308        self.hybrid_crypto
309            .verify_hybrid_bundle_compressed(&self.bundle, message, &compressed_sig)
310    }
311}
312
313pub mod qurox {
314    use super::*;
315
316    pub fn quantum_signer() -> Result<QuantumSigner> {
317        QuantumSigner::new()
318    }
319
320    pub fn hybrid_signer() -> Result<HybridSigner> {
321        HybridSigner::new()
322    }
323
324    pub fn quantum_encryptor() -> Result<QuantumEncryptor> {
325        QuantumEncryptor::new()
326    }
327
328    /// Hybrid with `HybridRequired` — both signatures must verify.
329    pub fn secure_signer() -> Result<HybridSigner> {
330        let policy = HybridPolicy {
331            security_level: SecurityLevel::Hybrid,
332            transition_mode: TransitionMode::HybridRequired,
333            classical_algorithm: ClassicalAlgorithm::EcdsaK256,
334            post_quantum_algorithm: PostQuantumAlgorithm::MlDsa44,
335            compression_enabled: true,
336            compression_config: None,
337        };
338        HybridSigner::with_policy(policy)
339    }
340
341    /// Hybrid with compression enabled. Use `sign_compact` / `verify_compact`.
342    pub fn compact_signer() -> Result<HybridSigner> {
343        let policy = HybridPolicy {
344            security_level: SecurityLevel::Hybrid,
345            transition_mode: TransitionMode::HybridOptional,
346            classical_algorithm: ClassicalAlgorithm::EcdsaK256,
347            post_quantum_algorithm: PostQuantumAlgorithm::MlDsa44,
348            compression_enabled: true,
349            compression_config: None,
350        };
351        HybridSigner::with_policy(policy)
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn test_quantum_signer() {
361        let signer = QuantumSigner::new().unwrap();
362        let message = b"quantum test message";
363
364        let signature = signer.sign(message).unwrap();
365        let is_valid = signer.verify(message, &signature).unwrap();
366
367        assert!(is_valid);
368        assert!(!signature.is_empty());
369        assert!(!signer.public_key_bytes().is_empty());
370    }
371
372    #[test]
373    fn test_hybrid_signer() {
374        let signer = HybridSigner::new().unwrap();
375        let message = b"hybrid test message";
376
377        let signature = signer.sign(message).unwrap();
378        let is_valid = signer.verify(message, &signature).unwrap();
379
380        assert!(is_valid);
381        assert!(!signature.is_empty());
382    }
383
384    #[test]
385    fn test_compact_signing() {
386        let signer = HybridSigner::new().unwrap();
387        let message = b"compact test message";
388
389        let compact_sig = signer.sign_compact(message).unwrap();
390        let is_valid = signer.verify_compact(message, &compact_sig).unwrap();
391
392        assert!(is_valid);
393        assert!(!compact_sig.is_empty());
394    }
395
396    #[test]
397    fn test_quantum_encryptor() {
398        let encryptor = QuantumEncryptor::new().unwrap();
399
400        let (ciphertext, shared_secret1) = encryptor.encapsulate().unwrap();
401        let shared_secret2 = encryptor.decapsulate(&ciphertext).unwrap();
402
403        assert_eq!(shared_secret1.as_slice(), shared_secret2.as_slice());
404        assert!(!ciphertext.is_empty());
405        assert!(!shared_secret1.is_empty());
406    }
407
408    #[test]
409    fn test_quantum_verifier_cross_party() {
410        let alice = QuantumSigner::new().unwrap();
411        let message = b"signed by alice";
412
413        let sig = alice.sign(message).unwrap();
414
415        // Bob builds a verifier from Alice's public key — no private key involved
416        let bob = QuantumVerifier::from_bytes(&alice.public_key_bytes()).unwrap();
417        assert!(bob.verify(message, &sig).unwrap());
418
419        // Wrong message must fail
420        assert!(!bob.verify(b"not alice's message", &sig).unwrap());
421    }
422
423    #[test]
424    fn test_hybrid_verifier_cross_party() {
425        let alice = HybridSigner::new().unwrap();
426        let message = b"hybrid signed by alice";
427
428        let sig = alice.sign(message).unwrap();
429
430        // Bob gets Alice's public bundle and verifies without knowing her private keys
431        let bob = alice.verifier();
432        assert!(bob.verify(message, &sig).unwrap());
433
434        // Wrong message must fail
435        assert!(!bob.verify(b"tampered", &sig).unwrap());
436    }
437
438    #[test]
439    fn test_hybrid_verifier_compact_cross_party() {
440        let alice = HybridSigner::new().unwrap();
441        let message = b"compact hybrid signed by alice";
442
443        let compact_sig = alice.sign_compact(message).unwrap();
444
445        let bob = alice.verifier();
446        assert!(bob.verify_compact(message, &compact_sig).unwrap());
447    }
448
449    #[test]
450    fn test_hybrid_verifier_rejects_wrong_signer() {
451        let alice = HybridSigner::new().unwrap();
452        let eve = HybridSigner::new().unwrap();
453        let message = b"signed by alice";
454
455        let sig = alice.sign(message).unwrap();
456
457        // Eve's verifier uses Eve's public keys — must reject Alice's signature
458        let eve_verifier = eve.verifier();
459        assert!(!eve_verifier.verify(message, &sig).unwrap());
460    }
461
462    #[test]
463    fn test_qurox_api() {
464        // Test simple API
465        let quantum = qurox::quantum_signer().unwrap();
466        let hybrid = qurox::hybrid_signer().unwrap();
467        let encryptor = qurox::quantum_encryptor().unwrap();
468        let secure = qurox::secure_signer().unwrap();
469        let compact = qurox::compact_signer().unwrap();
470
471        let message = b"qurox api test";
472
473        // Quantum signing
474        let q_sig = quantum.sign(message).unwrap();
475        assert!(quantum.verify(message, &q_sig).unwrap());
476
477        // Hybrid signing
478        let h_sig = hybrid.sign(message).unwrap();
479        assert!(hybrid.verify(message, &h_sig).unwrap());
480
481        // Secure signer
482        let s_sig = secure.sign(message).unwrap();
483        assert!(secure.verify(message, &s_sig).unwrap());
484
485        // Compact signing
486        let c_sig = compact.sign_compact(message).unwrap();
487        assert!(compact.verify_compact(message, &c_sig).unwrap());
488
489        // Quantum encryption
490        let (ct, ss1) = encryptor.encapsulate().unwrap();
491        let ss2 = encryptor.decapsulate(&ct).unwrap();
492        assert_eq!(ss1.as_slice(), ss2.as_slice());
493    }
494}