From b73d39c6e8111138cf5203b611bf5bcb81cd3be6 Mon Sep 17 00:00:00 2001 From: Ahmed <> Date: Sat, 29 Jun 2024 12:30:03 +0200 Subject: [PATCH] ntru: Add necessary algebra Signed-off-by: Ahmed <> --- Cargo.lock | 5 +- ntru/Cargo.toml | 1 + ntru/src/algebra/f3.rs | 71 ++++++++++++++ ntru/src/algebra/fq.rs | 213 ++++++++++++++++++++++++++++++++++++++++ ntru/src/algebra/mod.rs | 4 + ntru/src/algebra/r3.rs | 119 ++++++++++++++++++++++ ntru/src/algebra/rq.rs | 109 ++++++++++++++++++++ ntru/src/lib.rs | 1 + 8 files changed, 521 insertions(+), 2 deletions(-) create mode 100644 ntru/src/algebra/f3.rs create mode 100644 ntru/src/algebra/fq.rs create mode 100644 ntru/src/algebra/mod.rs create mode 100644 ntru/src/algebra/r3.rs create mode 100644 ntru/src/algebra/rq.rs diff --git a/Cargo.lock b/Cargo.lock index 84e00f5..424e7a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,6 +369,7 @@ name = "ntru" version = "0.1.0" dependencies = [ "hybrid-array 0.2.0-rc.9", + "rayon", ] [[package]] @@ -506,9 +507,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", diff --git a/ntru/Cargo.toml b/ntru/Cargo.toml index 91747a3..0a48eaf 100644 --- a/ntru/Cargo.toml +++ b/ntru/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" hybrid-array = { path="../../hybrid-array", features = ["extra-sizes"] } [dev-dependencies] +rayon="1.10.0" \ No newline at end of file diff --git a/ntru/src/algebra/f3.rs b/ntru/src/algebra/f3.rs new file mode 100644 index 0000000..7bc18c9 --- /dev/null +++ b/ntru/src/algebra/f3.rs @@ -0,0 +1,71 @@ +//! arithmetic mod 3 + +use core::ops::Deref; + +use crate::const_time::i32_mod_u14; + +use super::fq::Fq; + +/// always represented as -1,0,1 +#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)] +pub struct Small(i8); + +impl Small { + pub const ZERO: Small = Small(0); + pub const ONE: Small = Small(1); + pub const MONE: Small = Small(-1); + + pub(super) fn new_i32(n: i32) -> Self { + debug_assert!(n < 2); + debug_assert!(n > -2); + Small(n as i8) + } + pub(super) fn new_i8(n: i8) -> Self { + debug_assert!(n < 2); + debug_assert!(n > -2); + Small(n) + } + + #[must_use] + pub const fn freeze(x: i16) -> Self { + Small((i32_mod_u14((x as i32) + 1, 3).wrapping_sub(1)) as i8) + } +} + +/// the benefit is from outside, anyone can access the inner value as number, +/// but no one can modify it without refreezing +impl Deref for Small { + type Target = i8; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Small { + fn from(value: Fq) -> Self { + Small::freeze(*value) + } +} + +#[cfg(test)] +mod test { + use super::Small; + fn naive_freeze(x: i16) -> i8 { + // returns values in the set [-2, 2] + let res = (x % 3) as i8; + if res > 1 { + return res - 3; + } + if res < -1 { + return res + 3; + } + res + } + #[test] + fn test_freeze() { + for i in i16::MIN..i16::MAX { + assert_eq!(*Small::freeze(i), naive_freeze(i)); + } + } +} diff --git a/ntru/src/algebra/fq.rs b/ntru/src/algebra/fq.rs new file mode 100644 index 0000000..8221f46 --- /dev/null +++ b/ntru/src/algebra/fq.rs @@ -0,0 +1,213 @@ +//! arithmetic mod q + +use crate::{ + const_time::i32_mod_u14, + params::{NtruCommon, NtruLRPrime}, +}; +use core::marker::PhantomData; +use core::ops::Deref; + +/// always represented as `-F::Q12...F::Q12` +#[derive(Copy, Clone)] +pub struct Inner { + inner: i16, + marker: PhantomData, +} + +impl

Default for Inner

