From bb6b56d45ba1c30aaccb2c093e7a11265c029155 Mon Sep 17 00:00:00 2001 From: Ahmed <> Date: Mon, 8 Jul 2024 21:45:42 +0200 Subject: [PATCH] ntru: Add nist tests for streamlined ntru Signed-off-by: Ahmed <> --- Cargo.lock | 46 ++++++++- ntru/Cargo.toml | 3 + ntru/tests/nist.rs | 236 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 ntru/tests/nist.rs diff --git a/Cargo.lock b/Cargo.lock index 635e87e..8b7f8f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -83,6 +94,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.1" @@ -129,7 +150,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -150,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -276,6 +297,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -296,6 +326,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -368,7 +407,10 @@ dependencies = [ name = "ntru" version = "0.1.0" dependencies = [ + "aes", + "hex", "hybrid-array 0.2.0-rc.9", + "itertools 0.13.0", "rand_core", "rayon", "sha2", diff --git a/ntru/Cargo.toml b/ntru/Cargo.toml index 1f3584f..5bde6b6 100644 --- a/ntru/Cargo.toml +++ b/ntru/Cargo.toml @@ -9,4 +9,7 @@ rand_core = "0.6.4" sha2 = "0.10.8" [dev-dependencies] +aes="0.8.4" +hex = "0.4.3" +itertools = "0.13.0" rayon="1.10.0" diff --git a/ntru/tests/nist.rs b/ntru/tests/nist.rs new file mode 100644 index 0000000..4a7707e --- /dev/null +++ b/ntru/tests/nist.rs @@ -0,0 +1,236 @@ +//! Checking that our NTRU-Prime is generating same output when compared to nist submission + +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +use aes::{ + cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, + Aes256, +}; +use hybrid_array::sizes::{U1013, U1277, U16, U32, U653, U761, U857, U953}; +use itertools::{izip, Itertools}; +use ntru::{ + encoded::AsymEnc, + hashes::HashOps, + kem::{decap, encap, key_gen}, + params::Streamlined, +}; +use rand_core::{CryptoRng, RngCore, SeedableRng}; + +fn aes256_ecb( + key: &GenericArray, + crt: &GenericArray, + buffer: &mut GenericArray, +) { + let cipher = Aes256::new(key); + cipher.encrypt_block_b2b(crt, buffer); +} +struct AesDrbg { + key: GenericArray, + v: GenericArray, +} +impl AesDrbg { + fn update(&mut self, seed_material: Option<&[u8; 48]>) { + let mut tmp: [GenericArray; 3] = Default::default(); + for i in 0..3 { + for j in (1..=15).rev() { + if self.v[j] == 0xff { + self.v[j] = 0x00; + } else { + self.v[j] += 1; + break; + } + } + aes256_ecb(&self.key, &self.v, &mut tmp[i]); + } + if let Some(seed) = seed_material { + for i in 0..48 { + tmp[i / 16][i % 16] ^= seed[i]; + } + } + self.key[..16].copy_from_slice(&tmp[0]); + self.key[16..].copy_from_slice(&tmp[1]); + self.v.copy_from_slice(&tmp[2]); + } +} +impl CryptoRng for AesDrbg {} + +struct U8L48([u8; 48]); + +impl Default for U8L48 { + fn default() -> Self { + U8L48([0; 48]) + } +} + +impl AsMut<[u8]> for U8L48 { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} +impl AsRef<[u8]> for U8L48 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl SeedableRng for AesDrbg { + type Seed = U8L48; + + fn from_seed(seed: Self::Seed) -> Self { + let entropy_input = seed.0; + let mut drbg = AesDrbg { + key: GenericArray::default(), + v: GenericArray::default(), + }; + drbg.update(Some(&entropy_input)); + drbg + } +} + +impl RngCore for AesDrbg { + fn next_u32(&mut self) -> u32 { + let mut bytes = [0u8; 4]; + self.fill_bytes(&mut bytes); + u32::from_le_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + unimplemented!() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + let mut block = GenericArray::::default(); + let mut i = 0; + let mut xlen = dest.len(); + while xlen > 0 { + for j in (1..=15).rev() { + if self.v[j] == 0xff { + self.v[j] = 0x00; + } else { + self.v[j] += 1; + break; + } + } + aes256_ecb(&self.key, &self.v, &mut block); + if xlen > 15 { + dest[i..i + 16].copy_from_slice(&block); + i += 16; + xlen -= 16; + } else { + dest[i..i + xlen].copy_from_slice(&block[..xlen]); + xlen = 0; + } + } + self.update(None) + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +fn seed_builder(max: usize) -> Vec { + let mut seeds = Vec::with_capacity(100); + let mut entropy = [0u8; 48]; + for i in 0u8..48 { + entropy[i as usize] = i; + } + let mut rng = AesDrbg::from_seed(U8L48(entropy)); + for _ in 0..max { + let mut s = U8L48::default(); + rng.fill_bytes(&mut s.0); + seeds.push(s) + } + seeds +} + +struct TestEntry { + seed: Vec, + pk: Vec, + sk: Vec, + ct: Vec, + ss: Vec, +} + +impl TestEntry { + fn from_file(path: &str) -> Vec { + let file = File::open(path).unwrap(); + let mut ret = Vec::with_capacity(100); + for mut lines in &BufReader::new(file) + .lines() + .flatten() + .filter(|x| !(x.is_empty() || x.starts_with('#'))) + .chunks(6) + { + lines.next(); // we ignore the count line + let seed = hex::decode(lines.next().unwrap().split(" ").last().unwrap()).unwrap(); + let pk = hex::decode(lines.next().unwrap().split(" ").last().unwrap()).unwrap(); + let sk = hex::decode(lines.next().unwrap().split(" ").last().unwrap()).unwrap(); + let ct = hex::decode(lines.next().unwrap().split(" ").last().unwrap()).unwrap(); + let ss = hex::decode(lines.next().unwrap().split(" ").last().unwrap()).unwrap(); + ret.push(TestEntry { + seed, + pk, + sk, + ct, + ss, + }); + } + assert_eq!(ret.len(), 100); + ret + } +} + +#[test] +fn test_rng() { + let seeds = seed_builder(100); + let tests = TestEntry::from_file("test_data/ntrulpr653.rsp"); + for i in 0..100 { + assert_eq!(seeds[i].as_ref(), &tests[i].seed) + } +} + +fn test_config(config: &str) { + let seeds = seed_builder(100); + let path = format!("test_data/{config}.rsp"); + let tests = TestEntry::from_file(&path); + for (seed, test) in izip!(seeds, tests) { + let mut rng = AesDrbg::from_seed(seed); + let (sk, pk) = key_gen::(&mut rng); + assert_eq!(&pk.0 as &[u8], &test.pk); + assert_eq!(sk.to_bytes(), test.sk); + let (ct, ss) = encap(&mut rng, &pk); + assert_eq!(ct.to_bytes(), test.ct); + assert_eq!(&ss as &[u8], &test.ss); + let ss2: [u8; 32] = decap(&ct, &sk); + assert_eq!(&ss2 as &[u8], &test.ss); + } +} + +#[test] +fn test_sntrup1013() { + test_config::>("sntrup1013"); +} +#[test] +fn test_sntrup1277() { + test_config::>("sntrup1277"); +} +#[test] +fn test_sntrup653() { + test_config::>("sntrup653"); +} +#[test] +fn test_sntrup761() { + test_config::>("sntrup761"); +} +#[test] +fn test_sntrup857() { + test_config::>("sntrup857"); +} +#[test] +fn test_sntrup953() { + test_config::>("sntrup953"); +}