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