{ + fn default() -> Self { + Self { + inner: 0, + marker: PhantomData, + } + } +} +/// we need this type for the following reason, there is an expressivity +/// problem, that is `FqInner` implements only `Clone` and `Copy` if +/// `T: Clone + Copy`. In this case, we do not require `T: Clone + Copy` +/// So to bypass this we can: +/// A- manually implment Clone + Copy +/// B - Add Clone+Copy a trait bounds for T +/// C - This trick which is saying that we use static reference to T which is always Clone + Copy +/// D - Use third party crates like derivatives. +pub type Fq = Inner<&'static Params>; + +/// the benefit is from outside, anyone can access the inner value as number, +/// but no one can modify it without refreezing +impl Deref for Fq { + type Target = i16; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} +// TODO should we have `T: Clone + Copy` or should we specify +// trait bounds for the derive (either by manual +// implementation or via derivative) +impl Fq { + const Q12: u16 = ((Params::Q - 1) / 2); + pub(super) fn new_i32(n: i32) -> Self { + debug_assert!(n < Self::Q12 as i32); + debug_assert!(n > -(Self::Q12 as i32)); + Fq { + inner: n as i16, + marker: PhantomData, + } + } + pub(super) fn new_i16(n: i16) -> Self { + debug_assert!(n < Self::Q12 as i16); + debug_assert!(n > -(Self::Q12 as i16)); + Fq { + inner: n, + marker: PhantomData, + } + } + + pub(super) fn new_i8(n: i8) -> Self { + let n = n as i16; + debug_assert!(n < Self::Q12 as i16); + debug_assert!(n > -(Self::Q12 as i16)); + Fq { + inner: n, + marker: PhantomData, + } + } + + /// x must not be close to top int32 + #[must_use] + pub const fn freeze(x: i32) -> Self { + debug_assert!(x <= i32::MAX - Self::Q12 as i32); + Fq { + inner: i32_mod_u14(x + Self::Q12 as i32, Params::Q).wrapping_sub(Self::Q12) as i16, + marker: PhantomData, + } + } + /// caclucates the multiplicative inverse of a1 + /// a1 must not be zero + #[must_use] + pub const fn recip(a1: Self) -> Self { + debug_assert!(a1.inner != 0); + let mut i = 1; + let mut ai = a1; + while i < Params::Q - 2 { + // we have to use `a1.0` instead of deref to maintian + // the const status of the function + ai = Fq::freeze(a1.inner as i32 * ai.inner as i32); + i += 1; + } + ai + } +} + +///TODO tests for both funtions +impl Fq { + #[must_use] + pub const fn top(self) -> i8 { + ((Params::TAU1 * (self.inner + Params::TAU0) as i32 + 16384) >> 15) as i8 + } + #[must_use] + pub const fn right(t: i8) -> Self { + Fq::freeze(Params::TAU3 * t as i32 - Params::TAU2) + } +} + +#[cfg(test)] +mod test { + use super::Fq; + use crate::params::*; + use rayon::prelude::*; + use std::io::{stdout, Write}; + + fn naive_freeze(x: i32, q: u16) -> i16 { + let res = (x % (q as i32)) as i16; + if res > ((q as i16 - 1) / 2) { + return res - q as i16; + } + if res < -((q as i16 - 1) / 2) { + return res + q as i16; + } + res + } + #[test] + #[ignore = "Expected to take ~ 1 hour to finish on single core"] + fn test_fq_freezer() { + // if i is close to i32::Max we overflow and crash + // we also need to chunk things a bit + (i32::MIN..i32::MAX - S1277::Q as i32) + .into_par_iter() + .chunks(0xffffff) + .for_each(|chunk| { + print!("."); + stdout().flush().unwrap(); + for i in chunk { + // all viable Q values from section 3.4 of NTRU NIST submission + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S653::Q)); + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S761::Q)); + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S857::Q)); + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S953::Q)); + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S1013::Q)); + assert_eq!(*Fq::::freeze(i), naive_freeze(i, S1277::Q)); + } + }) + } + #[test] + fn test_f_s653_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } + #[test] + fn test_f_s761_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } + #[test] + fn test_f_s857_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } + #[test] + fn test_f_s953_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } + #[test] + fn test_f_s1013_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } + #[test] + fn test_f_s1277_recip() { + // note that zero has no recip, so we skip zero + for i in (-(Fq::::Q12 as i32)..0).chain(1..Fq::::Q12 as i32) { + assert_eq!( + *Fq::::freeze(i * *Fq::::recip(Fq::::freeze(i)) as i32), + 1 + ) + } + } +} diff --git a/ntru/src/algebra/mod.rs b/ntru/src/algebra/mod.rs new file mode 100644 index 0000000..ec922dc --- /dev/null +++ b/ntru/src/algebra/mod.rs @@ -0,0 +1,4 @@ +pub mod f3; +pub mod fq; +pub mod r3; +pub mod rq; diff --git a/ntru/src/algebra/r3.rs b/ntru/src/algebra/r3.rs new file mode 100644 index 0000000..b9d2596 --- /dev/null +++ b/ntru/src/algebra/r3.rs @@ -0,0 +1,119 @@ +use super::{f3::Small, rq::Rq}; +use crate::{ + const_time::{crypto_sort_u32, i16_negative_mask, i16_nonzero_mask}, + params::NtruCommon, +}; +use hybrid_array::{typenum::Unsigned, Array}; + +pub struct R3(pub(super) Array); + +impl R3 { + pub fn weight_w_mask(&self) -> i32 { + let mut weight = 0i16; + for s in &self.0 { + weight += (**s & 1) as i16; + } + i16_nonzero_mask(weight - Params::W) + } + /// returns self * other in `R3` + /// # Panics + /// this function should naver panic + #[must_use] + pub fn mult(&self, other: &Self) -> Self { + let p = Params::P::to_usize(); + let mut fg = Array::::default(); + //TODO maybe map? + for i in 0..p { + let mut result = Small::default(); + for j in 0..=i { + result = Small::freeze(*result as i16 + *self.0[j] as i16 + *other.0[i - j] as i16); + } + fg[i] = result; + } + //TODO maybe map? + for i in p..p + p - 1 { + let mut result = Small::default(); + for j in i - p + 1..p { + result = Small::freeze(*result as i16 + *self.0[j] as i16 + *other.0[i - j] as i16); + } + fg[i] = result; + } + for i in (p..=p + p - 2).rev() { + fg[i - p] = Small::freeze(*fg[i - p] as i16 + *fg[i] as i16); + fg[i - p + 1] = Small::freeze(*fg[i - p + 1] as i16 + *fg[i] as i16); + } + Self(Array::try_from(&fg[..p]).unwrap()) + } + /// Returns Tuple of recip and 0 if recip succeeded or tuple of garbage filled R3 and -1 if recip failed; + pub fn recip(&self) -> (Self, i32) { + let mut f = Array::::default(); + let mut g = Array::::default(); + let mut v = Array::::default(); + let mut r = Array::::default(); + r[0] = Small::ONE; + f[0] = Small::ONE; + f[Params::P::USIZE - 1] = Small::MONE; + f[Params::P::USIZE] = Small::MONE; + for i in 0..Params::P::USIZE { + g[Params::P::USIZE - 1 - i] = self.0[i]; + } + let mut delta = 1; + for _ in 0..Params::PPM1::USIZE { + for i in (1..=Params::P::USIZE).rev() { + v[i] = v[i - 1]; + } + v[0] = Small::ZERO; + let sign = -*g[0] as i16 * *f[0] as i16; + let swap = i16_negative_mask(-delta as i16) * i16_nonzero_mask(*g[0] as i16); + delta ^= swap & (delta ^ -delta); + delta += 1; + for i in 0..Params::P1::USIZE { + let t = swap & (*f[i] ^ *g[i]) as i32; + f[i] = Small::new_i32(*f[i] as i32 ^ t); + g[i] = Small::new_i32(*g[i] as i32 ^ t); + let t = swap & (*v[i] ^ *r[i]) as i32; + v[i] = Small::new_i32(*v[i] as i32 ^ t); + r[i] = Small::new_i32(*r[i] as i32 ^ t); + } + //TODO merge the two loops? + for i in 0..Params::P1::USIZE { + g[i] = Small::freeze(*g[i] as i16 + sign * *f[i] as i16); + } + for i in 0..Params::P1::USIZE { + r[i] = Small::freeze(*r[i] as i16 + sign * *v[i] as i16); + } + for i in 0..Params::P::USIZE { + g[i] = g[i + 1]; + } + g[Params::P::USIZE] = Small::ZERO; + } + let sign = *f[0]; + let mut out = Array::::default(); + for i in 0..Params::P::USIZE { + out[i] = Small::new_i8(sign * *v[Params::P::USIZE - 1 - i]); + } + (R3(out), i16_nonzero_mask(delta as i16)) + } + /// sorting to generate short polynomial + pub fn short_from_list(data_in: &Array) -> Self { + let mut l = Array::::default(); + for i in 0..Params::W as usize { + l[i] = data_in[i] & (-2i32 as u32); + } + for i in Params::W as usize..Params::P::USIZE { + l[i] = (data_in[i] & (-3i32 as u32)) | 1; + } + crypto_sort_u32(&mut l); + let mut out = Array::::default(); + for i in 0..Params::P::USIZE { + out[i] = Small::new_i32(((l[i] & 3) - 1) as i32); + } + R3(out) + } +} + +impl From> for R3

