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