Skip to content

Commit 1b7026e

Browse files
committed
Add vartime GCD and Jacobi symbol functions
1 parent eb2997b commit 1b7026e

File tree

7 files changed

+397
-4
lines changed

7 files changed

+397
-4
lines changed

src/gcd.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use crate::{Integer, Limb, NonZero, Word};
2+
3+
/// Compute the greatest common divisor (GCD) of this number and another.
4+
/// Variable time in both arguments.
5+
pub fn gcd_vartime<T: Integer>(lhs: &T, rhs: &T) -> T {
6+
// This we can check since it doesn't affect the return type,
7+
// even though `n` will not be 0 either in the application.
8+
if lhs.is_zero().into() {
9+
return rhs.clone();
10+
}
11+
12+
if rhs.is_zero().into() {
13+
return lhs.clone();
14+
}
15+
16+
// Normalize input: the resulting (a, b) are both small, a >= b, and b != 0.
17+
18+
let mut a = lhs.clone();
19+
let mut b = rhs.clone();
20+
21+
let mut a_ref = &mut a;
22+
let mut b_ref = &mut b;
23+
24+
if a_ref > b_ref {
25+
(a_ref, b_ref) = (b_ref, a_ref);
26+
}
27+
28+
// Euclidean algorithm.
29+
loop {
30+
let r = a_ref.rem_vartime(&NonZero::new(b_ref.clone()).unwrap());
31+
if r.is_zero().into() {
32+
return b_ref.clone();
33+
}
34+
35+
(a_ref, b_ref) = (b_ref, a_ref);
36+
*b_ref = r;
37+
}
38+
}
39+
40+
/// Compute the greatest common divisor (GCD) of this number and another.
41+
/// Variable time in both arguments.
42+
pub fn gcd_limb_vartime<T: Integer>(lhs: &T, rhs: NonZero<Limb>) -> Limb {
43+
// Allowing `rhs = 0` would require us to have the return type of `T`
44+
// (since `gcd(n, 0) = n`).
45+
46+
// This we can check since it doesn't affect the return type,
47+
// even though `n` will not be 0 either in the application.
48+
if lhs.is_zero().into() {
49+
return rhs.0;
50+
}
51+
52+
// Normalize input: the resulting (a, b) are both small, `a >= b`, and `b != 0`.
53+
let (mut a, mut b): (Word, Word) = if lhs.bits_vartime() > Limb::BITS {
54+
let n = lhs.rem_vartime(&NonZero::new(T::from(rhs.0)).unwrap());
55+
(rhs.0 .0, n.as_ref()[0].0)
56+
} else {
57+
// In this branch `lhs` fits in a `Limb`,
58+
// so we can safely take the first limb and use it.
59+
let n = lhs.as_ref()[0];
60+
if n > rhs.0 {
61+
(n.0, rhs.0 .0)
62+
} else {
63+
(rhs.0 .0, n.0)
64+
}
65+
};
66+
67+
// Euclidean algorithm.
68+
// Binary GCD algorithm could be used here,
69+
// but the performance impact of this code is negligible.
70+
loop {
71+
let r = a % b;
72+
if r == 0 {
73+
return Limb(b);
74+
}
75+
(a, b) = (b, r)
76+
}
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use crate::{Limb, NonZero, Word, U128};
82+
use num_bigint::BigUint;
83+
use num_integer::Integer;
84+
use proptest::prelude::*;
85+
86+
use super::gcd_limb_vartime;
87+
88+
#[test]
89+
fn corner_cases() {
90+
assert_eq!(
91+
gcd_limb_vartime(&U128::from(0u64), NonZero::new(Limb(5)).unwrap()),
92+
Limb(5)
93+
);
94+
assert_eq!(
95+
gcd_limb_vartime(&U128::from(1u64), NonZero::new(Limb(11 * 13 * 19)).unwrap()),
96+
Limb(1)
97+
);
98+
assert_eq!(
99+
gcd_limb_vartime(&U128::from(7u64 * 11 * 13), NonZero::new(Limb(1)).unwrap()),
100+
Limb(1)
101+
);
102+
assert_eq!(
103+
gcd_limb_vartime(
104+
&U128::from(7u64 * 11 * 13),
105+
NonZero::new(Limb(11 * 13 * 19)).unwrap()
106+
),
107+
Limb(11 * 13)
108+
);
109+
}
110+
111+
prop_compose! {
112+
fn uint()(bytes in any::<[u8; 16]>()) -> U128 {
113+
U128::from_le_slice(&bytes) | U128::ONE
114+
}
115+
}
116+
117+
proptest! {
118+
#[test]
119+
fn fuzzy(m in any::<Word>(), n in uint()) {
120+
if m == 0 {
121+
return Ok(());
122+
}
123+
124+
let m_bi = BigUint::from(m);
125+
let n_bi = BigUint::from_bytes_be(n.to_be_bytes().as_ref());
126+
let gcd_ref: Word = n_bi.gcd(&m_bi).try_into().unwrap();
127+
128+
let gcd_test = gcd_limb_vartime(&n, NonZero::new(Limb(m)).unwrap()).0;
129+
assert_eq!(gcd_test, gcd_ref);
130+
}
131+
}
132+
}