{ + fn from(value: Rq

) -> Self { + Self(value.0.iter().map(|fq| Into::::into(*fq)).collect()) + } +} diff --git a/ntru/src/algebra/rq.rs b/ntru/src/algebra/rq.rs new file mode 100644 index 0000000..9e6e18e --- /dev/null +++ b/ntru/src/algebra/rq.rs @@ -0,0 +1,109 @@ +use super::{f3::Small, fq::Fq, r3::R3}; +use crate::{ + const_time::{i16_negative_mask, i16_nonzero_mask}, + params::NtruCommon, +}; +use hybrid_array::{typenum::Unsigned, Array}; + +pub struct Rq(pub(super) Array, Params::P>); + +impl Rq { + /// calculates self * other in the ring Rq + /// # Panics + /// This functions should never panic + #[must_use] + pub fn mult_r3(&self, other: &R3) -> Self { + let p = Params::P::USIZE; + let mut fg = Array::, Params::PPM1>::default(); + //TODO maybe map? + for i in 0..p { + let mut result = Fq::::default(); + for j in 0..=i { + result = Fq::freeze(*result as i32 + *self.0[j] as i32 + *other.0[i - j] as i32); + } + fg[i] = result; + } + //TODO maybe map? for perfomance + for i in p..p + p - 1 { + let mut result = Fq::::default(); + for j in i - p + 1..p { + result = Fq::freeze(*result as i32 + *self.0[j] as i32 + *other.0[i - j] as i32); + } + fg[i] = result; + } + for i in (p..=p + p - 2).rev() { + fg[i - p] = Fq::freeze(*fg[i - p] as i32 + *fg[i] as i32); + fg[i - p + 1] = Fq::freeze(*fg[i - p + 1] as i32 + *fg[i] as i32); + } + Self(Array::try_from(&fg[..p]).unwrap()) + } + /// returns 3*self in Rq + #[must_use] + pub fn mult3(&self) -> Self { + let mut res = Array::, Params::P>::default(); + for i in 0..Params::P::USIZE { + res[i] = Fq::freeze(*self.0[i] as i32 * 3); + } + Rq(res) + } + /// Returns Tuple of `1/3*data_in` and 0 if recip succeeded or tuple of garbage filled Fq and -1 if recip failed; + pub fn recip3(data_in: &R3) -> (Self, i32) { + let mut f = Array::, Params::P1>::default(); + let mut g = Array::, Params::P1>::default(); + let mut v = Array::, Params::P1>::default(); + let mut r = Array::, Params::P1>::default(); + r[0] = Fq::recip(Fq::new_i8(3)); + f[0] = Fq::new_i32(1); + f[Params::P::USIZE - 1] = Fq::new_i8(-1); + f[Params::P::USIZE] = Fq::new_i8(-1); + for i in 0..Params::P::USIZE { + g[Params::P::USIZE - 1 - i] = Fq::new_i8(*data_in.0[i]); + } + let mut delta = 1; + for _ in 0..Params::PPM1::USIZE { + for i in (1..=Params::P::USIZE).rev() { + v[i] = v[i - 1]; + } + v[0] = Fq::new_i8(0); + let swap = i16_negative_mask(-delta as i16) * i16_nonzero_mask(*g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + for i in 0..Params::P1::USIZE { + let t = swap & (*f[i] ^ *g[i]) as i32; + f[i] = Fq::new_i32(*f[i] as i32 ^ t); + g[i] = Fq::new_i32(*g[i] as i32 ^ t); + let t = swap & (*v[i] ^ *r[i]) as i32; + v[i] = Fq::new_i32(*v[i] as i32 ^ t); + r[i] = Fq::new_i32(*r[i] as i32 ^ t); + } + let f0 = *f[0] as i32; + let g0 = *g[0] as i32; + //TODO merge the two loops? + for i in 0..Params::P1::USIZE { + g[i] = Fq::freeze(f0 * *g[i] as i32 - g0 * *f[i] as i32); + } + for i in 0..Params::P1::USIZE { + r[i] = Fq::freeze(f0 * *r[i] as i32 - g0 * *v[i] as i32); + } + for i in 0..Params::P::USIZE { + g[i] = g[i + 1]; + } + g[Params::P::USIZE] = Fq::new_i8(0); + } + let scale = *Fq::::recip(f[0]) as i32; + let mut out = Array::, Params::P>::default(); + for i in 0..Params::P::USIZE { + out[i] = Fq::freeze(scale * *v[Params::P::USIZE - 1 - i] as i32); + } + (Rq(out), i16_nonzero_mask(delta as i16)) + } + /// rounded polynomials mod q + #[must_use] + pub fn round(&self) -> Self { + let mut out = Array::, Params::P>::default(); + for i in 0..Params::P::USIZE { + out[i] = Fq::new_i16(*self.0[i] - *Small::freeze(*self.0[i]) as i16); + } + Rq(out) + } +} diff --git a/ntru/src/lib.rs b/ntru/src/lib.rs index d733348..d76117b 100644 --- a/ntru/src/lib.rs +++ b/ntru/src/lib.rs @@ -17,5 +17,6 @@ clippy::similar_names, )] +pub mod algebra; pub mod const_time; pub mod params;