1#![cfg_attr(not(feature = "std"), no_std)]
70#![warn(clippy::all, clippy::pedantic)]
71#![warn(missing_debug_implementations)]
72#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
73
74use rand_core::{CryptoRng, RngCore};
75use subtle::ConstantTimeEq;
76use x25519_dalek::{PublicKey as XPub, StaticSecret};
77use zeroize::{Zeroize, ZeroizeOnDrop};
78
79pub const X25519_BYTES: usize = 32;
81
82pub const X25519_SS_BYTES: usize = 32;
84
85pub const MLKEM_SS_BYTES: usize = 32;
87
88pub const SHARED_SECRET_BYTES: usize = MLKEM_SS_BYTES + X25519_SS_BYTES;
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub struct LengthError {
94 pub expected: usize,
95 pub got: usize,
96}
97
98impl core::fmt::Display for LengthError {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 write!(
101 f,
102 "wrong byte length: expected {}, got {}",
103 self.expected, self.got
104 )
105 }
106}
107
108#[cfg(feature = "std")]
109impl std::error::Error for LengthError {}
110
111fn x25519_keypair_from_seed(seed: [u8; 32]) -> (StaticSecret, XPub) {
113 let sk = StaticSecret::from(seed);
114 let pk = XPub::from(&sk);
115 (sk, pk)
116}
117
118fn fill_seed_pair<R: RngCore + CryptoRng>(rng: &mut R) -> ([u8; 32], [u8; 64]) {
120 let mut x = [0u8; 32];
121 let mut m = [0u8; 64];
122 rng.fill_bytes(&mut x);
123 rng.fill_bytes(&mut m);
124 (x, m)
125}
126
127macro_rules! hybrid_kem {
133 ($name:ident, $pq:ident, $pq_pk_ty:ident, $pq_sk_ty:ident, $pq_ct_ty:ident,
134 $ek_ty:ident, $dk_ty:ident, $ct_ty:ident, $ss_ty:ident,
135 $pq_pk:expr, $pq_sk:expr, $pq_ct:expr,
136 $ek_size:expr, $dk_size:expr, $ct_size:expr) => {
137 #[derive(Debug)]
138 pub struct $name;
139
140 impl $name {
141 pub const ENCAPSULATION_KEY_SIZE: usize = $ek_size;
143 pub const DECAPSULATION_KEY_SIZE: usize = $dk_size;
145 pub const CIPHERTEXT_SIZE: usize = $ct_size;
147 pub const SHARED_SECRET_SIZE: usize = SHARED_SECRET_BYTES;
149
150 pub fn keygen<R: RngCore + CryptoRng>(rng: &mut R) -> ($ek_ty, $dk_ty) {
151 let (x_seed, m_seed) = fill_seed_pair(rng);
152 let (xsk, xpk) = x25519_keypair_from_seed(x_seed);
153 let (mpk, msk) = mlkem::$pq::keygen_deterministic(&m_seed);
154
155 let mut ek = [0u8; $ek_size];
156 ek[..$pq_pk].copy_from_slice(mpk.as_bytes());
157 ek[$pq_pk..].copy_from_slice(xpk.as_bytes());
158
159 let mut dk = [0u8; $dk_size];
160 dk[..$pq_sk].copy_from_slice(msk.as_bytes());
161 dk[$pq_sk..].copy_from_slice(&xsk.to_bytes());
162
163 ($ek_ty(ek), $dk_ty(dk))
164 }
165
166 pub fn encapsulate<R: RngCore + CryptoRng>(
167 ek: &$ek_ty,
168 rng: &mut R,
169 ) -> ($ct_ty, $ss_ty) {
170 let mpk_bytes: &[u8; $pq_pk] =
171 (&ek.0[..$pq_pk]).try_into().expect("ek length checked");
172 let xpk_bytes: &[u8; X25519_BYTES] =
173 (&ek.0[$pq_pk..]).try_into().expect("ek length checked");
174 let mpk = mlkem::$pq_pk_ty::from_bytes(mpk_bytes);
175 let xpk = XPub::from(*xpk_bytes);
176
177 let (mct, mss) = mlkem::$pq::encapsulate(&mpk, rng);
179
180 let mut x_seed = [0u8; 32];
182 rng.fill_bytes(&mut x_seed);
183 let xsk = ReusableSecretWrapper::from(x_seed);
184 let xpk_eph = XPub::from(&xsk.0);
185 let xss = xsk.0.diffie_hellman(&xpk);
186
187 let mut ct = [0u8; $ct_size];
188 ct[..$pq_ct].copy_from_slice(mct.as_bytes());
189 ct[$pq_ct..].copy_from_slice(xpk_eph.as_bytes());
190
191 let mut ss = [0u8; SHARED_SECRET_BYTES];
192 ss[..MLKEM_SS_BYTES].copy_from_slice(mss.as_bytes());
193 ss[MLKEM_SS_BYTES..].copy_from_slice(xss.as_bytes());
194
195 ($ct_ty(ct), $ss_ty(ss))
196 }
197
198 pub fn decapsulate(dk: &$dk_ty, ct: &$ct_ty) -> $ss_ty {
199 let msk_bytes: &[u8; $pq_sk] =
200 (&dk.0[..$pq_sk]).try_into().expect("dk length checked");
201 let xsk_bytes: &[u8; X25519_BYTES] =
202 (&dk.0[$pq_sk..]).try_into().expect("dk length checked");
203 let msk = mlkem::$pq_sk_ty::from_bytes(msk_bytes);
204 let xsk = StaticSecret::from(*xsk_bytes);
205
206 let mct_bytes: &[u8; $pq_ct] =
207 (&ct.0[..$pq_ct]).try_into().expect("ct length checked");
208 let xpk_bytes: &[u8; X25519_BYTES] =
209 (&ct.0[$pq_ct..]).try_into().expect("ct length checked");
210 let mct = mlkem::$pq_ct_ty::from_bytes(mct_bytes);
211 let xpk = XPub::from(*xpk_bytes);
212
213 let mss = mlkem::$pq::decapsulate(&msk, &mct);
214 let xss = xsk.diffie_hellman(&xpk);
215
216 let mut ss = [0u8; SHARED_SECRET_BYTES];
217 ss[..MLKEM_SS_BYTES].copy_from_slice(mss.as_bytes());
218 ss[MLKEM_SS_BYTES..].copy_from_slice(xss.as_bytes());
219 $ss_ty(ss)
220 }
221 }
222
223 #[derive(Clone)]
224 pub struct $ek_ty(pub(crate) [u8; $ek_size]);
225 #[derive(Clone, ZeroizeOnDrop)]
226 pub struct $dk_ty(pub(crate) [u8; $dk_size]);
227 #[derive(Clone)]
228 pub struct $ct_ty(pub(crate) [u8; $ct_size]);
229 #[derive(Clone, ZeroizeOnDrop)]
230 pub struct $ss_ty(pub(crate) [u8; SHARED_SECRET_BYTES]);
231
232 impl $ek_ty {
233 pub fn as_bytes(&self) -> &[u8; $ek_size] {
234 &self.0
235 }
236 pub fn from_bytes(b: &[u8; $ek_size]) -> Self {
237 Self(*b)
238 }
239 }
240 impl $dk_ty {
241 pub fn as_bytes(&self) -> &[u8; $dk_size] {
242 &self.0
243 }
244 pub fn from_bytes(b: &[u8; $dk_size]) -> Self {
245 Self(*b)
246 }
247 }
248 impl $ct_ty {
249 pub fn as_bytes(&self) -> &[u8; $ct_size] {
250 &self.0
251 }
252 pub fn from_bytes(b: &[u8; $ct_size]) -> Self {
253 Self(*b)
254 }
255 }
256 impl $ss_ty {
257 pub fn as_bytes(&self) -> &[u8; SHARED_SECRET_BYTES] {
258 &self.0
259 }
260 }
261
262 impl AsRef<[u8]> for $ek_ty {
263 fn as_ref(&self) -> &[u8] {
264 &self.0
265 }
266 }
267 impl AsRef<[u8]> for $ct_ty {
268 fn as_ref(&self) -> &[u8] {
269 &self.0
270 }
271 }
272 impl AsRef<[u8]> for $ss_ty {
273 fn as_ref(&self) -> &[u8] {
274 &self.0
275 }
276 }
277 impl AsRef<[u8]> for $dk_ty {
278 fn as_ref(&self) -> &[u8] {
279 &self.0
280 }
281 }
282
283 impl TryFrom<&[u8]> for $ek_ty {
284 type Error = LengthError;
285 fn try_from(b: &[u8]) -> Result<Self, LengthError> {
286 if b.len() != $ek_size {
287 return Err(LengthError {
288 expected: $ek_size,
289 got: b.len(),
290 });
291 }
292 let mut a = [0u8; $ek_size];
293 a.copy_from_slice(b);
294 Ok(Self(a))
295 }
296 }
297 impl TryFrom<&[u8]> for $ct_ty {
298 type Error = LengthError;
299 fn try_from(b: &[u8]) -> Result<Self, LengthError> {
300 if b.len() != $ct_size {
301 return Err(LengthError {
302 expected: $ct_size,
303 got: b.len(),
304 });
305 }
306 let mut a = [0u8; $ct_size];
307 a.copy_from_slice(b);
308 Ok(Self(a))
309 }
310 }
311 impl TryFrom<&[u8]> for $dk_ty {
312 type Error = LengthError;
313 fn try_from(b: &[u8]) -> Result<Self, LengthError> {
314 if b.len() != $dk_size {
315 return Err(LengthError {
316 expected: $dk_size,
317 got: b.len(),
318 });
319 }
320 let mut a = [0u8; $dk_size];
321 a.copy_from_slice(b);
322 Ok(Self(a))
323 }
324 }
325
326 impl PartialEq for $ek_ty {
327 fn eq(&self, other: &Self) -> bool {
328 self.0.ct_eq(&other.0).into()
329 }
330 }
331 impl Eq for $ek_ty {}
332 impl PartialEq for $ct_ty {
333 fn eq(&self, other: &Self) -> bool {
334 self.0.ct_eq(&other.0).into()
335 }
336 }
337 impl Eq for $ct_ty {}
338 impl PartialEq for $ss_ty {
339 fn eq(&self, other: &Self) -> bool {
340 self.0.ct_eq(&other.0).into()
341 }
342 }
343 impl Eq for $ss_ty {}
344 impl PartialEq for $dk_ty {
345 fn eq(&self, other: &Self) -> bool {
346 self.0.as_slice().ct_eq(other.0.as_slice()).into()
347 }
348 }
349 impl Eq for $dk_ty {}
350
351 impl core::fmt::Debug for $ek_ty {
352 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
353 write!(
354 f,
355 concat!(stringify!($ek_ty), "(..{} bytes..)"),
356 self.0.len()
357 )
358 }
359 }
360 impl core::fmt::Debug for $dk_ty {
361 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
362 write!(f, concat!(stringify!($dk_ty), "(..REDACTED..)"))
363 }
364 }
365 impl core::fmt::Debug for $ct_ty {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 write!(
368 f,
369 concat!(stringify!($ct_ty), "(..{} bytes..)"),
370 self.0.len()
371 )
372 }
373 }
374 impl core::fmt::Debug for $ss_ty {
375 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
376 write!(f, concat!(stringify!($ss_ty), "(..REDACTED..)"))
377 }
378 }
379
380 impl Zeroize for $dk_ty {
381 fn zeroize(&mut self) {
382 self.0.zeroize();
383 }
384 }
385 impl Zeroize for $ss_ty {
386 fn zeroize(&mut self) {
387 self.0.zeroize();
388 }
389 }
390 };
391}
392
393struct ReusableSecretWrapper(StaticSecret);
397impl From<[u8; 32]> for ReusableSecretWrapper {
398 fn from(b: [u8; 32]) -> Self {
399 Self(StaticSecret::from(b))
400 }
401}
402
403hybrid_kem!(
408 X25519MlKem768,
409 MlKem768,
410 PublicKey768,
411 SecretKey768,
412 Ciphertext768,
413 EncapsKey768,
414 DecapsKey768,
415 Ciphertext768Hybrid,
416 SharedSecret768Hybrid,
417 1184,
418 2400,
419 1088,
420 1216,
421 2432,
422 1120
423);
424
425hybrid_kem!(
430 X25519MlKem1024,
431 MlKem1024,
432 PublicKey1024,
433 SecretKey1024,
434 Ciphertext1024,
435 EncapsKey1024,
436 DecapsKey1024,
437 Ciphertext1024Hybrid,
438 SharedSecret1024Hybrid,
439 1568,
440 3168,
441 1568,
442 1600,
443 3200,
444 1600
445);