From e5cf60f9da1db1679bbf47e02124ed6fefe5007c Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 8 Jul 2026 17:57:27 -0600 Subject: [PATCH] x-wing: factor KATs into an integration test That way we don't need to conditionally gate them on `std` or have gating of `no_std` whatsoever. --- x-wing/src/lib.rs | 104 +----------------------- x-wing/tests/kats.rs | 85 +++++++++++++++++++ x-wing/{src => tests}/test-vectors.json | 0 3 files changed, 87 insertions(+), 102 deletions(-) create mode 100644 x-wing/tests/kats.rs rename x-wing/{src => tests}/test-vectors.json (100%) diff --git a/x-wing/src/lib.rs b/x-wing/src/lib.rs index 9cf7b72d..1620deb4 100644 --- a/x-wing/src/lib.rs +++ b/x-wing/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), no_std)] +#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc( @@ -363,109 +363,9 @@ fn read_from(reader: &mut Shake256Reader) -> [u8; N] { #[cfg(test)] mod tests { - use crate::{Kem, XWingKem}; - use core::convert::Infallible; - use getrandom::SysRng; - use ml_kem::array::Array; - use rand_core::{TryCryptoRng, TryRng, UnwrapErr, utils}; - use serde::Deserialize; - + #[cfg(feature = "getrandom")] use super::*; - pub(crate) struct SeedRng { - pub(crate) seed: Vec, - } - - impl SeedRng { - fn new(seed: Vec) -> SeedRng { - SeedRng { seed } - } - } - - impl TryRng for SeedRng { - type Error = Infallible; - - fn try_next_u32(&mut self) -> Result { - utils::next_word_via_fill(self) - } - - fn try_next_u64(&mut self) -> Result { - utils::next_word_via_fill(self) - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { - dest.copy_from_slice(&self.seed[0..dest.len()]); - self.seed.drain(0..dest.len()); - Ok(()) - } - } - - #[derive(Deserialize)] - struct TestVector { - #[serde(deserialize_with = "hex::serde::deserialize")] - seed: Vec, - - #[serde(deserialize_with = "hex::serde::deserialize")] - eseed: Vec, - - #[serde(deserialize_with = "hex::serde::deserialize")] - ss: [u8; 32], - - #[serde(deserialize_with = "hex::serde::deserialize")] - sk: [u8; 32], - - #[serde(deserialize_with = "hex::serde::deserialize")] - pk: Vec, //[u8; PUBLIC_KEY_SIZE], - - #[serde(deserialize_with = "hex::serde::deserialize")] - ct: Vec, //[u8; 1120], - } - - impl TryCryptoRng for SeedRng {} - - /// Test with test vectors from: - #[test] - fn rfc_test_vectors() { - let test_vectors = - serde_json::from_str::>(include_str!("test-vectors.json")).unwrap(); - - for test_vector in test_vectors { - run_test(test_vector); - } - } - - fn run_test(test_vector: TestVector) { - let mut seed = SeedRng::new(test_vector.seed); - let (sk, pk) = XWingKem::generate_keypair_from_rng(&mut seed); - - assert_eq!(sk.as_bytes(), &test_vector.sk); - assert_eq!(&*pk.to_bytes(), test_vector.pk.as_slice()); - - let mut eseed = SeedRng::new(test_vector.eseed); - let (ct, ss) = pk.encapsulate_with_rng(&mut eseed); - - assert_eq!(ss, test_vector.ss); - assert_eq!(&*ct, test_vector.ct.as_slice()); - - let ss = sk.decapsulate(&ct); - assert_eq!(ss, test_vector.ss); - } - - #[test] - fn ciphertext_serialize() { - let mut rng = UnwrapErr(SysRng); - - let ct_a = CiphertextMessage { - ct_m: Array::generate_from_rng(&mut rng), - ct_x: <[u8; 32]>::generate_from_rng(&mut rng).into(), - }; - - let bytes = ct_a.to_bytes(); - let ct_b = CiphertextMessage::from(&bytes); - - assert!(ct_a == ct_b); - } - #[test] #[cfg(feature = "getrandom")] fn key_serialize() { diff --git a/x-wing/tests/kats.rs b/x-wing/tests/kats.rs new file mode 100644 index 00000000..9129b8b4 --- /dev/null +++ b/x-wing/tests/kats.rs @@ -0,0 +1,85 @@ +//! X-Wing Known Answer Tests (KATs): test vectors + +use core::convert::Infallible; +use rand_core::{TryCryptoRng, TryRng, utils}; +use serde::Deserialize; +use x_wing::{Decapsulate, Encapsulate, Kem, KeyExport, XWingKem}; + +#[derive(Deserialize)] +struct TestVector { + #[serde(deserialize_with = "hex::serde::deserialize")] + seed: Vec, + + #[serde(deserialize_with = "hex::serde::deserialize")] + eseed: Vec, + + #[serde(deserialize_with = "hex::serde::deserialize")] + ss: [u8; 32], + + #[serde(deserialize_with = "hex::serde::deserialize")] + sk: [u8; 32], + + #[serde(deserialize_with = "hex::serde::deserialize")] + pk: Vec, //[u8; PUBLIC_KEY_SIZE], + + #[serde(deserialize_with = "hex::serde::deserialize")] + ct: Vec, //[u8; 1120], +} + +pub(crate) struct SeedRng { + pub(crate) seed: Vec, +} + +impl SeedRng { + fn new(seed: Vec) -> SeedRng { + SeedRng { seed } + } +} + +impl TryRng for SeedRng { + type Error = Infallible; + + fn try_next_u32(&mut self) -> Result { + utils::next_word_via_fill(self) + } + + fn try_next_u64(&mut self) -> Result { + utils::next_word_via_fill(self) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + dest.copy_from_slice(&self.seed[0..dest.len()]); + self.seed.drain(0..dest.len()); + Ok(()) + } +} + +impl TryCryptoRng for SeedRng {} + +/// Test with test vectors from: +#[test] +fn rfc_test_vectors() { + let test_vectors = + serde_json::from_str::>(include_str!("test-vectors.json")).unwrap(); + + for test_vector in test_vectors { + run_test(test_vector); + } +} + +fn run_test(test_vector: TestVector) { + let mut seed = SeedRng::new(test_vector.seed); + let (sk, pk) = XWingKem::generate_keypair_from_rng(&mut seed); + + assert_eq!(sk.as_bytes(), &test_vector.sk); + assert_eq!(&*pk.to_bytes(), test_vector.pk.as_slice()); + + let mut eseed = SeedRng::new(test_vector.eseed); + let (ct, ss) = pk.encapsulate_with_rng(&mut eseed); + + assert_eq!(ss, test_vector.ss); + assert_eq!(&*ct, test_vector.ct.as_slice()); + + let ss = sk.decapsulate(&ct); + assert_eq!(ss, test_vector.ss); +} diff --git a/x-wing/src/test-vectors.json b/x-wing/tests/test-vectors.json similarity index 100% rename from x-wing/src/test-vectors.json rename to x-wing/tests/test-vectors.json