1use crate::primitives::provider::NoRng;
24use crate::primitives::provider::hpke_rs::{
25 ChaCha20Poly1305, DhKem25519, HkdfSha256, Hpke, HpkeLibcrux, HpkePrivateKey, HpkePublicKey,
26 Mode,
27};
28use crate::primitives::provider::kem::MlKem768;
29use crate::primitives::provider::traits::OwnedKem as Kem;
30use crate::primitives::ristretto255::DHPublicKey;
31use alloc::string::String;
32use alloc::vec::Vec;
33use anyhow::Error;
34use hpke_rs::libcrux::HpkeLibcruxPrng;
35use rand_core::{CryptoRng, RngCore};
36use serde::de::Error as _;
37
38use crate::primitives::dh_akem::{
39 DH_AKEM_ENCAPS_SECRET_LEN, deterministic_keygen as dhakem_derand,
40};
41use crate::primitives::dh_akem::{DhAkemPrivateKey, DhAkemPublicKey, generate_dh_akem_keypair};
42use crate::primitives::mlkem::{
43 MLKEM768PrivateKey, MLKEM768PublicKey, deterministic_keygen as mlkem_derand,
44 generate_mlkem768_keypair,
45};
46
47use crate::primitives::mlkem::LEN_MLKEM_SHAREDSECRET_ENCAPS;
48
49const PSK_ID: &[u8] = b"SD-pskAPKE";
52
53const LEN_MLKEM_ENCAPS_RAND: usize = 32;
55
56#[derive(Debug, Clone)]
61pub struct MessagePublicKey {
62 pub(crate) dhakem: DhAkemPublicKey, pub(crate) mlkem: MLKEM768PublicKey, }
65
66pub struct MessagePrivateKey {
71 pub(crate) dhakem: DhAkemPrivateKey, pub(crate) mlkem: MLKEM768PrivateKey, }
74
75pub struct MessageKeyPair {
77 sk: MessagePrivateKey,
78 pk: MessagePublicKey,
79}
80
81impl MessageKeyPair {
82 pub(crate) fn new(sk: MessagePrivateKey, pk: MessagePublicKey) -> Self {
83 Self { sk, pk }
84 }
85
86 pub fn public_key(&self) -> &MessagePublicKey {
88 &self.pk
89 }
90
91 pub fn private_key(&self) -> &MessagePrivateKey {
93 &self.sk
94 }
95}
96
97impl MessagePublicKey {
98 pub const LEN: usize = DhAkemPublicKey::LEN + MLKEM768PublicKey::LEN;
99
100 pub fn as_bytes(&self) -> Vec<u8> {
102 let mut out = Vec::new();
103 out.extend_from_slice(self.dhakem.as_bytes());
104 out.extend_from_slice(self.mlkem.as_bytes());
105 out
106 }
107
108 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
114 if bytes.len() != Self::LEN {
115 return Err(anyhow::anyhow!(
116 "Invalid MessagePublicKey length: expected {}, got {}",
117 Self::LEN,
118 bytes.len()
119 ));
120 }
121
122 let dhakem_bytes: [u8; DhAkemPublicKey::LEN] = bytes[..DhAkemPublicKey::LEN]
123 .try_into()
124 .expect("checked length");
125 let mlkem_bytes: [u8; MLKEM768PublicKey::LEN] = bytes[DhAkemPublicKey::LEN..]
126 .try_into()
127 .expect("checked length");
128
129 Ok(Self {
130 dhakem: DhAkemPublicKey::from_bytes(dhakem_bytes),
131 mlkem: MLKEM768PublicKey::from_bytes(mlkem_bytes),
132 })
133 }
134}
135
136#[cfg_attr(hax, hax_lib::exclude)]
137impl serde::Serialize for MessagePublicKey {
138 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
139 ser.serialize_str(&hex::encode(self.as_bytes()))
140 }
141}
142
143#[cfg_attr(hax, hax_lib::exclude)]
144impl<'de> serde::Deserialize<'de> for MessagePublicKey {
145 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
146 let s = String::deserialize(de)?;
147 let bytes = hex::decode(s.trim()).map_err(D::Error::custom)?;
148 Self::from_bytes(&bytes).map_err(D::Error::custom)
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct MessageCiphertext {
155 pub(crate) c2: [u8; LEN_MLKEM_SHAREDSECRET_ENCAPS],
157 pub(crate) c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN],
159 pub(crate) cp: Vec<u8>,
161}
162
163impl MessageCiphertext {
164 pub fn len(&self) -> usize {
166 self.c2.len() + self.c1.len() + self.cp.len()
167 }
168
169 pub fn as_bytes(&self) -> Vec<u8> {
171 let mut out = Vec::with_capacity(self.len());
172 out.extend_from_slice(&self.c2);
173 out.extend_from_slice(&self.c1);
174 out.extend_from_slice(&self.cp);
175 out
176 }
177
178 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
184 const FIXED: usize = LEN_MLKEM_SHAREDSECRET_ENCAPS + DH_AKEM_ENCAPS_SECRET_LEN;
185 if bytes.len() < FIXED {
186 return Err(anyhow::anyhow!(
187 "MessageCiphertext too short: expected at least {}, got {}",
188 FIXED,
189 bytes.len()
190 ));
191 }
192
193 let (c2, rest) = bytes.split_at(LEN_MLKEM_SHAREDSECRET_ENCAPS);
194 let (c1, cp) = rest.split_at(DH_AKEM_ENCAPS_SECRET_LEN);
195 Ok(Self {
196 c2: c2.try_into().expect("checked length"),
197 c1: c1.try_into().expect("checked length"),
198 cp: cp.to_vec(),
199 })
200 }
201}
202
203#[cfg_attr(hax, hax_lib::exclude)]
204impl serde::Serialize for MessageCiphertext {
205 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
206 ser.serialize_str(&hex::encode(self.as_bytes()))
207 }
208}
209
210#[cfg_attr(hax, hax_lib::exclude)]
211impl<'de> serde::Deserialize<'de> for MessageCiphertext {
212 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
213 let s = String::deserialize(de)?;
214 let bytes = hex::decode(s.trim()).map_err(D::Error::custom)?;
215 Self::from_bytes(&bytes).map_err(D::Error::custom)
216 }
217}
218
219pub fn keygen<R: RngCore + CryptoRng>(rng: &mut R) -> Result<MessageKeyPair, Error> {
225 let (sk1, pk1) = generate_dh_akem_keypair(rng)?; let (sk2, pk2) = generate_mlkem768_keypair(rng)?; Ok(MessageKeyPair {
228 sk: MessagePrivateKey {
229 dhakem: sk1,
230 mlkem: sk2,
231 },
232 pk: MessagePublicKey {
233 dhakem: pk1,
234 mlkem: pk2,
235 },
236 })
237}
238
239pub(crate) fn deterministic_keygen(
243 dh_seed: [u8; DhAkemPrivateKey::SEED_LEN],
244 mlkem_seed: [u8; MLKEM768PrivateKey::SEED_LEN],
245) -> Result<MessageKeyPair, Error> {
246 let (sk1, pk1) = dhakem_derand(dh_seed)?;
247 let (sk2, pk2) = mlkem_derand(mlkem_seed)?;
248 Ok(MessageKeyPair {
249 sk: MessagePrivateKey {
250 dhakem: sk1,
251 mlkem: sk2,
252 },
253 pk: MessagePublicKey {
254 dhakem: pk1,
255 mlkem: pk2,
256 },
257 })
258}
259
260pub fn auth_enc<R: RngCore + CryptoRng>(
271 rng: &mut R,
272 kp: &MessageKeyPair, pk: &MessagePublicKey, m: &[u8],
275 ad: &[u8], info_incl: &DHPublicKey,
277) -> Result<MessageCiphertext, Error> {
278 let mut seed = [0u8; 32];
279 rng.fill_bytes(&mut seed);
280
281 let prng = HpkeLibcruxPrng::try_from_seed(seed)
282 .map_err(|e| anyhow::anyhow!("hpke prng instantiation failed: {:?}", e))?;
283 let mut hpke = Hpke::<HpkeLibcrux>::new_with_rng(
284 Mode::AuthPsk,
285 DhKem25519,
286 HkdfSha256,
287 ChaCha20Poly1305,
288 prng,
289 );
290
291 let mut randomness = [0u8; LEN_MLKEM_ENCAPS_RAND];
292 rng.fill_bytes(&mut randomness);
293
294 let (k2, c2) = MlKem768::encaps(pk.mlkem.as_bytes(), &randomness)
296 .map_err(|e| anyhow::anyhow!("ML-KEM encapsulation failed: {:?}", e))?;
297
298 let pkr1: HpkePublicKey = pk.dhakem.clone().into();
300 let sks1: HpkePrivateKey = kp.private_key().dhakem.clone().into();
301
302 let mut full_info = Vec::new();
304 full_info.extend_from_slice(&c2);
305 full_info.extend_from_slice(&info_incl.into_bytes());
306 full_info.extend_from_slice(&kp.pk.as_bytes());
307
308 let (c1_vec, cp) = hpke
309 .seal(
310 &pkr1,
311 &full_info,
312 ad,
313 m,
314 Some(&k2),
315 Some(PSK_ID),
316 Some(&sks1),
317 )
318 .map_err(|e| anyhow::anyhow!("SD-APKE AuthEnc failed: {:?}", e))?;
319
320 let c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN] = c1_vec
322 .as_slice()
323 .try_into()
324 .expect("DHKEM(X25519) encapsulation output has unexpected length");
325
326 Ok(MessageCiphertext { c1, cp, c2 })
327}
328
329pub fn auth_dec(
340 sk: &MessagePrivateKey, pk: &MessagePublicKey, ct: &MessageCiphertext,
343 ad: &[u8],
344 info_incl: &DHPublicKey,
345) -> Result<Vec<u8>, Error> {
346 let mut hpke = Hpke::<HpkeLibcrux<NoRng>>::new_with_rng(
347 Mode::AuthPsk,
348 DhKem25519,
349 HkdfSha256,
350 ChaCha20Poly1305,
351 NoRng, );
353
354 let k2 = MlKem768::decaps(&ct.c2, sk.mlkem.as_bytes())
356 .map_err(|e| anyhow::anyhow!("ML-KEM decapsulation failed: {:?}", e))?;
357
358 let skr1: HpkePrivateKey = sk.dhakem.clone().into();
360 let pks1: HpkePublicKey = pk.dhakem.clone().into();
361
362 let mut full_info = Vec::new();
364 full_info.extend_from_slice(&ct.c2);
365 full_info.extend_from_slice(&info_incl.into_bytes());
366 full_info.extend_from_slice(&pk.as_bytes());
367
368 hpke.open(
369 &ct.c1,
370 &skr1,
371 &full_info,
372 ad,
373 &ct.cp,
374 Some(&k2),
375 Some(PSK_ID),
376 Some(&pks1),
377 )
378 .map_err(|e| anyhow::anyhow!("SD-APKE AuthDec failed: {:?}", e))
379}
380
381#[cfg(test)]
382mod tests {
383 use crate::primitives::ristretto255::generate_dh_keypair;
384
385 use super::*;
386 use crate::metadata::LEN_METADATA_CIPHERTEXT;
387 use crate::primitives::xwing::LEN_XWING_SHAREDSECRET_ENCAPS;
388
389 use proptest::prelude::*;
390 use rand_chacha::ChaCha20Rng;
391 use rand_core::SeedableRng;
392
393 fn message_ct(c1: Vec<u8>, cp: Vec<u8>, c2: Vec<u8>) -> MessageCiphertext {
394 MessageCiphertext {
395 c1: c1.try_into().expect("c1 length"),
396 cp,
397 c2: c2.try_into().expect("c2 length"),
398 }
399 }
400
401 proptest! {
402 #[test]
403 fn test_message_ciphertext_byte_roundtrip(
404 c1 in prop::collection::vec(any::<u8>(), DH_AKEM_ENCAPS_SECRET_LEN),
405 cp in prop::collection::vec(any::<u8>(), 0..128),
406 c2 in prop::collection::vec(any::<u8>(), LEN_MLKEM_SHAREDSECRET_ENCAPS),
407 ) {
408 let ct = message_ct(c1, cp, c2);
409 let restored = MessageCiphertext::from_bytes(&ct.as_bytes()).expect("valid bytes");
410 prop_assert_eq!(ct.as_bytes(), restored.as_bytes());
411 }
412 }
413
414 fn get_rng() -> ChaCha20Rng {
415 let mut seed = [0u8; 32];
416 getrandom::fill(&mut seed).expect("OS random source failed");
417 ChaCha20Rng::from_seed(seed)
418 }
419
420 proptest! {
421 #[test]
422 fn test_auth_enc_dec_roundtrip(
423 m in proptest::collection::vec(any::<u8>(), 0..200),
424 ad in proptest::collection::vec(any::<u8>(), 0..64),
425 ) {
426 let mut rng = get_rng();
427 let sender_kp = keygen(&mut rng).expect("KGen failed");
428 let recipient_kp = keygen(&mut rng).expect("KGen failed");
429 let dh_fetch = generate_dh_keypair(&mut rng).1;
430
431 let ct = auth_enc(
432 &mut rng,
433 &sender_kp,
434 recipient_kp.public_key(),
435 &m, &ad, &dh_fetch,
436 ).expect("AuthEnc failed");
437
438 let decrypted = auth_dec(
439 recipient_kp.private_key(),
440 sender_kp.public_key(),
441 &ct, &ad, &dh_fetch,
442 ).expect("AuthDec failed");
443
444 prop_assert_eq!(m, decrypted);
445 }
446 }
447
448 #[test]
449 fn test_auth_dec_wrong_recipient_fails() {
450 let mut rng = get_rng();
451 let sender_kp = keygen(&mut rng).expect("KGen failed");
452 let recipient_kp = keygen(&mut rng).expect("KGen failed");
453 let recipient_fetch = generate_dh_keypair(&mut rng).1;
454 let wrong_kp = keygen(&mut rng).expect("KGen failed");
455
456 let ct = auth_enc(
457 &mut rng,
458 &sender_kp,
459 recipient_kp.public_key(),
460 b"secret",
461 b"ad",
462 &recipient_fetch,
463 )
464 .expect("AuthEnc failed");
465
466 assert!(
467 auth_dec(
468 wrong_kp.private_key(),
469 sender_kp.public_key(),
470 &ct,
471 b"ad",
472 &recipient_fetch,
473 )
474 .is_err()
475 );
476 }
477}