use core::ops::{Add, Mul, Neg, Sub};
use std::marker::PhantomData;
use crate::field_ops::{FieldFromRepr, FieldOps, FieldRandom};
use crate::fp_element::FpElement;
use crypto_bigint::{modular::ConstPrimeMontyParams, Uint};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
pub trait IrreduciblePoly<MOD, const LIMBS: usize, const M: usize>: 'static
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
fn modulus() -> [FpElement<MOD, LIMBS>; M];
}
pub trait TonelliShanksConstants<MOD, const LIMBS: usize, const M: usize, const N: usize>:
'static
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
const ORDER: Uint<N>;
const HALF_ORDER: Uint<N>;
const S: u64;
const T: Uint<N>;
const TWOSM1: Uint<N>;
const PROJENATOR_EXP: Uint<N>;
fn root_of_unity() -> [FpElement<MOD, LIMBS>; M];
}
pub struct FpExt<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
pub coeffs: [FpElement<MOD, LIMBS>; M],
_phantom: PhantomData<P>,
_order: PhantomData<TSCONSTS>,
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
pub fn new(coeffs: [FpElement<MOD, LIMBS>; M]) -> Self {
Self {
coeffs,
_phantom: PhantomData,
_order: PhantomData,
}
}
pub fn from_base(a: FpElement<MOD, LIMBS>) -> Self {
let mut coeffs = std::array::from_fn(|_| FpElement::zero());
coeffs[0] = a;
Self::new(coeffs)
}
pub fn coeff(&self, i: usize) -> &FpElement<MOD, LIMBS> {
&self.coeffs[i]
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Clone
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn clone(&self) -> Self {
Self {
coeffs: self.coeffs.clone(),
_phantom: PhantomData,
_order: PhantomData,
}
}
}
impl<MOD, const LIMBS: usize, const M: usize, P, const N: usize, TSCONSTS> Copy
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
[FpElement<MOD, LIMBS>; M]: Copy,
{
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> PartialEq
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn eq(&self, other: &Self) -> bool {
self.coeffs
.iter()
.zip(other.coeffs.iter())
.all(|(a, b)| a == b)
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Eq
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> std::fmt::Debug
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
FpElement<MOD, LIMBS>: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FpExt{:?}", self.coeffs.as_ref())
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> std::fmt::Display
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
FpElement<MOD, LIMBS>: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut terms = Vec::new();
for (i, coeff) in self.coeffs.iter().enumerate() {
if bool::from(coeff.is_zero()) {
continue;
}
let is_one = bool::from(coeff.is_one());
match i {
0 => terms.push(format!("{coeff}")),
1 if is_one => terms.push("x".to_string()),
1 => terms.push(format!("{coeff}*x")),
_ if is_one => terms.push(format!("x^{i}")),
_ => terms.push(format!("{coeff}*x^{i}")),
}
}
if terms.is_empty() {
write!(f, "0")
} else {
write!(f, "{}", terms.join(" + "))
}
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Default
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn default() -> Self {
Self::zero()
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> ConditionallySelectable
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let mut res_coeffs = [FpElement::zero(); M];
for i in 0..M {
res_coeffs[i] = FpElement::conditional_select(&a.coeffs[i], &b.coeffs[i], choice);
}
Self::new(res_coeffs)
}
fn conditional_assign(&mut self, other: &Self, choice: Choice) {
for i in 0..M {
self.coeffs[i].conditional_assign(&other.coeffs[i], choice);
}
}
fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
for i in 0..M {
FpElement::conditional_swap(&mut a.coeffs[i], &mut b.coeffs[i], choice);
}
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> ConstantTimeEq
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn ct_eq(&self, other: &Self) -> Choice {
let mut acc = Choice::from(1u8);
for i in 0..M {
acc &= self.coeffs[i].ct_eq(&other.coeffs[i]);
}
acc
}
fn ct_ne(&self, other: &Self) -> Choice {
!self.ct_eq(other)
}
}
type Poly<MOD, const LIMBS: usize> = Vec<FpElement<MOD, LIMBS>>;
#[allow(dead_code)]
fn poly_add<MOD, const LIMBS: usize>(
a: &[FpElement<MOD, LIMBS>],
b: &[FpElement<MOD, LIMBS>],
) -> Poly<MOD, LIMBS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
let len = a.len().max(b.len());
(0..len)
.map(|i| {
let ai = a.get(i).cloned().unwrap_or_else(|| FpElement::zero());
let bi = b.get(i).cloned().unwrap_or_else(|| FpElement::zero());
FieldOps::add(&ai, &bi)
})
.collect()
}
fn poly_sub<MOD, const LIMBS: usize>(
a: &[FpElement<MOD, LIMBS>],
b: &[FpElement<MOD, LIMBS>],
) -> Poly<MOD, LIMBS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
let len = a.len().max(b.len());
(0..len)
.map(|i| {
let ai = a.get(i).cloned().unwrap_or_else(|| FpElement::zero());
let bi = b.get(i).cloned().unwrap_or_else(|| FpElement::zero());
FieldOps::sub(&ai, &bi)
})
.collect()
}
fn poly_scale<MOD, const LIMBS: usize>(
a: &[FpElement<MOD, LIMBS>],
s: &FpElement<MOD, LIMBS>,
) -> Poly<MOD, LIMBS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
a.iter().map(|ai| FieldOps::mul(ai, s)).collect()
}
fn poly_mul<MOD, const LIMBS: usize>(
a: &[FpElement<MOD, LIMBS>],
b: &[FpElement<MOD, LIMBS>],
) -> Poly<MOD, LIMBS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
if a.is_empty() || b.is_empty() {
return vec![];
}
let mut out = vec![FpElement::zero(); a.len() + b.len() - 1];
for (i, ai) in a.iter().enumerate() {
for (j, bj) in b.iter().enumerate() {
let t = FieldOps::mul(ai, bj);
out[i + j] = FieldOps::add(&out[i + j], &t);
}
}
out
}
fn poly_normalize<MOD, const LIMBS: usize>(mut a: Poly<MOD, LIMBS>) -> Poly<MOD, LIMBS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
while a.last().map_or(false, |c| bool::from(c.is_zero())) {
a.pop();
}
a
}
fn poly_divmod<MOD, const LIMBS: usize>(
a: &[FpElement<MOD, LIMBS>],
b: &[FpElement<MOD, LIMBS>],
) -> (Poly<MOD, LIMBS>, Poly<MOD, LIMBS>)
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
let b = poly_normalize(b.to_vec());
assert!(!b.is_empty(), "poly_divmod: divisor is zero");
let mut rem = poly_normalize(a.to_vec());
if rem.len() < b.len() {
return (vec![], rem);
}
let b_lead_inv = b
.last()
.unwrap()
.invert()
.expect("poly_divmod: leading coefficient of b is not invertible");
let out_len = rem.len() - b.len() + 1;
let mut quot = vec![FpElement::zero(); out_len];
for i in (0..out_len).rev() {
let rem_deg = i + b.len() - 1;
let coeff = FieldOps::mul(&rem[rem_deg], &b_lead_inv);
if bool::from(coeff.is_zero()) {
continue;
}
quot[i] = coeff.clone();
for (j, bj) in b.iter().enumerate() {
let t = FieldOps::mul(&coeff, bj);
rem[i + j] = FieldOps::sub(&rem[i + j], &t);
}
}
(quot, poly_normalize(rem))
}
fn poly_reduce<MOD, const LIMBS: usize, const M: usize>(
a: Vec<FpElement<MOD, LIMBS>>,
modulus: &[FpElement<MOD, LIMBS>; M],
) -> [FpElement<MOD, LIMBS>; M]
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
let mut a = a;
while a.len() < M {
a.push(FpElement::zero());
}
for i in (M..a.len()).rev() {
let lead = a[i].clone();
if bool::from(lead.is_zero()) {
continue;
}
for j in 0..M {
let t = FieldOps::mul(&lead, &modulus[j]);
a[i - M + j] = FieldOps::sub(&a[i - M + j], &t);
}
a[i] = FpElement::zero();
}
std::array::from_fn(|i| {
if i < a.len() {
a[i].clone()
} else {
FpElement::zero()
}
})
}
fn poly_ext_gcd<MOD, const LIMBS: usize>(
a: Poly<MOD, LIMBS>,
b: Poly<MOD, LIMBS>,
) -> (Poly<MOD, LIMBS>, Poly<MOD, LIMBS>)
where
MOD: ConstPrimeMontyParams<LIMBS>,
{
let mut r0 = poly_normalize(a);
let mut r1 = poly_normalize(b);
let mut s0: Poly<MOD, LIMBS> = vec![FpElement::one()]; let mut s1: Poly<MOD, LIMBS> = vec![];
while !r1.is_empty() {
let (q, r) = poly_divmod(&r0, &r1);
let q_s1 = poly_mul(&q, &s1);
let s_new = poly_normalize(poly_sub(&s0, &q_s1));
s0 = s1;
s1 = s_new;
r0 = r1;
r1 = poly_normalize(r);
}
(r0, s0)
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Add
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
type Output = Self;
fn add(self, rhs: Self) -> Self {
FieldOps::add(&self, &rhs)
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Sub
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self {
FieldOps::sub(&self, &rhs)
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Mul
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
type Output = Self;
fn mul(self, rhs: Self) -> Self {
FieldOps::mul(&self, &rhs)
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> Neg
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
type Output = Self;
fn neg(self) -> Self {
FieldOps::negate(&self)
}
}
fn ts_loop<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>(
x: &mut FpExt<MOD, LIMBS, M, N, P, TSCONSTS>,
w: &FpExt<MOD, LIMBS, M, N, P, TSCONSTS>,
) where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
let mut v = TSCONSTS::S as u32;
*x = x.mul(*w);
let mut b = x.mul(*w);
let mut z = FpExt::new(TSCONSTS::root_of_unity());
for max_v in (1..=TSCONSTS::S as u32).rev() {
let mut k = 1u32;
let mut tmp = b.square();
let mut j_less_than_v: Choice = 1.into();
for j in 2..max_v {
let tmp_is_one = tmp.ct_eq(&FpExt::one());
let squared = FpExt::conditional_select(&tmp, &z, tmp_is_one).square();
tmp = FpExt::conditional_select(&squared, &tmp, tmp_is_one);
let new_z = FpExt::conditional_select(&z, &squared, tmp_is_one);
j_less_than_v &= !j.ct_eq(&v);
k = u32::conditional_select(&j, &k, tmp_is_one);
z = FpExt::conditional_select(&z, &new_z, j_less_than_v);
}
let result = x.mul(z);
*x = FpExt::conditional_select(&result, x, b.ct_eq(&FpExt::one()));
z = z.square();
b = b.mul(z);
v = k;
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> FieldOps
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn zero() -> Self {
Self::new(std::array::from_fn(|_| FpElement::zero()))
}
fn one() -> Self {
let mut c: [FpElement<MOD, LIMBS>; M] = std::array::from_fn(|_| FpElement::zero());
c[0] = FpElement::one();
Self::new(c)
}
fn from_u64(x: u64) -> Self {
Self::from_base(FpElement::<MOD, LIMBS>::from_u64(x))
}
fn is_zero(&self) -> Choice {
self.ct_eq(&Self::zero())
}
fn is_one(&self) -> Choice {
Self::ct_eq(self, &Self::one())
}
fn negate(&self) -> Self {
Self::new(std::array::from_fn(|i| self.coeffs[i].negate()))
}
fn add(&self, rhs: &Self) -> Self {
Self::new(std::array::from_fn(|i| {
FieldOps::add(&self.coeffs[i], &rhs.coeffs[i])
}))
}
fn sub(&self, rhs: &Self) -> Self {
Self::new(std::array::from_fn(|i| {
FieldOps::sub(&self.coeffs[i], &rhs.coeffs[i])
}))
}
fn mul(&self, rhs: &Self) -> Self {
let product = poly_mul(&self.coeffs, &rhs.coeffs);
Self::new(poly_reduce(product, &P::modulus()))
}
fn square(&self) -> Self {
<Self as FieldOps>::mul(self, self)
}
fn double(&self) -> Self {
Self::new(std::array::from_fn(|i| self.coeffs[i].double()))
}
fn invert(&self) -> CtOption<Self> {
let is_invertible = !self.is_zero();
let mut f: Poly<MOD, LIMBS> = P::modulus().iter().cloned().collect();
f.push(FpElement::one());
let a: Poly<MOD, LIMBS> = self.coeffs.iter().cloned().collect();
let (gcd, s) = poly_ext_gcd(a, f);
let g0 = gcd.into_iter().next().unwrap_or(FpElement::zero()); let g_inv = g0.invert().unwrap_or(FpElement::zero());
let s_scaled = poly_scale(&s, &g_inv);
CtOption::new(
Self::new(poly_reduce(s_scaled, &P::modulus())),
is_invertible,
)
}
fn frobenius(&self) -> Self {
let p = FpElement::<MOD, LIMBS>::characteristic();
<Self as FieldOps>::pow(self, &p)
}
fn norm(&self) -> Self {
let mut result = self.clone();
let mut conj = self.frobenius();
for _ in 1..M {
result = <Self as FieldOps>::mul(&result, &conj);
conj = conj.frobenius();
}
result
}
fn trace(&self) -> Self {
let mut result = self.clone();
let mut conj = self.frobenius();
for _ in 1..M {
result = <Self as FieldOps>::add(&result, &conj);
conj = conj.frobenius();
}
result
}
fn sqrt(&self) -> CtOption<Self> {
let mut x = *self;
let exp = TSCONSTS::PROJENATOR_EXP;
let exp_limbs = exp.as_limbs().map(|limb| limb.0);
let w = x.pow_vartime(&exp_limbs);
ts_loop(&mut x, &w);
CtOption::new(
x,
x.mul(x).ct_eq(self), )
}
fn inverse_and_sqrt(&self) -> (CtOption<Self>, CtOption<Self>) {
let is_invertible = !self.is_zero();
let mut mysqrt = *self;
let exp = TSCONSTS::PROJENATOR_EXP;
let exp_limbs = exp.as_limbs().map(|limb| limb.0);
let w = mysqrt.pow_vartime(&exp_limbs);
ts_loop(&mut mysqrt, &w);
let e0 = TSCONSTS::TWOSM1;
let e0_limbs = e0.as_limbs().map(|limb| limb.0);
let e1 = e0.sub(Uint::<N>::from_u64(1));
let e1_limbs = e1.as_limbs().map(|limb| limb.0);
let myinv = self
.pow_vartime(&e1_limbs)
.mul((self.mul(&w.pow_vartime(&[4 as u64]))).pow_vartime(&e0_limbs));
(
CtOption::new(myinv, is_invertible),
CtOption::new(
mysqrt,
mysqrt.mul(mysqrt).ct_eq(self), ),
)
}
fn inv_sqrt(&self) -> CtOption<Self> {
let (inv, sqrt) = self.inverse_and_sqrt();
inv.and_then(|a| sqrt.map(|b| a * b))
}
fn invertme_sqrtother(&self, rhs: &Self) -> (CtOption<Self>, CtOption<Self>) {
let is_invertible = !self.is_zero();
let x = self.mul(self).mul(*rhs);
let (myinv, mysqrt) = x.inverse_and_sqrt();
let myinv_value = myinv.unwrap_or(Self::zero());
let mysqrt_value = mysqrt.unwrap_or(Self::zero());
let inv_value = self.mul(rhs).mul(myinv_value);
let sqrt_value = inv_value.mul(mysqrt_value);
let inv_is_some = is_invertible & myinv.is_some();
let sqrt_is_some = inv_is_some & mysqrt.is_some() & (sqrt_value.mul(sqrt_value)).ct_eq(rhs);
(
CtOption::new(inv_value, inv_is_some),
CtOption::new(sqrt_value, sqrt_is_some),
)
}
fn sqrt_ratio(&self, rhs: &Self) -> CtOption<Self> {
let x = self.mul(&self.mul(self)).mul(*rhs);
let (myinv, mysqrt) = x.inverse_and_sqrt();
let myinv_value = myinv.unwrap_or(Self::zero());
let mysqrt_value = mysqrt.unwrap_or(Self::zero());
let ans_value = self.mul(self).mul(myinv_value).mul(mysqrt_value);
let inv_is_some = myinv.is_some();
let ans_is_some =
inv_is_some & mysqrt.is_some() & (mysqrt_value.mul(mysqrt_value)).ct_eq(&x);
CtOption::new(ans_value, ans_is_some)
}
fn legendre(&self) -> i8 {
let exp = TSCONSTS::HALF_ORDER;
let exp_limbs = exp.as_limbs().map(|limb| limb.0);
let symb = self.pow(&exp_limbs);
let ret = i8::conditional_select(&-1, &1, symb.is_one());
i8::conditional_select(&0, &ret, !symb.is_zero())
}
fn characteristic() -> Vec<u64> {
FpElement::<MOD, LIMBS>::characteristic()
}
fn degree() -> u32 {
M as u32
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS> FieldRandom
for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
fn random(rng: &mut (impl rand::CryptoRng + rand::Rng)) -> Self {
let coeffs = std::array::from_fn(|_| FpElement::<MOD, LIMBS>::random(rng));
Self::new(coeffs)
}
}
impl<MOD, const LIMBS: usize, const M: usize, const N: usize, P, TSCONSTS>
FieldFromRepr for FpExt<MOD, LIMBS, M, N, P, TSCONSTS>
where
MOD: ConstPrimeMontyParams<LIMBS>,
P: IrreduciblePoly<MOD, LIMBS, M>,
TSCONSTS: TonelliShanksConstants<MOD, LIMBS, M, N>,
{
type Repr = [FpElement<MOD, LIMBS>; M];
fn from_repr(x: Self::Repr) -> Self {
Self::new(x)
}
}