Skip to main content

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}