Skip to main content

qurox_pq/
lib.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//! Post-quantum cryptography library. Implements NIST FIPS 203/204/205 alongside
16//! classical ECDSA, with hybrid mode for gradual migration.
17//!
18//! Start with [`simple::qurox`] for the high-level API, or use [`QuroxCrypto`]
19//! directly if you need access to raw keypairs and signatures.
20
21pub mod algorithms;
22pub mod bridge;
23pub mod compression;
24pub mod errors;
25pub mod simple;
26pub mod types;
27
28pub use bridge::*;
29pub use errors::*;
30pub use types::*;
31
32pub use simple::qurox;
33pub use simple::{HybridSigner, HybridVerifier, QuantumEncryptor, QuantumSigner, QuantumVerifier};
34pub use algorithms::ecdsa::EcdsaCurve;
35
36use algorithms::*;
37use compression::*;
38
39pub struct QuroxCrypto;
40
41impl QuroxCrypto {
42    pub fn generate_ecdsa_keypair(curve: EcdsaCurve) -> Result<KeyPair> {
43        EcdsaCrypto::generate_keypair(curve)
44    }
45
46    pub fn generate_schnorr_keypair() -> Result<KeyPair> {
47        SchnorrCrypto::generate_keypair()
48    }
49
50    pub fn generate_mldsa_keypair() -> Result<KeyPair> {
51        MlDsaCrypto::generate_keypair()
52    }
53
54    pub fn generate_slh_dsa_keypair() -> Result<KeyPair> {
55        SlhDsaCrypto::generate_keypair()
56    }
57
58    pub fn generate_mlkem_keypair() -> Result<KeyPair> {
59        MlKemCrypto::generate_keypair()
60    }
61
62    pub fn sign(private_key: &PrivateKey, message: &[u8]) -> Result<Signature> {
63        match private_key.algorithm {
64            Algorithm::EcdsaK256 | Algorithm::EcdsaP256 => EcdsaCrypto::sign(private_key, message),
65            Algorithm::Schnorr => SchnorrCrypto::sign(private_key, message),
66            Algorithm::MlDsa44 => MlDsaCrypto::sign(private_key, message),
67            Algorithm::SlhDsaSha2128f => SlhDsaCrypto::sign(private_key, message),
68            Algorithm::MlKem768 => Err(CryptoError::Generic(
69                "ML-KEM is for encryption, not signing".to_string(),
70            )),
71            Algorithm::Hybrid => Err(CryptoError::Generic(
72                "Use sign_hybrid for hybrid signatures".to_string(),
73            )),
74        }
75    }
76
77    pub fn verify(public_key: &PublicKey, message: &[u8], signature: &Signature) -> Result<bool> {
78        match public_key.algorithm {
79            Algorithm::EcdsaK256 | Algorithm::EcdsaP256 => {
80                EcdsaCrypto::verify(public_key, message, signature)
81            }
82            Algorithm::Schnorr => SchnorrCrypto::verify(public_key, message, signature),
83            Algorithm::MlDsa44 => MlDsaCrypto::verify(public_key, message, signature),
84            Algorithm::SlhDsaSha2128f => SlhDsaCrypto::verify(public_key, message, signature),
85            Algorithm::MlKem768 => Err(CryptoError::Generic(
86                "ML-KEM is for encryption, not signing".to_string(),
87            )),
88            Algorithm::Hybrid => Err(CryptoError::Generic(
89                "Use verify_hybrid for hybrid signatures".to_string(),
90            )),
91        }
92    }
93
94    pub fn encapsulate(public_key: &PublicKey) -> Result<EncryptionResult> {
95        match public_key.algorithm {
96            Algorithm::MlKem768 => MlKemCrypto::encapsulate(public_key),
97            _ => Err(CryptoError::Generic(
98                "Algorithm does not support encapsulation".to_string(),
99            )),
100        }
101    }
102
103    pub fn decapsulate(
104        private_key: &PrivateKey,
105        ciphertext: &[u8],
106    ) -> Result<zeroize::Zeroizing<Vec<u8>>> {
107        match private_key.algorithm {
108            Algorithm::MlKem768 => MlKemCrypto::decapsulate(private_key, ciphertext),
109            _ => Err(CryptoError::Generic(
110                "Algorithm does not support decapsulation".to_string(),
111            )),
112        }
113    }
114
115    pub fn create_hybrid_crypto(policy: HybridPolicy) -> HybridCrypto {
116        HybridCrypto::new(policy)
117    }
118
119    pub fn create_hybrid_crypto_default() -> HybridCrypto {
120        HybridCrypto::new_default()
121    }
122
123    pub fn generate_hybrid_keypair(hybrid_crypto: &HybridCrypto) -> Result<HybridKeyPair> {
124        hybrid_crypto.generate_hybrid_keypair()
125    }
126
127    pub fn sign_hybrid(
128        hybrid_crypto: &HybridCrypto,
129        hybrid_keypair: &HybridKeyPair,
130        message: &[u8],
131    ) -> Result<HybridSignature> {
132        hybrid_crypto.sign_hybrid(hybrid_keypair, message)
133    }
134
135    pub fn verify_hybrid(
136        hybrid_crypto: &HybridCrypto,
137        hybrid_keypair: &HybridKeyPair,
138        message: &[u8],
139        signature: &HybridSignature,
140    ) -> Result<bool> {
141        hybrid_crypto.verify_hybrid(hybrid_keypair, message, signature)
142    }
143
144    pub fn create_compression_engine(config: CompressionConfig) -> CompressionEngine {
145        CompressionEngine::new(config)
146    }
147
148    pub fn create_compression_engine_default() -> CompressionEngine {
149        CompressionEngine::new_default()
150    }
151
152    pub fn compress_hybrid_signature(
153        hybrid_crypto: &HybridCrypto,
154        signature: &HybridSignature,
155    ) -> Result<CompressedHybridSignature> {
156        hybrid_crypto.compress_signature(signature)
157    }
158
159    pub fn decompress_hybrid_signature(
160        hybrid_crypto: &HybridCrypto,
161        compressed: &CompressedHybridSignature,
162    ) -> Result<HybridSignature> {
163        hybrid_crypto.decompress_signature(compressed)
164    }
165
166    pub fn sign_hybrid_compressed(
167        hybrid_crypto: &HybridCrypto,
168        hybrid_keypair: &HybridKeyPair,
169        message: &[u8],
170    ) -> Result<CompressedHybridSignature> {
171        hybrid_crypto.sign_hybrid_compressed(hybrid_keypair, message)
172    }
173
174    pub fn verify_hybrid_compressed(
175        hybrid_crypto: &HybridCrypto,
176        hybrid_keypair: &HybridKeyPair,
177        message: &[u8],
178        compressed_signature: &CompressedHybridSignature,
179    ) -> Result<bool> {
180        hybrid_crypto.verify_hybrid_compressed(hybrid_keypair, message, compressed_signature)
181    }
182
183    pub fn compress_signature_with_metrics(
184        hybrid_crypto: &HybridCrypto,
185        signature: &HybridSignature,
186    ) -> Result<(CompressedHybridSignature, CompressionMetrics)> {
187        hybrid_crypto.compress_signature_with_metrics(signature)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_unified_api_ecdsa_k256() {
197        let keypair = QuroxCrypto::generate_ecdsa_keypair(EcdsaCurve::K256).unwrap();
198        let message = b"test message";
199
200        let signature = QuroxCrypto::sign(&keypair.private_key, message).unwrap();
201        let is_valid = QuroxCrypto::verify(&keypair.public_key, message, &signature).unwrap();
202
203        assert!(is_valid);
204    }
205
206    #[test]
207    fn test_unified_api_ecdsa_p256() {
208        let keypair = QuroxCrypto::generate_ecdsa_keypair(EcdsaCurve::P256).unwrap();
209        let message = b"test message";
210
211        let signature = QuroxCrypto::sign(&keypair.private_key, message).unwrap();
212        let is_valid = QuroxCrypto::verify(&keypair.public_key, message, &signature).unwrap();
213
214        assert!(is_valid);
215    }
216
217    #[test]
218    fn test_unified_api_schnorr() {
219        let keypair = QuroxCrypto::generate_schnorr_keypair().unwrap();
220        let message = b"test message";
221
222        let signature = QuroxCrypto::sign(&keypair.private_key, message).unwrap();
223        let is_valid = QuroxCrypto::verify(&keypair.public_key, message, &signature).unwrap();
224
225        assert!(is_valid);
226    }
227
228    #[test]
229    fn test_unified_api_mldsa() {
230        let keypair = QuroxCrypto::generate_mldsa_keypair().unwrap();
231        let message = b"post-quantum test";
232
233        let signature = QuroxCrypto::sign(&keypair.private_key, message).unwrap();
234        let is_valid = QuroxCrypto::verify(&keypair.public_key, message, &signature).unwrap();
235
236        assert!(is_valid);
237    }
238
239    #[test]
240    fn test_unified_api_slh_dsa() {
241        let keypair = QuroxCrypto::generate_slh_dsa_keypair().unwrap();
242        let message = b"stateless hash test";
243
244        let signature = QuroxCrypto::sign(&keypair.private_key, message).unwrap();
245        let is_valid = QuroxCrypto::verify(&keypair.public_key, message, &signature).unwrap();
246
247        assert!(is_valid);
248    }
249
250    #[test]
251    fn test_unified_api_mlkem() {
252        let keypair = QuroxCrypto::generate_mlkem_keypair().unwrap();
253
254        let encryption_result = QuroxCrypto::encapsulate(&keypair.public_key).unwrap();
255        let decrypted_secret =
256            QuroxCrypto::decapsulate(&keypair.private_key, &encryption_result.ciphertext).unwrap();
257
258        assert_eq!(encryption_result.shared_secret.as_slice(), decrypted_secret.as_slice());
259    }
260
261    #[test]
262    fn test_hybrid_crypto_api() {
263        let hybrid_crypto = QuroxCrypto::create_hybrid_crypto_default();
264        let hybrid_keypair = QuroxCrypto::generate_hybrid_keypair(&hybrid_crypto).unwrap();
265        let message = b"hybrid crypto test";
266
267        let signature = QuroxCrypto::sign_hybrid(&hybrid_crypto, &hybrid_keypair, message).unwrap();
268        let is_valid =
269            QuroxCrypto::verify_hybrid(&hybrid_crypto, &hybrid_keypair, message, &signature)
270                .unwrap();
271
272        assert!(is_valid);
273        assert_eq!(signature.metadata.security_level, SecurityLevel::Hybrid);
274    }
275
276    #[test]
277    fn test_compression_api() {
278        let hybrid_crypto = QuroxCrypto::create_hybrid_crypto_default();
279        let hybrid_keypair = QuroxCrypto::generate_hybrid_keypair(&hybrid_crypto).unwrap();
280        let message = b"compression api test";
281
282        let compressed_signature =
283            QuroxCrypto::sign_hybrid_compressed(&hybrid_crypto, &hybrid_keypair, message).unwrap();
284        let is_valid = QuroxCrypto::verify_hybrid_compressed(
285            &hybrid_crypto,
286            &hybrid_keypair,
287            message,
288            &compressed_signature,
289        )
290        .unwrap();
291
292        assert!(is_valid);
293    }
294
295    #[test]
296    fn test_compression_engine_api() {
297        let compression_engine = QuroxCrypto::create_compression_engine_default();
298        let test_data = b"compression engine test data".repeat(10);
299
300        let compressed = compression_engine.compress_data(&test_data).unwrap();
301        let decompressed = compression_engine.decompress_data(&compressed).unwrap();
302
303        assert_eq!(test_data, decompressed);
304    }
305}