fp/fp_ext.rs
1//! Generic extension field $\mathbb{F}_{p^M} = \mathbb{F}_p\[x\] / (f(x))$.
2//!
3//! # Overview
4//!
5//! Given a prime $p$ and a monic irreducible polynomial $f$ of degree
6//! $M$ over $\\mathbb{F}\_{p}$, the extension field $\mathbb{F}_{{p^M}} =
7//! \mathbb{F}_p\[x\] / (f(x))$ is the set of polynomials of degree
8//! $M$ with coefficients in $\mathbb{F}_p$, where arithmetic is done
9//! modulo $f$.
10//!
11//! This module provides a single generic type [`FpExt<MOD, LIMBS, M,
12//! N, P, TSCONTS>`] that covers *any* such extension. The
13//! irreducible polynomial is supplied via the zero-size marker trait
14//! [`IrreduciblePoly`].
15//!
16//! # Example
17//!
18//! ```
19//! use crypto_bigint::{const_prime_monty_params, Uint};
20//! use fp::field_ops::FieldOps;
21//! use fp::fp_element::FpElement;
22//! use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants};
23//!
24//! /* Setup the underlying prime field */
25//! const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2);
26//! type Fp19 = FpElement<Fp19Mod, 1>;
27//!
28//! fn fp(n: u64) -> Fp19 {
29//! Fp19::from_u64(n)
30//! }
31//!
32//! /* Setput the irreducible polynomial */
33//! struct QuadPoly;
34//! impl IrreduciblePoly<Fp19Mod, 1, 2> for QuadPoly {
35//! fn modulus() -> [Fp19; 2] {
36//! [Fp19::one(), Fp19::zero()]
37//! }
38//! }
39//!
40//! /* Setup the Tonelli--Shanks constants */
41//! struct TSQuad;
42//! impl TonelliShanksConstants<Fp19Mod, 1, 2, 1> for TSQuad {
43//! // p^2 - 1
44//! const ORDER: Uint<1> = Uint::<1>::from_u64(360);
45//! // (p^2 - 1) / 2
46//! const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180);
47//! // p^2 - 1 = 2^S * T with T odd
48//! const S: u64 = 3;
49//! const T: Uint<1> = Uint::<1>::from_u64(45);
50//! // (T - 1) / 2
51//! const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22);
52//! // 2^(S - 1)
53//! const TWOSM1: Uint<1> = Uint::<1>::from_u64(4);
54//! // 2^S root of unity
55//! fn root_of_unity() -> [FpElement<Fp19Mod, 1>; 2] {
56//! [Fp19::from_u64(3), Fp19::from_u64(3)]
57//! }
58//! }
59//!
60//! /* Define the extension field */
61//! type F19_2 = FpExt<Fp19Mod, 1, 2, 1, QuadPoly, TSQuad>;
62//!
63//! /* Some standard tests */
64//! let a = F19_2::new([fp(3), fp(2)]);
65//! let ainv = F19_2::new([fp(9), fp(13)]);
66//! let asquare = F19_2::new([fp(5), fp(12)]);
67//! assert_eq!(a.invert().unwrap(), ainv);
68//! assert_eq!(a.square(), asquare);
69//! assert_eq!(asquare.sqrt().unwrap().square(), asquare);
70//! ```
71//!
72//! # Representation
73//!
74//! An element $a \in \mathbb{F}\_{p^M}$ is stored as exactly $M$
75//! base-field coefficients so that `coeffs = [a_0, a_1, ...,
76//! a_{M-1}]` cooresponds to
77//! $$
78//! a_0 + a_1 x + ... + a_{M-1}x^{M-1} \pmod{ f(x) }
79//! $$
80//!
81//! # Operations and costs
82//!
83//! | Operation | Algorithm | Base-field cost |
84//! |-------------|----------------------------------|--------------------------|
85//! | Add / Sub | Coefficient-wise | $M$ adds |
86//! | Negate | Coefficient-wise | $M$ negs |
87//! | Double | Coefficient-wise | $M$ doubles |
88//! | Multiply | Schoolbook + reduction mod f | $M^2$ muls + $M^2$ adds |
89//! | Square | Same as multiply (self * self) | $M^2$ muls + $M^2$ adds |
90//! | Invert | Polynomial extended GCD | $O(M^2)$ |
91//! | Frobenius | self^p via square-and-multiply | $O(M^2 \log p)$ |
92//! | Norm | Product of M Galois conjugates | $O(M^3 \log p)$ |
93//! | Trace | Sum of M Galois conjugates | $O(M^2 \log p)$ |
94
95use core::ops::{Add, Mul, Neg, Sub};
96use std::marker::PhantomData;
97
98use crate::field_ops::{FieldFromRepr, FieldOps, FieldRandom};
99use crate::fp_element::FpElement;
100use crypto_bigint::{modular::ConstPrimeMontyParams, Uint};
101use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
102
103// ===========================================================================
104// IrreduciblePoly — one thing callers need to implement for a new field
105// ===========================================================================
106
107/// Supplies the monic irreducible polynomial
108/// $$f(x) = x^M + c_{M-1} x^{M-1} + ... + c_1 x + c_0$$
109/// that defines the extension $\mathbb{F}_p^M = \mathbb{F}_p\[x\] / (f(x))$.
110///
111/// # Convention
112///
113/// `modulus()` returns the **non-leading** coefficients in ascending degree order:
114/// `[c_0, c_1, ..., c_{M-1}]`.
115/// The leading coefficient 1 (coefficient of $x^M$) is implicit.
116///
117/// # Example: $\mathbb{F}_{19^2}$
118///
119/// The polynomial $f(x) = x^2 + 1$ is irreducible over $\mathbb{F}_{19}$.
120/// ```
121/// # use crypto_bigint::{const_prime_monty_params, Uint};
122/// # use fp::fp_element::FpElement;
123/// # use fp::field_ops::FieldOps;
124/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants};
125/// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2);
126/// type Fp19 = FpElement<Fp19Mod, 1>;
127/// struct MyQuadPoly;
128/// impl IrreduciblePoly<Fp19Mod, 1, 2> for MyQuadPoly {
129/// fn modulus() -> [FpElement<Fp19Mod, 1>; 2] {
130/// // f(x) = x^2 + 0x + 1 -> [1, 0]
131/// [FpElement::one(), FpElement::zero()]
132/// }
133/// }
134/// ```
135///
136/// # Example: $\mathbb{F}_{19^3}$
137///
138/// Note that $f(x) = x^3 + 17$ is irreducible over $\mathbb{F}_{19}$.
139/// ```
140/// # use crypto_bigint::{const_prime_monty_params, Uint};
141/// # use fp::fp_element::FpElement;
142/// # use fp::field_ops::FieldOps;
143/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants};
144/// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2);
145/// type Fp19 = FpElement<Fp19Mod, 1>;
146/// struct MyCubicPoly;
147/// impl IrreduciblePoly<Fp19Mod, 1, 3> for MyCubicPoly {
148/// fn modulus() -> [FpElement<Fp19Mod, 1>; 3] {
149/// // f(x) = x^3 + 0x^2 + 0x + 17 -> [17, 0, 0]
150/// [FpElement::from_u64(17), FpElement::zero(), FpElement::zero()]
151/// }
152/// }
153/// ```
154pub trait IrreduciblePoly<MOD, const LIMBS: usize, const M: usize>: 'static
155where
156 MOD: ConstPrimeMontyParams<LIMBS>,
157{
158 /// Non-leading coefficients `[c_0, c_1, ..., c_{M-1}]` of the defining polynomial.
159 fn modulus() -> [FpElement<MOD, LIMBS>; M];
160}
161
162/*
163===========================================================================
164TonelliShanksConstants - The only other thing users have to
165implement for a new field
166===========================================================================
167*/
168
169/// Supplies the constants for Tonelli--Shanks algorithm.
170///
171/// The `usize` constant `N` in the trait definition should be chosen
172/// so that $p^M$ fits inside a `Uint<N>`. Required constants are
173/// listed below all are derived by writing $p^M - 1 = 2^S T$ for some
174/// odd $T$.
175///
176/// # Example: $\mathbb{F}_{19^2}$
177/// Note that we have a factorisation $19^2 - 1 = 2^3 \cdot 45$
178/// ```
179/// # use crypto_bigint::{const_prime_monty_params, Uint};
180/// # use fp::fp_element::FpElement;
181/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants};
182/// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2);
183/// type Fp19 = FpElement<Fp19Mod, 1>;
184/// struct TSQuad;
185/// impl TonelliShanksConstants<Fp19Mod, 1, 2, 1> for TSQuad {
186/// // p^2 - 1
187/// const ORDER: Uint<1> = Uint::<1>::from_u64(360);
188/// // (p^2 - 1) / 2
189/// const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180);
190/// // p^2 - 1 = 2^S * T with T odd
191/// const S: u64 = 3;
192/// const T: Uint<1> = Uint::<1>::from_u64(45);
193/// // (T - 1) / 2
194/// const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22);
195/// // 2^(S - 1)
196/// const TWOSM1: Uint<1> = Uint::<1>::from_u64(4);
197/// // 2^S root of unity
198/// fn root_of_unity() -> [FpElement<Fp19Mod, 1>; 2] {
199/// [Fp19::from_u64(3), Fp19::from_u64(3)]
200/// }
201/// }
202/// ```
203///
204/// # Todo
205///
206/// - [ ] Compute these constants at compile time to bury this from
207/// the end user.
208pub trait TonelliShanksConstants<MOD, const LIMBS: usize, const M: usize, const N: usize>:
209 'static
210where
211 MOD: ConstPrimeMontyParams<LIMBS>,
212{
213 /// Multiplicative group order $p^M - 1$
214 const ORDER: Uint<N>;
215
216 /// Half mult group order $(p^M - 1) / 2$
217 const HALF_ORDER: Uint<N>;
218
219 /// Write $p^M - 1 = 2^S T$ with $T$ odd
220 const S: u64;
221
222 /// Write $p^M - 1 = 2^S T$ with $T$ odd
223 const T: Uint<N>;
224
225 /// Constant $2^{S - 1}$
226 const TWOSM1: Uint<N>;
227
228 /// Projenator exponent of the TS algorithm this is $(T - 1) / 2$
229 const PROJENATOR_EXP: Uint<N>;
230
231 /// $2^S$ root of unity
232 fn root_of_unity() -> [FpElement<MOD, LIMBS>; M];
233}
234
235// ===========================================================================
236// FpExt — element of Fp^M
237// ===========================================================================
238
239/// An element of the extension field $\mathbb{F}_{p^M} =
240/// \mathbb{F}_p\[x\] / (f(x))$.
241///
242/// `P` is a zero-size marker type implementing [`IrreduciblePoly`].
243/// `M` is the extension degree (number of base-field coefficients stored).
244/// `N` is the number limbs needed to store p^M
245pub struct FpExt<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
246where
247 MOD: ConstPrimeMontyParams<LIMBS>,
248 P: IrreduciblePoly<MOD, LIMBS, M>,
249 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
250{
251 /// Coefficients in ascending degree, that is `coeffs[i]` is
252 /// the coefficient of $x^i$ (zero indexed).
253 pub coeffs: [FpElement<MOD, LIMBS>; M],
254 _phantom: PhantomData<P>,
255 _order: PhantomData<TSCONSTS>,
256}
257
258// ---------------------------------------------------------------------------
259// Constructors
260// ---------------------------------------------------------------------------
261
262impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
263 FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
264where
265 MOD: ConstPrimeMontyParams<LIMBS>,
266 P: IrreduciblePoly<MOD, LIMBS, M>,
267 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
268{
269 /// Construct from a coefficient array `[a_0, ..., a_{M-1}]`.
270 pub fn new(coeffs: [FpElement<MOD, LIMBS>; M]) -> Self {
271 Self {
272 coeffs,
273 _phantom: PhantomData,
274 _order: PhantomData,
275 }
276 }
277
278 /// Embed a base-field element as $a + 0x + ... + 0x^{M-1}$.
279 pub fn from_base(a: FpElement<MOD, LIMBS>) -> Self {
280 let mut coeffs = std::array::from_fn(|_| FpElement::zero());
281 coeffs[0] = a;
282 Self::new(coeffs)
283 }
284
285 /// Return the coefficient of $x^i$.
286 pub fn coeff(&self, i: usize) -> &FpElement<MOD, LIMBS> {
287 &self.coeffs[i]
288 }
289}
290
291// ---------------------------------------------------------------------------
292// Clone / Copy / PartialEq / Eq / Debug
293// (manual impls so we don't over-constrain the bounds the way #[derive] would)
294// ---------------------------------------------------------------------------
295
296impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Clone
297 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
298where
299 MOD: ConstPrimeMontyParams<LIMBS>,
300 P: IrreduciblePoly<MOD, LIMBS, M>,
301 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
302{
303 fn clone(&self) -> Self {
304 Self {
305 coeffs: self.coeffs.clone(),
306 _phantom: PhantomData,
307 _order: PhantomData,
308 }
309 }
310}
311
312// FpElement is Copy (derives it), so [FpElement; M] is Copy too.
313impl<MOD, const LIMBS: usize, const M: usize, P, const N: usize, TSCONSTS> Copy
314 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
315where
316 MOD: ConstPrimeMontyParams<LIMBS>,
317 P: IrreduciblePoly<MOD, LIMBS, M>,
318 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
319 [FpElement<MOD, LIMBS>; M]: Copy,
320{
321}
322
323impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> PartialEq
324 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
325where
326 MOD: ConstPrimeMontyParams<LIMBS>,
327 P: IrreduciblePoly<MOD, LIMBS, M>,
328 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
329{
330 fn eq(&self, other: &Self) -> bool {
331 self.coeffs
332 .iter()
333 .zip(other.coeffs.iter())
334 .all(|(a, b)| a == b)
335 }
336}
337
338impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Eq
339 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
340where
341 MOD: ConstPrimeMontyParams<LIMBS>,
342 P: IrreduciblePoly<MOD, LIMBS, M>,
343 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
344{
345}
346
347impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> std::fmt::Debug
348 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
349where
350 MOD: ConstPrimeMontyParams<LIMBS>,
351 P: IrreduciblePoly<MOD, LIMBS, M>,
352 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
353 FpElement<MOD, LIMBS>: std::fmt::Debug,
354{
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 write!(f, "FpExt{:?}", self.coeffs.as_ref())
357 }
358}
359
360// ---------------------------------------------------------------------------
361// Pretty Display — polynomial form over Fp
362// ---------------------------------------------------------------------------
363
364/// Shows the element as $a_0 + a_1 x + a_2 x^2 + ...$ with zero
365/// coefficients suppressed. The zero element prints as `0`.
366impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> std::fmt::Display
367 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
368where
369 MOD: ConstPrimeMontyParams<LIMBS>,
370 P: IrreduciblePoly<MOD, LIMBS, M>,
371 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
372 FpElement<MOD, LIMBS>: std::fmt::Display,
373{
374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375 let mut terms = Vec::new();
376
377 for (i, coeff) in self.coeffs.iter().enumerate() {
378 if bool::from(coeff.is_zero()) {
379 continue;
380 }
381
382 let is_one = bool::from(coeff.is_one());
383
384 match i {
385 0 => terms.push(format!("{coeff}")),
386 1 if is_one => terms.push("x".to_string()),
387 1 => terms.push(format!("{coeff}*x")),
388 _ if is_one => terms.push(format!("x^{i}")),
389 _ => terms.push(format!("{coeff}*x^{i}")),
390 }
391 }
392
393 if terms.is_empty() {
394 write!(f, "0")
395 } else {
396 write!(f, "{}", terms.join(" + "))
397 }
398 }
399}
400
401// ---------------------------------------------------------------------------
402// CtOption functionalities
403// ---------------------------------------------------------------------------
404
405impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Default
406 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
407where
408 MOD: ConstPrimeMontyParams<LIMBS>,
409 P: IrreduciblePoly<MOD, LIMBS, M>,
410 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
411{
412 fn default() -> Self {
413 Self::zero()
414 }
415}
416
417impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> ConditionallySelectable
418 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
419where
420 MOD: ConstPrimeMontyParams<LIMBS>,
421 P: IrreduciblePoly<MOD, LIMBS, M>,
422 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
423{
424 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
425 let mut res_coeffs = [FpElement::zero(); M];
426 for i in 0..M {
427 res_coeffs[i] = FpElement::conditional_select(&a.coeffs[i], &b.coeffs[i], choice);
428 }
429 Self::new(res_coeffs)
430 }
431
432 fn conditional_assign(&mut self, other: &Self, choice: Choice) {
433 for i in 0..M {
434 self.coeffs[i].conditional_assign(&other.coeffs[i], choice);
435 }
436 }
437
438 fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
439 for i in 0..M {
440 FpElement::conditional_swap(&mut a.coeffs[i], &mut b.coeffs[i], choice);
441 }
442 }
443}
444
445impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> ConstantTimeEq
446 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
447where
448 MOD: ConstPrimeMontyParams<LIMBS>,
449 P: IrreduciblePoly<MOD, LIMBS, M>,
450 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
451{
452 fn ct_eq(&self, other: &Self) -> Choice {
453 let mut acc = Choice::from(1u8);
454 for i in 0..M {
455 acc &= self.coeffs[i].ct_eq(&other.coeffs[i]);
456 }
457 acc
458 }
459
460 fn ct_ne(&self, other: &Self) -> Choice {
461 !self.ct_eq(other)
462 }
463}
464
465// ===========================================================================
466// Private polynomial helpers
467//
468// All intermediate polynomial arithmetic uses Vec<FpElement> so that we
469// never need arithmetic on const generic bounds (e.g. [T; M+M-1]), which is
470// not yet stable in Rust. The conversion back to [T; M] happens only at the
471// boundary (poly_reduce).
472// ===========================================================================
473
474type Poly<MOD, const LIMBS: usize> = Vec<FpElement<MOD, LIMBS>>;
475
476/// Coefficient-wise addition. Output length = max(|a|, |b|).
477#[allow(dead_code)]
478fn poly_add<MOD, const LIMBS: usize>(
479 a: &[FpElement<MOD, LIMBS>],
480 b: &[FpElement<MOD, LIMBS>],
481) -> Poly<MOD, LIMBS>
482where
483 MOD: ConstPrimeMontyParams<LIMBS>,
484{
485 let len = a.len().max(b.len());
486 (0..len)
487 .map(|i| {
488 let ai = a.get(i).cloned().unwrap_or_else(|| FpElement::zero());
489 let bi = b.get(i).cloned().unwrap_or_else(|| FpElement::zero());
490 FieldOps::add(&ai, &bi)
491 })
492 .collect()
493}
494
495/// Coefficient-wise subtraction.
496fn poly_sub<MOD, const LIMBS: usize>(
497 a: &[FpElement<MOD, LIMBS>],
498 b: &[FpElement<MOD, LIMBS>],
499) -> Poly<MOD, LIMBS>
500where
501 MOD: ConstPrimeMontyParams<LIMBS>,
502{
503 let len = a.len().max(b.len());
504 (0..len)
505 .map(|i| {
506 let ai = a.get(i).cloned().unwrap_or_else(|| FpElement::zero());
507 let bi = b.get(i).cloned().unwrap_or_else(|| FpElement::zero());
508 FieldOps::sub(&ai, &bi)
509 })
510 .collect()
511}
512
513/// Multiply every coefficient by a scalar.
514fn poly_scale<MOD, const LIMBS: usize>(
515 a: &[FpElement<MOD, LIMBS>],
516 s: &FpElement<MOD, LIMBS>,
517) -> Poly<MOD, LIMBS>
518where
519 MOD: ConstPrimeMontyParams<LIMBS>,
520{
521 a.iter().map(|ai| FieldOps::mul(ai, s)).collect()
522}
523
524/// Schoolbook polynomial multiplication. Output degree = deg(a) + deg(b).
525fn poly_mul<MOD, const LIMBS: usize>(
526 a: &[FpElement<MOD, LIMBS>],
527 b: &[FpElement<MOD, LIMBS>],
528) -> Poly<MOD, LIMBS>
529where
530 MOD: ConstPrimeMontyParams<LIMBS>,
531{
532 if a.is_empty() || b.is_empty() {
533 return vec![];
534 }
535 let mut out = vec![FpElement::zero(); a.len() + b.len() - 1];
536 for (i, ai) in a.iter().enumerate() {
537 for (j, bj) in b.iter().enumerate() {
538 let t = FieldOps::mul(ai, bj);
539 out[i + j] = FieldOps::add(&out[i + j], &t);
540 }
541 }
542 out
543}
544
545/// Remove trailing zero coefficients (i.e. normalise to the canonical form
546/// where the leading coefficient is non-zero, or the empty Vec for the zero poly).
547fn poly_normalize<MOD, const LIMBS: usize>(mut a: Poly<MOD, LIMBS>) -> Poly<MOD, LIMBS>
548where
549 MOD: ConstPrimeMontyParams<LIMBS>,
550{
551 while a.last().map_or(false, |c| bool::from(c.is_zero())) {
552 a.pop();
553 }
554 a
555}
556
557/// Polynomial long division. Returns `(quotient, remainder)` such that
558/// `a = quotient * b + remainder` with `deg(remainder) < deg(b)`.
559///
560/// Requires b ≠ 0 and its leading coefficient invertible (always true over Fp).
561fn poly_divmod<MOD, const LIMBS: usize>(
562 a: &[FpElement<MOD, LIMBS>],
563 b: &[FpElement<MOD, LIMBS>],
564) -> (Poly<MOD, LIMBS>, Poly<MOD, LIMBS>)
565where
566 MOD: ConstPrimeMontyParams<LIMBS>,
567{
568 let b = poly_normalize(b.to_vec());
569 assert!(!b.is_empty(), "poly_divmod: divisor is zero");
570
571 let mut rem = poly_normalize(a.to_vec());
572
573 if rem.len() < b.len() {
574 return (vec![], rem);
575 }
576
577 let b_lead_inv = b
578 .last()
579 .unwrap()
580 .invert()
581 .expect("poly_divmod: leading coefficient of b is not invertible");
582
583 let out_len = rem.len() - b.len() + 1;
584 let mut quot = vec![FpElement::zero(); out_len];
585
586 // Process from the highest-degree term of the quotient down to degree 0.
587 for i in (0..out_len).rev() {
588 // The current highest remaining degree is i + deg(b).
589 let rem_deg = i + b.len() - 1;
590 let coeff = FieldOps::mul(&rem[rem_deg], &b_lead_inv);
591 if bool::from(coeff.is_zero()) {
592 continue;
593 }
594 quot[i] = coeff.clone();
595 for (j, bj) in b.iter().enumerate() {
596 let t = FieldOps::mul(&coeff, bj);
597 rem[i + j] = FieldOps::sub(&rem[i + j], &t);
598 }
599 }
600
601 (quot, poly_normalize(rem))
602}
603
604/// Reduce polynomial $a$ modulo the irreducible $f(x) = x^M + \sum_j c_j x^j$.
605///
606/// Uses the substitution rule:
607/// $$x^M \equiv − (c_0 + c_1 x + ... + c_{M-1} x^{M-1})$$
608///
609/// Sweeps from the highest degree of `a` down to `M`, eliminating one
610/// term per step. Each step costs `M` multiplications and `M`
611/// additions in $\mathbb{F}_p$. Note that $c_i$ is `modulus[i]` below.
612fn poly_reduce<MOD, const LIMBS: usize, const M: usize>(
613 a: Vec<FpElement<MOD, LIMBS>>,
614 modulus: &[FpElement<MOD, LIMBS>; M],
615) -> [FpElement<MOD, LIMBS>; M]
616where
617 MOD: ConstPrimeMontyParams<LIMBS>,
618{
619 let mut a = a;
620 // Ensure we have at least M slots.
621 while a.len() < M {
622 a.push(FpElement::zero());
623 }
624
625 // Eliminate all terms of degree >= M, from the top down.
626 for i in (M..a.len()).rev() {
627 let lead = a[i].clone();
628 if bool::from(lead.is_zero()) {
629 continue;
630 }
631 // x^i = x^{i−M} x^M \equiv −x^{i−M} Σ_j modulus[j]x^j
632 // → subtract leadmodulus[j] from position (i−M+j) for each j.
633 for j in 0..M {
634 let t = FieldOps::mul(&lead, &modulus[j]);
635 a[i - M + j] = FieldOps::sub(&a[i - M + j], &t);
636 }
637 a[i] = FpElement::zero();
638 }
639
640 // Copy the first M coefficients into a fixed-size array.
641 std::array::from_fn(|i| {
642 if i < a.len() {
643 a[i].clone()
644 } else {
645 FpElement::zero()
646 }
647 })
648}
649
650/// Polynomial extended GCD over Fp.
651///
652/// Returns `(g, s)` satisfying `as \equiv g (mod b)`, where `g = gcd(a, b)`.
653///
654/// When `b` is irreducible and `a` is not a multiple of `b`, `g` is a nonzero
655/// constant (units are the GCD of coprime polynomials over a field), so
656/// `a⁻¹ \equiv s g⁻¹ (mod b)`.
657///
658/// Uses the standard iterative Euclidean algorithm:
659/// ```text
660/// (r₀, s₀) = (a, 1)
661/// (r₁, s₁) = (b, 0)
662/// while r₁ ≠ 0:
663/// q = r₀ / r₁
664/// (r₀, r₁) = (r₁, r₀ − qr₁)
665/// (s₀, s₁) = (s₁, s₀ − qs₁)
666/// return (r₀, s₀)
667/// ```
668fn poly_ext_gcd<MOD, const LIMBS: usize>(
669 a: Poly<MOD, LIMBS>,
670 b: Poly<MOD, LIMBS>,
671) -> (Poly<MOD, LIMBS>, Poly<MOD, LIMBS>)
672// (gcd, s)
673where
674 MOD: ConstPrimeMontyParams<LIMBS>,
675{
676 let mut r0 = poly_normalize(a);
677 let mut r1 = poly_normalize(b);
678 let mut s0: Poly<MOD, LIMBS> = vec![FpElement::one()]; // 1
679 let mut s1: Poly<MOD, LIMBS> = vec![]; // 0
680
681 while !r1.is_empty() {
682 let (q, r) = poly_divmod(&r0, &r1);
683 // s_new = s0 − qs1
684 let q_s1 = poly_mul(&q, &s1);
685 let s_new = poly_normalize(poly_sub(&s0, &q_s1));
686 s0 = s1;
687 s1 = s_new;
688 r0 = r1;
689 r1 = poly_normalize(r);
690 }
691
692 (r0, s0)
693}
694
695// ===========================================================================
696// Operator overloads (delegate to the FieldOps methods below)
697// ===========================================================================
698
699impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Add
700 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
701where
702 MOD: ConstPrimeMontyParams<LIMBS>,
703 P: IrreduciblePoly<MOD, LIMBS, M>,
704 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
705{
706 type Output = Self;
707 fn add(self, rhs: Self) -> Self {
708 FieldOps::add(&self, &rhs)
709 }
710}
711
712impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Sub
713 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
714where
715 MOD: ConstPrimeMontyParams<LIMBS>,
716 P: IrreduciblePoly<MOD, LIMBS, M>,
717 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
718{
719 type Output = Self;
720 fn sub(self, rhs: Self) -> Self {
721 FieldOps::sub(&self, &rhs)
722 }
723}
724
725impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Mul
726 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
727where
728 MOD: ConstPrimeMontyParams<LIMBS>,
729 P: IrreduciblePoly<MOD, LIMBS, M>,
730 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
731{
732 type Output = Self;
733 fn mul(self, rhs: Self) -> Self {
734 FieldOps::mul(&self, &rhs)
735 }
736}
737
738impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Neg
739 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
740where
741 MOD: ConstPrimeMontyParams<LIMBS>,
742 P: IrreduciblePoly<MOD, LIMBS, M>,
743 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
744{
745 type Output = Self;
746 fn neg(self) -> Self {
747 FieldOps::negate(&self)
748 }
749}
750
751// ===========================================================================
752// Helper functions for Tonelli-Shanks algorithm
753// ===========================================================================
754
755/// The loop part of the Tonelli--Shanks algorithm
756///
757/// Takes as input a (reference to an) element `x` of FpM and the
758/// projenator `w = x^((t-1)/2)` for `x`. The function then modifies
759/// `x` and return the final value sqrt(x) if `x` is a quadratic
760/// residue in FpM
761///
762/// # Arguments
763///
764/// * `x` - The value to take the sqrt of (type: &mut FpExt)
765/// * `w` - The projenator for x (type: &FpExt)
766///
767/// # Note
768///
769/// Most of this is directly taken from the Fp case at
770/// <https://github.com/SamFrengley/ff-sqrtratio/blob/8e5aa6f934f32d9b9cff56177d9943a2effcd390/ff_derive/src/lib.rs>
771/// under an MIT liscence.
772fn ts_loop<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>(
773 x: &mut FpExt<MOD, LIMBS, M, N, P, TSCONSTS>,
774 w: &FpExt<MOD, LIMBS, M, N, P, TSCONSTS>,
775) where
776 MOD: ConstPrimeMontyParams<LIMBS>,
777 P: IrreduciblePoly<MOD, LIMBS, M>,
778 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
779{
780 let mut v = TSCONSTS::S as u32;
781 *x = x.mul(*w);
782 let mut b = x.mul(*w);
783 let mut z = FpExt::new(TSCONSTS::root_of_unity());
784
785 for max_v in (1..=TSCONSTS::S as u32).rev() {
786 let mut k = 1u32;
787 let mut tmp = b.square();
788 let mut j_less_than_v: Choice = 1.into();
789
790 for j in 2..max_v {
791 let tmp_is_one = tmp.ct_eq(&FpExt::one());
792 let squared = FpExt::conditional_select(&tmp, &z, tmp_is_one).square();
793 tmp = FpExt::conditional_select(&squared, &tmp, tmp_is_one);
794 let new_z = FpExt::conditional_select(&z, &squared, tmp_is_one);
795
796 j_less_than_v &= !j.ct_eq(&v);
797
798 k = u32::conditional_select(&j, &k, tmp_is_one);
799 z = FpExt::conditional_select(&z, &new_z, j_less_than_v);
800 }
801
802 let result = x.mul(z);
803 *x = FpExt::conditional_select(&result, x, b.ct_eq(&FpExt::one()));
804 z = z.square();
805 b = b.mul(z);
806 v = k;
807 }
808}
809
810// ===========================================================================
811// FieldOps implementation
812// ===========================================================================
813
814impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> FieldOps
815 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
816where
817 MOD: ConstPrimeMontyParams<LIMBS>,
818 P: IrreduciblePoly<MOD, LIMBS, M>,
819 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
820{
821 // --- Identity elements --------------------------------------------------
822
823 fn zero() -> Self {
824 Self::new(std::array::from_fn(|_| FpElement::zero()))
825 }
826
827 fn one() -> Self {
828 let mut c: [FpElement<MOD, LIMBS>; M] = std::array::from_fn(|_| FpElement::zero());
829 c[0] = FpElement::one();
830 Self::new(c)
831 }
832
833 fn from_u64(x: u64) -> Self {
834 Self::from_base(FpElement::<MOD, LIMBS>::from_u64(x))
835 }
836
837 // --- Predicates ---------------------------------------------------------
838
839 fn is_zero(&self) -> Choice {
840 self.ct_eq(&Self::zero())
841 }
842
843 fn is_one(&self) -> Choice {
844 Self::ct_eq(self, &Self::one())
845 }
846
847 // --- Core arithmetic ----------------------------------------------------
848
849 fn negate(&self) -> Self {
850 Self::new(std::array::from_fn(|i| self.coeffs[i].negate()))
851 }
852
853 fn add(&self, rhs: &Self) -> Self {
854 Self::new(std::array::from_fn(|i| {
855 FieldOps::add(&self.coeffs[i], &rhs.coeffs[i])
856 }))
857 }
858
859 fn sub(&self, rhs: &Self) -> Self {
860 Self::new(std::array::from_fn(|i| {
861 FieldOps::sub(&self.coeffs[i], &rhs.coeffs[i])
862 }))
863 }
864
865 /// Schoolbook multiplication followed by reduction modulo $f(x)$.
866 ///
867 /// Product has degree $\leq 2M−2$, then each high-degree term is
868 /// replaced using $x^M \equiv −\sum_j \texttt{modulus}\[j\] x^j$
869 /// until all degrees are below $M$.
870 fn mul(&self, rhs: &Self) -> Self {
871 let product = poly_mul(&self.coeffs, &rhs.coeffs);
872 Self::new(poly_reduce(product, &P::modulus()))
873 }
874
875 fn square(&self) -> Self {
876 // Calls our own FieldOps::mul, not Mul::mul (operator), to avoid the
877 // supertrait ambiguity documented in field_ops.rs.
878 <Self as FieldOps>::mul(self, self)
879 }
880
881 fn double(&self) -> Self {
882 Self::new(std::array::from_fn(|i| self.coeffs[i].double()))
883 }
884
885 // --- Inversion ----------------------------------------------------------
886
887 /// Inversion via polynomial extended GCD.
888 ///
889 /// Finds $s$ such that $\texttt{self} \times s \equiv 1 \pmod{f}$
890 /// by computing $\gcd(\texttt{self}, f) = g$ (a nonzero constant
891 /// if `self` is nonzero) and then returning $s g^{-1} \pmod{f}$.
892 fn invert(&self) -> CtOption<Self> {
893 let is_invertible = !self.is_zero();
894
895 // Build the full irreducible polynomial as a Vec:
896 // f = [c_0, c_1, ..., c_{M-1}, 1] (coefficients in ascending degree)
897 let mut f: Poly<MOD, LIMBS> = P::modulus().iter().cloned().collect();
898 f.push(FpElement::one()); // leading x^M term
899
900 let a: Poly<MOD, LIMBS> = self.coeffs.iter().cloned().collect();
901 let (gcd, s) = poly_ext_gcd(a, f);
902
903 // gcd is a nonzero constant in Fp (since f is irreducible and a ≠ 0).
904 let g0 = gcd.into_iter().next().unwrap_or(FpElement::zero()); // the constant term = gcd value
905 let g_inv = g0.invert().unwrap_or(FpElement::zero()); // invert in Fp
906
907 // self⁻¹ = s(x) g⁻¹ reduced mod f
908 let s_scaled = poly_scale(&s, &g_inv);
909 CtOption::new(
910 Self::new(poly_reduce(s_scaled, &P::modulus())),
911 is_invertible,
912 )
913 }
914
915 // --- Frobenius ----------------------------------------------------------
916
917 /// `φ_p(a) = a^p` — the p-power Frobenius endomorphism.
918 ///
919 /// Computed via square-and-multiply using the characteristic p retrieved
920 /// from the base field.
921 fn frobenius(&self) -> Self {
922 let p = FpElement::<MOD, LIMBS>::characteristic();
923 <Self as FieldOps>::pow(self, &p)
924 }
925
926 // --- Norm and trace -----------------------------------------------------
927
928 /// `N_{Fp^M/Fp}(a) = ∏_{i=0}^{M-1} φ_p^i(a)` — product of all Galois conjugates.
929 ///
930 /// The result lies in Fp (all higher coefficients are 0), but is returned
931 /// embedded in Fp^M for uniformity with the [`FieldOps`] signature.
932 fn norm(&self) -> Self {
933 let mut result = self.clone();
934 let mut conj = self.frobenius();
935 for _ in 1..M {
936 result = <Self as FieldOps>::mul(&result, &conj);
937 conj = conj.frobenius();
938 }
939 result
940 }
941
942 /// `Tr_{Fp^M/Fp}(a) = Σ_{i=0}^{M-1} φ_p^i(a)` — sum of all Galois conjugates.
943 ///
944 /// Like `norm`, the result lies in Fp but is returned embedded in Fp^M.
945 fn trace(&self) -> Self {
946 let mut result = self.clone();
947 let mut conj = self.frobenius();
948 for _ in 1..M {
949 result = <Self as FieldOps>::add(&result, &conj);
950 conj = conj.frobenius();
951 }
952 result
953 }
954
955 // --- Square root --------------------------------------------------------
956
957 /// Tonelli--Shanks squareroot algorithm
958 ///
959 /// Implementation of the Tonelli--Shanks square root algorithm. Requires
960 /// only a factorisation as $p^M - 1 = 2^K N$ so can compute this at
961 /// compile time by truncating zeros.
962 ///
963 /// # Arguments
964 ///
965 /// * `&self` - An element of Fp^M (type: &self)
966 ///
967 /// # Returns
968 ///
969 /// `self^(1/2)` a choice of squareroot (type: Self)
970 fn sqrt(&self) -> CtOption<Self> {
971 let mut x = *self;
972 let exp = TSCONSTS::PROJENATOR_EXP;
973 let exp_limbs = exp.as_limbs().map(|limb| limb.0);
974 // TODO: generate the addition chain for this specific constant
975 // this is constant time since exp_limbs is always(!) the same
976 let w = x.pow_vartime(&exp_limbs);
977 ts_loop(&mut x, &w);
978 CtOption::new(
979 x,
980 x.mul(x).ct_eq(self), // Only return Some if it's the square root.
981 )
982 }
983
984 /// Inverse and sqrt in one exponentiation
985 ///
986 /// Computes the inverse and squareroot of `self` in one
987 /// exponentiation using the tricks in Scott's article
988 ///
989 /// # Arguments
990 ///
991 /// * `&self` - An element of Fp^M (type: &self)
992 ///
993 /// # Returns
994 ///
995 /// `(myinv, mysqrt)` which is `self.invert()` and `self.sqrt()`
996 /// (type: `CtOption<Self>`, `CtOption<Self>`)
997 fn inverse_and_sqrt(&self) -> (CtOption<Self>, CtOption<Self>) {
998 let is_invertible = !self.is_zero();
999
1000 let mut mysqrt = *self;
1001 let exp = TSCONSTS::PROJENATOR_EXP;
1002 let exp_limbs = exp.as_limbs().map(|limb| limb.0);
1003 // TODO: generate the addition chain for this specific constant
1004 // this is constant time since exp_limbs is always(!) the same
1005 let w = mysqrt.pow_vartime(&exp_limbs);
1006 ts_loop(&mut mysqrt, &w);
1007
1008 let e0 = TSCONSTS::TWOSM1;
1009 let e0_limbs = e0.as_limbs().map(|limb| limb.0);
1010 let e1 = e0.sub(Uint::<N>::from_u64(1));
1011 let e1_limbs = e1.as_limbs().map(|limb| limb.0);
1012
1013 // Compute x^(2^(S - 1) - 1) * (x * w^4)^(2^(S - 1))
1014 let myinv = self
1015 .pow_vartime(&e1_limbs)
1016 .mul((self.mul(&w.pow_vartime(&[4 as u64]))).pow_vartime(&e0_limbs));
1017
1018 (
1019 CtOption::new(myinv, is_invertible),
1020 CtOption::new(
1021 mysqrt,
1022 mysqrt.mul(mysqrt).ct_eq(self), // Only return Some if it's the square root.
1023 ),
1024 )
1025 }
1026
1027 /// Inverse of squareroot of `self` in 1 exponentiation
1028 ///
1029 /// Computes 1/sqrt(self) using the trick from Mike Scott's
1030 /// "Tricks of the trade" article Section 2
1031 /// <https://eprint.iacr.org/2020/1497>
1032 ///
1033 /// # Arguments
1034 ///
1035 /// * `&self` - Description of &self (type: self)
1036 ///
1037 /// # Returns
1038 ///
1039 /// The inverse of the squareroot of `self` (type: `CtOption<Self>`)
1040 fn inv_sqrt(&self) -> CtOption<Self> {
1041 let (inv, sqrt) = self.inverse_and_sqrt();
1042 inv.and_then(|a| sqrt.map(|b| a * b))
1043 }
1044
1045 /// Inverse of `self` and squareroot of `rhs` in 1 exponentiation
1046 ///
1047 /// Computes `1/self` and `rhs.sqrt()` simulaineously using the
1048 /// trick from Mike Scott's "Tricks of the trade" article Section
1049 /// 2 <https://eprint.iacr.org/2020/1497>
1050 ///
1051 /// # Returns
1052 ///
1053 /// The inverse of `self` and square root fo `rhs`. Theq former is
1054 /// none if and only if `self` is nonzero and the latter is not
1055 /// none if and only if there exists a squareroot of `rhs` in FpM
1056 /// (type: (`CtOption<Self>`, `CtOption<Self>`))
1057 fn invertme_sqrtother(&self, rhs: &Self) -> (CtOption<Self>, CtOption<Self>) {
1058 let is_invertible = !self.is_zero();
1059
1060 let x = self.mul(self).mul(*rhs);
1061 let (myinv, mysqrt) = x.inverse_and_sqrt();
1062
1063 let myinv_value = myinv.unwrap_or(Self::zero());
1064 let mysqrt_value = mysqrt.unwrap_or(Self::zero());
1065 let inv_value = self.mul(rhs).mul(myinv_value);
1066 let sqrt_value = inv_value.mul(mysqrt_value);
1067
1068 let inv_is_some = is_invertible & myinv.is_some();
1069
1070 let sqrt_is_some = inv_is_some & mysqrt.is_some() & (sqrt_value.mul(sqrt_value)).ct_eq(rhs);
1071
1072 (
1073 CtOption::new(inv_value, inv_is_some),
1074 CtOption::new(sqrt_value, sqrt_is_some),
1075 )
1076 }
1077
1078 /// Computes the squareroot of a ratio `self/rhs`
1079 ///
1080 /// Computes `sqrt(self/rhs)` in one exponentiation using the
1081 /// trick from Mike Scott's "Tricks of the trade" article Section
1082 /// 2 <https://eprint.iacr.org/2020/1497>
1083 ///
1084 /// # Arguments
1085 ///
1086 /// * `&self` - Element of FpM (type: self)
1087 /// * `rhs` - Element of FpM (type: &Self)
1088 ///
1089 /// # Returns
1090 ///
1091 /// The squareroot of the ratio `self/rhs` is not none if and only
1092 /// if `rhs` is invertible and the ratio has an FpM squareroot
1093 /// (type: `CtOption<Self>`)
1094 fn sqrt_ratio(&self, rhs: &Self) -> CtOption<Self> {
1095 let x = self.mul(&self.mul(self)).mul(*rhs);
1096 let (myinv, mysqrt) = x.inverse_and_sqrt();
1097
1098 let myinv_value = myinv.unwrap_or(Self::zero());
1099 let mysqrt_value = mysqrt.unwrap_or(Self::zero());
1100 let ans_value = self.mul(self).mul(myinv_value).mul(mysqrt_value);
1101
1102 let inv_is_some = myinv.is_some();
1103 let ans_is_some =
1104 inv_is_some & mysqrt.is_some() & (mysqrt_value.mul(mysqrt_value)).ct_eq(&x);
1105
1106 CtOption::new(ans_value, ans_is_some)
1107 }
1108
1109 /// a is a QR in Fp^M iff a^{(p^M-1)/2} = 1.
1110 ///
1111 /// Implements the "Legendre symbol" which is 1 if and only if we
1112 /// have a quadratic residue in FpM
1113 /// WARNING: Not constant time if `self` is zero
1114 ///
1115 /// # Arguments
1116 ///
1117 /// * `&self` - Element of FpM (type: self)
1118 ///
1119 /// # Returns
1120 ///
1121 /// Either `0` if `&self` is `0`, `1` if `&self` is a QR or `-1` if
1122 /// `&self` is not a QR. (type: i8)
1123 fn legendre(&self) -> i8 {
1124 let exp = TSCONSTS::HALF_ORDER;
1125 let exp_limbs = exp.as_limbs().map(|limb| limb.0);
1126 let symb = self.pow(&exp_limbs); // note, this is constant time since exp is constant
1127
1128 let ret = i8::conditional_select(&-1, &1, symb.is_one());
1129 i8::conditional_select(&0, &ret, !symb.is_zero())
1130 }
1131
1132 // --- Utilities ----------------------------------------------------------
1133
1134 fn characteristic() -> Vec<u64> {
1135 // Same prime p as the base field.
1136 FpElement::<MOD, LIMBS>::characteristic()
1137 }
1138
1139 /// Degree of Fp^M over the prime subfield Fp.
1140 fn degree() -> u32 {
1141 M as u32
1142 }
1143}
1144
1145// ===========================================================================
1146// Cryptographically secure random sampling
1147// ===========================================================================
1148
1149impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> FieldRandom
1150 for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
1151where
1152 MOD: ConstPrimeMontyParams<LIMBS>,
1153 P: IrreduciblePoly<MOD, LIMBS, M>,
1154 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
1155{
1156 /// Sample a uniformly random element of Fp^M by drawing each
1157 /// coefficient independently from Fp.
1158 fn random(rng: &mut (impl rand::CryptoRng + rand::Rng)) -> Self {
1159 let coeffs = std::array::from_fn(|_| FpElement::<MOD, LIMBS>::random(rng));
1160 Self::new(coeffs)
1161 }
1162}
1163
1164impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
1165FieldFromRepr for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
1166where
1167 MOD: ConstPrimeMontyParams<LIMBS>,
1168 P: IrreduciblePoly<MOD, LIMBS, M>,
1169 TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
1170{
1171 type Repr = [FpElement<MOD, LIMBS>; M];
1172
1173 fn from_repr(x: Self::Repr) -> Self {
1174 Self::new(x)
1175 }
1176}