Skip to content

Commit b73d39c

Browse files
author
Ahmed
committed
ntru: Add necessary algebra
Signed-off-by: Ahmed <>
1 parent 7bc3334 commit b73d39c

File tree

8 files changed

+521
-2
lines changed

8 files changed

+521
-2
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ntru/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ edition = "2021"
77
hybrid-array = { path="../../hybrid-array", features = ["extra-sizes"] }
88

99
[dev-dependencies]
10+
rayon="1.10.0"

ntru/src/algebra/f3.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//! arithmetic mod 3
2+
3+
use core::ops::Deref;
4+
5+
use crate::const_time::i32_mod_u14;
6+
7+
use super::fq::Fq;
8+
9+
/// always represented as -1,0,1
10+
#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
11+
pub struct Small(i8);
12+
13+
impl Small {
14+
pub const ZERO: Small = Small(0);
15+
pub const ONE: Small = Small(1);
16+
pub const MONE: Small = Small(-1);
17+
18+
pub(super) fn new_i32(n: i32) -> Self {
19+
debug_assert!(n < 2);
20+
debug_assert!(n > -2);
21+
Small(n as i8)
22+
}
23+
pub(super) fn new_i8(n: i8) -> Self {
24+
debug_assert!(n < 2);
25+
debug_assert!(n > -2);
26+
Small(n)
27+
}
28+
29+
#[must_use]
30+
pub const fn freeze(x: i16) -> Self {
31+
Small((i32_mod_u14((x as i32) + 1, 3).wrapping_sub(1)) as i8)
32+
}
33+
}
34+
35+
/// the benefit is from outside, anyone can access the inner value as number,
36+
/// but no one can modify it without refreezing
37+
impl Deref for Small {
38+
type Target = i8;
39+
40+
fn deref(&self) -> &Self::Target {
41+
&self.0
42+
}
43+
}
44+
45+
impl<Q> From<Fq<Q>> for Small {
46+
fn from(value: Fq<Q>) -> Self {
47+
Small::freeze(*value)
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod test {
53+
use super::Small;
54+
fn naive_freeze(x: i16) -> i8 {
55+
// returns values in the set [-2, 2]
56+
let res = (x % 3) as i8;
57+
if res > 1 {
58+
return res - 3;
59+
}
60+
if res < -1 {
61+
return res + 3;
62+
}
63+
res
64+
}
65+
#[test]
66+
fn test_freeze() {
67+
for i in i16::MIN..i16::MAX {
68+
assert_eq!(*Small::freeze(i), naive_freeze(i));
69+
}
70+
}
71+
}

ntru/src/algebra/fq.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//! arithmetic mod q
2+
3+
use crate::{
4+
const_time::i32_mod_u14,
5+
params::{NtruCommon, NtruLRPrime},
6+
};
7+
use core::marker::PhantomData;
8+
use core::ops::Deref;
9+
10+
/// always represented as `-F::Q12...F::Q12`
11+
#[derive(Copy, Clone)]
12+
pub struct Inner<Params> {
13+
inner: i16,
14+
marker: PhantomData<Params>,
15+
}
16+
17+
impl<P> Default for Inner<P> {
18+
fn default() -> Self {
19+
Self {
20+
inner: 0,
21+
marker: PhantomData,
22+
}
23+
}
24+
}
25+
/// we need this type for the following reason, there is an expressivity
26+
/// problem, that is `FqInner<T>` implements only `Clone` and `Copy` if
27+
/// `T: Clone + Copy`. In this case, we do not require `T: Clone + Copy`
28+
/// So to bypass this we can:
29+
/// A- manually implment Clone + Copy
30+
/// B - Add Clone+Copy a trait bounds for T
31+
/// C - This trick which is saying that we use static reference to T which is always Clone + Copy
32+
/// D - Use third party crates like derivatives.
33+
pub type Fq<Params> = Inner<&'static Params>;
34+
35+
/// the benefit is from outside, anyone can access the inner value as number,
36+
/// but no one can modify it without refreezing
37+
impl<Params> Deref for Fq<Params> {
38+
type Target = i16;
39+
40+
fn deref(&self) -> &Self::Target {
41+
&self.inner
42+
}
43+
}
44+
// TODO should we have `T: Clone + Copy` or should we specify
45+
// trait bounds for the derive (either by manual
46+
// implementation or via derivative)
47+
impl<Params: NtruCommon> Fq<Params> {
48+
const Q12: u16 = ((Params::Q - 1) / 2);
49+
pub(super) fn new_i32(n: i32) -> Self {
50+
debug_assert!(n < Self::Q12 as i32);
51+
debug_assert!(n > -(Self::Q12 as i32));
52+
Fq {
53+
inner: n as i16,
54+
marker: PhantomData,
55+
}
56+
}
57+
pub(super) fn new_i16(n: i16) -> Self {
58+
debug_assert!(n < Self::Q12 as i16);
59+
debug_assert!(n > -(Self::Q12 as i16));
60+
Fq {
61+
inner: n,
62+
marker: PhantomData,
63+
}
64+
}
65+
66+
pub(super) fn new_i8(n: i8) -> Self {
67+
let n = n as i16;
68+
debug_assert!(n < Self::Q12 as i16);
69+
debug_assert!(n > -(Self::Q12 as i16));
70+
Fq {
71+
inner: n,
72+
marker: PhantomData,
73+
}
74+
}
75+
76+
/// x must not be close to top int32
77+
#[must_use]
78+
pub const fn freeze(x: i32) -> Self {
79+
debug_assert!(x <= i32::MAX - Self::Q12 as i32);
80+
Fq {
81+
inner: i32_mod_u14(x + Self::Q12 as i32, Params::Q).wrapping_sub(Self::Q12) as i16,
82+
marker: PhantomData,
83+
}
84+
}
85+
/// caclucates the multiplicative inverse of a1
86+
/// a1 must not be zero
87+
#[must_use]
88+
pub const fn recip(a1: Self) -> Self {
89+
debug_assert!(a1.inner != 0);
90+
let mut i = 1;
91+
let mut ai = a1;
92+
while i < Params::Q - 2 {
93+
// we have to use `a1.0` instead of deref to maintian
94+
// the const status of the function
95+
ai = Fq::freeze(a1.inner as i32 * ai.inner as i32);
96+
i += 1;
97+
}
98+
ai
99+
}
100+
}
101+
102+
///TODO tests for both funtions
103+
impl<Params: NtruLRPrime + NtruCommon> Fq<Params> {
104+
#[must_use]
105+
pub const fn top(self) -> i8 {
106+
((Params::TAU1 * (self.inner + Params::TAU0) as i32 + 16384) >> 15) as i8
107+
}
108+
#[must_use]
109+
pub const fn right(t: i8) -> Self {
110+
Fq::freeze(Params::TAU3 * t as i32 - Params::TAU2)
111+
}
112+
}
113+
114+
#[cfg(test)]
115+
mod test {
116+
use super::Fq;
117+
use crate::params::*;
118+
use rayon::prelude::*;
119+
use std::io::{stdout, Write};
120+
121+
fn naive_freeze(x: i32, q: u16) -> i16 {
122+
let res = (x % (q as i32)) as i16;
123+
if res > ((q as i16 - 1) / 2) {
124+
return res - q as i16;
125+
}
126+
if res < -((q as i16 - 1) / 2) {
127+
return res + q as i16;
128+
}
129+
res
130+
}
131+
#[test]
132+
#[ignore = "Expected to take ~ 1 hour to finish on single core"]
133+
fn test_fq_freezer() {
134+
// if i is close to i32::Max we overflow and crash
135+
// we also need to chunk things a bit
136+
(i32::MIN..i32::MAX - S1277::Q as i32)
137+
.into_par_iter()
138+
.chunks(0xffffff)
139+
.for_each(|chunk| {
140+
print!(".");
141+
stdout().flush().unwrap();
142+
for i in chunk {
143+
// all viable Q values from section 3.4 of NTRU NIST submission
144+
assert_eq!(*Fq::<S653>::freeze(i), naive_freeze(i, S653::Q));
145+
assert_eq!(*Fq::<S761>::freeze(i), naive_freeze(i, S761::Q));
146+
assert_eq!(*Fq::<S857>::freeze(i), naive_freeze(i, S857::Q));
147+
assert_eq!(*Fq::<S953>::freeze(i), naive_freeze(i, S953::Q));
148+
assert_eq!(*Fq::<S1013>::freeze(i), naive_freeze(i, S1013::Q));
149+
assert_eq!(*Fq::<S1277>::freeze(i), naive_freeze(i, S1277::Q));
150+
}
151+
})
152+
}
153+
#[test]
154+
fn test_f_s653_recip() {
155+
// note that zero has no recip, so we skip zero
156+
for i in (-(Fq::<S653>::Q12 as i32)..0).chain(1..Fq::<S653>::Q12 as i32) {
157+
assert_eq!(
158+
*Fq::<S653>::freeze(i * *Fq::<S653>::recip(Fq::<S653>::freeze(i)) as i32),
159+
1
160+
)
161+
}
162+
}
163+
#[test]
164+
fn test_f_s761_recip() {
165+
// note that zero has no recip, so we skip zero
166+
for i in (-(Fq::<S761>::Q12 as i32)..0).chain(1..Fq::<S761>::Q12 as i32) {
167+
assert_eq!(
168+
*Fq::<S761>::freeze(i * *Fq::<S761>::recip(Fq::<S761>::freeze(i)) as i32),
169+
1
170+
)
171+
}
172+
}
173+
#[test]
174+
fn test_f_s857_recip() {
175+
// note that zero has no recip, so we skip zero
176+
for i in (-(Fq::<S857>::Q12 as i32)..0).chain(1..Fq::<S857>::Q12 as i32) {
177+
assert_eq!(
178+
*Fq::<S857>::freeze(i * *Fq::<S857>::recip(Fq::<S857>::freeze(i)) as i32),
179+
1
180+
)
181+
}
182+
}
183+
#[test]
184+
fn test_f_s953_recip() {
185+
// note that zero has no recip, so we skip zero
186+
for i in (-(Fq::<S953>::Q12 as i32)..0).chain(1..Fq::<S953>::Q12 as i32) {
187+
assert_eq!(
188+
*Fq::<S953>::freeze(i * *Fq::<S953>::recip(Fq::<S953>::freeze(i)) as i32),
189+
1
190+
)
191+
}
192+
}
193+
#[test]
194+
fn test_f_s1013_recip() {
195+
// note that zero has no recip, so we skip zero
196+
for i in (-(Fq::<S1013>::Q12 as i32)..0).chain(1..Fq::<S1013>::Q12 as i32) {
197+
assert_eq!(
198+
*Fq::<S1013>::freeze(i * *Fq::<S1013>::recip(Fq::<S1013>::freeze(i)) as i32),
199+
1
200+
)
201+
}
202+
}
203+
#[test]
204+
fn test_f_s1277_recip() {
205+
// note that zero has no recip, so we skip zero
206+
for i in (-(Fq::<S1277>::Q12 as i32)..0).chain(1..Fq::<S1277>::Q12 as i32) {
207+
assert_eq!(
208+
*Fq::<S1277>::freeze(i * *Fq::<S1277>::recip(Fq::<S1277>::freeze(i)) as i32),
209+
1
210+
)
211+
}
212+
}
213+
}

ntru/src/algebra/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod f3;
2+
pub mod fq;
3+
pub mod r3;
4+
pub mod rq;

0 commit comments

Comments
 (0)