class_groups/discriminant.rs
1//! Methods for sampling and working with discriminants (and the maps between them)
2//!
3//! We specifically provide the maps from
4//! "A Cryptosystem Based on Non-maximal Imaginary Quadratic Orders with Fast Decryption" by
5//! Detlef Hühnlein, Michael J. Jacobson, Jr., Sachar Paulus, and Tsuyoshi Takagi
6//! (<https://link.springer.com/content/pdf/10.1007/bfb0054134.pdf>). These maps traditionally note
7//! the prime conductor as `q`, yet the following implementations consistently denote it as `p`.
8//! This is as "Linearly Homomorphic Encryption from DDH"
9//! by Guilhem Castagnos and Fabien Laguillaumie (<https://eprint.iacr.org/2025/047>) denote the
10//! prime conductor as `p` and we prefer consistency with the latter paper.
11//!
12//! The two relevant maps from
13//! "A Cryptosystem Based on Non-maximal Imaginary Quadratic Orders with Fast Decryption" require
14//! a `FindIdealPrimeTo` function. We specify it here as a `coprime_from` function, specified
15//!/ as follows:
16//!
17//! ```py
18//! fn coprime_form(a, b, c, p) {
19//! if gcd(a, p) == 1 {
20//! return (a, b)
21//! }
22//! if gcd(c, p) == 1 {
23//! return (c, -b)
24//! }
25//! return (c + (b + a), -(b + (2 * a)))
26//! }
27//! ````
28//!
29//! Our function has the same bounds on the input, that the form is primitive, and achieves the
30//! same bounds on the output, that the `a` coefficient is coprime to the prime `p`. Note we do
31//! input the `c` coefficient in order to calculate the result yet yield the result without its `c`
32//! coefficient. If necessary, it can be calculated via the yielded `a, b` coefficients and the
33//! corresponding discriminant, as usual.
34//!
35//! For correctness, it should be noted there are three cases:
36//!
37//! 1) If `gcd(a, p) == 1`, the output is the input
38//! 2) Else if `gcd(c, p) == 1`, we apply the transformation `(a, b, c)` -> `(c, -b, a)`
39//! 3) Else, we apply the transformation `(a, b, c)` -> `(a, b + 2 a, c + b + a)` followed by the
40//! transformation `(a, b, c)` -> `(c, -b, a)`. This is a special case of the general
41//! transformation, `(a, b + 2 m a, c + m b + m^2 a)`, with `m = 1`.
42//!
43//! Having established the form is equivalent, we are left to argue the resulting `a` coefficient
44//! is coprime to `p`. This is a result of `p` being prime and the form being primitive such that
45//! `gcd(a, b, c) = 1`. Accordingly, either `a`, `b`, or `c` must be coprime to `q`, as if all
46//! weren't, we'd have the contradiction `gcd(a, b, c) = q`. If `a` is coprime, the form is left
47//! as-is. If `c` is coprime, we return the equivalent (unreduced) form where the new `a`
48//! coefficient is the old `c` coefficient. Finally, if neither `a` nor `c` are coprime, we know
49//! their sum `a + c` is a multiple of `q` and `b` must be coprime to `q`. Accordingly,
50//! `(a + c) + b` will be coprime to `q`, hence why we return an equivalent form with that as the
51//! `a` coefficient. We do specify this as `c + (b + a)`, which is equivalent, as we are able to
52//! bound `b + a` as being positive for reduced positive definite forms, simplifying the bounds on
53//! that intermediary term.
54
55use rand::CryptoRng;
56use ::crypto_bigint::{
57 Choice, CtOption, CtEq, CtGt, CtSelect, CtAssign, Zero, One, NonZero, Odd, Limb, CheckedAdd,
58 CheckedSub, Mul, ConcatenatingMul, ConcatenatingSquare, Div, Rem, Gcd, NegMod, MulMod, SquareMod,
59 InvertMod, BitOps, Encoding, RandomBits, RandomMod, UnsignedWithMontyForm,
60};
61
62use crate::Element;
63
64/// For a primitive reduced positive definite form of negative discriminant, return the
65/// equivalent form whose `a` coefficient is coprime to the prime `p`.
66///
67/// The inputs MUST have sufficient capacity to calculate `a + b + c, b + 2 a`. Additionally,
68/// `a, b, c, p` MUST be of the same size.
69///
70/// This function runs in constant time. It WILL NOT return `None` if the inputs are satisfied but
71/// MAY return `None` if for invalid inputs, if it fails to find an equivalent form with a coprime
72/// `a` coefficient.
73#[must_use]
74fn coprime_form<P, U: AsRef<[Limb]> + AsMut<[Limb]> + CtSelect + Gcd<P, Output: One>>(
75 mut a: U,
76 (mut b_positive, mut b_abs): (Choice, U),
77 mut c: U,
78 p: &P,
79) -> CtOption<(U, (Choice, U))> {
80 let a_is_coprime = a.gcd(p).is_one();
81 let c_is_coprime = c.gcd(p).is_one();
82
83 // If neither are coprime, map with `m = 1`
84 let neither_a_c_coprime = !(a_is_coprime | c_is_coprime);
85 let mut correct = Choice::TRUE;
86 {
87 // We start by calculating `b + a`, stored to where `b` is, negating `b` if necessary
88 {
89 let mut carry = Limb::from(u8::from(!b_positive));
90 let mask = Limb::ZERO.wrapping_sub(carry);
91 for (b_limb, a_limb) in b_abs.as_mut().iter_mut().zip(a.as_ref()) {
92 let new_limb;
93 (new_limb, carry) = ((*b_limb) ^ mask).carrying_add(*a_limb, carry);
94 *b_limb = Limb::ct_select(b_limb, &new_limb, neither_a_c_coprime);
95 }
96 /*
97 We require either:
98 - No change was applied
99 - `b` was negative, as the difference of two numbers of equivalent capacity always fits
100 within the capacity of one said numbers
101 - `carry` is zero (this did not overflow)
102 */
103 correct &= (!neither_a_c_coprime) | (!b_positive) | carry.is_zero();
104 // $0 \le b + a$, so if we just calculated `b + a`, this is positive
105 b_positive |= neither_a_c_coprime;
106 }
107
108 // We now sum `b + a` into `c` and update `b + a` to `b + 2a` to complete the map with `m = 1`
109 {
110 let mut c_carry = Limb::ZERO;
111 let mut b_carry = Limb::ZERO;
112 for ((a_limb, b_limb), c_limb) in a.as_ref().iter().zip(b_abs.as_mut()).zip(c.as_mut()) {
113 let new_c_limb;
114 (new_c_limb, c_carry) =
115 c_limb.carrying_add(Limb::ct_select(&Limb::ZERO, b_limb, neither_a_c_coprime), c_carry);
116 *c_limb = new_c_limb;
117
118 let new_b_limb;
119 (new_b_limb, b_carry) =
120 b_limb.carrying_add(Limb::ct_select(&Limb::ZERO, a_limb, neither_a_c_coprime), b_carry);
121 *b_limb = new_b_limb;
122 }
123 correct &= c_carry.is_zero() & b_carry.is_zero();
124 }
125 }
126
127 // If `a` was not coprime, perform the swap with `c` (necessary when `c` was coprime or `b` was)
128 {
129 let swap = !a_is_coprime;
130 U::ct_swap(&mut a, &mut c, swap);
131 b_positive ^= swap;
132 }
133
134 correct &= a.gcd(p).is_one();
135 CtOption::new((a, (b_positive, b_abs)), correct)
136}
137
138/// Check if two little-endian encodings represent the same number.
139///
140/// This function runs in time independent to the encoded values and is considered to run in
141/// constant time, though it is in time variable to the lengths of the inputs (which are not
142/// considered secrets).
143///
144/// This function does not check the encodings are canonical and does allow trailing zero bytes.
145#[must_use]
146fn le_malleable_eq(a: &[u8], b: &[u8]) -> Choice {
147 let mut eq = Choice::TRUE;
148
149 // Check mutually present bytes for equality
150 let mutual_len = a.len().min(b.len());
151 for (a, b) in a[.. mutual_len].iter().zip(&b[.. mutual_len]) {
152 eq &= a.ct_eq(b);
153 }
154
155 // Check non-mutual bytes are zero
156 for a in &a[mutual_len ..] {
157 eq &= a.ct_eq(&0);
158 }
159 for b in &b[mutual_len ..] {
160 eq &= b.ct_eq(&0);
161 }
162
163 eq
164}
165
166/// A discriminant of a class group.
167pub trait Discriminant {}
168/// A negative discriminant of a class group.
169pub trait NegativeDiscriminant: Discriminant {
170 /// An upper bound on the order of the class group with this discriminant.
171 ///
172 /// This returns `k` such that $2^k$ is greater than or equal to the order (class
173 /// number) of this group. The returned `k` is not required to be minimal or calculated by any
174 /// specific formula, so long as $2^k$ is greater than or equal to a proven bound on the order of
175 /// this group.
176 ///
177 /// The provided implementation runs in variable time. The provided implementation MAY panic if
178 /// this discriminant is ill-defined or absurdly large.
179 #[must_use]
180 fn upper_bound_on_order(&self) -> u32 {
181 /*
182 Per Section 5.10.1 of A Course in Computational Algebraic Number Theory by Henri Cohen, for
183 all discriminants less than `-4`, an upper bound on the class number is
184 $ln(|D|) sqrt(|D|) / \pi$. We simplify this to
185 $2^{\lceil log_2(log_2(|D|) sqrt(|D|) / 2) \rceil}$.
186
187 `-1, -2` are not congruent to `0` nor `1` modulo `4`, as required to be a discriminant of a
188 class group. `-3, -4` both have the class number `1` and are therefore satisfied by any
189 output we return (as $0$ is the smallest possible result and $2^0$ is greater than or equal
190 to the class number of discriminants `-3, -4`).
191
192 This means the following is complete for all negative discriminants.
193 */
194 let absolute_value = self.absolute_value();
195 let mut absolute_value = absolute_value.as_ref();
196 while absolute_value.last() == Some(&0) {
197 absolute_value = &absolute_value[.. (absolute_value.len() - 1)];
198 }
199 let discriminant_bits = u32::try_from(8 * absolute_value.len()).expect("4 GB discriminant?") -
200 absolute_value
201 .last()
202 .expect("negative discriminant's absolute value was zero")
203 .leading_zeros();
204 let sqrt_bits = discriminant_bits.div_ceil(2);
205 let logarithm_bits = discriminant_bits.ilog2() + 1;
206 logarithm_bits + sqrt_bits - 1
207 }
208
209 /// The absolute value of this discriminant.
210 ///
211 /// This is returned as its little-endian encoding.
212 #[must_use]
213 fn absolute_value(&self) -> impl AsRef<[u8]>;
214}
215/// An odd discriminant.
216pub trait OddDiscriminant: Discriminant {}
217/// A fundamental (square-free) discriminant.
218pub trait FundamentalDiscriminant: Discriminant {
219 /// Take an element of the class group with fundamental discriminant and apply the injection such
220 /// that it is mapped to an element of the class group with non-fundamental discriminant.
221 ///
222 /// This implements Algorithm 2, `GoToNonMaxOrder`. We specify it as follows for primitive forms
223 /// of fundamental negative discriminants:
224 ///
225 /// ```py
226 /// fn inject(a, b, c, p) {
227 /// discriminant = (b * b) - (4 * a * c)
228 /// (a, b) = coprime_form(a, b, c, p)
229 /// return reduce(a, b * p, discriminant * p * p)
230 /// }
231 /// ```
232 ///
233 /// This is slightly different in that we do not specify the reduction of $b \mod 2 a$. Instead,
234 /// we assume the existence of a reduction function, `reduce`, which inputs the `a, b`
235 /// coefficients of an unreduced form and its discriminant before yielding a reduced form.
236 ///
237 /// The resulting form is primitive however. First, the input is bound to be primitive. Second,
238 /// `coprime_form` yields an equivalent form, where equivalence preserves primitivity. Finally,
239 /// `coprime_form` yields a form whose `a` coefficient is coprime to `p`, so scaling the `b`
240 /// coefficient by `p` won't affect the greatest common divisor of `a, b`.
241 ///
242 /// This function MAY panic or return an incorrect result if `element` is not of this
243 /// discriminant. This function runs in time only variable to the discriminant, the length of the
244 /// encoding of `p`, and `E::a_b_c_discriminant` (which may be implemented in constant-time).
245 #[cfg(feature = "alloc")] // TODO: no-`alloc`
246 #[must_use]
247 fn inject<E: Element>(&self, element: impl Element, p: &impl Encoding) -> E
248 where
249 Self: NegativeDiscriminant,
250 {
251 use crypto_bigint::{Resize as _, BoxedUint};
252
253 let (a, (b_positive, b_abs), c, discriminant_abs) = element.a_b_c_discriminant();
254 assert!(bool::from(le_malleable_eq(self.absolute_value().as_ref(), discriminant_abs.as_ref())));
255
256 // This is only vartime with regards to the length of the encoding
257 let a = BoxedUint::from_le_slice_vartime(a.as_ref());
258 let b_abs = BoxedUint::from_le_slice_vartime(b_abs.as_ref());
259 let c = BoxedUint::from_le_slice_vartime(c.as_ref());
260
261 let discriminant_abs = BoxedUint::from_le_slice_vartime(discriminant_abs.as_ref());
262 let p = {
263 let p = p.to_le_bytes();
264 BoxedUint::from_le_slice_vartime(p.as_ref())
265 };
266
267 let discriminant_abs = discriminant_abs.concatenating_mul(p.concatenating_square());
268
269 let bits_precision = 2 + a.bits_precision().max(b_abs.bits_precision()).max(c.bits_precision());
270 let p = p.resize(bits_precision);
271 let (a, (b_positive, b_abs)) = coprime_form(
272 a.resize(bits_precision),
273 (b_positive, b_abs.resize(bits_precision)),
274 c.resize(bits_precision),
275 &p,
276 )
277 .expect("could not find a coprime form (non-primitive or unreduced?)");
278
279 let b_abs = b_abs.concatenating_mul(&p);
280
281 // TODO: Tighten this
282 let log_2_bound = 8 + bits_precision.max(discriminant_abs.bits_precision());
283 let discriminant_abs = discriminant_abs.resize(log_2_bound);
284 /*
285 The form is valid. The numbers are within `log_2_bound`. The numbers are the same size, and
286 with a spare bit of capacity. This causes our call to `partial_reduce` to be valid.
287 */
288 let (a, (b_positive, b_abs), c) = crate::crypto_bigint::partial_reduce(
289 log_2_bound,
290 a.resize(log_2_bound),
291 (b_positive, b_abs.resize(log_2_bound)),
292 &discriminant_abs,
293 );
294 /*
295 As correct for `partial_reduce`, we are correct for `reduce`. We do tighten our bound to the
296 square root of the discriminant, but this is a bound on the output from `partial_reduce`.
297 */
298 let discriminant_bits = discriminant_abs.bits_vartime();
299 let sqrt_discriminant_bits = discriminant_bits.div_ceil(2);
300 let (a, (b_positive, b_abs), c) =
301 crate::crypto_bigint::reduce(sqrt_discriminant_bits, a, (b_positive, b_abs), c);
302
303 /*
304 SAFETY:
305
306 This form is well-defined. For $b^2 - 4 a c = -|delta|$, we want to assert
307 $(b p)^2 - 4 a c' = -|delta| p^2$ has a solution for `c'` when given arbitrary `p`.
308
309 $b b p p + |delta| p p = 4 a c'$
310 $(b b + |delta|) p p = 4 a c'$
311
312 $4 a$ is a divisor of $b b + |delta|$ and therefore there is a solution for $c'$.
313
314 This form is primitive as the input was primitive, and while `b` now has a `p` factor, `p` is
315 coprime to `a`. Additionally, the reduction preserves primitivity.
316
317 This form is reduced as we've explicitly reduced it.
318 */
319 let discriminant_bits = usize::try_from(discriminant_bits).unwrap();
320 let sqrt_discriminant_bits = usize::try_from(sqrt_discriminant_bits).unwrap();
321 unsafe {
322 E::from_coefficients(
323 &a.to_le_bytes().as_ref()[.. sqrt_discriminant_bits.div_ceil(8)],
324 (b_positive, &b_abs.to_le_bytes().as_ref()[.. sqrt_discriminant_bits.div_ceil(8)]),
325 &c.to_le_bytes()[.. discriminant_bits.div_ceil(8)],
326 &discriminant_abs.to_le_bytes()[.. discriminant_bits.div_ceil(8)],
327 )
328 }
329 }
330}
331
332struct WithoutTrailingZeroBytes<B: AsRef<[u8]>>(B);
333impl<B: AsRef<[u8]>> AsRef<[u8]> for WithoutTrailingZeroBytes<B> {
334 fn as_ref(&self) -> &[u8] {
335 let mut bytes = self.0.as_ref();
336 while bytes.last() == Some(&0) {
337 bytes = &bytes[.. (bytes.len() - 1)];
338 }
339 bytes
340 }
341}
342
343/// A fundamental discriminant as part of the CL15 cryptosystem.
344///
345/// This is constructed as detailed in "Linearly Homomorphic Encryption from DDH" by
346/// Guilhem Castagnos and Fabien Laguillaumie (<https://eprint.iacr.org/2025/047>), corresponding
347/// to $\Delta_K$.
348///
349/// `Up` is the numeric type used to represent the prime `p`. `Udk` is the numeric type used to
350/// represent the discriminant's absolute value, the product $q * p$.
351#[derive(Clone)]
352pub struct Cl15k<Up, Udk> {
353 p: Odd<Up>,
354 absolute_value: Udk,
355}
356impl<Up, Udk> Discriminant for Cl15k<Up, Udk> {}
357impl<Up, Udk: Encoding> NegativeDiscriminant for Cl15k<Up, Udk> {
358 /// This runs in time variable to the bit-length of the discriminant.
359 fn absolute_value(&self) -> impl AsRef<[u8]> {
360 WithoutTrailingZeroBytes(self.absolute_value.to_le_bytes())
361 }
362}
363impl<Up, Udk> OddDiscriminant for Cl15k<Up, Udk> {}
364impl<Up, Udk> FundamentalDiscriminant for Cl15k<Up, Udk> {}
365
366impl<Up, Udk> Cl15k<Up, Udk> {
367 /// The prime `p` from the setup.
368 #[must_use]
369 pub fn p(&self) -> &Up {
370 &self.p
371 }
372}
373
374/// A non-fundamental discriminant as part of the CL15 cryptosystem.
375///
376/// This is constructed as detailed in Linearly Homomorphic Encryption from DDH by
377/// Guilhem Castagnos and Fabien Laguillaumie (<https://eprint.iacr.org/2025/047>), correspond to
378/// $\Delta_p$.
379///
380/// `Up` is the numeric type used to represent the prime `p`. `Udk` is the numeric type used to
381/// represent the fundamental discriminant's absolute value, the product $q * p$. `Udp` is the
382/// numeric type used to represent the non-fundamental discriminant's absolute value, the product
383/// $q * p^3$.
384#[derive(Clone)]
385pub struct Cl15p<Up, Up2, Udk, Udp> {
386 fundamental: Cl15k<Up, Udk>,
387 p_square: Up2,
388 absolute_value: Udp,
389}
390impl<Up, Up2, Udk, Udp> Discriminant for Cl15p<Up, Up2, Udk, Udp> {}
391impl<Up: BitOps, Up2, Udk: Encoding, Udp: Encoding> NegativeDiscriminant
392 for Cl15p<Up, Up2, Udk, Udp>
393{
394 /// This runs in variable time to this discriminant.
395 fn upper_bound_on_order(&self) -> u32 {
396 /*
397 B.1 of Linearly Homomorphic Encryption from DDH establishes the order of this discriminant is
398 equal to the order of the fundamental discriminant multiplied by `p` if the fundamental
399 discriminant is less than `4`. As $q > 4 p$, no fundamental discriminant within the CL15
400 cryptosystem will be greater than or equal to `-4`.
401
402 While this bound can be relaxed, as discussed in Section 4.1, we do not implement or support
403 that extension of the scheme.
404 */
405 self.fundamental.upper_bound_on_order() + self.fundamental.p.as_ref().bits_vartime()
406 }
407
408 /// This runs in time variable to the bit-length of the discriminant.
409 fn absolute_value(&self) -> impl AsRef<[u8]> {
410 WithoutTrailingZeroBytes(self.absolute_value.to_le_bytes())
411 }
412}
413impl<Up, Up2, Udk, Udp> OddDiscriminant for Cl15p<Up, Up2, Udk, Udp> {}
414
415/// An error when sampling discriminants for the CL15 cryptosystem.
416#[derive(Debug)]
417pub enum Cl15Error {
418 /// The odd prime `p` was too small.
419 SmallP,
420 /// There were no candidates for the odd prime `q`.
421 ///
422 /// In effect, this means the fundamental discriminant was too small with regards to the
423 /// specified odd prime `p`.
424 NoQ,
425}
426
427impl<
428 Up: Clone
429 + AsRef<[Limb]>
430 + AsMut<[Limb]>
431 + CtAssign
432 + Zero
433 + One
434 + NegMod<Output = Up>
435 + MulMod<Output = Up>
436 + SquareMod<Output = Up>
437 + ConcatenatingSquare
438 + BitOps,
439 Udk: Clone
440 + AsRef<[Limb]>
441 + AsMut<[Limb]>
442 + CtGt
443 + One
444 + CheckedAdd
445 + CheckedSub<Udk>
446 + for<'a> Mul<&'a Up, Output = Udk>
447 // TODO: `for<'a> ConcatenatingMul<&'a <Up as ConcatenatingSquare>::Output, Output: 'static>`
448 + ConcatenatingMul<<Up as ConcatenatingSquare>::Output>
449 + for<'a> Div<&'a NonZero<Up>, Output = Udk>
450 + for<'a> Rem<&'a NonZero<Up>, Output = Up>
451 + BitOps
452 + Encoding
453 + RandomBits
454 + RandomMod
455 + UnsignedWithMontyForm,
456>
457 Cl15p<
458 Up,
459 <Up as ConcatenatingSquare>::Output,
460 Udk,
461 <Udk as ConcatenatingMul<<Up as ConcatenatingSquare>::Output>>::Output,
462 >
463{
464 /// Sample a fundamental discriminant as described by the `Gen` algorithm of CL15.
465 ///
466 /// This function runs in variable time.
467 ///
468 /// `bits_of_security` DOES NOT correspond to the hardness of finding the order of the resulting
469 /// group. `bits_of_security` is used to configure the primality tests and for the requirement
470 /// $p > 2^{bits_of_security}$, as (loosely) required for a $2^{-bits_of_security}$ likelihood
471 /// the that unknown order is divisible by `p` (a requirement of the cryptosystem). The relation
472 /// of `bits_of_security` to `fundamental_discriminant_bit_length` is completely unchecked.
473 ///
474 /// `fundamental_discriminant_bit_length` will the bit-length of the fundamental discriminant.
475 /// `1827` is SUGGESTED as the bit-length of the fundamental discriminant for 128-bit security.
476 /// Please review <https://eprint.iacr.org/2020/196> for context on choices.
477 ///
478 /// `p` MUST be an odd prime and is specified by its little-endian encoding. It is undefined
479 /// behavior to specify a `p` which is not actually an odd prime.
480 // TODO: `OddPrime` which is `unsafe` to construct then this which is safe?
481 pub fn sample(
482 mut rng: impl CryptoRng,
483 bits_of_security: u32,
484 fundamental_discriminant_bit_length: u32,
485 p: Odd<Up>,
486 ) -> Result<Self, Cl15Error> {
487 // TODO: https://github.com/RustCrypto/crypto-bigint/1275
488 #[allow(non_snake_case)]
489 let Udk_zero_with_precision = |bits_precision| -> Udk {
490 struct Zero;
491 impl crypto_bigint::rand_core::TryRng for Zero {
492 type Error = crypto_bigint::rand_core::Infallible;
493 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
494 Ok(0)
495 }
496 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
497 Ok(0)
498 }
499 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
500 for b in dst {
501 *b = 0;
502 }
503 Ok(())
504 }
505 }
506 let result = Udk::random_bits_with_precision(&mut Zero, 0, bits_precision);
507 debug_assert!(bool::from(result.is_zero()));
508 result
509 };
510
511 let mu = p.as_ref().bits_vartime();
512 /*
513 $mu = \lfloor log_2(p) \rfloor + 1$, so to check $p \ge 2^{bits_of_security}$, we need to
514 check $\floor log_2(p) \rfloor \ge bits_of_security$.
515
516 As cited in Linearly Homomorphic Encryption from DDH, Conjecture 5.10.1 (Cohen-Lenstra) of
517 A Course in Computational Algebraic Number Theory establishes the probability an odd prime
518 divides the order as $1 - \prod_{1 \le k \le \inf} (1 - p^{-k})$. It's clear that each
519 factor is less than one and therefore the product gets smaller and smaller. For simplicity,
520 we limit the expression to solely $k = 1$ and consider solely $1 - (1 - p^{-1})$ which is
521 equal to probability $1 / p$. In this case, it's clear how requiring the odd prime $p$ to be
522 greater than $2^{bits_of_security}$ is sufficient to achieve this goal. While a tighter bound
523 is possible, we do not bother here.
524 */
525 if (mu - 1) < bits_of_security {
526 Err(Cl15Error::SmallP)?;
527 }
528
529 let q = {
530 /*
531 Find the lowest, highest numbers `q` could be while still effecting the desired bit-length
532 of the fundamental_discriminant.
533
534 The lower bound is `(1 << (fundamental_discriminant_bit_length - 1)) / p`.
535 The upper bound is `((1 << fundamental_discriminant_bit_length) - 1) / p`.
536 */
537 let mut lower_bound_inclusive = Udk_zero_with_precision(fundamental_discriminant_bit_length);
538 lower_bound_inclusive.set_bit(fundamental_discriminant_bit_length - 1, Choice::TRUE);
539 debug_assert_eq!(lower_bound_inclusive.bits_vartime(), fundamental_discriminant_bit_length);
540 lower_bound_inclusive = lower_bound_inclusive / p.as_nz_ref();
541
542 let mut upper_bound_inclusive = Udk_zero_with_precision(fundamental_discriminant_bit_length);
543 for bit in 0 .. fundamental_discriminant_bit_length {
544 upper_bound_inclusive.set_bit(bit, Choice::TRUE);
545 }
546 debug_assert_eq!(upper_bound_inclusive.bits_vartime(), fundamental_discriminant_bit_length);
547 upper_bound_inclusive = upper_bound_inclusive / p.as_nz_ref();
548
549 // Require `q >= 4 p` (which is not prime, inherently effecting `q > 4 p`)
550 {
551 let lower_bound_inclusive_lt_4p = {
552 let lower_bound_inclusive = <_ as AsRef<[Limb]>>::as_ref(&lower_bound_inclusive);
553 let p = <_ as AsRef<[Limb]>>::as_ref(&p);
554
555 let mut borrow = Limb::ZERO;
556 let mut carry = Limb::ZERO;
557 /*
558 The virtual length is the greater length between the two numbers, though we assign an
559 extra virtual limb to `p` as `4 p` may require more limbs to represent. The iterators
560 over the numbers' limbs are extended with `0` up to the virtual length.
561 */
562 let virtual_len = lower_bound_inclusive.len().max(1 + p.len());
563 for (lower_bound_inclusive, p) in lower_bound_inclusive
564 .iter()
565 .chain(core::iter::repeat(&Limb::ZERO))
566 .take(virtual_len)
567 .zip(p.iter().chain(core::iter::repeat(&Limb::ZERO)).take(virtual_len))
568 {
569 let four_p = ((*p) << 2) | carry;
570 carry = (*p) >> (Limb::BITS - 2);
571 let _diff_limb;
572 (_diff_limb, borrow) = lower_bound_inclusive.borrowing_sub(four_p, borrow);
573 }
574 debug_assert!(bool::from(carry.is_zero()));
575 !borrow.is_zero()
576 };
577
578 // If `lower_bound_inclusive < 4 p`, set `lower_bound_inclusive = 4 p`
579 if bool::from(lower_bound_inclusive_lt_4p) {
580 if (2 + p.bits_vartime()) > fundamental_discriminant_bit_length {
581 Err(Cl15Error::NoQ)?;
582 }
583 let mut lower_bound_inclusive =
584 <_ as AsMut<[Limb]>>::as_mut(&mut lower_bound_inclusive).iter_mut();
585 let p = <_ as AsRef<[Limb]>>::as_ref(&p);
586 let mut carry = Limb::ZERO;
587 for (lower_bound_inclusive, p) in (&mut lower_bound_inclusive).zip(p) {
588 let four_p = ((*p) << 2) | carry;
589 carry = (*p) >> (Limb::BITS - 2);
590 *lower_bound_inclusive = four_p;
591 }
592 if bool::from(!carry.is_zero()) {
593 *lower_bound_inclusive.next().unwrap() = carry;
594 }
595 }
596 }
597
598 let mut seed = {
599 /*
600 Sample a starting point for `q` within `lower_bound_inclusive ..= upper_bound_inclusive`.
601
602 We do this by sampling from `0 ..= (upper_bound_inclusive - lower_bound_inclusive)` to
603 ensure this sampling has a reasonable termination bound. This sampling procedure will
604 always terminate if the last sampled byte is `0`, and therefore should terminate within
605 ~256 runs (even in the worst case where all other bits are `0`).
606 */
607 let sample_range =
608 Option::<Udk>::from(upper_bound_inclusive.checked_sub(&lower_bound_inclusive))
609 .ok_or(Cl15Error::NoQ)?;
610
611 let mut starting_point_in_range = sample_range.to_le_bytes();
612 for b in starting_point_in_range.as_mut() {
613 *b = 0;
614 }
615 while {
616 rng.fill_bytes(
617 &mut starting_point_in_range.as_mut()
618 [.. usize::try_from(sample_range.bits_vartime().div_ceil(8)).unwrap()],
619 );
620 bool::from(Udk::from_le_bytes(starting_point_in_range.clone()).ct_gt(&sample_range))
621 } {}
622 let starting_point_in_range = Udk::from_le_bytes(starting_point_in_range);
623
624 lower_bound_inclusive.checked_add(&starting_point_in_range).expect(
625 "result is less than or equal to `upper_bound_inclusive`, which has the same capacity",
626 )
627 };
628
629 let mut lower_bound_inclusive = Some(lower_bound_inclusive);
630 loop {
631 let q = match super::primes::next_prime(&mut rng, seed.clone(), bits_of_security) {
632 Ok(q) => q,
633 // If the next `q` would be too big, loop around to the smallest candidate
634 Err(super::primes::Error::Capacity) => {
635 // If we've looped multiple times, there are no numbers satisfying `q`
636 seed = lower_bound_inclusive.take().ok_or(Cl15Error::NoQ)?;
637 continue;
638 }
639 // While there may be a `q`, we are unable to find it with the requirements given to us
640 Err(super::primes::Error::NoMillerRabin) => Err(Cl15Error::NoQ)?,
641 };
642
643 if bool::from(q.ct_gt(&upper_bound_inclusive)) {
644 seed = lower_bound_inclusive.take().ok_or(Cl15Error::NoQ)?;
645 continue;
646 }
647
648 // In case this `q` isn't selected, advance the seed to `q + 1`
649 seed = match Option::<Udk>::from(q.checked_add(&Udk::one())) {
650 Some(q_plus_one) => q_plus_one,
651 // This `q` is within bounds but the next seed loops around to the lower bound
652 None => lower_bound_inclusive.take().ok_or(Cl15Error::NoQ)?,
653 };
654
655 /*
656 Discriminants must be congruent to $0$ or $3 \mod 4$, here the latter.
657
658 We only take the product of the very first limb, effectively reducing `p, q` by
659 $2^{Limb::BITS}$, as we only need what the result is congruent to $\mod 4$. This is
660 reduction preserves the desired congruency so long as `Limb::BITS >= 2`.
661 */
662 const {
663 assert!(Limb::BITS >= 2);
664 }
665 if {
666 let product =
667 <_ as AsRef<[Limb]>>::as_ref(&p)[0].wrapping_mul(<_ as AsRef<[Limb]>>::as_ref(&q)[0]);
668 (product.0 & 0b11) != 0b11
669 } {
670 continue;
671 }
672
673 /*
674 Ensure $p$ is a quadratic non-residue modulo $q$, as specified within CL15's `Gen`
675 algorithm. The stated reason is so the 2-Sylow subgroup is isomorphic to $Z/2Z$ (stated
676 to require $legendre(p, q) = legendre(q, p) = -1$).
677
678 Note per quadratic reciprocity, $legendre(p, q) = legendre(q, p)$ if and only if not both
679 $p, q$ are congruent to $3 \mod 4$. As their product is congruent to $3 \mod 4$, they
680 cannot each simultaneously be congruent to $3 \mod 4$, as $3 \mod 4$ is not a square.
681 This allows us to solely check one Legendre symbol.
682
683 With this in mind, we actually check $q$ is a quadratic non-residue modulo $p$ as $p$ is
684 a smaller number and therefore offers faster arithmetic to perform the check with.
685 */
686 if {
687 let q_mod_p = q.clone().rem(p.as_nz_ref());
688 crate::crypto_bigint::legendre_symbol(q_mod_p, &p) !=
689 ::crypto_bigint::JacobiSymbol::MinusOne
690 } {
691 continue;
692 }
693
694 break q;
695 }
696 };
697
698 let fundamental_discriminant_absolute_value = q.mul(p.as_ref());
699 debug_assert_eq!(
700 fundamental_discriminant_absolute_value.bits_vartime(),
701 fundamental_discriminant_bit_length
702 );
703
704 let p_square = p.as_ref().concatenating_square();
705
706 let non_fundamental_discriminant_absolute_value =
707 fundamental_discriminant_absolute_value.concatenating_mul(p_square.clone());
708
709 Ok(Cl15p {
710 fundamental: Cl15k { p, absolute_value: fundamental_discriminant_absolute_value },
711 p_square,
712 absolute_value: non_fundamental_discriminant_absolute_value,
713 })
714 }
715}
716
717impl<Up, Up2, Udk, Udp> Cl15p<Up, Up2, Udk, Udp> {
718 /// The fundamental discriminant.
719 #[must_use]
720 pub fn fundamental_discriminant(&self) -> &Cl15k<Up, Udk> {
721 &self.fundamental
722 }
723}
724
725impl<Up: Encoding, Up2: Encoding, Udk: Clone + AsMut<[Limb]> + Encoding, Udp: Encoding>
726 Cl15p<Up, Up2, Udk, Udp>
727{
728 /// The element of `p`-order with an easy discrete-log problem.
729 ///
730 /// This runs in time variable to the bit-length of the discriminant.
731 #[must_use]
732 pub fn f<E: Element>(&self) -> E {
733 /*
734 $b^2 + |delta| = 4 a c = p^2 + (q p p^2) = (q p + 1) p^2$
735 $a = p^2$ so $c = (q p + 1) / 4$
736
737 This `c` coefficient exists as $q p \cong 3 \mod 4$, per how `q` was chosen during the setup.
738 */
739 let c = {
740 // `q p`
741 let mut c = self.fundamental.absolute_value.clone();
742 {
743 let c = <_ as AsMut<[Limb]>>::as_mut(&mut c);
744 // `q p` -> `q p + 1`
745 let mut carry = Limb::ONE;
746 for c_limb in c.iter_mut() {
747 let new_limb;
748 (new_limb, carry) = c_limb.carrying_add(Limb::ZERO, carry);
749 *c_limb = new_limb;
750 }
751 // Shift right by two to divide by four
752 carry <<= Limb::BITS - 2;
753 for c_limb in c.iter_mut().rev() {
754 let new_limb = carry | ((*c_limb) >> 2);
755 carry = (*c_limb) << (Limb::BITS - 2);
756 *c_limb = new_limb;
757 }
758 debug_assert!(bool::from(carry.is_zero()));
759 }
760 c
761 };
762
763 let discriminant_abs = WithoutTrailingZeroBytes(self.absolute_value.to_le_bytes());
764 let discriminant_abs = discriminant_abs.as_ref();
765 let discriminant_bytes = discriminant_abs.len();
766 // This is a lossy approximation
767 let sqrt_discriminant_bytes = discriminant_bytes.div_ceil(2);
768
769 // We bound the encodings of `a, b, c` based on the discriminant
770 let a = self.p_square.to_le_bytes();
771 let a = a.as_ref();
772 let a = &a[.. sqrt_discriminant_bytes.min(a.len())];
773
774 let b_positive = Choice::TRUE;
775 let b_abs = self.fundamental.p.to_le_bytes();
776 let b_abs = b_abs.as_ref();
777 let b_abs = &b_abs[.. sqrt_discriminant_bytes.min(b_abs.len())];
778
779 let c = c.to_le_bytes();
780 let c = c.as_ref();
781 let c = &c[.. discriminant_bytes.min(c.len())];
782
783 /*
784 SAFETY:
785
786 This form is well-defined, as we defined (and calculated) the satisfactory `c` coefficient
787 above.
788
789 This form is primitive as `c = (q p + 1) / 4` and `q p + 1 is coprime to `b = p` when
790 $p \ne 1$. If $p \eq 1$, then $b = 1$ and $gcd(a, b, c) = 1$ (and the form is primitive).
791 However, as $p$ is an odd prime, the case $p \eq 1$ is unnecessary to argue.
792
793 This form is reduced as $|b| < a, b \ne a$ and $a < sqrt(|delta| / 4)$. For the latter claim,
794 we require $q > 4 p$ during our setup, where this discriminant is of form $q p^3$. Therefore,
795 $p^2 < sqrt(|delta| / 4)$.
796 */
797 unsafe { E::from_coefficients(a, (b_positive, b_abs), c, discriminant_abs) }
798 }
799}
800
801impl<Up: BitOps + Encoding, Up2, Udk: Clone + AsMut<[Limb]> + Encoding, Udp: Encoding>
802 Cl15p<Up, Up2, Udk, Udp>
803{
804 /// Take an element of the class group with non-fundamental discriminant and apply the surjection
805 /// such that it is mapped to an element of the class group with fundamental discriminant.
806 ///
807 /// This implements Algorithm 3, `GoToMaxOrder`. We specify it as follows for primitive forms
808 /// of negative discriminants where `discriminant / (p * p)` is fundamental:
809 ///
810 /// ```py
811 /// fn surject(a, b, c, p) {
812 /// discriminant = (b * b) - (4 * a * c)
813 /// fundamental_discriminant = discriminant / (p * p)
814 /// assert (fundamental_discriminant * p * p) == discriminant
815 /// (a, b) = coprime_form(a, b, c, p)
816 /// (mu, lambda, _one) = xgcd(p, a)
817 /// return reduce(
818 /// a,
819 /// (b * mu) + (a * (fundamental_discriminant % 2) * lambda),
820 /// discriminant / (p * p)
821 /// )
822 /// }
823 /// ```
824 ///
825 /// As with [`FundamentalDiscriminant::inject`], we do not specify the reduction of $b \mod 2 a$.
826 /// Instead, we again assume the existence of a reduction function, `reduce`, which inputs the
827 /// `a, b` coefficients of an unreduced form and its discriminant before yielding a reduced form.
828 /// We also assume an `xgcd` function, which for `xgcd(x, y)`, returns `(u, v, d)` such that
829 /// `u x + v y = d`.
830 ///
831 /// This function MAY panic or return an incorrect result if `element` is not of this
832 /// discriminant. This function runs in time only variable to this discriminant and
833 /// `E::a_b_c_discriminant` (which may or may not be implemented in constant-time).
834 #[cfg(feature = "alloc")] // TODO: no-`alloc`
835 #[must_use]
836 pub fn surject<E: Element>(&self, element: impl Element) -> E {
837 use crypto_bigint::{Resize as _, BoxedUint};
838
839 let (a, (b_positive, b_abs), c, discriminant_abs) = element.a_b_c_discriminant();
840 assert!(bool::from(le_malleable_eq(self.absolute_value().as_ref(), discriminant_abs.as_ref())));
841
842 // This is only vartime with regards to the length of the encoding
843 let a = BoxedUint::from_le_slice_vartime(a.as_ref());
844 let b_abs = BoxedUint::from_le_slice_vartime(b_abs.as_ref());
845 let c = BoxedUint::from_le_slice_vartime(c.as_ref());
846 let p = self.fundamental.p.to_le_bytes();
847 let p = BoxedUint::from_le_slice_vartime(p.as_ref());
848
849 let bits_precision = 2 + a.bits_precision().max(b_abs.bits_precision()).max(c.bits_precision());
850 let p = p.resize(bits_precision);
851 let (a, (mut b_positive, b_abs)) = coprime_form(
852 a.resize(bits_precision),
853 (b_positive, b_abs.resize(bits_precision)),
854 c.resize(bits_precision),
855 &p,
856 )
857 .expect("could not find a coprime form (non-primitive or unreduced?)");
858
859 /*
860 We calculate our new `b` coefficient modulo `2 a`, which represents an equivalent form.
861
862 We do not consider the `fundamental_discriminant % 2` term as we know the fundamental
863 discriminant to be odd, and therefore the term to be equal to `1`, which is the identity (as
864 it's used in a multiplication).
865
866 We do not calculate `a * lambda` but solely `a * (lambda & 1)`, as we know `a` generates a
867 `2`-subgroup of `2 a` with addition. However, as we have `(mu * p) - 1 = lambda * a`, we also
868 know that the trailing zero bits in `(mu * p) - 1` is equal to the trailing zero bits in
869 `lambda * a`. This lets us determine `(lambda & 1) == 0` as
870 `trailing_zeroes((mu * p) - 1) > trailing_zeroes(a)`.
871 */
872 let b_abs = {
873 let a = NonZero::new(a.clone())
874 .expect("`a` is non-zero for a positive definite form of negative discriminant");
875 let mu = p.invert_mod(&a).expect("`a` is coprime to `p`");
876 let lambda_is_even =
877 (mu.concatenating_mul(&p) - BoxedUint::one()).trailing_zeros().ct_gt(&a.trailing_zeros());
878
879 let a = a.get();
880 let two_a = NonZero::new(a.clone().concatenating_add(&a))
881 .expect("`2 a` is non-zero as `a` is non-zero");
882
883 let b_mu = b_abs.mul_mod(&mu, &two_a);
884 let b_mu = <_>::ct_select(&b_mu.clone().neg_mod(&two_a), &b_mu, b_positive);
885 b_positive = Choice::TRUE;
886
887 // This is a subtraction as the `lambda` coefficient is negative
888 b_mu.sub_mod(
889 &<_>::ct_select(&BoxedUint::zero_like(&a), &a, !lambda_is_even)
890 .resize(b_mu.bits_precision()),
891 &two_a,
892 )
893 };
894
895 let discriminant_abs =
896 BoxedUint::from_le_slice_vartime(self.fundamental_discriminant().absolute_value().as_ref());
897
898 // TODO: Tighten this
899 let log_2_bound =
900 8 + bits_precision.max(b_abs.bits_precision()).max(discriminant_abs.bits_precision());
901 let discriminant_abs = discriminant_abs.resize(log_2_bound);
902 /*
903 The form is valid. The numbers are within `log_2_bound`. The numbers are the same size, and
904 with a spare bit of capacity. This causes our call to `partial_reduce` to be valid.
905 */
906 let (a, (b_positive, b_abs), c) = crate::crypto_bigint::partial_reduce(
907 log_2_bound,
908 a.resize(log_2_bound),
909 (b_positive, b_abs.resize(log_2_bound)),
910 &discriminant_abs,
911 );
912 /*
913 As correct for `partial_reduce`, we are correct for `reduce`. We do tighten our bound to the
914 square root of the discriminant, but this is a bound on the output from `partial_reduce`.
915 */
916 let discriminant_bits = discriminant_abs.bits_vartime();
917 let sqrt_discriminant_bits = discriminant_bits.div_ceil(2);
918 let (a, (b_positive, b_abs), c) =
919 crate::crypto_bigint::reduce(sqrt_discriminant_bits, a, (b_positive, b_abs), c);
920
921 /*
922 SAFETY:
923
924 This form is well-defined (TODO).
925
926 This form is primitive as it has a fundamental discriminant. Per a remark following
927 Definition 5.2.3 of A Course in Computational Algebraic Number Theory by Henri Cohen,
928 any quadratic form of fundamental discriminant is primitive.
929
930 This form is reduced as we've explicitly reduced it.
931 */
932 let discriminant_bits = usize::try_from(discriminant_bits).unwrap();
933 let sqrt_discriminant_bits = usize::try_from(sqrt_discriminant_bits).unwrap();
934 unsafe {
935 E::from_coefficients(
936 &a.to_le_bytes().as_ref()[.. sqrt_discriminant_bits.div_ceil(8)],
937 (b_positive, &b_abs.to_le_bytes().as_ref()[.. sqrt_discriminant_bits.div_ceil(8)]),
938 &c.to_le_bytes()[.. discriminant_bits.div_ceil(8)],
939 &discriminant_abs.to_le_bytes()[.. discriminant_bits.div_ceil(8)],
940 )
941 }
942 }
943
944 /// Apply the coset labeling function to an element of this discriminant.
945 ///
946 /// This is equivalent to the following:
947 ///
948 /// ```py
949 /// fn coset_labeling_function(a, b, c, p) {
950 /// return inject(surject(a, b, c, p), p)
951 /// }
952 /// ```
953 ///
954 /// This function MAY panic or return an incorrect result if `element` is not of this
955 /// discriminant. This function runs in time only variable to this discriminant and
956 /// `E::a_b_c_discriminant` (which may or may not be implemented in constant-time).
957 #[cfg(feature = "alloc")] // TODO: no-`alloc`
958 #[must_use]
959 pub fn coset_labeling_function<E: Element>(&self, element: impl Element) -> E {
960 self.fundamental_discriminant().inject(self.surject::<E>(element), self.fundamental.p.as_ref())
961 }
962}
963
964impl<
965 Up: Clone + CtSelect + Zero + NegMod<Output = Up> + InvertMod<Output = Up> + BitOps + Encoding,
966 Up2: Encoding,
967 Udk,
968 Udp: Clone
969 + CtEq
970 + for<'a> Mul<&'a Up, Output = Udp>
971 + for<'a> Div<&'a NonZero<Up>, Output = Udp>
972 + Encoding,
973> Cl15p<Up, Up2, Udk, Udp>
974{
975 /// Solve for the discrete logarithm of an element of order-`p`.
976 ///
977 /// We specify this as follows, for a reduced form's `a, b` coefficients and the prime `p`:
978 ///
979 /// ```py
980 /// fn discrete_logarithm(a, b, p) {
981 /// if (a, b) == (1, 1) {
982 /// return 0
983 /// }
984 /// assert a == p^2
985 /// x_tilde = (b / p)
986 /// assert (x_tilde * p) == b
987 /// (u, _v, _one) = xgcd(x_tilde, p)
988 /// return u
989 /// }
990 /// ```
991 ///
992 /// The `if` is used to check if the element is identity and therefore has a discrete-logarithm
993 /// of `0`. Else, we apply the defined methodology of `Solve` (presented in Figure 2) from
994 /// Linearly Homomorphic Encryption from DDH by Guilhem Castagnos and Fabien Laguillaumie
995 /// (<https://eprint.iacr.org/2025/047>). We explicitly specify the calculation of
996 /// $\tilde{x}^{-1} \mod p$ via `xgcd(x_tilde, p)` as we've already assumed the existence of an
997 /// `xgcd` function elsewhere in our specification, though other methods would work as well and
998 /// MAY be used instead (such as by Fermat's Little Theorem or a Bernstein-Yang inversion).
999 ///
1000 /// This function runs time only variable to this discriminant and `E::a_b_c_discriminant` (which
1001 /// may or may not be implemented in constant-time).
1002 #[must_use]
1003 pub fn discrete_logarithm(&self, element: impl Element) -> CtOption<Up> {
1004 let identity = element.is_identity();
1005 let (a, (b_positive, b_abs), _c, discriminant_abs) = element.a_b_c_discriminant();
1006
1007 let correct_discriminant =
1008 le_malleable_eq(self.absolute_value.to_le_bytes().as_ref(), discriminant_abs.as_ref());
1009 let correct_a_coefficient = le_malleable_eq(self.p_square.to_le_bytes().as_ref(), a.as_ref());
1010
1011 let b_abs = b_abs.as_ref();
1012
1013 let b_abs = {
1014 /*
1015 We use the little-endian encoding of the absolute value of the discriminant as we know it
1016 will have sufficient size to contain the `b` coefficient, bounded to be less than the
1017 square root of the discriminant.
1018 */
1019 let mut repr = self.absolute_value.to_le_bytes();
1020 // Zeroize the representation
1021 {
1022 let repr = repr.as_mut();
1023 for b in repr.iter_mut() {
1024 *b = 0;
1025 }
1026 // Set it to the `b` coefficient
1027 let mutual_len = repr.len().min(b_abs.len());
1028 repr[.. mutual_len].copy_from_slice(&b_abs[.. mutual_len]);
1029 }
1030 Udp::from_le_bytes(repr)
1031 };
1032
1033 let x_tilde = b_abs.clone().div(self.fundamental.p.as_nz_ref());
1034 let correct_b_coefficient = x_tilde.clone().mul(self.fundamental.p.as_ref()).ct_eq(&b_abs);
1035
1036 let x_tilde = x_tilde.to_le_bytes();
1037 let x_tilde = {
1038 // `x_tilde <= p` per Proposition 1 of Linearly Homomorphic Encryption from DDH
1039 let x_tilde = x_tilde.as_ref();
1040 let mut p_repr = self.fundamental.p.as_ref().to_le_bytes();
1041 {
1042 let p_repr = p_repr.as_mut();
1043 for b in p_repr.iter_mut() {
1044 *b = 0;
1045 }
1046 let mutual_len = p_repr.len().min(x_tilde.len());
1047 p_repr[.. mutual_len].copy_from_slice(&x_tilde[.. mutual_len]);
1048 }
1049 Up::from_le_bytes(p_repr)
1050 };
1051 let inverse =
1052 Up::ct_select(&x_tilde.neg_mod(self.fundamental.p.as_nz_ref()), &x_tilde, b_positive)
1053 .invert_mod(self.fundamental.p.as_nz_ref())
1054 .filter_by(correct_discriminant & correct_a_coefficient & correct_b_coefficient);
1055 inverse.or(CtOption::new(
1056 Up::zero_like(self.fundamental.p.as_ref()),
1057 correct_discriminant & identity,
1058 ))
1059 }
1060}