Skip to content

Commit 7bc3334

Browse files
author
Ahmed
committed
ntru: Add constant time operations
1- constan time division 2- constant time `if x i< 0` & `if x == 0` 2- constant time bitonic sort Signed-off-by: Ahmed <>
1 parent a9b5814 commit 7bc3334

File tree

5 files changed

+275
-0
lines changed

5 files changed

+275
-0
lines changed

ntru/src/const_time/masks.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// return -1 if x!=0; else return 0
2+
#[must_use]
3+
pub const fn i16_nonzero_mask(x: i16) -> i32 {
4+
let u: u16 = x as u16;
5+
let mut v: u32 = u as u32;
6+
v = (!v).wrapping_add(1); // in reference code they did v = -v;
7+
v >>= 31;
8+
((!v).wrapping_add(1)) as i32
9+
}
10+
11+
/// return -1 if x<0; otherwise return 0
12+
#[must_use]
13+
pub const fn i16_negative_mask(x: i16) -> i32 {
14+
let mut u: u16 = x as u16;
15+
u >>= 15;
16+
-(u as i32)
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use super::*;
22+
23+
#[test]
24+
fn test_i16_nonzero_mask_exhaust() {
25+
assert_eq!(i16_nonzero_mask(0), 0);
26+
for i in 1..i16::MAX {
27+
assert_eq!(i16_nonzero_mask(i), -1);
28+
}
29+
for i in i16::MIN..-1 {
30+
assert_eq!(i16_nonzero_mask(i), -1);
31+
}
32+
}
33+
#[test]
34+
fn test_i16_negative_mask() {
35+
for i in 0..i16::MAX {
36+
assert_eq!(i16_negative_mask(i), 0);
37+
}
38+
for i in i16::MIN..-1 {
39+
assert_eq!(i16_negative_mask(i), -1);
40+
}
41+
}
42+
}

ntru/src/const_time/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod num;
2+
mod sort;
3+
mod masks;
4+
5+
pub use num::*;
6+
pub use sort::*;
7+
pub use masks::*;

ntru/src/const_time/num.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! best effort constant time divide
2+
3+
/// this function is optimized out at release mode
4+
const fn debug_checks(m: u32, v: u32, x: u32) {
5+
debug_assert!(m > 0);
6+
debug_assert!(m < 16384);
7+
debug_assert!(v * m <= u32::pow(2, 31));
8+
debug_assert!(u32::pow(2, 31) < v * m + m);
9+
let x = x as u64;
10+
let m = m as u64;
11+
let v = v as u64;
12+
debug_assert!(x * v * m <= u64::pow(2, 31) * x);
13+
debug_assert!(u64::pow(2, 31) * x <= x * v * (m) + x * (m - 1));
14+
}
15+
16+
/// constant time division
17+
/// this function returns quotient and remainder
18+
/// this function is problemetic according to original implmentation:
19+
/// CPU division instruction typically takes time depending on x.
20+
/// This software is designed to take time independent of x.
21+
/// Time still varies depending on m; user must ensure that m is constant.
22+
/// Time also varies on CPUs where multiplication is variable-time.
23+
/// There could be more CPU issues.
24+
/// There could also be compiler issues.
25+
#[must_use]
26+
pub const fn u32_divmod_u14(mut x: u32, m: u16) -> (u32, u16) {
27+
let m = m as u32;
28+
let mut v = 0x8000_0000_u32;
29+
v /= m;
30+
// the following asserts must be guaranteed by th caller of divmod
31+
debug_checks(m, v, x);
32+
let mut q = 0;
33+
let mut qpart = (((x as u64) * (v as u64)) >> 31) as u32;
34+
x -= qpart * m;
35+
q += qpart;
36+
debug_assert!(x < 49146);
37+
38+
qpart = (((x as u64) * (v as u64)) >> 31) as u32;
39+
x -= qpart * m;
40+
q += qpart;
41+
42+
x = x.wrapping_sub(m);
43+
q += 1;
44+
let mask = (!(x >> 31)).wrapping_add(1);
45+
x = x.wrapping_add(mask & m);
46+
q = q.wrapping_add(mask);
47+
debug_assert!(x <= m);
48+
(q, x as u16)
49+
}
50+
51+
#[must_use]
52+
pub const fn u32_div_u14(x: u32, m: u16) -> u32 {
53+
u32_divmod_u14(x, m).0
54+
}
55+
56+
#[must_use]
57+
pub const fn u32_mod_u14(x: u32, m: u16) -> u16 {
58+
u32_divmod_u14(x, m).1
59+
}
60+
61+
#[must_use]
62+
pub const fn i32_divmod_u14(x: i32, m: u16) -> (i32, u16) {
63+
let (mut uq, mut ur) = u32_divmod_u14(0x8000_0000_u32.wrapping_add(x as u32), m);
64+
let (uq2, ur2) = u32_divmod_u14(0x8000_0000, m);
65+
ur = ur.wrapping_sub(ur2);
66+
uq = uq.wrapping_sub(uq2);
67+
let mask = (!((ur >> 15) as u32)).wrapping_add(1);
68+
ur = ur.wrapping_add(mask as u16 & m);
69+
uq = uq.wrapping_add(mask);
70+
(uq as i32, ur)
71+
}
72+
73+
#[must_use]
74+
pub const fn i32_div_u14(x: i32, m: u16) -> i32 {
75+
i32_divmod_u14(x, m).0
76+
}
77+
78+
#[must_use]
79+
pub const fn i32_mod_u14(x: i32, m: u16) -> u16 {
80+
i32_divmod_u14(x, m).1
81+
}

