ark_ff/fields/cyclotomic.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
/// Fields that have a cyclotomic multiplicative subgroup, and which can
/// leverage efficient inversion and squaring algorithms for elements in this subgroup.
/// If a field has multiplicative order p^d - 1, the cyclotomic subgroups refer to subgroups of order φ_n(p),
/// for any n < d, where φ_n is the [n-th cyclotomic polynomial](https://en.wikipedia.org/wiki/Cyclotomic_polynomial).
///
/// ## Note
///
/// Note that this trait is unrelated to the `Group` trait from the `ark_ec` crate. That trait
/// denotes an *additive* group, while this trait denotes a *multiplicative* group.
pub trait CyclotomicMultSubgroup: crate::Field {
/// Is the inverse fast to compute? For example, in quadratic extensions, the inverse
/// can be computed at the cost of negating one coordinate, which is much faster than
/// standard inversion.
/// By default this is `false`, but should be set to `true` for quadratic extensions.
const INVERSE_IS_FAST: bool = false;
/// Compute a square in the cyclotomic subgroup. By default this is computed using [`Field::square`](crate::Field::square), but for
/// degree 12 extensions, this can be computed faster than normal squaring.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_square(&self) -> Self {
let mut result = *self;
*result.cyclotomic_square_in_place()
}
/// Square `self` in place. By default this is computed using
/// [`Field::square_in_place`](crate::Field::square_in_place), but for degree 12 extensions,
/// this can be computed faster than normal squaring.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_square_in_place(&mut self) -> &mut Self {
self.square_in_place()
}
/// Compute the inverse of `self`. See [`Self::INVERSE_IS_FAST`] for details.
/// Returns [`None`] if `self.is_zero()`, and [`Some`] otherwise.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_inverse(&self) -> Option<Self> {
let mut result = *self;
result.cyclotomic_inverse_in_place().copied()
}
/// Compute the inverse of `self`. See [`Self::INVERSE_IS_FAST`] for details.
/// Returns [`None`] if `self.is_zero()`, and [`Some`] otherwise.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_inverse_in_place(&mut self) -> Option<&mut Self> {
self.inverse_in_place()
}
/// Compute a cyclotomic exponentiation of `self` with respect to `e`.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_exp(&self, e: impl AsRef<[u64]>) -> Self {
let mut result = *self;
result.cyclotomic_exp_in_place(e);
result
}
/// Set `self` to be the result of exponentiating `self` by `e`,
/// using efficient cyclotomic algorithms.
///
/// # Warning
///
/// This method should be invoked only when `self` is in the cyclotomic subgroup.
fn cyclotomic_exp_in_place(&mut self, e: impl AsRef<[u64]>) {
if self.is_zero() {
return;
}
if Self::INVERSE_IS_FAST {
// We only use NAF-based exponentiation if inverses are fast to compute.
let naf = crate::biginteger::arithmetic::find_naf(e.as_ref());
exp_loop(self, naf.into_iter().rev())
} else {
exp_loop(
self,
crate::bits::BitIteratorBE::without_leading_zeros(e.as_ref()).map(|e| e as i8),
)
};
}
}
/// Helper function to calculate the double-and-add loop for exponentiation.
fn exp_loop<F: CyclotomicMultSubgroup, I: Iterator<Item = i8>>(f: &mut F, e: I) {
// If the inverse is fast and we're using naf, we compute the inverse of the base.
// Otherwise we do nothing with the variable, so we default it to one.
let self_inverse = if F::INVERSE_IS_FAST {
f.cyclotomic_inverse().unwrap() // The inverse must exist because self is not zero.
} else {
F::one()
};
let mut res = F::one();
let mut found_nonzero = false;
for value in e {
if found_nonzero {
res.cyclotomic_square_in_place();
}
if value != 0 {
found_nonzero = true;
if value > 0 {
res *= &*f;
} else if F::INVERSE_IS_FAST {
// only use naf if inversion is fast.
res *= &self_inverse;
}
}
}
*f = res;
}