src/jacobi.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//! Jacobi symbol calculation.
2+
3+
use crate::{Integer, Limb, NonZero, Odd, Word};
4+
5+
/// Possible values of Jacobi symbol.
6+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
7+
pub enum JacobiSymbol {
8+
/// `0`
9+
Zero,
10+
/// `1`
11+
One,
12+
/// `-1`
13+
MinusOne,
14+
}
15+
16+
impl core::ops::Neg for JacobiSymbol {
17+
type Output = Self;
18+
fn neg(self) -> Self {
19+
match self {
20+
Self::Zero => Self::Zero,
21+
Self::One => Self::MinusOne,
22+
Self::MinusOne => Self::One,
23+
}
24+
}
25+
}
26+
27+
// A helper trait to generalize some functions over Word and Uint.
28+
trait SmallMod {
29+
fn mod8(&self) -> Word;
30+
fn mod4(&self) -> Word;
31+
}
32+
33+
impl SmallMod for Word {
34+
fn mod8(&self) -> Word {
35+
self & 7
36+
}
37+
fn mod4(&self) -> Word {
38+
self & 3
39+
}
40+
}
41+
42+
impl<T: Integer> SmallMod for T {
43+
fn mod8(&self) -> Word {
44+
self.as_ref()[0].0 & 7
45+
}
46+
fn mod4(&self) -> Word {
47+
self.as_ref()[0].0 & 3
48+
}
49+
}
50+
51+
/// Transforms `(a/p)` -> `(r/p)` for odd `p`, where the resulting `r` is odd, and `a = r * 2^s`.
52+
/// Takes a Jacobi symbol value, and returns `r` and the new Jacobi symbol,
53+
/// negated if the transformation changes parity.
54+
///
55+
/// Note that the returned `r` is odd.
56+
fn reduce_numerator<V: SmallMod>(j: JacobiSymbol, a: Word, p: &V) -> (JacobiSymbol, Word) {
57+
let p_mod_8 = p.mod8();
58+
let s = a.trailing_zeros();
59+
let j = if (s & 1) == 1 && (p_mod_8 == 3 || p_mod_8 == 5) {
60+
-j
61+
} else {
62+
j
63+
};
64+
(j, a >> s)
65+
}
66+
67+
/// Transforms `(a/p)` -> `(p/a)` for odd and coprime `a` and `p`.
68+
/// Takes a Jacobi symbol value, and returns the swapped pair and the new Jacobi symbol,
69+
/// negated if the transformation changes parity.
70+
fn swap<T: SmallMod, V: SmallMod>(j: JacobiSymbol, a: T, p: V) -> (JacobiSymbol, V, T) {
71+
let j = if a.mod4() == 1 || p.mod4() == 1 {
72+
j
73+
} else {
74+
-j
75+
};
76+
(j, p, a)
77+
}
78+
79+
/// Returns the Jacobi symbol `(a/p)` given an odd `p`. Panics on even `p`.
80+
pub fn jacobi_symbol_vartime<T: Integer>(a: i32, p_long: &Odd<T>) -> JacobiSymbol {
81+
let p_long = p_long.0.clone();
82+
83+
let result = JacobiSymbol::One; // Keep track of all the sign flips here.
84+
85+
// Deal with a negative `a` first:
86+
// (-a/n) = (-1/n) * (a/n)
87+
// = (-1)^((n-1)/2) * (a/n)
88+
// = (-1 if n = 3 mod 4 else 1) * (a/n)
89+
let (result, a_pos) = {
90+
let result = if a < 0 && p_long.mod4() == 3 {
91+
-result
92+
} else {
93+
result
94+
};
95+
(result, a.abs_diff(0))
96+
};
97+
98+
// A degenerate case.
99+
if a_pos == 1 || p_long == T::one() {
100+
return result;
101+
}
102+
103+
let a_limb = Limb::from(a_pos);
104+
105+
// Normalize input: at the end we want `a < p`, `p` odd, and both fitting into a `Word`.
106+
let (result, a, p): (JacobiSymbol, Word, Word) = if p_long.bits_vartime() <= Limb::BITS {
107+
let a = a_limb.0;
108+
let p = p_long.as_ref()[0].0;
109+
(result, a % p, p)
110+
} else {
111+
let (result, a) = reduce_numerator(result, a_limb.0, &p_long);
112+
if a == 1 {
113+
return result;
114+
}
115+
let (result, a_long, p) = swap(result, a, p_long);
116+
// Can unwrap here, since `p` is swapped with `a`,
117+
// and `a` would be odd after `reduce_numerator()`.
118+
let a = a_long.rem_vartime(&NonZero::new(T::from(p)).unwrap());
119+
(result, a.as_ref()[0].0, p)
120+
};
121+
122+
let mut result = result;
123+
let mut a = a;
124+
let mut p = p;
125+
126+
loop {
127+
if a == 0 {
128+
return JacobiSymbol::Zero;
129+
}
130+
131+
// At this point `p` is odd (either coming from outside of the `loop`,
132+
// or from the previous iteration, where a previously reduced `a`
133+
// was swapped into its place), so we can call this.
134+
(result, a) = reduce_numerator(result, a, &p);
135+
136+
if a == 1 {
137+
return result;
138+
}
139+
140+
// At this point both `a` and `p` are odd: `p` was odd before,
141+
// and `a` is odd after `reduce_numerator()`.
142+
// Note that technically `swap()` only returns a valid `result` if `a` and `p` are coprime.
143+
// But if they are not, we will return `Zero` eventually,
144+
// which is not affected by any sign changes.
145+
(result, a, p) = swap(result, a, p);
146+
147+
a %= p;
148+
}
149+
}
150+
151+
#[cfg(test)]
152+
mod tests {
153+
use crate::{Odd, U128};
154+
use num_bigint::{BigInt, Sign};
155+
use num_modular::ModularSymbols;
156+
use proptest::prelude::*;
157+
158+
use super::{jacobi_symbol_vartime, JacobiSymbol};
159+
160+
#[test]
161+
fn jacobi_symbol_neg_zero() {
162+
// This does not happen during normal operation, since we return zero as soon as we get it.
163+
// So just covering it for the completness' sake.
164+
assert_eq!(-JacobiSymbol::Zero, JacobiSymbol::Zero);
165+
}
166+
167+
// Reference from `num-modular` - supports long `p`, but only positive `a`.
168+
fn jacobi_symbol_ref(a: i32, p: &U128) -> JacobiSymbol {
169+
let a_bi = BigInt::from(a);
170+
let p_bi = BigInt::from_bytes_be(Sign::Plus, p.to_be_bytes().as_ref());
171+
let j = a_bi.jacobi(&p_bi);
172+
if j == 1 {
173+
JacobiSymbol::One
174+
} else if j == -1 {
175+
JacobiSymbol::MinusOne
176+
} else {
177+
JacobiSymbol::Zero
178+
}
179+
}
180+
181+
#[test]
182+
fn small_values() {
183+
// Test small values, using a reference implementation.
184+
for a in -31i32..31 {
185+
for p in (1u32..31).step_by(2) {
186+
let p_long = U128::from(p);
187+
let j_ref = jacobi_symbol_ref(a, &p_long);
188+
let j = jacobi_symbol_vartime(a, &Odd::new(p_long).unwrap());
189+
assert_eq!(j, j_ref);
190+
}
191+
}
192+
}
193+
194+
#[test]
195+
fn big_values() {
196+
// a = x, p = x * y, where x and y are big primes. Should give 0.
197+
let a = 2147483647i32; // 2^31 - 1, a prime
198+
let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59)
199+
assert_eq!(
200+
jacobi_symbol_vartime(a, &Odd::new(p).unwrap()),
201+
JacobiSymbol::Zero
202+
);
203+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::Zero);
204+
205+
// a = x^2 mod p, should give 1.
206+
let a = 659456i32; // Obtained from x = 2^70
207+
let p = U128::from_be_hex("ffffffffffffffffffffffffffffff5f"); // 2^128 - 161 - not a prime
208+
assert_eq!(
209+
jacobi_symbol_vartime(a, &Odd::new(p).unwrap()),
210+
JacobiSymbol::One
211+
);
212+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One);
213+
214+
let a = i32::MIN; // -2^31, check that no overflow occurs
215+
let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59)
216+
assert_eq!(
217+
jacobi_symbol_vartime(a, &Odd::new(p).unwrap()),
218+
JacobiSymbol::One
219+
);
220+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One);
221+
}
222+
223+
prop_compose! {
224+
fn odd_uint()(bytes in any::<[u8; 16]>()) -> Odd<U128> {
225+
Odd::new(U128::from_le_slice(&bytes) | U128::ONE).unwrap()
226+
}
227+
}
228+
229+
proptest! {
230+
#[test]
231+
fn fuzzy(a in any::<i32>(), p in odd_uint()) {
232+
let j_ref = jacobi_symbol_ref(a, &p);
233+
let j = jacobi_symbol_vartime(a, &p);
234+
assert_eq!(j, j_ref);
235+
}
236+
}
237+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ pub mod modular;
168168
mod array;
169169
mod checked;
170170
mod const_choice;
171+
mod gcd;
172+
mod jacobi;
171173
mod limb;
172174
mod non_zero;
173175
mod odd;
@@ -179,6 +181,8 @@ mod wrapping;
179181
pub use crate::{
180182
checked::Checked,
181183
const_choice::{ConstChoice, ConstCtOption},
184+
gcd::{gcd_limb_vartime, gcd_vartime},
185+
jacobi::{jacobi_symbol_vartime, JacobiSymbol},
182186
limb::{Limb, WideWord, Word},
183187
non_zero::NonZero,
184188
odd::Odd,

0 commit comments

Comments
 (0)