use crate::{
Deserializable, HpkeError, Serializable,
kdf::one_stage_kdf::labeled_derive,
kem::{KemTrait, SharedSecret},
util::{enforce_equal_len, enforce_outbuf_len, kem_suite_id},
};
use hybrid_array::typenum::{Prod, Sum, U3, U32, U64, U1024, Unsigned};
use rand_core::CryptoRng;
use shake::Shake256;
use subtle::{Choice, ConstantTimeEq};
use x_wing::{Decapsulator, KeyExport, TryKeyInit, kem::Decapsulate};
use zeroize::Zeroize;
type U1216 = Sum<U1024, Prod<U64, U3>>;
type U1120 = Sum<Sum<U1024, U64>, U32>;
const XWING_ENCAP_RANDOMNESS_SIZE: usize = 64;
#[derive(Clone)]
pub struct PrivateKey(x_wing::DecapsulationKey);
impl Serializable for PrivateKey {
type OutputSize = U32;
fn write_exact(&self, buf: &mut [u8]) {
enforce_outbuf_len::<Self>(buf);
buf.copy_from_slice(self.0.as_bytes());
}
}
impl Deserializable for PrivateKey {
fn from_bytes(encoded: &[u8]) -> Result<Self, HpkeError> {
enforce_equal_len(Self::OutputSize::USIZE, encoded.len())?;
let mut arr = [0u8; Self::OutputSize::USIZE];
arr.copy_from_slice(encoded);
Ok(PrivateKey(arr.into()))
}
}
impl ConstantTimeEq for PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.as_bytes().ct_eq(other.0.as_bytes())
}
}
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for PrivateKey {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey(x_wing::EncapsulationKey);
impl Serializable for PublicKey {
type OutputSize = U1216;
fn write_exact(&self, buf: &mut [u8]) {
enforce_outbuf_len::<Self>(buf);
buf.copy_from_slice(&self.0.to_bytes());
}
}
impl Deserializable for PublicKey {
fn from_bytes(encoded: &[u8]) -> Result<Self, HpkeError> {
enforce_equal_len(Self::OutputSize::USIZE, encoded.len())?;
let arr = encoded.try_into().unwrap();
let pk = x_wing::EncapsulationKey::new(arr).map_err(|_| HpkeError::ValidationError)?;
Ok(PublicKey(pk))
}
}
#[derive(Clone)]
pub struct EncappedKey(x_wing::Ciphertext);
impl Serializable for EncappedKey {
type OutputSize = U1120;
fn write_exact(&self, buf: &mut [u8]) {
buf.copy_from_slice(&self.0);
}
}
impl Deserializable for EncappedKey {
fn from_bytes(bytes: &[u8]) -> Result<Self, HpkeError> {
x_wing::Ciphertext::try_from(bytes)
.map_err(|_| HpkeError::ValidationError)
.map(EncappedKey)
}
}
pub struct XWing;
impl XWing {
pub(crate) fn encap_deterministic(
pk_recip: &PublicKey,
randomness: &[u8; XWING_ENCAP_RANDOMNESS_SIZE],
) -> Result<(SharedSecret<Self>, EncappedKey), HpkeError> {
let (ct, ss) = pk_recip.0.encapsulate_deterministic(randomness.into());
Ok((SharedSecret(ss), EncappedKey(ct)))
}
}
impl KemTrait for XWing {
const KEM_ID: u16 = 0x647a;
type NSecret = U32;
type PublicKey = PublicKey;
type PrivateKey = PrivateKey;
type EncappedKey = EncappedKey;
fn sk_to_pk(sk: &PrivateKey) -> PublicKey {
PublicKey(sk.0.encapsulation_key().clone())
}
fn derive_keypair(ikm: &[u8]) -> (PrivateKey, PublicKey) {
let suite_id = kem_suite_id::<Self>();
let mut sk_bytes = [0u8; <PrivateKey as Serializable>::OutputSize::USIZE];
labeled_derive::<Shake256>(&suite_id, &[ikm], b"DeriveKeyPair", &[b""], &mut sk_bytes);
let sk = PrivateKey::from_bytes(&sk_bytes).unwrap();
let pk = Self::sk_to_pk(&sk);
sk_bytes.zeroize();
(sk, pk)
}
fn decap(
sk_recip: &PrivateKey,
pk_sender_id: Option<&PublicKey>,
encapped_key: &EncappedKey,
) -> Result<SharedSecret<Self>, HpkeError> {
assert!(
pk_sender_id.is_none(),
"X-Wing doesn't support authenticated encapsulation. Use Base or Psk operation mode."
);
let ss = sk_recip.0.decapsulate(&encapped_key.0);
Ok(SharedSecret(ss))
}
fn encap_with_rng(
pk_recip: &PublicKey,
sender_id_keypair: Option<(&PrivateKey, &PublicKey)>,
csprng: &mut impl CryptoRng,
) -> Result<(SharedSecret<Self>, EncappedKey), HpkeError> {
assert!(
sender_id_keypair.is_none(),
"X-Wing doesn't support authenticated encapsulation. Use Base or Psk operation mode."
);
let mut randomness = [0u8; XWING_ENCAP_RANDOMNESS_SIZE];
csprng.fill_bytes(&mut randomness);
let res = Self::encap_deterministic(pk_recip, &randomness);
randomness.zeroize();
res
}
}
#[cfg(all(test, feature = "kat"))]
impl crate::kat_tests::TestableKem for XWing {
type EphemeralKey = core::convert::Infallible;
fn encap_with_eph(
_pk_recip: &Self::PublicKey,
_sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>,
_sk_eph: Self::EphemeralKey,
) -> Result<(SharedSecret<Self>, Self::EncappedKey), HpkeError> {
unimplemented!()
}
fn encap_det(
pk_recip: &Self::PublicKey,
sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>,
randomness: &[u8],
) -> Result<(SharedSecret<Self>, Self::EncappedKey), HpkeError> {
assert!(
sender_id_keypair.is_none(),
"X-Wing does not support authenticated encapsulation"
);
XWing::encap_deterministic(pk_recip, randomness.try_into().unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip() {
let mut csprng = rand::rng();
let (sk, pk) = XWing::gen_keypair_with_rng(&mut csprng);
let (shared_secret, encapped_key) =
XWing::encap_with_rng(&pk, None, &mut csprng).expect("encapsulation failed");
let shared_secret_recipient =
XWing::decap(&sk, None, &EncappedKey(encapped_key.0)).expect("decapsulation failed");
assert_eq!(shared_secret.0, shared_secret_recipient.0);
}
}