class_groups/element.rs
1use core::{fmt::Debug, ops::Neg};
2#[cfg(feature = "std")]
3use std::io;
4
5use crypto_bigint::{Choice, CtOption};
6
7/// A primitive positive definite binary quadratic form of negative odd discriminant (not
8/// necessarily fundamental) with `a, b, c` coefficients.
9///
10/// # Safety
11///
12/// Implementations MUST return well-defined coefficients for a primitive _reduced_ positive
13/// definite binary quadratic form of a negative odd discriminant (the one whose value is
14/// yielded). It is undefined behavior to not do so.
15pub unsafe trait Coefficients {
16 /// Fetch the `a, b, c` coefficients of the single reduced form equivalent to this form and the
17 /// absolute value of its negative discriminant.
18 ///
19 /// The coefficients and absolute value of the discriminant are little-endian encoded.
20 /// `b` is represented by a sign bit, if `b` is positive (greater than or equal to zero), and the
21 /// encoding of its absolute value. Values MAY have trailing zeroes.
22 #[expect(clippy::type_complexity)]
23 fn a_b_c_discriminant(
24 self,
25 ) -> (impl AsRef<[u8]>, (Choice, impl AsRef<[u8]>), impl AsRef<[u8]>, impl AsRef<[u8]>);
26}
27
28/// An binary quadratic form corresponding to an element of a class group.
29///
30/// This binary quadratic form is bound to being a primitive positive definite binary
31/// quadratic form of negative odd discriminant (not necessarily fundamental). We bound to
32/// primitive forms to ensure forms are invertible and for faster composition. We bound to
33/// positive definite (of negative discriminant) forms to ensure there's a single reduced form, and
34/// as the theory sufficiently diverges it doesn't make practical sense to attempt to
35/// simultaneously support both in a single optimized library. We bound to odd discriminants as
36/// specializing enables ~5% faster reduction. Additionally, all of these bounds are allowed by the
37/// currently desired use cases.
38///
39/// This binary quadratic form has a specific discriminant. Operations between binary quadratic
40/// forms with distinct discriminants are _always_ undefined behavior (and technically considered
41/// `unsafe` even if not so annotated) and MUST NOT be done.
42///
43/// Operations with the binary quadratic forms are not notated multiplicatively but additively.
44/// The composition of two forms is referred to as addition, and the composition of a form with
45/// itself is referred to as doubling. The inverse of an element is notated as its negation.
46///
47/// Implementations of this trait MAY run in variable time.
48pub trait Element:
49 Sized + Send + Sync + Clone + PartialEq + Eq + Debug + Neg<Output = Self> + Coefficients
50{
51 /// The identity element.
52 ///
53 /// The negative discriminant is specified by the little-endian encoding of its absolute value.
54 ///
55 /// This MUST panic if the discriminant is not a valid odd discriminant.
56 fn identity(discriminant_abs: impl AsRef<[u8]>) -> Self;
57
58 /// If this element is the identity.
59 fn is_identity(&self) -> Choice;
60
61 /// Double this element.
62 ///
63 /// This is generally faster than adding an element to itself as it's allowed to specialize on
64 /// this special case.
65 #[must_use]
66 fn double(&self) -> Self;
67 /// Add two elements.
68 #[must_use]
69 fn add(&self, other: &Self) -> Self;
70 /// Subtract one element from another.
71 #[must_use]
72 fn sub(&self, other: Self) -> Self;
73
74 /// Load a form from its coefficients.
75 ///
76 /// The coefficients and absolute value of the discriminant are little-endian encoded. `b` is
77 /// represented by a sign bit, if `b` is positive (greater than or equal to zero), and the
78 /// encoding of its absolute value. Values MAY have trailing zeroes.
79 ///
80 /// # Safety
81 ///
82 /// The coefficients MUST specify a primitive _reduced_ positive definite binary quadratic form
83 /// of negative odd discriminant, the one specified via `discriminant_abs`. Implementations MAY
84 /// exhibit undefined behavior if any of these requirements aren't satisfied, hence this being
85 /// marked `unsafe`. This MUST NOT be unsafe for any other reason (such as if the coefficients
86 /// exceed the type's bounds) but MAY panic if documented bounds on the discriminant aren't met.
87 // Currently, all provided implementations will simply be incorrect (and may panic) if these
88 // conditions aren't met. The usage of `unsafe` is simply to allow implementations to introduce
89 // `unsafe` operations around these preconditions, when all loaded forms should be from
90 // (un)compressed encodings which perform validation at time of decode.
91 #[must_use]
92 unsafe fn from_coefficients(
93 a: impl AsRef<[u8]>,
94 b: (Choice, impl AsRef<[u8]>),
95 c: impl AsRef<[u8]>,
96 discriminant_abs: impl AsRef<[u8]>,
97 ) -> Self;
98
99 /// Compress an element.
100 ///
101 /// This MUST implement the defined specification for the compression of binary quadratic forms.
102 /// Implementations MUST only error if the underlying IO errors, being error-free themselves.
103 /// This allows callers to `unwrap` this result when the underlying IO is known to be error-free
104 /// (such as when a `Vec`).
105 ///
106 /// The provided implementation runs in variable time and MAY panic for absurdly large
107 /// coefficients.
108 #[cfg(feature = "std")]
109 fn compress(self, mut writer: impl io::Write) -> io::Result<()> {
110 use crypto_bigint::{NonZero, BoxedUint};
111
112 let (a, (b_positive, b_abs), _c, discriminant_abs) = self.a_b_c_discriminant();
113 let a = a.as_ref();
114 let b_abs = b_abs.as_ref();
115 let discriminant_abs = discriminant_abs.as_ref();
116
117 let a = BoxedUint::from_le_slice_vartime(a);
118 let a = NonZero::new(a).expect("`a > 0` when `delta < 0`");
119
120 let b_abs = BoxedUint::from_le_slice_vartime(b_abs);
121
122 let discriminant_abs = BoxedUint::from_le_slice_vartime(discriminant_abs);
123
124 writer.write_all(&crate::crypto_bigint::encode_compressed_binary_quadratic_form(
125 a,
126 b_positive,
127 b_abs,
128 &discriminant_abs,
129 ))
130 }
131
132 /// Decompress an element of the specified discriminant.
133 ///
134 /// This MUST implement the defined specification for the decompression of binary quadratic
135 /// forms. The discriminant MUST be negative and odd, specified by the little-endian encoding of
136 /// its absolute value.
137 ///
138 /// The provided implementation runs in variable time and MAY error for absurdly large
139 /// discriminants.
140 #[cfg(feature = "std")]
141 fn decompress(reader: impl io::Read, discriminant_abs: impl AsRef<[u8]>) -> io::Result<Self> {
142 let discriminant_abs = discriminant_abs.as_ref();
143
144 {
145 let lsb = discriminant_abs.first().ok_or(io::Error::other("zero-length discriminant"))?;
146 if (lsb & 1) != 1 {
147 Err(io::Error::other("non-odd discriminant"))?;
148 }
149 }
150
151 let (a, (b_positive, b_abs), c) =
152 crate::crypto_bigint::decode_compressed_binary_quadratic_form(
153 reader,
154 &::crypto_bigint::BoxedUint::from_le_slice(
155 discriminant_abs,
156 u32::try_from(8 * discriminant_abs.len())
157 .map_err(|_| io::Error::other("absurdly large discriminant?"))?,
158 )
159 .map_err(|e| {
160 io::Error::other(alloc::format!(
161 "container overflowed despite precision proportional to length of the encoding: {e:?}"
162 ))
163 })?,
164 )
165 .map_err(|e| io::Error::other(alloc::format!("{e:?}")))?;
166 let a = a.to_le_bytes();
167 let b_abs = b_abs.to_le_bytes();
168 let c = c.to_le_bytes();
169
170 /*
171 SAFETY: These coefficients must be well-defined for this to be safe, and
172 `decode_compressed_binary_quadratic_form` is validated to return a well-defined primitive
173 reduced positive definite binary quadratic form of the specified negative discriminant. It
174 DOES NOT bound the discriminant to be odd, as we do here, yet this function already checked
175 the discriminant is odd.
176 */
177 Ok(unsafe { Self::from_coefficients(a, (b_positive, b_abs), c, discriminant_abs) })
178 }
179
180 /// Encode an element without compression.
181 ///
182 /// This SHOULD NOT be used by protocols. The compressed encoding SHOULD be used unless there's
183 /// an explicit reason to use uncompressed encodings (such as the stronger bounds on termination
184 /// or amenability for constant-time implementations).
185 ///
186 /// This MUST implement the defined specification for the uncompressed encoding of binary
187 /// quadratic forms.
188 fn uncompressed_encode(self) -> impl AsRef<[u8]>;
189
190 /// Decode an element of the specified discriminant without compression.
191 ///
192 /// This SHOULD NOT be used by protocols. The compressed encoding SHOULD be used unless there's
193 /// an explicit reason to use uncompressed encodings (such as the stronger bounds on termination
194 /// or amenability for constant-time implementations).
195 ///
196 /// This MUST implement the defined specification for the uncompressed decoding of binary
197 /// quadratic forms. The discriminant MUST be negative and odd, specified by the little-endian
198 /// encoding of its absolute value. Implementations MUST return `None` if `buf.len()` is not
199 /// exactly `2 * ((floor(log_2(-discriminant)) / 2) + 1).div_ceil(8)` OR if the discriminant is
200 /// even. Implementations MAY not support discriminants whose absolute values are encoded with
201 /// trailing zero bytes.
202 fn uncompressed_decode(
203 buf: impl AsRef<[u8]>,
204 discriminant_abs: impl AsRef<[u8]>,
205 ) -> CtOption<Self>;
206
207 /// Create an element of this type from another element.
208 fn from(source: impl Element) -> Self {
209 let (a, b, c, discriminant) = source.a_b_c_discriminant();
210 /*
211 SAFETY: These coefficients must be well-defined for this to be safe,
212 and `a_b_c_discriminant` is bounded to return well-defined coefficients.
213
214 If we wanted a safe variant, we could instead defer to the uncompressed encoding, but that
215 would validate the point on decode to ensure its safety (which is unnecessary for our
216 purposes).
217 */
218 unsafe { Self::from_coefficients(a, b, c, discriminant) }
219 }
220
221 /// Sample a uniform element from a subgroup of the class group and return its square.
222 ///
223 /// The discriminant MUST be negative and is specified by the little-endian encoding of its
224 /// absolute value. The discriminant MUST be odd.
225 ///
226 /// This element is deterministic to the seed, has an unknown relationship to other elements of
227 /// the class group, and may be presumed to be a generator of (most of) the squares of the class
228 /// group. These properties make this function suitable for use as a hash-to-element for _some_
229 /// use cases, and we claim _most_ of the desirable use cases. This function MUST NOT be assumed
230 /// to output uniform elements of the class group or to be indistinguishable. The element is
231 /// squared as the Decisional Diffie-Hellman problem is easy over the entire class group and it's
232 /// its squares which form a generally-desirable subgroup
233 /// ("Linearly Homomorphic Encryption from DDH" by Guilhem Castagnos and Fabien Laguillaumie,
234 /// <https://eprint.iacr.org/2015/047>, Appendix B.4).
235 ///
236 /// Implementations MUST implement the specification for sampling a random element of the class
237 /// group, as the provided implementation does, with identical results. The provided
238 /// implementation executes in variable time and all implementations SHOULD be assumed to execute
239 /// in variable time due to the specification only having a statistical bound for its termination
240 /// (requiring sampling a prime number).
241 ///
242 /// "How (not) to hash into class groups of imaginary quadratic fields?", by
243 /// István András Seres, Péter Burcsi, and Péter Kutas (<https://eprint.iacr.org/2024/034>), is
244 /// referred to as primary academic reference for the analysis and justification of such hash
245 /// functions. We specify (and implement) the second construction, attributed to the conference
246 /// version of "Efficient verifiable delay functions" by Benjamin Wesolowski, and _not_ the
247 /// "Improved version". This is _not_ uniform over the class group as a whole, solely the binary
248 /// quadratic forms of prime ideal, assuming the underlying hash-to-prime function is itself
249 /// uniform. This is presented as _sufficient_ for most purposes and is remarkably
250 /// straightforward to implement, hence it being the reasonable choice for specification.
251 ///
252 /// ```py
253 /// fn next_prime_ideal_squared(seed, discriminant) {
254 /// # Assert the discriminant is negative and sufficiently large there is such a prime ideal
255 /// assert discriminant < -200
256 /// # Assert the seed is within the expected bound
257 /// assert 0 <= seed
258 /// assert seed <= sqrt((-discriminant) / 4)
259 ///
260 /// i = 0
261 /// seed = next_odd_prime(seed)
262 /// if seed > sqrt((-discriminant) / 4) {
263 /// # Increment modulo our upper bound to the first odd prime
264 /// seed = 3
265 /// }
266 /// while jacobi(discriminant, seed) != 1 {
267 /// i += 1
268 /// seed = next_odd_prime(seed + 1)
269 /// if seed > sqrt((-discriminant) / 4) {
270 /// seed = 3
271 /// }
272 /// }
273 ///
274 /// b = mod_sqrt(delta, p)
275 /// if (i % 2) == 1 {
276 /// b = -b
277 /// }
278 ///
279 /// # Return the composition of the sampled prime ideal with itself
280 /// prime_ideal = (p, b)
281 /// return composition(prime_ideal, prime_ideal)
282 /// }
283 /// ```
284 ///
285 /// `seed` is required to be a uniform element in the range $[0, sqrt(|discriminant| / 4)]$.
286 /// `next_odd_prime` tests if `seed` is an odd prime, incrementing it if it is not, until it is.
287 /// `jacobi(x, y)` yields the Jacobi symbol of `x` over `y` where as `y` is an odd prime, this is
288 /// equivalent to the Lagrange symbol. `mod_sqrt(x, y)` returns the _odd_ square root of `x`
289 /// modulo `y` such that $mod_sqrt(x, y)^2 \cong x \mod y$ if one exists, which our precondition
290 /// of checking the Jacobi symbol ensures. `mod_sqrt` is RECOMMENDED to be implemented via the
291 /// Tonelli-Shanks algorithms but MAY be implemented with alternatives such as Cipolla's.
292 /// `composition` returns the composition (the result of the group operation, the product in
293 /// multiplicative notation, the sum in additive notation as this library generally prefers) of
294 /// two binary quadratic forms (corresponding to `NUCOMP` or similar, or as the first form is
295 /// equal to the second form, `NUDUPL` or similar).
296 ///
297 /// For the validity of the results, we primarily defer to
298 /// "How (not) to hash into class groups of imaginary quadratic fields?" (which did prove this
299 /// construction secure and uniform to the prime ideals for primes in the range
300 /// $[0, sqrt(|discriminant| / 4)]$). We choose our sign differently from the suggested negative
301 /// if $p % 3 = 2$, as for any prime $p$, that would fix a single $b$ coefficient (despite it
302 /// naturally having two potential solutions as $|b| < a$). We intead use the amount of rejected
303 /// non-residues to decide the sign of $b$, which is uniform modulo `2` if the distribution of
304 /// prime quadratic residues to prime quadratic non-residues in a prime field is itself uniform.
305 /// The ordinality of the set of non-zero quadratic residues is equivalent to the ordinality of
306 /// the set of non-zero quadratic non-residues, so this isn't impossible, though exact analysis
307 /// regarding the distribution of primes and the distribution of quadratic non-residues within a
308 /// prime field have been open problems with decades (if not centuries, if not millenia) of
309 /// literature. Even though we can't prove a tight bound for the bias of this method, we do claim
310 /// this method sufficient for our purposes.
311 ///
312 /// The provided implementation MAY panic upon invalid or absurdly large inputs. It accepts an
313 /// RNG to perform primality tests against the prime numbers with though the RNG is not used to
314 /// sample potential coefficients for the binary quadratic form and will not impact the result
315 /// (except if a primality test fails). `bits_of_security` parameterizes the statistical odds of
316 /// a sampled prime number actually being prime. The provided implementation MAY panic if
317 /// `bits_of_security` causes there to be no recognized configuration for the Miller-Rabin
318 /// primality tests, for the prime numbers considered as candidates.
319 #[cfg(feature = "alloc")] // TODO: no-`alloc`
320 fn next_prime_ideal_squared(
321 mut rng: impl rand::CryptoRng,
322 seed: crypto_bigint::BoxedUint,
323 discriminant_abs: impl AsRef<[u8]>,
324 bits_of_security: u32,
325 ) -> Self {
326 use crypto_bigint::{CtEq as _, NonZero, Odd, Resize as _, ConcatenatingSquare as _, BoxedUint};
327 use crate::crypto_bigint::sqrt_mod_p_vartime;
328
329 let discriminant_abs = discriminant_abs.as_ref();
330 assert_eq!(discriminant_abs[0] & 1, 1, "discriminant wasn't odd");
331 let discriminant_abs = BoxedUint::from_le_slice_vartime(discriminant_abs);
332 // TODO: Confirm this bound is tightly defined
333 assert!(discriminant_abs > BoxedUint::from(200u8));
334 // As the discriminant is odd, this integer is less than the fractional square root which we
335 // have as an exclusive bound
336 let inclusive_end = discriminant_abs.wrapping_shr_vartime(2).floor_sqrt();
337 assert!(seed <= inclusive_end);
338 let mut seed = seed.resize(inclusive_end.bits_precision());
339 if seed.bits_vartime() <= 2 {
340 seed = BoxedUint::from(3u8).resize(inclusive_end.bits_precision());
341 }
342
343 let mut i = 0u64;
344 seed = match super::primes::next_prime(&mut rng, seed, bits_of_security) {
345 Ok(seed) => seed,
346 // Set this past `inclusive_end` so the following code wraps it
347 Err(super::primes::Error::Capacity) => inclusive_end.concatenating_add(BoxedUint::one()),
348 Err(super::primes::Error::NoMillerRabin) => {
349 panic!("bits of security effected no Miller-Rabin configuration")
350 }
351 };
352 if seed > inclusive_end {
353 seed = BoxedUint::from(3u8);
354 }
355 let mut b_abs;
356 while {
357 let seed = Odd::new(seed.clone()).expect("odd prime wasn't odd?");
358 let discriminant_abs_mod_a = discriminant_abs.rem(seed.as_nz_ref());
359 let discriminant_mod_a = discriminant_abs_mod_a.neg_mod(seed.as_nz_ref());
360 b_abs = sqrt_mod_p_vartime(discriminant_mod_a, &seed);
361 b_abs.is_none()
362 } {
363 i += 1;
364 seed = match super::primes::next_prime(
365 &mut rng,
366 seed.concatenating_add(BoxedUint::one()),
367 bits_of_security,
368 ) {
369 Ok(seed) => seed,
370 Err(super::primes::Error::Capacity) => inclusive_end.concatenating_add(BoxedUint::one()),
371 Err(super::primes::Error::NoMillerRabin) => {
372 panic!("bits of security effected no Miller-Rabin configuration")
373 }
374 };
375 if seed > inclusive_end {
376 seed = BoxedUint::from(3u8);
377 }
378 seed = seed.resize(inclusive_end.bits_precision());
379 }
380
381 let (b_positive, b_abs) = {
382 let b_abs = b_abs.unwrap();
383
384 #[cfg(debug_assertions)]
385 {
386 let seed = NonZero::new(seed.clone()).unwrap();
387 debug_assert_eq!(
388 b_abs.concatenating_square().rem(&seed),
389 discriminant_abs.rem(&seed).neg_mod(&seed)
390 );
391 }
392
393 let b_negative = (i & 1).ct_eq(&1);
394 let b_positive = !b_negative;
395 (b_positive, b_abs)
396 };
397
398 let a = seed;
399
400 // $b^2 - 4ac = -|delta|, b^2 + |delta| = 4ac$ as $delta < 0$
401 // TODO: Add a proof on why `b^2 + |delta|` is divisible by `4ac`
402 let (four_c, zero) = (b_abs.concatenating_square().concatenating_add(&discriminant_abs))
403 .div_rem(&NonZero::new(a.clone()).unwrap());
404 assert!(
405 bool::from(zero.is_zero()),
406 "didn't correctly sample a prime ideal ($b^2 + |delta|$ not divisible by `a`)"
407 );
408 let (c, zero) = four_c.div_rem(&NonZero::new(BoxedUint::from(4u8)).unwrap());
409 assert!(
410 bool::from(zero.is_zero()),
411 "didn't correctly sample a prime ideal ($(b^2 + |delta|)/a$ not divisible by `4`)"
412 );
413
414 let trim = |number: BoxedUint| {
415 let mut number = number.to_le_bytes().to_vec();
416 while number.last() == Some(&0) {
417 number.pop().unwrap();
418 }
419 number
420 };
421
422 /*
423 Per Lemma 5.3.4 of A Course in Computational Algebraic Number Theory by Henri Cohen, if
424 $a < sqrt(|D| / 4)$ and $-a < b \le a$, then the form is reduced. We bound
425 $a \le \lfloor sqrt(|D| / 4) \rfloor < sqrt(|D| / 4)$, the former explicitly, the latter by
426 virtue of $D$ being odd (and therefore ensuring a fractional result for the ideal
427 evaluation).
428
429 We know our form is primitive as $a$ is prime and $b < a$.
430
431 The `c` coefficient does satisfy the equation $b^2 - 4 a c = delta$.
432
433 The discriminant was bound to be negative and odd, the former inherent by our treatment of
434 it, the latter explicitly checked with an assertion.
435
436 Therefore, this is safe to call.
437 */
438 let prime_ideal = unsafe {
439 Self::from_coefficients(trim(a), (b_positive, trim(b_abs)), trim(c), trim(discriminant_abs))
440 };
441 prime_ideal.double()
442 }
443}