Skip to content

Commit a56616a

Browse files
add Jacobi symbol calculation
Signed-off-by: Andrew Whitehead <[email protected]>
1 parent ae429de commit a56616a

File tree

16 files changed

+680
-145
lines changed

16 files changed

+680
-145
lines changed

benches/const_monty.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ fn bench_montgomery_ops<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
119119
)
120120
});
121121

122+
group.bench_function("jacobi_symbol", |b| {
123+
b.iter_batched(
124+
|| ConstMontyForm::random(&mut rng),
125+
|a| a.jacobi_symbol(),
126+
BatchSize::SmallInput,
127+
)
128+
});
129+
130+
group.bench_function("jacobi_symbol_vartime", |b| {
131+
b.iter_batched(
132+
|| ConstMontyForm::random(&mut rng),
133+
|a| a.jacobi_symbol_vartime(),
134+
BatchSize::SmallInput,
135+
)
136+
});
137+
122138
group.bench_function("lincomb, U256*U256+U256*U256", |b| {
123139
b.iter_batched(
124140
|| ConstMontyForm::random(&mut rng),

benches/uint.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,46 @@ fn bench_gcd(c: &mut Criterion) {
468468
group.finish();
469469
}
470470

471+
fn mod_symbols_bench<const LIMBS: usize>(g: &mut BenchmarkGroup<WallTime>, _x: Uint<LIMBS>) {
472+
let mut rng = make_rng();
473+
474+
g.bench_function(BenchmarkId::new("jacobi_symbol", LIMBS), |b| {
475+
b.iter_batched(
476+
|| {
477+
(
478+
OddUint::<LIMBS>::random(&mut rng),
479+
Uint::<LIMBS>::random(&mut rng),
480+
)
481+
},
482+
|(f, g)| black_box(g.jacobi_symbol(&f)),
483+
BatchSize::SmallInput,
484+
)
485+
});
486+
487+
g.bench_function(BenchmarkId::new("jacobi_symbol_vartime", LIMBS), |b| {
488+
b.iter_batched(
489+
|| {
490+
(
491+
OddUint::<LIMBS>::random(&mut rng),
492+
Uint::<LIMBS>::random(&mut rng),
493+
)
494+
},
495+
|(f, g)| black_box(g.jacobi_symbol_vartime(&f)),
496+
BatchSize::SmallInput,
497+
)
498+
});
499+
}
500+
501+
fn bench_mod_symbols(c: &mut Criterion) {
502+
let mut group = c.benchmark_group("modular symbols");
503+
504+
mod_symbols_bench(&mut group, Uint::<1>::ZERO);
505+
mod_symbols_bench(&mut group, Uint::<4>::ZERO);
506+
mod_symbols_bench(&mut group, Uint::<64>::ZERO);
507+
508+
group.finish();
509+
}
510+
471511
fn bench_shl(c: &mut Criterion) {
472512
let mut group = c.benchmark_group("left shift");
473513

@@ -631,6 +671,7 @@ criterion_group!(
631671
bench_mul,
632672
bench_division,
633673
bench_gcd,
674+
bench_mod_symbols,
634675
bench_shl,
635676
bench_shr,
636677
bench_invert_mod,

src/int.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod encoding;
2323
mod from;
2424
mod gcd;
2525
mod invert_mod;
26+
mod mod_symbol;
2627
mod mul;
2728
mod mul_uint;
2829
mod neg;

src/int/mod_symbol.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! Support for computing modular symbols.
2+
3+
use crate::{ConstChoice, Int, Odd, Uint};
4+
5+
impl<const LIMBS: usize> Int<LIMBS> {
6+
/// Compute the Jacobi symbol `(self|rhs)`.
7+
///
8+
/// For prime `rhs`, this corresponds to the Legendre symbol and
9+
/// indicates whether `self` is quadratic residue modulo `rhs`.
10+
pub const fn jacobi_symbol(&self, rhs: &Odd<Uint<LIMBS>>) -> i8 {
11+
let (abs, sign) = self.abs_sign();
12+
let jacobi = abs.jacobi_symbol(rhs) as i64;
13+
// (-self|rhs) = -(self|rhs) iff rhs = 3 mod 4
14+
let swap = sign.and(ConstChoice::from_word_eq(rhs.as_ref().limbs[0].0 & 3, 3));
15+
swap.select_i64(jacobi, -jacobi) as i8
16+
}
17+
18+
/// Compute the Jacobi symbol `(self|rhs)`.
19+
///
20+
/// For prime `rhs`, this corresponds to the Legendre symbol and
21+
/// indicates whether `self` is quadratic residue modulo `rhs`.
22+
///
23+
/// This method executes in variable-time for the value of `self`.
24+
pub const fn jacobi_symbol_vartime(&self, rhs: &Odd<Uint<LIMBS>>) -> i8 {
25+
let (abs, sign) = self.abs_sign();
26+
let jacobi = abs.jacobi_symbol(rhs);
27+
if sign.is_true_vartime() && rhs.as_ref().limbs[0].0 & 3 == 3 {
28+
-jacobi
29+
} else {
30+
jacobi
31+
}
32+
}
33+
}
34+
35+
#[cfg(test)]
36+
mod tests {
37+
use crate::{I256, U256};
38+
39+
#[test]
40+
fn jacobi_quad_residue() {
41+
// Two semiprimes with no common factors, and
42+
// f is quadratic residue modulo g
43+
let f = I256::from(59i32 * 67);
44+
let g = U256::from(61u32 * 71).to_odd().unwrap();
45+
let res = f.jacobi_symbol(&g);
46+
let res_vartime = f.jacobi_symbol_vartime(&g);
47+
assert_eq!(res, 1);
48+
assert_eq!(res, res_vartime);
49+
}
50+
51+
#[test]
52+
fn jacobi_non_quad_residue() {
53+
// f and g have no common factors, but
54+
// f is not quadratic residue modulo g
55+
let f = I256::from(59i32 * 67 + 2);
56+
let g = U256::from(61u32 * 71).to_odd().unwrap();
57+
let res = f.jacobi_symbol(&g);
58+
let res_vartime = f.jacobi_symbol_vartime(&g);
59+
assert_eq!(res, -1);
60+
assert_eq!(res, res_vartime);
61+
}
62+
63+
#[test]
64+
fn jacobi_non_coprime() {
65+
let f = I256::from(4391633i32);
66+
let g = U256::from(2022161u32).to_odd().unwrap();
67+
let res = f.jacobi_symbol(&g);
68+
let res_vartime = f.jacobi_symbol_vartime(&g);
69+
assert_eq!(res, 0);
70+
assert_eq!(res, res_vartime);
71+
}
72+
73+
#[test]
74+
fn jacobi_zero() {
75+
assert_eq!(I256::ZERO.jacobi_symbol(&U256::ONE.to_odd().unwrap()), 1);
76+
}
77+
78+
#[test]
79+
fn jacobi_neg_one() {
80+
let f = I256::ONE;
81+
assert_eq!(f.jacobi_symbol(&U256::ONE.to_odd().unwrap()), 1);
82+
assert_eq!(f.jacobi_symbol(&U256::from(3u8).to_odd().unwrap()), 1);
83+
}
84+
85+
#[test]
86+
fn jacobi_int() {
87+
// Two semiprimes with no common factors, and
88+
// f is quadratic residue modulo g
89+
let f = I256::from(59i32 * 67);
90+
let g = U256::from(61u32 * 71).to_odd().unwrap();
91+
let res = f.jacobi_symbol(&g);
92+
let res_vartime = f.jacobi_symbol_vartime(&g);
93+
assert_eq!(res, 1);
94+
assert_eq!(res, res_vartime);
95+
96+
// let res = f.checked_neg().unwrap().jacobi_symbol(&g);
97+
// let res_vartime = f.jacobi_symbol_vartime(&g);
98+
// assert_eq!(res, -1);
99+
// assert_eq!(res, res_vartime);
100+
}
101+
}

src/modular/bingcd/compact.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ impl<const LIMBS: usize> Uint<LIMBS> {
5353
.bitand(&mask)
5454
}
5555

56-
/// Compact `self` to a form containing the concatenation of its bit ranges `[0, K-1)`
57-
/// and `[n-K-1, n)`.
56+
/// Compact `self` to a form containing the concatenation of its bit ranges `[0, K)`
57+
/// and `[n-K, n)`.
5858
///
5959
/// Assumes `K ≤ Uint::<SUMMARY_LIMBS>::BITS`, `n ≤ Self::BITS` and `n ≥ 2K`.
6060
#[inline(always)]
@@ -67,13 +67,13 @@ impl<const LIMBS: usize> Uint<LIMBS> {
6767
debug_assert!(n >= 2 * K);
6868

6969
// safe to vartime; this function is vartime in length only, which is a public constant
70-
let hi = self.section_vartime_length(n - K - 1, K + 1);
70+
let hi = self.section_vartime_length(n - K, K);
7171
// safe to vartime; this function is vartime in idx and length only, which are both public
7272
// constants
73-
let lo = self.section_vartime(0, K - 1);
73+
let lo = self.section_vartime(0, K);
7474
// safe to vartime; shl_vartime is variable in the value of shift only. Since this shift
7575
// is a public constant, the constant time property of this algorithm is not impacted.
76-
hi.shl_vartime(K - 1).bitxor(&lo)
76+
hi.shl_vartime(K).bitxor(&lo)
7777
}
7878

7979
/// Vartime equivalent of [`Self::compact`].
@@ -87,13 +87,13 @@ impl<const LIMBS: usize> Uint<LIMBS> {
8787
debug_assert!(n >= 2 * K);
8888

8989
// safe to vartime; this function is vartime in length only, which is a public constant
90-
let hi = self.section_vartime(n - K - 1, K + 1);
90+
let hi = self.section_vartime(n - K, K);
9191
// safe to vartime; this function is vartime in idx and length only, which are both public
9292
// constants
93-
let lo = self.section_vartime(0, K - 1);
93+
let lo = self.section_vartime(0, K);
9494
// safe to vartime; shl_vartime is variable in the value of shift only. Since this shift
9595
// is a public constant, the constant time property of this algorithm is not impacted.
96-
hi.shl_vartime(K - 1).bitxor(&lo)
96+
hi.shl_vartime(K).bitxor(&lo)
9797
}
9898
}
9999

@@ -105,7 +105,7 @@ mod tests {
105105
fn test_compact() {
106106
let val =
107107
U256::from_be_hex("CFCF1535CEBE19BBF289933AB8645189397450A32BFEC57579FB7EB14E27D101");
108-
let target = U128::from_be_hex("BBF289933AB86451F9FB7EB14E27D101");
108+
let target = U128::from_be_hex("BBF289933AB8645179FB7EB14E27D101");
109109

110110
let compact = val.compact::<64, { U128::LIMBS }>(200);
111111
assert_eq!(compact, target);

0 commit comments

Comments
 (0)