1use crate::{MlsError, Result};
10use saorsa_pqc::api::{
11 hpke::{HpkeConfig, HpkeContext as PqcHpkeContext, HpkeRecipient, HpkeSender},
12 kdf::KdfAlgorithm,
13 MlDsa, MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature, MlDsaVariant, MlKem, MlKemCiphertext,
14 MlKemPublicKey, MlKemSecretKey, MlKemSharedSecret, MlKemVariant, SlhDsa, SlhDsaPublicKey,
15 SlhDsaSecretKey, SlhDsaSignature, SlhDsaVariant,
16};
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20#[allow(non_camel_case_types)]
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[repr(u16)]
29pub enum CipherSuiteId {
30 #[deprecated(
33 since = "0.3.0",
34 note = "Use SPEC2_MLS_128_MLKEM768_AES128GCM_SHA256_MLDSA65 (0x0B01) for PQC-only"
35 )]
36 MLS_128_MLKEM768_AES128GCM_SHA256_MLDSA65 = 0x0A01,
37 #[deprecated(
39 since = "0.3.0",
40 note = "Hybrid suites not allowed in SPEC-2 PQC-only mode"
41 )]
42 MLS_128_HYBRID_X25519_MLKEM768_AES128GCM_SHA256_MLDSA65 = 0x0A02,
43 #[deprecated(
45 since = "0.3.0",
46 note = "Use SPEC2_MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87 (0x0B02) for PQC-only"
47 )]
48 MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87 = 0x0A03,
49 #[deprecated(
51 since = "0.3.0",
52 note = "Use SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65 (0x0B01) for PQC-only"
53 )]
54 MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65 = 0x0A04,
55
56 SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65 = 0x0B01,
59 SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87 = 0x0B02,
61 #[allow(dead_code)] SPEC2_MLS_192_MLKEM1024_CHACHA20POLY1305_SHA384_SLHDSA192 = 0x0B03,
64}
65
66impl CipherSuiteId {
67 #[must_use]
68 pub const fn as_u16(self) -> u16 {
69 self as u16
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum MlsKem {
76 MlKem512,
77 MlKem768,
78 MlKem1024,
79 #[deprecated(
81 since = "0.3.0",
82 note = "Hybrid KEMs not allowed in SPEC-2 PQC-only mode"
83 )]
84 HybridX25519MlKem768,
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
89pub enum MlsSignature {
90 MlDsa44,
91 MlDsa65,
92 MlDsa87,
93 SlhDsa128,
94 SlhDsa192,
95 SlhDsa256,
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
100pub enum MlsAead {
101 Aes128Gcm,
102 Aes256Gcm,
103 ChaCha20Poly1305,
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
108pub enum MlsHash {
109 Sha256,
110 Sha384,
111 Sha512,
112 Blake3,
113 Sha3_256,
114 Sha3_512,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
119pub struct CipherSuite {
120 pub id: CipherSuiteId,
121 pub kem: MlsKem,
122 pub signature: MlsSignature,
123 pub aead: MlsAead,
124 pub hash: MlsHash,
125}
126
127impl CipherSuite {
128 pub const fn new(
129 id: CipherSuiteId,
130 kem: MlsKem,
131 signature: MlsSignature,
132 aead: MlsAead,
133 hash: MlsHash,
134 ) -> Self {
135 Self {
136 id,
137 kem,
138 signature,
139 aead,
140 hash,
141 }
142 }
143
144 #[must_use]
145 pub const fn id(&self) -> CipherSuiteId {
146 self.id
147 }
148
149 #[must_use]
150 pub const fn kem(&self) -> MlsKem {
151 self.kem
152 }
153
154 #[must_use]
155 pub const fn signature(&self) -> MlsSignature {
156 self.signature
157 }
158
159 #[must_use]
160 pub const fn aead(&self) -> MlsAead {
161 self.aead
162 }
163
164 #[must_use]
165 pub const fn hash(&self) -> MlsHash {
166 self.hash
167 }
168
169 #[must_use]
170 pub fn from_id(id: CipherSuiteId) -> Option<Self> {
171 REGISTRY.iter().copied().find(|suite| suite.id == id)
172 }
173
174 #[must_use]
175 pub fn all() -> &'static [CipherSuite] {
176 ®ISTRY
177 }
178
179 #[must_use]
181 #[allow(deprecated)] pub fn ml_kem_variant(&self) -> MlKemVariant {
183 match self.kem {
184 MlsKem::MlKem512 => MlKemVariant::MlKem512,
185 MlsKem::MlKem768 | MlsKem::HybridX25519MlKem768 => MlKemVariant::MlKem768,
186 MlsKem::MlKem1024 => MlKemVariant::MlKem1024,
187 }
188 }
189
190 #[must_use]
193 pub fn uses_slh_dsa(&self) -> bool {
194 matches!(
195 self.signature,
196 MlsSignature::SlhDsa128 | MlsSignature::SlhDsa192 | MlsSignature::SlhDsa256
197 )
198 }
199
200 pub fn ml_dsa_variant(&self) -> Result<MlDsaVariant> {
208 match self.signature {
209 MlsSignature::MlDsa44 => Ok(MlDsaVariant::MlDsa44),
210 MlsSignature::MlDsa65 => Ok(MlDsaVariant::MlDsa65),
211 MlsSignature::MlDsa87 => Ok(MlDsaVariant::MlDsa87),
212 MlsSignature::SlhDsa128 | MlsSignature::SlhDsa192 | MlsSignature::SlhDsa256 => Err(
213 MlsError::CryptoError("ml_dsa_variant() called on an SLH-DSA suite".to_string()),
214 ),
215 }
216 }
217
218 pub fn slh_dsa_variant(&self) -> Result<SlhDsaVariant> {
226 match self.signature {
227 MlsSignature::SlhDsa128 => Ok(SlhDsaVariant::Sha2_128f),
229 MlsSignature::SlhDsa192 => Ok(SlhDsaVariant::Sha2_128f), MlsSignature::SlhDsa256 => Ok(SlhDsaVariant::Sha2_256f),
231 MlsSignature::MlDsa44 | MlsSignature::MlDsa65 | MlsSignature::MlDsa87 => Err(
232 MlsError::CryptoError("slh_dsa_variant() called on an ML-DSA suite".to_string()),
233 ),
234 }
235 }
236
237 #[must_use]
239 pub fn key_size(&self) -> usize {
240 match self.aead {
241 MlsAead::Aes128Gcm => 16,
242 MlsAead::Aes256Gcm | MlsAead::ChaCha20Poly1305 => 32,
243 }
244 }
245
246 #[must_use]
248 pub fn nonce_size(&self) -> usize {
249 12
250 }
251
252 #[must_use]
254 pub fn hash_size(&self) -> usize {
255 match self.hash {
256 MlsHash::Sha256 => 32,
257 MlsHash::Sha384 => 48,
258 MlsHash::Sha512 => 64,
259 MlsHash::Blake3 => 32,
260 MlsHash::Sha3_256 => 32,
261 MlsHash::Sha3_512 => 64,
262 }
263 }
264
265 #[must_use]
274 #[allow(deprecated)] pub fn is_pqc_only(&self) -> bool {
276 if matches!(self.kem, MlsKem::HybridX25519MlKem768) {
278 return false;
279 }
280
281 matches!(
283 self.id,
284 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65
285 | CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87
286 | CipherSuiteId::SPEC2_MLS_192_MLKEM1024_CHACHA20POLY1305_SHA384_SLHDSA192
287 )
288 }
289
290 #[must_use]
292 pub fn is_spec2(&self) -> bool {
293 (self.id.as_u16() & 0xFF00) == 0x0B00
294 }
295
296 #[must_use]
298 pub fn is_deprecated(&self) -> bool {
299 (self.id.as_u16() & 0xFF00) == 0x0A00
300 }
301}
302
303impl Default for CipherSuite {
304 fn default() -> Self {
305 CipherSuite::new(
308 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
309 MlsKem::MlKem768,
310 MlsSignature::MlDsa65,
311 MlsAead::ChaCha20Poly1305,
312 MlsHash::Sha256,
313 )
314 }
315}
316
317#[allow(deprecated)]
322const REGISTRY: [CipherSuite; 7] = [
323 CipherSuite::new(
325 CipherSuiteId::MLS_128_MLKEM768_AES128GCM_SHA256_MLDSA65,
326 MlsKem::MlKem768,
327 MlsSignature::MlDsa65,
328 MlsAead::Aes128Gcm,
329 MlsHash::Sha256,
330 ),
331 CipherSuite::new(
332 CipherSuiteId::MLS_128_HYBRID_X25519_MLKEM768_AES128GCM_SHA256_MLDSA65,
333 MlsKem::HybridX25519MlKem768,
334 MlsSignature::MlDsa65,
335 MlsAead::Aes128Gcm,
336 MlsHash::Sha256,
337 ),
338 CipherSuite::new(
339 CipherSuiteId::MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87,
340 MlsKem::MlKem1024,
341 MlsSignature::MlDsa87,
342 MlsAead::Aes256Gcm,
343 MlsHash::Sha512,
344 ),
345 CipherSuite::new(
346 CipherSuiteId::MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
347 MlsKem::MlKem768,
348 MlsSignature::MlDsa65,
349 MlsAead::ChaCha20Poly1305,
350 MlsHash::Sha256,
351 ),
352 CipherSuite::new(
354 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
355 MlsKem::MlKem768,
356 MlsSignature::MlDsa65,
357 MlsAead::ChaCha20Poly1305,
358 MlsHash::Sha256,
359 ),
360 CipherSuite::new(
361 CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87,
362 MlsKem::MlKem1024,
363 MlsSignature::MlDsa87,
364 MlsAead::ChaCha20Poly1305,
365 MlsHash::Sha512,
366 ),
367 CipherSuite::new(
369 CipherSuiteId::SPEC2_MLS_192_MLKEM1024_CHACHA20POLY1305_SHA384_SLHDSA192,
370 MlsKem::MlKem1024,
371 MlsSignature::SlhDsa192,
372 MlsAead::ChaCha20Poly1305,
373 MlsHash::Sha384,
374 ),
375];
376
377pub struct Hash {
379 pub suite: CipherSuite,
380}
381
382impl Hash {
383 #[must_use]
384 pub fn new(suite: CipherSuite) -> Self {
385 Self { suite }
386 }
387
388 #[must_use]
390 pub fn hash(&self, data: &[u8]) -> Vec<u8> {
391 use saorsa_pqc::api::hash::Blake3Hasher;
392 use saorsa_pqc::api::traits::Hash as HashTrait;
393
394 let mut hasher = Blake3Hasher::new();
395 hasher.update(data);
396 let output = hasher.finalize();
397 output.as_ref().to_vec()
398 }
399
400 pub fn hmac(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
406 use saorsa_pqc::api::hmac::HmacSha3_256;
407 use saorsa_pqc::api::traits::Mac;
408
409 let mut mac = HmacSha3_256::new(key)
410 .map_err(|e| MlsError::CryptoError(format!("HMAC key error: {e:?}")))?;
411 mac.update(data);
412 let output = mac.finalize();
413 Ok(output.as_ref().to_vec())
414 }
415}
416
417#[derive(Debug, Clone)]
419pub struct KeySchedule {
420 suite: CipherSuite,
421}
422
423impl KeySchedule {
424 #[must_use]
425 pub fn new(suite: CipherSuite) -> Self {
426 Self { suite }
427 }
428
429 pub fn derive_secret(&self, secret: &[u8], label: &str, context: &[u8]) -> Result<Vec<u8>> {
435 use saorsa_pqc::api::kdf::HkdfSha3_256;
436 use saorsa_pqc::api::traits::Kdf;
437
438 let info = Self::build_hkdf_label(label, context, self.suite.hash_size());
439 let mut output = vec![0u8; self.suite.hash_size()];
440
441 HkdfSha3_256::derive(secret, None, &info, &mut output)
442 .map_err(|e| MlsError::CryptoError(format!("HKDF error: {e:?}")))?;
443 Ok(output)
444 }
445
446 pub fn derive_keys(
452 &self,
453 salt: &[u8],
454 secret: &[u8],
455 labels: &[&str],
456 lengths: &[usize],
457 ) -> Result<Vec<Vec<u8>>> {
458 let mut results = Vec::new();
459
460 for (label, &length) in labels.iter().zip(lengths.iter()) {
461 let key = self.derive_secret(secret, label, salt)?;
462 results.push(key[..length].to_vec());
463 }
464 Ok(results)
465 }
466
467 pub fn derive_key(
473 &self,
474 salt: &[u8],
475 secret: &[u8],
476 info: &[u8],
477 length: usize,
478 ) -> Result<Vec<u8>> {
479 use saorsa_pqc::api::kdf::HkdfSha3_256;
480 use saorsa_pqc::api::traits::Kdf;
481
482 let mut output = vec![0u8; length];
483 HkdfSha3_256::derive(secret, Some(salt), info, &mut output)
484 .map_err(|e| MlsError::CryptoError(format!("HKDF error: {e:?}")))?;
485 Ok(output)
486 }
487
488 fn build_hkdf_label(label: &str, context: &[u8], length: usize) -> Vec<u8> {
489 let mut info = Vec::new();
490 info.extend_from_slice(&u16::try_from(length).unwrap_or(u16::MAX).to_be_bytes());
491 info.push(
492 u8::try_from(b"tls13 ".len()).unwrap_or(u8::MAX)
493 + u8::try_from(label.len()).unwrap_or(u8::MAX),
494 );
495 info.extend_from_slice(b"tls13 ");
496 info.extend_from_slice(label.as_bytes());
497 info.push(u8::try_from(context.len()).unwrap_or(u8::MAX));
498 info.extend_from_slice(context);
499 info
500 }
501
502 pub fn export_secret(
511 &self,
512 exporter_secret: &[u8],
513 label: &str,
514 context: &[u8],
515 length: usize,
516 ) -> Result<Vec<u8>> {
517 use saorsa_pqc::api::kdf::HkdfSha3_256;
518 use saorsa_pqc::api::traits::Kdf;
519
520 let full_label = format!("mls exporter {}", label);
523 let info = Self::build_hkdf_label(&full_label, context, length);
524
525 let mut output = vec![0u8; length];
526 HkdfSha3_256::derive(exporter_secret, None, &info, &mut output)
527 .map_err(|e| MlsError::CryptoError(format!("Exporter derivation failed: {e:?}")))?;
528
529 Ok(output)
530 }
531}
532
533#[derive(Clone)]
535pub enum Signature {
536 MlDsa(MlDsaSignature),
538 SlhDsa(SlhDsaSignature),
540}
541
542impl Signature {
543 pub fn to_bytes(&self) -> Vec<u8> {
545 match self {
546 Signature::MlDsa(sig) => sig.to_bytes().to_vec(),
547 Signature::SlhDsa(sig) => sig.to_bytes().to_vec(),
548 }
549 }
550}
551
552#[derive(Clone)]
554pub struct DebugSignature(pub Signature);
555
556impl std::fmt::Debug for DebugSignature {
557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 match &self.0 {
559 Signature::MlDsa(sig) => write!(f, "MlDsaSignature(<{} bytes>)", sig.to_bytes().len()),
560 Signature::SlhDsa(sig) => {
561 write!(f, "SlhDsaSignature(<{} bytes>)", sig.to_bytes().len())
562 }
563 }
564 }
565}
566
567impl PartialEq for DebugSignature {
568 fn eq(&self, other: &Self) -> bool {
569 self.0.to_bytes() == other.0.to_bytes()
570 }
571}
572
573impl Eq for DebugSignature {}
574
575impl serde::Serialize for DebugSignature {
576 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
577 where
578 S: serde::Serializer,
579 {
580 serializer.serialize_bytes(&self.0.to_bytes())
581 }
582}
583
584impl<'de> serde::Deserialize<'de> for DebugSignature {
585 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
586 where
587 D: serde::Deserializer<'de>,
588 {
589 let bytes = <Vec<u8>>::deserialize(deserializer)?;
590 let signature = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &bytes)
593 .map_err(|e| serde::de::Error::custom(format!("Signature decode error: {e:?}")))?;
594 Ok(DebugSignature(Signature::MlDsa(signature)))
595 }
596}
597
598#[derive(Clone)]
600pub enum SignatureKey {
601 MlDsa {
603 secret: MlDsaSecretKey,
605 public: MlDsaPublicKey,
607 },
608 SlhDsa {
610 secret: SlhDsaSecretKey,
612 public: SlhDsaPublicKey,
614 },
615}
616
617pub struct KeyPair {
619 pub signature_key: SignatureKey,
621 pub kem_secret: MlKemSecretKey,
623 pub kem_public: MlKemPublicKey,
625 pub suite: CipherSuite,
627}
628
629impl std::fmt::Debug for KeyPair {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 f.debug_struct("KeyPair")
632 .field("suite", &self.suite)
633 .field("verifying_key", &"<hidden>")
634 .field("kem_public", &"<hidden>")
635 .finish_non_exhaustive()
636 }
637}
638
639impl KeyPair {
640 #[must_use]
648 pub fn generate(suite: CipherSuite) -> Self {
649 let signature_key = if suite.uses_slh_dsa() {
651 let variant = suite
654 .slh_dsa_variant()
655 .unwrap_or_else(|_| std::process::abort());
656 let slh_dsa = SlhDsa::new(variant);
657 let (public, secret) = slh_dsa
658 .generate_keypair()
659 .unwrap_or_else(|_| std::process::abort());
660 SignatureKey::SlhDsa { secret, public }
661 } else {
662 let variant = suite
663 .ml_dsa_variant()
664 .unwrap_or_else(|_| std::process::abort());
665 let ml_dsa = MlDsa::new(variant);
666 let (public, secret) = ml_dsa
667 .generate_keypair()
668 .unwrap_or_else(|_| std::process::abort());
669 SignatureKey::MlDsa { secret, public }
670 };
671
672 let ml_kem = MlKem::new(suite.ml_kem_variant());
674 let (kem_public, kem_secret) = ml_kem
675 .generate_keypair()
676 .unwrap_or_else(|_| std::process::abort());
677
678 Self {
679 signature_key,
680 kem_secret,
681 kem_public,
682 suite,
683 }
684 }
685
686 pub fn generate_from_seed(suite: CipherSuite, seed: &[u8]) -> Result<Self> {
700 use saorsa_pqc::api::kdf::HkdfSha3_256;
701 use saorsa_pqc::api::traits::Kdf;
702
703 if suite.uses_slh_dsa() {
704 return Err(MlsError::CryptoError(
705 "deterministic key generation is not supported for SLH-DSA suites".to_string(),
706 ));
707 }
708
709 let mut sig_xi = [0u8; 32];
711 let mut kem_d = [0u8; 32];
712 let mut kem_z = [0u8; 32];
713 HkdfSha3_256::derive(seed, None, b"saorsa-mls keypair v1 ml-dsa xi", &mut sig_xi)
714 .map_err(|e| MlsError::CryptoError(format!("HKDF error: {e:?}")))?;
715 HkdfSha3_256::derive(seed, None, b"saorsa-mls keypair v1 ml-kem d", &mut kem_d)
716 .map_err(|e| MlsError::CryptoError(format!("HKDF error: {e:?}")))?;
717 HkdfSha3_256::derive(seed, None, b"saorsa-mls keypair v1 ml-kem z", &mut kem_z)
718 .map_err(|e| MlsError::CryptoError(format!("HKDF error: {e:?}")))?;
719
720 let ml_dsa = MlDsa::new(suite.ml_dsa_variant()?);
721 let (public, secret) = ml_dsa.generate_keypair_from_seed(&sig_xi);
722 let signature_key = SignatureKey::MlDsa { secret, public };
723
724 let ml_kem = MlKem::new(suite.ml_kem_variant());
725 let (kem_public, kem_secret) = ml_kem.generate_keypair_from_seed(&kem_d, &kem_z);
726
727 sig_xi.zeroize();
728 kem_d.zeroize();
729 kem_z.zeroize();
730
731 Ok(Self {
732 signature_key,
733 kem_secret,
734 kem_public,
735 suite,
736 })
737 }
738
739 #[must_use]
741 pub fn verifying_key_bytes(&self) -> Vec<u8> {
742 match &self.signature_key {
743 SignatureKey::MlDsa { public, .. } => public.to_bytes().to_vec(),
744 SignatureKey::SlhDsa { public, .. } => public.to_bytes().to_vec(),
745 }
746 }
747
748 pub fn verifying_key(&self) -> Result<&MlDsaPublicKey> {
755 match &self.signature_key {
756 SignatureKey::MlDsa { public, .. } => Ok(public),
757 SignatureKey::SlhDsa { .. } => Err(MlsError::CryptoError(
758 "verifying_key() called on an SLH-DSA keypair".to_string(),
759 )),
760 }
761 }
762
763 #[must_use]
765 pub fn public_key(&self) -> &MlKemPublicKey {
766 &self.kem_public
767 }
768
769 pub fn sign(&self, message: &[u8]) -> Result<Signature> {
775 match &self.signature_key {
776 SignatureKey::MlDsa { secret, .. } => {
777 let ml_dsa = MlDsa::new(self.suite.ml_dsa_variant()?);
778 ml_dsa
779 .sign(secret, message)
780 .map(Signature::MlDsa)
781 .map_err(|e| MlsError::CryptoError(format!("ML-DSA signing failed: {e:?}")))
782 }
783 SignatureKey::SlhDsa { secret, .. } => {
784 let slh_dsa = SlhDsa::new(self.suite.slh_dsa_variant()?);
785 slh_dsa
786 .sign(secret, message)
787 .map(Signature::SlhDsa)
788 .map_err(|e| MlsError::CryptoError(format!("SLH-DSA signing failed: {e:?}")))
789 }
790 }
791 }
792
793 pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
797 match (&self.signature_key, signature) {
798 (SignatureKey::MlDsa { public, .. }, Signature::MlDsa(sig)) => {
799 let Ok(variant) = self.suite.ml_dsa_variant() else {
800 return false;
801 };
802 let ml_dsa = MlDsa::new(variant);
803 ml_dsa.verify(public, message, sig).unwrap_or(false)
804 }
805 (SignatureKey::SlhDsa { public, .. }, Signature::SlhDsa(sig)) => {
806 let Ok(variant) = self.suite.slh_dsa_variant() else {
807 return false;
808 };
809 let slh_dsa = SlhDsa::new(variant);
810 slh_dsa.verify(public, message, sig).unwrap_or(false)
811 }
812 _ => false,
814 }
815 }
816
817 pub fn encapsulate(
823 &self,
824 recipient_public: &MlKemPublicKey,
825 ) -> Result<(MlKemCiphertext, MlKemSharedSecret)> {
826 let ml_kem = MlKem::new(self.suite.ml_kem_variant());
827 let (shared_secret, ciphertext) = ml_kem
828 .encapsulate(recipient_public)
829 .map_err(|e| MlsError::CryptoError(format!("Encapsulation failed: {e:?}")))?;
830 Ok((ciphertext, shared_secret))
831 }
832
833 pub fn kem_decaps(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
839 let ml_kem = MlKem::new(self.suite.ml_kem_variant());
840
841 let ct = MlKemCiphertext::from_bytes(self.suite.ml_kem_variant(), ciphertext)
843 .map_err(|e| MlsError::CryptoError(format!("Invalid ciphertext: {e:?}")))?;
844
845 let shared_secret = ml_kem
846 .decapsulate(&self.kem_secret, &ct)
847 .map_err(|e| MlsError::CryptoError(format!("Decapsulation failed: {e:?}")))?;
848
849 Ok(shared_secret.to_bytes().to_vec())
850 }
851
852 pub fn decapsulate(&self, ciphertext: &MlKemCiphertext) -> Result<MlKemSharedSecret> {
858 let ml_kem = MlKem::new(self.suite.ml_kem_variant());
859 ml_kem
860 .decapsulate(&self.kem_secret, ciphertext)
861 .map_err(|e| MlsError::CryptoError(format!("Decapsulation failed: {e:?}")))
862 }
863}
864
865#[derive(Debug)]
867pub struct AeadCipher {
868 key: Vec<u8>,
869 suite: CipherSuite,
870}
871
872impl AeadCipher {
873 pub fn new(key: Vec<u8>, suite: CipherSuite) -> Result<Self> {
879 if key.len() != suite.key_size() {
880 return Err(MlsError::CryptoError(format!(
881 "Invalid key size: expected {}, got {}",
882 suite.key_size(),
883 key.len()
884 )));
885 }
886
887 Ok(Self { key, suite })
888 }
889
890 pub fn encrypt(
896 &self,
897 nonce: &[u8],
898 plaintext: &[u8],
899 associated_data: &[u8],
900 ) -> Result<Vec<u8>> {
901 use saorsa_pqc::api::symmetric::ChaCha20Poly1305 as PqcCipher;
903
904 if nonce.len() != self.suite.nonce_size() {
905 return Err(MlsError::CryptoError("Invalid nonce size".to_string()));
906 }
907
908 let key_array: [u8; 32] = self
910 .key
911 .clone()
912 .try_into()
913 .map_err(|_| MlsError::CryptoError("Invalid key size".to_string()))?;
914 let key = chacha20poly1305::Key::from(key_array);
915
916 let cipher = PqcCipher::new(&key);
917
918 let nonce_array: [u8; 12] = nonce
920 .try_into()
921 .map_err(|_| MlsError::CryptoError("Invalid nonce size".to_string()))?;
922 let nonce_obj = chacha20poly1305::Nonce::from(nonce_array);
923
924 let ciphertext = cipher
926 .encrypt_with_aad(&nonce_obj, plaintext, associated_data)
927 .map_err(|e| MlsError::CryptoError(format!("Encryption failed: {e:?}")))?;
928
929 let mut result = nonce.to_vec();
931 result.extend_from_slice(&ciphertext);
932 Ok(result)
933 }
934
935 pub fn decrypt(
941 &self,
942 nonce: &[u8],
943 ciphertext: &[u8],
944 associated_data: &[u8],
945 ) -> Result<Vec<u8>> {
946 use saorsa_pqc::api::symmetric::ChaCha20Poly1305 as PqcCipher;
948
949 if nonce.len() != self.suite.nonce_size() {
950 return Err(MlsError::CryptoError("Invalid nonce size".to_string()));
951 }
952
953 if ciphertext.len() < 12 {
956 return Err(MlsError::CryptoError("Ciphertext too short".to_string()));
957 }
958
959 let actual_nonce = &ciphertext[..12];
960 let actual_ciphertext = &ciphertext[12..];
961
962 let key_array: [u8; 32] = self
964 .key
965 .clone()
966 .try_into()
967 .map_err(|_| MlsError::CryptoError("Invalid key size".to_string()))?;
968 let key = chacha20poly1305::Key::from(key_array);
969
970 let cipher = PqcCipher::new(&key);
971
972 let nonce_array: [u8; 12] = actual_nonce
974 .try_into()
975 .map_err(|_| MlsError::CryptoError("Invalid nonce size".to_string()))?;
976 let nonce_obj = chacha20poly1305::Nonce::from(nonce_array);
977
978 let plaintext = cipher
980 .decrypt_with_aad(&nonce_obj, actual_ciphertext, associated_data)
981 .map_err(|e| MlsError::CryptoError(format!("Decryption failed: {e:?}")))?;
982
983 Ok(plaintext)
984 }
985
986 #[must_use]
988 pub fn key_size(&self) -> usize {
989 self.suite.key_size()
990 }
991
992 #[must_use]
994 pub fn nonce_size(&self) -> usize {
995 self.suite.nonce_size()
996 }
997}
998
999#[must_use]
1007pub fn random_bytes(len: usize) -> Vec<u8> {
1008 let mut bytes = vec![0u8; len];
1009 getrandom::fill(&mut bytes).unwrap_or_else(|_| std::process::abort());
1010 bytes
1011}
1012
1013#[must_use]
1015pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
1016 use subtle::ConstantTimeEq;
1017 a.ct_eq(b).into()
1018}
1019
1020pub mod labels {
1022 pub const ENCRYPTION: &str = "encryption";
1023 pub const AUTHENTICATION: &str = "authentication";
1024 pub const EXPORTER: &str = "exporter";
1025 pub const EXTERNAL: &str = "external";
1026 pub const CONFIRM: &str = "confirm";
1027 pub const MEMBERSHIP: &str = "membership";
1028 pub const RESUMPTION: &str = "resumption";
1029 pub const INIT: &str = "init";
1030 pub const SENDER_DATA: &str = "sender data";
1031 pub const WELCOME: &str = "welcome";
1032 pub const HANDSHAKE: &str = "handshake";
1033 pub const APPLICATION: &str = "application";
1034 pub const EPOCH_SECRET: &str = "epoch";
1036 pub const SENDER_DATA_SECRET: &str = "sender data secret";
1037 pub const HANDSHAKE_SECRET: &str = "handshake secret";
1038 pub const APPLICATION_SECRET: &str = "application secret";
1039 pub const EXPORTER_SECRET: &str = "exporter secret";
1040 pub const AUTHENTICATION_SECRET: &str = "authentication secret";
1041 pub const EXTERNAL_SECRET: &str = "external secret";
1042 pub const CONFIRMATION_KEY: &str = "confirmation key";
1043 pub const MEMBERSHIP_KEY: &str = "membership key";
1044 pub const RESUMPTION_PSK: &str = "resumption psk";
1045 pub const INIT_SECRET: &str = "init secret";
1046}
1047
1048#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
1050pub struct SecretBytes {
1051 inner: Vec<u8>,
1052}
1053
1054impl SecretBytes {
1055 #[must_use]
1056 pub fn new(bytes: Vec<u8>) -> Self {
1057 Self { inner: bytes }
1058 }
1059
1060 #[must_use]
1061 pub fn as_bytes(&self) -> &[u8] {
1062 &self.inner
1063 }
1064
1065 #[must_use]
1066 pub fn len(&self) -> usize {
1067 self.inner.len()
1068 }
1069
1070 #[must_use]
1071 pub fn is_empty(&self) -> bool {
1072 self.inner.is_empty()
1073 }
1074}
1075
1076impl From<Vec<u8>> for SecretBytes {
1077 fn from(bytes: Vec<u8>) -> Self {
1078 Self::new(bytes)
1079 }
1080}
1081
1082pub struct HpkeContext {
1086 inner: PqcHpkeContext,
1087}
1088
1089impl HpkeContext {
1090 pub fn export(&mut self, context: &[u8], length: usize) -> Result<Vec<u8>> {
1096 self.inner
1097 .export(context, length)
1098 .map_err(|e| MlsError::CryptoError(format!("HPKE export failed: {e:?}")))
1099 }
1100
1101 pub fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
1107 self.inner
1108 .seal(plaintext, aad)
1109 .map_err(|e| MlsError::CryptoError(format!("HPKE seal failed: {e:?}")))
1110 }
1111
1112 pub fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
1118 self.inner
1119 .open(ciphertext, aad)
1120 .map_err(|e| MlsError::CryptoError(format!("HPKE open failed: {e:?}")))
1121 }
1122}
1123
1124impl CipherSuite {
1125 fn hpke_config(&self) -> HpkeConfig {
1127 HpkeConfig {
1128 kem: self.ml_kem_variant(),
1129 kdf: match self.hash {
1130 MlsHash::Sha256 | MlsHash::Sha3_256 | MlsHash::Blake3 => KdfAlgorithm::HkdfSha3_256,
1131 MlsHash::Sha384 | MlsHash::Sha512 | MlsHash::Sha3_512 => KdfAlgorithm::HkdfSha3_512,
1132 },
1133 aead: match self.aead {
1134 MlsAead::ChaCha20Poly1305 => saorsa_pqc::api::aead::AeadCipher::ChaCha20Poly1305,
1135 MlsAead::Aes128Gcm | MlsAead::Aes256Gcm => {
1137 saorsa_pqc::api::aead::AeadCipher::Aes256Gcm
1138 }
1139 },
1140 }
1141 }
1142
1143 pub fn hpke_seal(
1149 &self,
1150 recipient_public_key: &MlKemPublicKey,
1151 plaintext: &[u8],
1152 aad: &[u8],
1153 info: &[u8],
1154 ) -> Result<(Vec<u8>, Vec<u8>)> {
1155 let config = self.hpke_config();
1156 let sender = HpkeSender::new(config);
1157
1158 let pk_bytes = recipient_public_key.to_bytes();
1160
1161 let (encapped_key, mut ctx) = sender
1163 .setup_base(&pk_bytes, info)
1164 .map_err(|e| MlsError::CryptoError(format!("HPKE setup failed: {e:?}")))?;
1165
1166 let ciphertext = ctx
1168 .seal(plaintext, aad)
1169 .map_err(|e| MlsError::CryptoError(format!("HPKE seal failed: {e:?}")))?;
1170
1171 Ok((encapped_key, ciphertext))
1172 }
1173
1174 pub fn kem_encaps_with_seed(
1184 &self,
1185 recipient_public_key: &MlKemPublicKey,
1186 _seed: &[u8],
1187 ) -> Result<(Vec<u8>, Vec<u8>)> {
1188 let ml_kem = MlKem::new(self.ml_kem_variant());
1191 let (shared_secret, ciphertext) = ml_kem
1192 .encapsulate(recipient_public_key)
1193 .map_err(|e| MlsError::CryptoError(format!("Encapsulation failed: {e:?}")))?;
1194
1195 Ok((ciphertext.to_bytes(), shared_secret.to_bytes().to_vec()))
1196 }
1197
1198 pub fn hpke_setup_sender(
1206 &self,
1207 recipient_public_key: &MlKemPublicKey,
1208 info: &[u8],
1209 ) -> Result<(Vec<u8>, HpkeContext)> {
1210 let config = self.hpke_config();
1211 let sender = HpkeSender::new(config);
1212
1213 let pk_bytes = recipient_public_key.to_bytes();
1214
1215 let (encapped_key, ctx) = sender
1216 .setup_base(&pk_bytes, info)
1217 .map_err(|e| MlsError::CryptoError(format!("HPKE sender setup failed: {e:?}")))?;
1218
1219 Ok((encapped_key, HpkeContext { inner: ctx }))
1220 }
1221}
1222
1223impl KeyPair {
1224 pub fn hpke_open(
1230 &self,
1231 encapped_key: &[u8],
1232 ciphertext: &[u8],
1233 aad: &[u8],
1234 info: &[u8],
1235 ) -> Result<Vec<u8>> {
1236 let mut ctx = self.hpke_setup_receiver(encapped_key, info)?;
1237 ctx.open(ciphertext, aad)
1238 }
1239
1240 pub fn hpke_setup_receiver(&self, encapped_key: &[u8], info: &[u8]) -> Result<HpkeContext> {
1248 let config = HpkeConfig {
1249 kem: self.suite.ml_kem_variant(),
1250 kdf: match self.suite.hash {
1251 MlsHash::Sha256 | MlsHash::Sha3_256 | MlsHash::Blake3 => KdfAlgorithm::HkdfSha3_256,
1252 MlsHash::Sha384 | MlsHash::Sha512 | MlsHash::Sha3_512 => KdfAlgorithm::HkdfSha3_512,
1253 },
1254 aead: match self.suite.aead {
1255 MlsAead::ChaCha20Poly1305 => saorsa_pqc::api::aead::AeadCipher::ChaCha20Poly1305,
1256 MlsAead::Aes128Gcm | MlsAead::Aes256Gcm => {
1258 saorsa_pqc::api::aead::AeadCipher::Aes256Gcm
1259 }
1260 },
1261 };
1262
1263 let recipient = HpkeRecipient::new(config);
1264
1265 let sk_bytes = self.kem_secret.to_bytes();
1267
1268 let ctx = recipient
1269 .setup_base(encapped_key, &sk_bytes, info)
1270 .map_err(|e| MlsError::CryptoError(format!("HPKE recipient setup failed: {e:?}")))?;
1271
1272 Ok(HpkeContext { inner: ctx })
1273 }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279
1280 #[test]
1281 fn test_cipher_suite_defaults() {
1282 let suite = CipherSuite::default();
1283 assert_eq!(
1285 suite.id(),
1286 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65
1287 );
1288 assert_eq!(suite.key_size(), 32); assert_eq!(suite.nonce_size(), 12);
1290 assert!(suite.is_pqc_only());
1291 assert!(suite.is_spec2());
1292 }
1293
1294 #[test]
1295 fn test_hash_operations() {
1296 let hash = Hash::new(CipherSuite::default());
1297 let data = b"test data";
1298 let result = hash.hash(data);
1299 assert_eq!(result.len(), 32);
1300
1301 let key = b"test key";
1302 let hmac_result = hash.hmac(key, data).unwrap();
1303 assert!(!hmac_result.is_empty());
1304 }
1305
1306 #[test]
1307 fn test_key_generation() {
1308 let kp1 = KeyPair::generate(CipherSuite::default());
1309 let kp2 = KeyPair::generate(CipherSuite::default());
1310
1311 assert_ne!(kp1.verifying_key_bytes(), kp2.verifying_key_bytes());
1313 }
1314
1315 #[test]
1316 fn test_signing_and_verification() {
1317 let kp = KeyPair::generate(CipherSuite::default());
1318 let message = b"test message";
1319
1320 let signature = kp.sign(message).unwrap();
1321
1322 assert!(kp.verify(message, &signature));
1324
1325 let wrong_message = b"wrong message";
1327 assert!(
1328 !kp.verify(wrong_message, &signature),
1329 "Signature should not verify with wrong message"
1330 );
1331 }
1332
1333 #[test]
1334 fn test_key_encapsulation() {
1335 let kp1 = KeyPair::generate(CipherSuite::default());
1336 let kp2 = KeyPair::generate(CipherSuite::default());
1337
1338 let (ciphertext, shared_secret1) = kp1.encapsulate(&kp2.kem_public).unwrap();
1340
1341 let shared_secret2 = kp2.decapsulate(&ciphertext).unwrap();
1343
1344 assert_eq!(shared_secret1.to_bytes(), shared_secret2.to_bytes());
1346 }
1347
1348 #[test]
1349 fn test_aead_encryption() {
1350 let suite = CipherSuite::from_id(
1352 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1353 )
1354 .expect("ChaCha20 suite should exist");
1355 let key = random_bytes(suite.key_size()); let cipher = AeadCipher::new(key, suite).unwrap();
1357 let nonce = random_bytes(12);
1358 let plaintext = b"secret message";
1359 let aad = b"associated data";
1360
1361 let ciphertext = cipher.encrypt(&nonce, plaintext, aad).unwrap();
1362 let decrypted = cipher.decrypt(&nonce, &ciphertext, aad).unwrap();
1363
1364 assert_eq!(decrypted, plaintext);
1365 }
1366
1367 #[test]
1368 fn test_key_derivation() {
1369 let ks = KeySchedule::new(CipherSuite::default());
1370 let secret = random_bytes(32);
1371 let derived = ks.derive_secret(&secret, "test", b"context").unwrap();
1372 assert_eq!(derived.len(), 32);
1373 }
1374
1375 #[test]
1376 fn test_secret_bytes_zeroize() {
1377 let data = vec![1, 2, 3, 4, 5];
1378 let secret = SecretBytes::new(data.clone());
1379 assert_eq!(secret.as_bytes(), &data);
1380 assert_eq!(secret.len(), 5);
1381 assert!(!secret.is_empty());
1382 }
1384
1385 #[test]
1388 fn test_spec2_default_suite() {
1389 let suite = CipherSuite::default();
1390 assert_eq!(
1392 suite.id(),
1393 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1394 "Default should be SPEC-2 PQC-only suite 0x0B01"
1395 );
1396 assert_eq!(suite.kem(), MlsKem::MlKem768);
1397 assert_eq!(suite.signature(), MlsSignature::MlDsa65);
1398 assert_eq!(suite.aead(), MlsAead::ChaCha20Poly1305);
1399 assert_eq!(suite.hash(), MlsHash::Sha256);
1400 assert!(suite.is_pqc_only(), "Default suite must be PQC-only");
1401 assert!(suite.is_spec2(), "Default suite must be SPEC-2");
1402 }
1403
1404 #[test]
1405 fn test_spec2_suite_0xb01_chacha_sha256() {
1406 let suite = CipherSuite::from_id(
1407 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1408 )
1409 .expect("Suite 0x0B01 should exist");
1410 assert_eq!(suite.key_size(), 32, "ChaCha20 key size");
1411 assert_eq!(suite.hash_size(), 32, "SHA256 hash size");
1412 assert_eq!(suite.aead(), MlsAead::ChaCha20Poly1305);
1413 assert!(suite.is_pqc_only());
1414 assert!(suite.is_spec2());
1415 assert!(!suite.is_deprecated());
1416 }
1417
1418 #[test]
1419 fn test_spec2_suite_0xb02_chacha_sha512() {
1420 let suite = CipherSuite::from_id(
1421 CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87,
1422 )
1423 .expect("Suite 0x0B02 should exist");
1424 assert_eq!(suite.kem(), MlsKem::MlKem1024, "High-security ML-KEM-1024");
1425 assert_eq!(
1426 suite.signature(),
1427 MlsSignature::MlDsa87,
1428 "High-security ML-DSA-87"
1429 );
1430 assert_eq!(suite.aead(), MlsAead::ChaCha20Poly1305);
1431 assert_eq!(suite.hash(), MlsHash::Sha512);
1432 assert_eq!(suite.key_size(), 32, "ChaCha20 key size");
1433 assert_eq!(suite.hash_size(), 64, "SHA512 hash size");
1434 assert!(suite.is_pqc_only());
1435 }
1436
1437 #[test]
1438 #[allow(deprecated)]
1439 fn test_hybrid_suite_not_pqc_only() {
1440 let suite = CipherSuite::from_id(
1441 CipherSuiteId::MLS_128_HYBRID_X25519_MLKEM768_AES128GCM_SHA256_MLDSA65,
1442 )
1443 .expect("Hybrid suite should exist for backwards compat");
1444 assert!(!suite.is_pqc_only(), "Hybrid suite must NOT be PQC-only");
1445 assert!(!suite.is_spec2(), "Hybrid suite is SPEC-PROD");
1446 assert!(suite.is_deprecated(), "Hybrid suite should be deprecated");
1447 }
1448
1449 #[test]
1450 #[allow(deprecated)]
1451 fn test_deprecated_suites_not_pqc_only() {
1452 let deprecated_ids = [
1453 CipherSuiteId::MLS_128_MLKEM768_AES128GCM_SHA256_MLDSA65,
1454 CipherSuiteId::MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA87,
1455 CipherSuiteId::MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1456 ];
1457
1458 for id in &deprecated_ids {
1459 let suite =
1460 CipherSuite::from_id(*id).expect("Deprecated suite should still be in registry");
1461 assert!(suite.is_deprecated(), "Suite {:?} should be deprecated", id);
1462 assert!(!suite.is_spec2(), "Suite {:?} is not SPEC-2", id);
1463 }
1466 }
1467
1468 #[test]
1469 fn test_pqc_only_policy_enforcement() {
1470 let spec2_ids = [
1472 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1473 CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87,
1474 ];
1475
1476 for id in &spec2_ids {
1477 let suite = CipherSuite::from_id(*id).expect("SPEC-2 suite should exist");
1478 assert!(
1479 suite.is_pqc_only(),
1480 "SPEC-2 suite {:?} must be PQC-only",
1481 id
1482 );
1483 assert_eq!(
1484 suite.aead(),
1485 MlsAead::ChaCha20Poly1305,
1486 "SPEC-2 suite {:?} must use ChaCha20Poly1305",
1487 id
1488 );
1489 }
1490 }
1491
1492 #[test]
1493 fn test_sha384_hash_size() {
1494 let suite = CipherSuite::new(
1496 CipherSuiteId::SPEC2_MLS_192_MLKEM1024_CHACHA20POLY1305_SHA384_SLHDSA192,
1497 MlsKem::MlKem1024,
1498 MlsSignature::SlhDsa192,
1499 MlsAead::ChaCha20Poly1305,
1500 MlsHash::Sha384,
1501 );
1502 assert_eq!(suite.hash_size(), 48, "SHA384 produces 48-byte output");
1503 }
1504
1505 #[test]
1506 fn test_registry_contains_seven_suites() {
1507 let all_suites = CipherSuite::all();
1508 assert_eq!(
1509 all_suites.len(),
1510 7,
1511 "Registry should contain 4 SPEC-PROD + 3 SPEC-2 suites (including optional SLH-DSA)"
1512 );
1513 }
1514
1515 #[test]
1516 fn test_suite_id_to_u16() {
1517 assert_eq!(
1518 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65.as_u16(),
1519 0x0B01
1520 );
1521 assert_eq!(
1522 CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87.as_u16(),
1523 0x0B02
1524 );
1525 assert_eq!(
1526 CipherSuiteId::SPEC2_MLS_192_MLKEM1024_CHACHA20POLY1305_SHA384_SLHDSA192.as_u16(),
1527 0x0B03
1528 );
1529 }
1530
1531 #[test]
1532 fn test_all_spec2_suites_functional() {
1533 let spec2_ids = [
1535 CipherSuiteId::SPEC2_MLS_128_MLKEM768_CHACHA20POLY1305_SHA256_MLDSA65,
1536 CipherSuiteId::SPEC2_MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA87,
1537 ];
1538
1539 for id in &spec2_ids {
1540 let suite = CipherSuite::from_id(*id).expect("Suite should exist");
1541
1542 let kp = KeyPair::generate(suite);
1544 assert!(!kp.verifying_key_bytes().is_empty());
1545
1546 let message = b"test message for SPEC-2";
1548 let signature = kp.sign(message).expect("Signing should succeed");
1549 assert!(
1550 kp.verify(message, &signature),
1551 "Verification should succeed for suite {:?}",
1552 id
1553 );
1554 }
1555 }
1556}
1557
1558#[derive(Clone)]
1560pub struct DebugMlDsaSignature(pub MlDsaSignature);
1561
1562impl std::fmt::Debug for DebugMlDsaSignature {
1563 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1564 write!(f, "MlDsaSignature(<{} bytes>)", self.0.to_bytes().len())
1565 }
1566}
1567
1568impl PartialEq for DebugMlDsaSignature {
1569 fn eq(&self, other: &Self) -> bool {
1570 self.0.to_bytes() == other.0.to_bytes()
1571 }
1572}
1573
1574impl Eq for DebugMlDsaSignature {}
1575
1576impl Serialize for DebugMlDsaSignature {
1577 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1578 where
1579 S: Serializer,
1580 {
1581 serde_wrappers::serialize_ml_dsa_signature(&self.0, serializer)
1582 }
1583}
1584
1585impl<'de> Deserialize<'de> for DebugMlDsaSignature {
1586 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1587 where
1588 D: Deserializer<'de>,
1589 {
1590 let signature = serde_wrappers::deserialize_ml_dsa_signature(deserializer)?;
1591 Ok(DebugMlDsaSignature(signature))
1592 }
1593}
1594
1595#[derive(Clone)]
1597pub struct DebugMlDsaPublicKey(pub MlDsaPublicKey);
1598
1599impl std::fmt::Debug for DebugMlDsaPublicKey {
1600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1601 write!(f, "MlDsaPublicKey(<{} bytes>)", self.0.to_bytes().len())
1602 }
1603}
1604
1605impl PartialEq for DebugMlDsaPublicKey {
1606 fn eq(&self, other: &Self) -> bool {
1607 self.0.to_bytes() == other.0.to_bytes()
1608 }
1609}
1610
1611impl Eq for DebugMlDsaPublicKey {}
1612
1613impl Serialize for DebugMlDsaPublicKey {
1614 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1615 where
1616 S: Serializer,
1617 {
1618 serde_wrappers::serialize_ml_dsa_public_key(&self.0, serializer)
1619 }
1620}
1621
1622impl<'de> Deserialize<'de> for DebugMlDsaPublicKey {
1623 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1624 where
1625 D: Deserializer<'de>,
1626 {
1627 let key = serde_wrappers::deserialize_ml_dsa_public_key(deserializer)?;
1628 Ok(DebugMlDsaPublicKey(key))
1629 }
1630}
1631
1632pub mod serde_wrappers {
1634 use super::{
1635 DebugMlDsaPublicKey, DebugMlDsaSignature, MlDsaPublicKey, MlDsaSignature, MlKemCiphertext,
1636 };
1637 use saorsa_pqc::{MlDsaVariant, MlKemVariant};
1638 use serde::{Deserialize, Deserializer, Serializer};
1639
1640 pub fn serialize_ml_kem_ciphertext<S>(
1646 ciphertext: &MlKemCiphertext,
1647 serializer: S,
1648 ) -> std::result::Result<S::Ok, S::Error>
1649 where
1650 S: Serializer,
1651 {
1652 let bytes = ciphertext.to_bytes();
1653 let variant = ciphertext.variant();
1654 serializer.serialize_str(&format!("{}:{}", variant as u8, hex::encode(&bytes)))
1655 }
1656
1657 pub fn deserialize_ml_kem_ciphertext<'de, D>(
1663 deserializer: D,
1664 ) -> std::result::Result<MlKemCiphertext, D::Error>
1665 where
1666 D: Deserializer<'de>,
1667 {
1668 use serde::de::Error;
1669 let s = String::deserialize(deserializer)?;
1670 let parts: Vec<&str> = s.split(':').collect();
1671 if parts.len() != 2 {
1672 return Err(D::Error::custom("Invalid MlKemCiphertext format"));
1673 }
1674
1675 let variant = match parts[0] {
1676 "0" => MlKemVariant::MlKem512,
1677 "1" => MlKemVariant::MlKem768,
1678 "2" => MlKemVariant::MlKem1024,
1679 _ => return Err(D::Error::custom("Invalid MlKemVariant")),
1680 };
1681
1682 let bytes = hex::decode(parts[1])
1683 .map_err(|e| D::Error::custom(format!("Hex decode error: {e}")))?;
1684
1685 MlKemCiphertext::from_bytes(variant, &bytes)
1686 .map_err(|e| D::Error::custom(format!("MlKemCiphertext decode error: {e:?}")))
1687 }
1688
1689 pub fn serialize_ml_dsa_signature<S>(
1695 signature: &MlDsaSignature,
1696 serializer: S,
1697 ) -> std::result::Result<S::Ok, S::Error>
1698 where
1699 S: Serializer,
1700 {
1701 let bytes = signature.to_bytes();
1702 serializer.serialize_str(&hex::encode(bytes))
1703 }
1704
1705 pub fn deserialize_ml_dsa_signature<'de, D>(
1711 deserializer: D,
1712 ) -> std::result::Result<MlDsaSignature, D::Error>
1713 where
1714 D: Deserializer<'de>,
1715 {
1716 use serde::de::Error;
1717 let s = String::deserialize(deserializer)?;
1718 let bytes =
1719 hex::decode(&s).map_err(|e| D::Error::custom(format!("Hex decode error: {e}")))?;
1720
1721 if bytes.len() != 3309 {
1723 return Err(D::Error::custom("Invalid MlDsaSignature size"));
1725 }
1726
1727 let array: [u8; 3309] = bytes
1728 .try_into()
1729 .map_err(|_| D::Error::custom("Failed to convert to array"))?;
1730
1731 MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &array)
1732 .map_err(|e| D::Error::custom(format!("MlDsaSignature decode error: {e:?}")))
1733 }
1734
1735 pub fn serialize_ml_dsa_public_key<S>(
1741 key: &MlDsaPublicKey,
1742 serializer: S,
1743 ) -> std::result::Result<S::Ok, S::Error>
1744 where
1745 S: Serializer,
1746 {
1747 let bytes = key.to_bytes();
1748 serializer.serialize_str(&hex::encode(bytes))
1749 }
1750
1751 pub fn deserialize_ml_dsa_public_key<'de, D>(
1757 deserializer: D,
1758 ) -> std::result::Result<MlDsaPublicKey, D::Error>
1759 where
1760 D: Deserializer<'de>,
1761 {
1762 use serde::de::Error;
1763 let s = String::deserialize(deserializer)?;
1764 let bytes =
1765 hex::decode(&s).map_err(|e| D::Error::custom(format!("Hex decode error: {e}")))?;
1766
1767 if bytes.len() != 1952 {
1769 return Err(D::Error::custom("Invalid MlDsaPublicKey size"));
1771 }
1772
1773 let array: [u8; 1952] = bytes
1774 .try_into()
1775 .map_err(|_| D::Error::custom("Failed to convert to array"))?;
1776
1777 MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, &array)
1778 .map_err(|e| D::Error::custom(format!("MlDsaPublicKey decode error: {e:?}")))
1779 }
1780
1781 pub fn serialize_debug_ml_dsa_signature<S>(
1787 signature: &DebugMlDsaSignature,
1788 serializer: S,
1789 ) -> std::result::Result<S::Ok, S::Error>
1790 where
1791 S: Serializer,
1792 {
1793 serialize_ml_dsa_signature(&signature.0, serializer)
1794 }
1795
1796 pub fn deserialize_debug_ml_dsa_signature<'de, D>(
1802 deserializer: D,
1803 ) -> std::result::Result<DebugMlDsaSignature, D::Error>
1804 where
1805 D: Deserializer<'de>,
1806 {
1807 let signature = deserialize_ml_dsa_signature(deserializer)?;
1808 Ok(DebugMlDsaSignature(signature))
1809 }
1810
1811 pub fn serialize_debug_ml_dsa_public_key<S>(
1817 key: &DebugMlDsaPublicKey,
1818 serializer: S,
1819 ) -> std::result::Result<S::Ok, S::Error>
1820 where
1821 S: Serializer,
1822 {
1823 serialize_ml_dsa_public_key(&key.0, serializer)
1824 }
1825
1826 pub fn deserialize_debug_ml_dsa_public_key<'de, D>(
1832 deserializer: D,
1833 ) -> std::result::Result<DebugMlDsaPublicKey, D::Error>
1834 where
1835 D: Deserializer<'de>,
1836 {
1837 let key = deserialize_ml_dsa_public_key(deserializer)?;
1838 Ok(DebugMlDsaPublicKey(key))
1839 }
1840}
1841
1842impl std::fmt::Display for MlsAead {
1844 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1845 match self {
1846 MlsAead::Aes128Gcm => write!(f, "AES-128-GCM"),
1847 MlsAead::Aes256Gcm => write!(f, "AES-256-GCM"),
1848 MlsAead::ChaCha20Poly1305 => write!(f, "ChaCha20Poly1305"),
1849 }
1850 }
1851}
1852
1853impl std::fmt::Display for MlsHash {
1854 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1855 match self {
1856 MlsHash::Sha256 => write!(f, "SHA256"),
1857 MlsHash::Sha384 => write!(f, "SHA384"),
1858 MlsHash::Sha512 => write!(f, "SHA512"),
1859 MlsHash::Blake3 => write!(f, "BLAKE3"),
1860 MlsHash::Sha3_256 => write!(f, "SHA3-256"),
1861 MlsHash::Sha3_512 => write!(f, "SHA3-512"),
1862 }
1863 }
1864}
1865
1866impl std::fmt::Display for MlsKem {
1867 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1868 match self {
1869 MlsKem::MlKem512 => write!(f, "ML-KEM-512"),
1870 MlsKem::MlKem768 => write!(f, "ML-KEM-768"),
1871 MlsKem::MlKem1024 => write!(f, "ML-KEM-1024"),
1872 #[allow(deprecated)]
1873 MlsKem::HybridX25519MlKem768 => write!(f, "Hybrid-X25519-ML-KEM-768"),
1874 }
1875 }
1876}
1877
1878impl std::fmt::Display for MlsSignature {
1879 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1880 match self {
1881 MlsSignature::MlDsa44 => write!(f, "ML-DSA-44"),
1882 MlsSignature::MlDsa65 => write!(f, "ML-DSA-65"),
1883 MlsSignature::MlDsa87 => write!(f, "ML-DSA-87"),
1884 MlsSignature::SlhDsa128 => write!(f, "SLH-DSA-128"),
1885 MlsSignature::SlhDsa192 => write!(f, "SLH-DSA-192"),
1886 MlsSignature::SlhDsa256 => write!(f, "SLH-DSA-256"),
1887 }
1888 }
1889}