1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
7)]
8
9#![cfg_attr(feature = "getrandom", doc = "```")]
16#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
17pub use kem::{
30 self, Decapsulate, Decapsulator, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport,
31 KeyInit, KeySizeUser, TryKeyInit,
32 common::rand_core::{CryptoRng, TryCryptoRng},
33};
34
35use core::fmt::{self, Debug};
36use ml_kem::{
37 FromSeed, MlKem768,
38 array::{
39 Array, ArrayN, AsArrayRef,
40 sizes::{U32, U1120, U1184, U1216},
41 },
42 ml_kem_768,
43};
44use sha3::Sha3_256;
45use shake::{ExtendableOutput, Shake256, Shake256Reader, XofReader};
46use x25519_dalek::{PublicKey, StaticSecret};
47
48#[cfg(feature = "zeroize")]
49use zeroize::{Zeroize, ZeroizeOnDrop};
50
51type MlKem768DecapsulationKey = ml_kem_768::DecapsulationKey;
52type MlKem768EncapsulationKey = ml_kem_768::EncapsulationKey;
53
54const X_WING_LABEL: &[u8; 6] = br"\.//^\";
55
56pub const ENCAPSULATION_KEY_SIZE: usize = 1216;
58pub const DECAPSULATION_KEY_SIZE: usize = 32;
60pub const CIPHERTEXT_SIZE: usize = 1120;
62pub const ENCAPSULATION_RANDOMNESS_SIZE: usize = 64;
64
65pub type Ciphertext = kem::Ciphertext<XWingKem>;
67pub type SharedKey = Array<u8, U32>;
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
72pub struct XWingKem;
73
74impl Kem for XWingKem {
75 type DecapsulationKey = DecapsulationKey;
76 type EncapsulationKey = EncapsulationKey;
77 type CiphertextSize = U1120;
78 type SharedKeySize = U32;
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct EncapsulationKey {
94 pk_m: MlKem768EncapsulationKey,
95 pk_x: PublicKey,
96}
97
98impl EncapsulationKey {
99 #[doc(hidden)]
106 #[cfg_attr(not(feature = "hazmat"), doc(hidden))]
107 #[expect(clippy::must_use_candidate)]
108 pub fn encapsulate_deterministic(
109 &self,
110 randomness: &ArrayN<u8, ENCAPSULATION_RANDOMNESS_SIZE>,
111 ) -> (Ciphertext, SharedKey) {
112 let (rand_m, rand_x) = randomness.split::<U32>();
114
115 let (ct_m, ss_m) = self.pk_m.encapsulate_deterministic(&rand_m);
117
118 let ek_x = StaticSecret::from(rand_x.0);
119 let ct_x = PublicKey::from(&ek_x);
121 let ss_x = ek_x.diffie_hellman(&self.pk_x);
123
124 let ss = combiner(&ss_m, &ss_x, &ct_x, &self.pk_x);
125 let ct = CiphertextMessage { ct_m, ct_x };
126
127 (ct.into(), ss)
128 }
129}
130
131impl Encapsulate for EncapsulationKey {
132 type Kem = XWingKem;
133
134 fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext, SharedKey)
135 where
136 R: CryptoRng + ?Sized,
137 {
138 #[allow(unused_mut)]
139 let mut randomness = Array::generate_from_rng(rng);
140 let res = self.encapsulate_deterministic(&randomness);
141
142 #[cfg(feature = "zeroize")]
143 randomness.zeroize();
144
145 res
146 }
147}
148
149impl KeySizeUser for EncapsulationKey {
150 type KeySize = U1216;
151}
152
153impl KeyExport for EncapsulationKey {
154 fn to_bytes(&self) -> Key<Self> {
155 let mut key_bytes = Key::<Self>::default();
156 let (m, x) = key_bytes.split_at_mut(1184);
157 m.copy_from_slice(&self.pk_m.to_bytes());
158 x.copy_from_slice(self.pk_x.as_bytes());
159 key_bytes
160 }
161}
162
163impl TryKeyInit for EncapsulationKey {
164 fn new(key_bytes: &Key<Self>) -> Result<Self, InvalidKey> {
165 let (m_bytes, x_bytes) = key_bytes.split_ref::<U1184>();
166
167 let pk_m = MlKem768EncapsulationKey::new(m_bytes)?;
168 let pk_x = PublicKey::from(x_bytes.0);
169
170 Ok(EncapsulationKey { pk_m, pk_x })
171 }
172}
173
174impl TryFrom<&[u8]> for EncapsulationKey {
175 type Error = InvalidKey;
176
177 fn try_from(key_bytes: &[u8]) -> Result<Self, InvalidKey> {
178 Self::new_from_slice(key_bytes)
179 }
180}
181
182#[derive(Clone)]
184pub struct DecapsulationKey {
185 sk: [u8; DECAPSULATION_KEY_SIZE],
186 ek: EncapsulationKey,
187}
188
189impl DecapsulationKey {
190 #[must_use]
192 pub fn as_bytes(&self) -> &[u8; DECAPSULATION_KEY_SIZE] {
193 &self.sk
194 }
195}
196
197impl Debug for DecapsulationKey {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.debug_struct("DecapsulationKey")
200 .field("ek", &self.ek)
201 .finish_non_exhaustive()
202 }
203}
204
205impl Decapsulate for DecapsulationKey {
206 #[allow(clippy::similar_names)] fn decapsulate(&self, ct: &Ciphertext) -> SharedKey {
208 let ct = CiphertextMessage::from(ct);
209 let (sk_m, sk_x, _pk_m, pk_x) = expand_key(&self.sk);
210
211 let ss_m = sk_m.decapsulate(&ct.ct_m);
212
213 let ss_x = sk_x.diffie_hellman(&ct.ct_x);
215
216 combiner(&ss_m, &ss_x, &ct.ct_x, &pk_x)
217 }
218}
219
220impl Decapsulator for DecapsulationKey {
221 type Kem = XWingKem;
222
223 fn encapsulation_key(&self) -> &EncapsulationKey {
224 &self.ek
225 }
226}
227
228impl Drop for DecapsulationKey {
229 fn drop(&mut self) {
230 #[cfg(feature = "zeroize")]
231 self.sk.zeroize();
232 }
233}
234
235impl From<[u8; DECAPSULATION_KEY_SIZE]> for DecapsulationKey {
236 fn from(sk: [u8; DECAPSULATION_KEY_SIZE]) -> Self {
237 DecapsulationKey::new(sk.as_array_ref())
238 }
239}
240
241impl Generate for DecapsulationKey {
242 fn try_generate_from_rng<R>(rng: &mut R) -> Result<Self, R::Error>
243 where
244 R: TryCryptoRng + ?Sized,
245 {
246 <[u8; DECAPSULATION_KEY_SIZE]>::try_generate_from_rng(rng).map(Into::into)
247 }
248}
249
250impl KeySizeUser for DecapsulationKey {
251 type KeySize = U32;
252}
253
254impl KeyInit for DecapsulationKey {
255 fn new(key: &Key<Self>) -> Self {
256 let (_sk_m, _sk_x, pk_m, pk_x) = expand_key(key.as_ref());
257 let ek = EncapsulationKey { pk_m, pk_x };
258 Self { sk: key.0, ek }
259 }
260}
261
262impl KeyExport for DecapsulationKey {
263 fn to_bytes(&self) -> Key<Self> {
264 self.sk.into()
265 }
266}
267
268#[cfg(feature = "zeroize")]
269impl ZeroizeOnDrop for DecapsulationKey {}
270
271fn expand_key(
272 sk: &[u8; DECAPSULATION_KEY_SIZE],
273) -> (
274 MlKem768DecapsulationKey,
275 StaticSecret,
276 MlKem768EncapsulationKey,
277 PublicKey,
278) {
279 use sha3::digest::Update;
280 let mut shaker = Shake256::default();
281 shaker.update(sk);
282 let mut expanded: Shake256Reader = shaker.finalize_xof();
283
284 let seed = read_from(&mut expanded).into();
285 let (sk_m, pk_m) = MlKem768::from_seed(&seed);
286
287 let sk_x = read_from(&mut expanded);
288 let sk_x = StaticSecret::from(sk_x);
289 let pk_x = PublicKey::from(&sk_x);
290
291 (sk_m, sk_x, pk_m, pk_x)
292}
293
294#[derive(Clone, Debug, PartialEq, Eq)]
296pub struct CiphertextMessage {
297 ct_m: ArrayN<u8, 1088>,
298 ct_x: PublicKey,
299}
300
301impl CiphertextMessage {
302 #[must_use]
305 pub fn to_bytes(&self) -> Ciphertext {
306 let mut buffer = Ciphertext::default();
307 buffer[0..1088].copy_from_slice(&self.ct_m);
308 buffer[1088..].copy_from_slice(self.ct_x.as_bytes());
309 buffer
310 }
311}
312
313impl From<&Ciphertext> for CiphertextMessage {
314 fn from(value: &Ciphertext) -> Self {
315 let mut ct_m = [0; 1088];
316 ct_m.copy_from_slice(&value[0..1088]);
317 let mut ct_x = [0; 32];
318 ct_x.copy_from_slice(&value[1088..]);
319
320 CiphertextMessage {
321 ct_m: ct_m.into(),
322 ct_x: ct_x.into(),
323 }
324 }
325}
326
327impl From<&CiphertextMessage> for Ciphertext {
328 #[inline]
329 fn from(msg: &CiphertextMessage) -> Self {
330 msg.to_bytes()
331 }
332}
333
334impl From<CiphertextMessage> for Ciphertext {
335 #[inline]
336 fn from(msg: CiphertextMessage) -> Self {
337 Self::from(&msg)
338 }
339}
340
341fn combiner(
342 ss_m: &ArrayN<u8, 32>,
343 ss_x: &x25519_dalek::SharedSecret,
344 ct_x: &PublicKey,
345 pk_x: &PublicKey,
346) -> SharedKey {
347 use sha3::Digest;
348
349 let mut hasher = Sha3_256::new();
350 hasher.update(ss_m);
351 hasher.update(ss_x);
352 hasher.update(ct_x);
353 hasher.update(pk_x.as_bytes());
354 hasher.update(X_WING_LABEL);
355 hasher.finalize()
356}
357
358fn read_from<const N: usize>(reader: &mut Shake256Reader) -> [u8; N] {
359 let mut data = [0; N];
360 reader.read(&mut data);
361 data
362}
363
364#[cfg(test)]
365mod tests {
366 #[cfg(feature = "getrandom")]
367 use super::*;
368
369 #[test]
370 #[cfg(feature = "getrandom")]
371 fn key_serialize() {
372 let (sk, pk) = XWingKem::generate_keypair();
373
374 let sk_bytes = sk.as_bytes();
375 let pk_bytes = pk.to_bytes();
376
377 let sk_b = DecapsulationKey::from(*sk_bytes);
378 let pk_b = EncapsulationKey::new(&pk_bytes).unwrap();
379
380 assert_eq!(sk.sk, sk_b.sk);
381 assert!(pk == pk_b);
382 }
383}