ntru/src/const_time/sort.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! sorting with data independent timing behavior
2+
3+
/// returns a tuple of two sorted elements such
4+
/// that the first is always less or equal to the
5+
/// second
6+
fn minmax(xi: u32, yi: u32) -> (u32, u32) {
7+
let xy = xi ^ yi;
8+
let mut c = yi.wrapping_sub(xi);
9+
c ^= xy & (c ^ yi ^ 0x8000_0000);
10+
c >>= 31;
11+
c = (!c).wrapping_add(1);
12+
c &= xy;
13+
(xi ^ c, yi ^ c)
14+
}
15+
16+
/// This function sorts a list in place taking the same
17+
/// amount of time only depending on the size of the list.
18+
pub fn crypto_sort_u32(list: &mut [u32]) {
19+
let n = list.len() as i32;
20+
if n < 2 {
21+
return;
22+
}
23+
let mut top = 1i32;
24+
while top < n.wrapping_sub(top) {
25+
top += top;
26+
}
27+
let mut p = top;
28+
while p > 0 {
29+
for i in 0..n.wrapping_sub(p) {
30+
if i & p == 0 {
31+
let (xi, yi) = minmax(list[i as usize], list[(i + p) as usize]);
32+
list[i as usize] = xi;
33+
list[(i + p) as usize] = yi;
34+
}
35+
}
36+
let mut q = top;
37+
while q > p {
38+
for i in 0..n.wrapping_sub(q) {
39+
if i & p == 0 {
40+
let (xi, yi) = minmax(list[(i + p) as usize], list[(i + q) as usize]);
41+
list[(i + p) as usize] = xi;
42+
list[(i + q) as usize] = yi;
43+
}
44+
}
45+
q >>= 1;
46+
}
47+
p >>= 1;
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod test {
53+
use super::{crypto_sort_u32, minmax};
54+
#[test]
55+
fn test_minmax_zero() {
56+
assert_eq!(minmax(0, 0), (0, 0));
57+
}
58+
#[test]
59+
fn test_minmax_sorted() {
60+
assert_eq!(minmax(1, 2), (1, 2));
61+
}
62+
#[test]
63+
fn test_minmax_unsorted() {
64+
assert_eq!(minmax(2, 1), (1, 2));
65+
}
66+
#[test]
67+
fn test_minmax_identical() {
68+
assert_eq!(minmax(1, 1), (1, 1));
69+
}
70+
#[test]
71+
fn test_minmax_large_sorted() {
72+
assert_eq!(minmax(u32::MAX - 1, u32::MAX), (u32::MAX - 1, u32::MAX));
73+
}
74+
#[test]
75+
fn test_minmax_large_unsorted() {
76+
assert_eq!(minmax(u32::MAX, u32::MAX - 1), (u32::MAX - 1, u32::MAX));
77+
}
78+
#[test]
79+
fn test_minmax_large_identical() {
80+
assert_eq!(minmax(u32::MAX, u32::MAX), (u32::MAX, u32::MAX));
81+
}
82+
#[test]
83+
fn test_one_item_sort() {
84+
let mut v = vec![1];
85+
crypto_sort_u32(&mut v);
86+
assert_eq!(v, [1]);
87+
}
88+
#[test]
89+
fn test_minmax_large_small() {
90+
assert_eq!(minmax(u32::MAX - 1, 4), (4, u32::MAX - 1));
91+
}
92+
#[test]
93+
fn test_minmax_small_large() {
94+
assert_eq!(minmax(4, u32::MAX), (4, u32::MAX));
95+
}
96+
#[test]
97+
fn test_empty_item_sort() {
98+
let mut v = vec![];
99+
crypto_sort_u32(&mut v);
100+
assert_eq!(v, []);
101+
}
102+
103+
#[test]
104+
fn test_two_item_sort() {
105+
let mut v = vec![1, 2];
106+
crypto_sort_u32(&mut v);
107+
assert_eq!(v, [1, 2]);
108+
let mut v = vec![2, 1];
109+
crypto_sort_u32(&mut v);
110+
assert_eq!(v, [1, 2]);
111+
}
112+
#[test]
113+
fn test_sort_zeros() {
114+
let mut v = vec![0; 100];
115+
crypto_sort_u32(&mut v);
116+
assert_eq!(v, &[0; 100]);
117+
}
118+
#[test]
119+
fn test_sort_ordered() {
120+
let mut v: Vec<_> = (0..100).collect();
121+
crypto_sort_u32(&mut v);
122+
assert_eq!(v, (0..100).collect::<Vec<u32>>());
123+
}
124+
125+
#[test]
126+
fn test_sort_rev() {
127+
let mut v: Vec<_> = (0..100).rev().collect();
128+
crypto_sort_u32(&mut v);
129+
assert_eq!(v, (0..100).collect::<Vec<u32>>());
130+
}
131+
#[test]
132+
fn test_sort_large() {
133+
let mut v: Vec<_> = (u32::MAX - 10000..u32::MAX).rev().collect();
134+
crypto_sort_u32(&mut v);
135+
assert_eq!(v, (u32::MAX - 10000..u32::MAX).collect::<Vec<u32>>());
136+
}
137+
138+
#[test]
139+
fn test_sort_long() {
140+
let mut v: Vec<_> = (0..1000000).rev().collect();
141+
crypto_sort_u32(&mut v);
142+
assert_eq!(v, (0..1000000).collect::<Vec<u32>>());
143+
}
144+
}

ntru/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
clippy::similar_names,
1818
)]
1919

20+
pub mod const_time;
2021
pub mod params;

0 commit comments

Comments
 (0)