pub use rkyber::fips202::{sha3_256, shake256};
pub use rkyber::fips203::sharedsecret_to_bytes;
pub use rkyber::fips203::{
MLKEM768Params, CIPHERTEXTM768, MLKEM768DECAPSKEY, MLKEM768ENCAPSKEY, SHAREDSECRET,
};
use rkyber::mlkem_internal::{
encaps_deterministic, encapsulation_key_from_bytes, encapsulation_key_to_bytes, keys_from_seed,
mlkem768_decaps, COINS,
};
use rkyber::randombytes::randombytes;
use core::fmt;
use core::fmt::Debug;
use hybrid_array::{
sizes::{U1088, U1120, U1184, U1216, U32, U6, U64, U96},
Array, ArrayN,
};
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
const X_WING_LABEL: &[u8; 6] = br"\.//^\";
pub const ENCAPSULATION_KEY_SIZE: usize = 1216;
pub const DECAPSULATION_KEY_SIZE: usize = 32;
pub const CIPHERTEXT_SIZE: usize = 1120;
pub const ENCAPSULATION_RANDOMNESS_SIZE: usize = 64;
#[derive(Debug, PartialEq)]
pub struct XWINGCIPHERTEXT {
pub ct_m: ArrayN<u8, 1088>,
pub ct_x: PublicKey,
}
impl Default for XWINGCIPHERTEXT {
fn default() -> Self {
XWINGCIPHERTEXT {
ct_m: Default::default(),
ct_x: PublicKey::from([0; 32]),
}
}
}
pub fn ciphertext_to_bytes(outbytes: &mut Array<u8, U1120>, inct: &XWINGCIPHERTEXT) {
for i in 0..CIPHERTEXT_SIZE - 32 {
outbytes[i] = inct.ct_m[i];
}
for i in 0..32 {
outbytes[CIPHERTEXT_SIZE - 32 + i] = inct.ct_x.to_bytes()[i];
}
}
pub fn ciphertext_from_bytes(outct: &mut XWINGCIPHERTEXT, inbytes: &Array<u8, U1120>) {
let (m_bytes, x_bytes) = inbytes.split_ref::<U1088>();
for i in 0..CIPHERTEXT_SIZE - 32 {
outct.ct_m[i] = m_bytes[i];
}
outct.ct_x = PublicKey::from(x_bytes.0);
}
#[derive(Clone, Debug, PartialEq)]
pub struct XWINGENCAPSKEY {
pub pk_m: MLKEM768ENCAPSKEY,
pub pk_x: PublicKey,
}
impl Default for XWINGENCAPSKEY {
fn default() -> Self {
XWINGENCAPSKEY {
pk_m: Default::default(),
pk_x: PublicKey::from([0; 32]),
}
}
}
pub fn xwingencapskey_to_bytes(outbytes: &mut Array<u8, U1216>, ek: &XWINGENCAPSKEY) {
encapsulation_key_to_bytes::<MLKEM768Params>(outbytes, &ek.pk_m);
for i in 0..32 {
outbytes[ENCAPSULATION_KEY_SIZE - 32 + i] = ek.pk_x.to_bytes()[i];
}
}
pub fn xwingencapskey_from_bytes(ek: &mut XWINGENCAPSKEY, inbytes: &Array<u8, U1216>) {
let (m_bytes, x_bytes) = inbytes.split_ref::<U1184>();
encapsulation_key_from_bytes::<MLKEM768Params>(&mut ek.pk_m, m_bytes);
ek.pk_x = PublicKey::from(x_bytes.0);
}
#[derive(Clone, Default, PartialEq)]
pub struct XWINGDECAPSKEY {
sk: [u8; DECAPSULATION_KEY_SIZE],
pub ek: XWINGENCAPSKEY,
}
pub fn xwingdecapskey_to_bytes(outbytes: &mut [u8; DECAPSULATION_KEY_SIZE], dk: &XWINGDECAPSKEY) {
for i in 0..DECAPSULATION_KEY_SIZE {
outbytes[i] = dk.sk[i];
}
}
pub fn xwingdecapskey_from_bytes(dk: &mut XWINGDECAPSKEY, inbytes: &[u8; DECAPSULATION_KEY_SIZE]) {
let mut _ek: XWINGENCAPSKEY = Default::default();
generate_keypair_derand(&mut _ek, dk, inbytes);
}
impl Debug for XWINGDECAPSKEY {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DecapsulationKey")
.field("ek", &self.ek)
.finish_non_exhaustive()
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for XWINGDECAPSKEY {}
pub fn generate_keypair(ek: &mut XWINGENCAPSKEY, dk: &mut XWINGDECAPSKEY) {
let mut coins: Array<u8, U32> = Default::default();
randombytes(&mut coins, 32);
generate_keypair_derand(ek, dk, (&coins).into());
#[cfg(feature = "zeroize")]
coins.zeroize();
}
pub fn generate_keypair_derand(
ek: &mut XWINGENCAPSKEY,
dk: &mut XWINGDECAPSKEY,
coins: &[u8; DECAPSULATION_KEY_SIZE],
) {
dk.sk = *coins;
let mut dk_m: MLKEM768DECAPSKEY = Default::default();
(ek.pk_x, _) = expand_key(&mut ek.pk_m, &mut dk_m, coins);
#[cfg(feature = "zeroize")]
dk_m.zeroize();
}
fn expand_key(
pk_m: &mut MLKEM768ENCAPSKEY,
sk_m: &mut MLKEM768DECAPSKEY,
sk: &[u8; DECAPSULATION_KEY_SIZE],
) -> (PublicKey, StaticSecret) {
let mut seed: Array<u8, U96> = Default::default();
shake256(&mut seed, 96, sk, 32);
let (mut rand_m, mut rand_x) = seed.split::<U64>();
keys_from_seed(pk_m, sk_m, &rand_m);
let sk_x = StaticSecret::from(rand_x.0);
let pk_x = PublicKey::from(&sk_x);
#[cfg(feature = "zeroize")]
seed.zeroize();
#[cfg(feature = "zeroize")]
rand_m.zeroize();
#[cfg(feature = "zeroize")]
rand_x.zeroize();
(pk_x, sk_x)
}
fn combiner(
outhash: &mut SHAREDSECRET,
ss_m: &SHAREDSECRET,
ss_x: &SharedSecret,
ct_x: &PublicKey,
pk_x: &PublicKey,
) {
sha3_256(
&mut outhash.ss_hat,
&ss_m
.ss_hat
.concat(Array::<u8, U32>(ss_x.to_bytes()))
.concat(Array::<u8, U32>(ct_x.to_bytes()))
.concat(Array::<u8, U32>(pk_x.to_bytes()))
.concat(Array::<u8, U6>(*X_WING_LABEL)),
134,
);
}
pub fn encaps(ss: &mut SHAREDSECRET, ct: &mut XWINGCIPHERTEXT, pk: &XWINGENCAPSKEY) {
let mut coins: Array<u8, U64> = Default::default();
randombytes(&mut coins, ENCAPSULATION_RANDOMNESS_SIZE);
encaps_derand(ss, ct, pk, &coins);
#[cfg(feature = "zeroize")]
coins.zeroize();
}
pub fn encaps_derand(
ss: &mut SHAREDSECRET,
ct: &mut XWINGCIPHERTEXT,
pk: &XWINGENCAPSKEY,
eseed: &Array<u8, U64>,
) {
let (mut rand_m, mut rand_x) = eseed.split::<U32>();
let ek_x = StaticSecret::from(rand_x.0);
let mut coins: COINS = Default::default();
coins.c_hat = rand_m;
let mut ct_m: CIPHERTEXTM768 = Default::default();
let mut ss_m: SHAREDSECRET = Default::default();
encaps_deterministic(&mut ct_m, &mut ss_m, &pk.pk_m, &coins);
let ct_x = PublicKey::from(&ek_x);
let ss_x = ek_x.diffie_hellman(&pk.pk_x);
combiner(ss, &ss_m, &ss_x, &ct_x, &pk.pk_x);
ct.ct_m = ct_m.ct_hat;
ct.ct_x = ct_x;
#[cfg(feature = "zeroize")]
rand_m.zeroize();
#[cfg(feature = "zeroize")]
coins.zeroize();
#[cfg(feature = "zeroize")]
rand_x.zeroize();
#[cfg(feature = "zeroize")]
ss_m.zeroize();
}
pub fn decaps(ss: &mut SHAREDSECRET, ct: &XWINGCIPHERTEXT, sk: &XWINGDECAPSKEY) {
let mut pk_m: MLKEM768ENCAPSKEY = Default::default();
let mut sk_m: MLKEM768DECAPSKEY = Default::default();
let (pk_x, sk_x) = expand_key(&mut pk_m, &mut sk_m, (&sk.sk).into());
let mut ct_m: CIPHERTEXTM768 = Default::default();
ct_m.ct_hat = ct.ct_m;
let ct_x = ct.ct_x;
let mut ss_m: SHAREDSECRET = Default::default();
mlkem768_decaps(&mut ss_m, &sk_m, &ct_m);
let ss_x = sk_x.diffie_hellman(&ct_x);
combiner(ss, &ss_m, &ss_x, &ct_x, &pk_x);
}