he_ring/number_ring/
mod.rs

1use feanor_math::algorithms::miller_rabin::is_prime;
2use feanor_math::divisibility::DivisibilityRing;
3use feanor_math::integer::{BigIntRing, IntegerRing, IntegerRingStore};
4use feanor_math::ordered::OrderedRingStore;
5use feanor_math::primitive_int::StaticRing;
6use feanor_math::ring::*;
7use feanor_math::rings::poly::PolyRing;
8use feanor_math::rings::zn::zn_64;
9use feanor_math::seq::*;
10
11use crate::cyclotomic::*;
12
13pub mod quotient;
14pub mod pow2_cyclotomic;
15pub mod odd_cyclotomic;
16pub mod hypercube;
17
18///
19/// Trait for objects that represent number rings (more concretely, orders in number
20/// fields), endowed with certain information that is necessary to perform HE-related
21/// operations efficiently.
22///  - The number ring should have a fixed "small basis", which consists of elements that
23///    have small canonical norm. This basis will be used for operations that care about
24///    "smallness" and noise growth.
25///  - The object should also provide functionality to search for primes `p` modulo which
26///    `R/p` is isomorphic to `Fp^n`, and thus `R/p` has the "multiplicative basis" corresponding
27///    to the unit vectors on the right hand side of `R/p ~ Fp^n`. This basis has the nice
28///    property that it allows computing multiplications component-wise, however note that it
29///    is not fixed but depends on `p`.
30///  - Finally, we also want the order to be generated by a single element `a`, which gives rise
31///    to the "coeff basis", given by `1, a, a^2, ..., a^(n - 1)`.
32/// 
33/// Two [`HENumberRing`]s that are considered equal as given by [`PartialEq`] should
34/// represent the same ring, and also all three basis should coincide (in case of the "multiplicative
35/// basis", it should coincide for every prime `p`).
36/// 
37pub trait HENumberRing: Send + Sync + PartialEq + Clone {
38
39    type Decomposed: HENumberRingMod;
40
41    fn mod_p(&self, Fp: zn_64::Zn) -> Self::Decomposed;
42
43    fn mod_p_required_root_of_unity(&self) -> usize;
44
45    ///
46    /// Returns an upper bound on the value
47    /// ```text
48    ///   sup_(x in R \ {0}) | x |_can / | x |_inf
49    /// ```
50    /// 
51    /// Note that while the canonical norm `|.|_can` depends only on the
52    /// number ring `R`, the infinity norm refers to the infinity norm
53    /// when written w.r.t. the "small basis".
54    /// 
55    fn inf_to_can_norm_expansion_factor(&self) -> f64;
56
57    ///
58    /// Returns an upper bound on the value
59    /// ```text
60    ///   sup_(x in R \ {0}) | x |_inf / | x |_can
61    /// ```
62    /// 
63    /// Note that while the canonical norm `|.|_can` depends only on the
64    /// number ring `R`, the infinity norm refers to the infinity norm
65    /// when written w.r.t. the "small basis".
66    /// 
67    fn can_to_inf_norm_expansion_factor(&self) -> f64;
68
69    ///
70    /// Returns an upper bound on the value
71    /// ```text
72    ///   sup_(x, y in R \ {0}) | xy |_inf / (| x |_inf | y |_inf)
73    /// ```
74    /// 
75    fn product_expansion_factor(&self) -> f64 {
76        self.inf_to_can_norm_expansion_factor().powi(2) * self.can_to_inf_norm_expansion_factor()
77    }
78
79    fn generating_poly<P>(&self, poly_ring: P) -> El<P>
80        where P: RingStore,
81            P::Type: PolyRing + DivisibilityRing,
82            <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing;
83
84    fn rank(&self) -> usize;
85}
86
87///
88/// Subtrait of [`HENumberRing`] for cyclotomic rings, which additionally requires
89/// an implementation of Galois automorphisms.
90/// 
91pub trait HECyclotomicNumberRing: HENumberRing<Decomposed: HECyclotomicNumberRingMod> {    
92
93    fn n(&self) -> usize;
94
95    fn galois_group(&self) -> CyclotomicGaloisGroup {
96        CyclotomicGaloisGroup::new(self.n() as u64)
97    }
98}
99
100///
101/// A [`HENumberRing`] `R` modulo a prime `p` that splits completely in `R`.
102/// 
103/// This object may define up to three different basis of `R / p`, with the following
104/// properties:
105///  - the "small basis" should consist of elements whose shortest lift to `R` has small
106///    canonical norm
107///  - the "multiplicative basis" should allow for component-wise multiplication, i.e. `bi * bi = bi`
108///    and `bi * bj = 0` for `i != j`
109///  - the "coeff basis" should consist of powers of a generator of the ring, which for
110///    cyclotomic rings should be the root of unity.
111/// Both "small basis" and "coeff basis" should be the reduction of a corresponding
112/// canonical basis of `R`.
113/// 
114/// Note that it is valid for any of these basis to coincide, and then implement the 
115/// corresponding conversions as no-ops.
116/// 
117/// This design is motivated by the example of `Z[𝝵_n]` for a composite `n`, since in
118/// this case, we need three different basis.
119///  - The "small basis" is the powerful basis `𝝵^(n/n1 * i1 + ... + n/nr * ir)` with
120///    `0 <= ij < phi(nj)`, where `nj` runs through pairwise coprime factors of `n`
121///  - The "multiplicative basis" is the preimage of the unit vector basis under `Fp[𝝵] -> Fp^phi(n)`
122///  - The "coeff basis" is the basis `1, 𝝵, 𝝵^2, ..., 𝝵^phi(n)`
123/// While one could choose "small basis" and "coeff basis" to be equal (after all, the
124/// elements `𝝵^i` are all "small"), staying in "small basis" whenever possible has
125/// performance benefits, because of the tensor-decomposition.
126/// 
127pub trait HENumberRingMod: Send + Sync + PartialEq {
128
129    fn base_ring(&self) -> &zn_64::Zn;
130
131    fn rank(&self) -> usize;
132
133    fn small_basis_to_mult_basis<V>(&self, data: V)
134        where V: SwappableVectorViewMut<zn_64::ZnEl>;
135
136    fn mult_basis_to_small_basis<V>(&self, data: V)
137        where V: SwappableVectorViewMut<zn_64::ZnEl>;
138
139    fn coeff_basis_to_small_basis<V>(&self, data: V)
140        where V: SwappableVectorViewMut<zn_64::ZnEl>;
141
142    fn small_basis_to_coeff_basis<V>(&self, data: V)
143        where V: SwappableVectorViewMut<zn_64::ZnEl>;
144}
145
146pub trait HECyclotomicNumberRingMod: HENumberRingMod {
147
148    fn n(&self) -> usize;
149
150    fn galois_group(&self) -> CyclotomicGaloisGroup {
151        CyclotomicGaloisGroup::new(self.n() as u64)
152    }
153
154    ///
155    /// Permutes the components of an element w.r.t. the mult basis to
156    /// obtain its image under the given Galois action.
157    /// 
158    fn permute_galois_action<V1, V2>(&self, src: V1, dst: V2, galois_element: CyclotomicGaloisGroupEl)
159        where V1: VectorView<zn_64::ZnEl>,
160            V2: SwappableVectorViewMut<zn_64::ZnEl>;
161}
162
163///
164/// Returns the largest prime number that is `<= leq_than` and `= 1 mod congruent_to_one_mod`,
165/// or `None` if there is no such prime number.
166/// 
167pub fn largest_prime_leq_congruent_to_one(leq_than: i64, congruent_to_one_mod: i64) -> Option<i64> {
168    assert!(leq_than > congruent_to_one_mod);
169    let mut current = leq_than - (leq_than - 1) % congruent_to_one_mod;
170    while !is_prime(&StaticRing::<i64>::RING, &current, 10) {
171        current -= congruent_to_one_mod;
172        if current <= 0 {
173            return None;
174        }
175    }
176    return Some(current);
177}
178
179///
180/// Attempts to find a list of distinct primes, each smaller than `2^max_bits_each_modulus`, whose 
181/// product is between `2^min_bits` and `2^max_bits`.
182/// 
183/// Only primes that are returned by the given function are used, which allows the caller to sample
184/// a list of primes that satisfy additional constraints, like being `= 1 mod n` for some integer `n`.
185/// More concretely, the given function `largest_prime_leq` should, on input `B`, return the largest
186/// prime that satisfies all desired constraint and is `<= B`, or `None` if no such prime exists.
187/// 
188/// This function operates on a best-effort basis, it might not find an accepted result in extreme cases
189/// where `min_bits` and `max_bits` are very small or very close together, even if a result theoretically
190/// exists. It is, however, quite reliable in most situations.
191/// 
192pub fn sample_primes<F>(min_bits: usize, max_bits: usize, max_bits_each_modulus: usize, largest_prime_leq: F) -> Option<Vec<El<BigIntRing>>>
193    where F: FnMut(El<BigIntRing>) -> Option<El<BigIntRing>>
194{
195    extend_sampled_primes(&[], min_bits, max_bits, max_bits_each_modulus, largest_prime_leq)
196}
197
198///
199/// Like [`sample_primes()`], but starts with a non-empty list of primes. All added primes are distinct
200/// from every prime that is already in the starting list.
201/// 
202/// Only primes that are returned by the given function are used, which allows the caller to sample
203/// a list of primes that satisfy additional constraints, like being `= 1 mod n` for some integer `n`.
204/// More concretely, the given function `largest_prime_leq` should, on input `B`, return the largest
205/// prime that satisfies all desired constraint and is `<= B`, or `None` if no such prime exists.
206/// 
207/// This function operates on a best-effort basis, it might not find an accepted result in extreme cases
208/// where `min_bits` and `max_bits` are very small or very close together, even if a result theoretically
209/// exists. It is, however, quite reliable in most situations.
210/// 
211pub fn extend_sampled_primes<F>(begin_with: &[El<BigIntRing>], min_bits: usize, max_bits: usize, max_bits_each_modulus: usize, mut largest_prime_leq: F) -> Option<Vec<El<BigIntRing>>>
212    where F: FnMut(El<BigIntRing>) -> Option<El<BigIntRing>>
213{
214    let ZZbig = BigIntRing::RING;
215    assert!(max_bits > min_bits);
216
217    let mut result = begin_with.iter().map(|p| ZZbig.clone_el(p)).collect::<Vec<_>>();
218    let mut current_bits = result.iter().map(|n| ZZbig.to_float_approx(n).log2()).sum::<f64>();
219    assert!((current_bits.floor() as usize) < max_bits);
220    let mut current_upper_bound = ZZbig.power_of_two(max_bits_each_modulus);
221
222    let min = |x, y| if ZZbig.is_gt(&x, &y) { y } else { x };
223
224    while current_bits < min_bits as f64 {
225
226        if min_bits as f64 - current_bits < max_bits_each_modulus as f64 {  
227            current_upper_bound = min(current_upper_bound, ZZbig.power_of_two(f64::min(max_bits as f64 - current_bits, max_bits_each_modulus as f64).floor() as usize));
228        } else {
229            let required_number_of_primes = ((min_bits as f64 - current_bits) / max_bits_each_modulus as f64).ceil() as usize;
230            current_upper_bound = min(current_upper_bound, ZZbig.power_of_two(f64::min((max_bits as f64 - current_bits) / required_number_of_primes as f64, max_bits_each_modulus as f64).floor() as usize));
231        }
232
233        let mut prime = largest_prime_leq(ZZbig.clone_el(&current_upper_bound))?;
234        current_upper_bound = ZZbig.sub_ref_fst(&prime, ZZbig.one());
235        while begin_with.iter().any(|p| ZZbig.eq_el(p, &prime)) {
236            prime = largest_prime_leq(ZZbig.clone_el(&current_upper_bound))?;
237            current_upper_bound = ZZbig.sub_ref_fst(&prime, ZZbig.one());
238        }
239        let bits = ZZbig.to_float_approx(&prime).log2();
240        current_bits += bits;
241        result.push(ZZbig.clone_el(&prime));
242    }
243    debug_assert!(ZZbig.is_geq(&ZZbig.prod(result.iter().map(|n| ZZbig.clone_el(n))), &ZZbig.power_of_two(min_bits)));
244    debug_assert!(ZZbig.is_lt(&ZZbig.prod(result.iter().map(|n| ZZbig.clone_el(n))), &ZZbig.power_of_two(max_bits)));
245    return Some(result);
246}
247
248#[cfg(test)]
249use feanor_math::integer::int_cast;
250#[cfg(test)]
251use feanor_math::pid::EuclideanRingStore;
252
253#[test]
254fn test_sample_primes() {
255    let ZZi64 = StaticRing::<i64>::RING;
256    let ZZbig = BigIntRing::RING;
257    let result = sample_primes(60, 62, 58, |b| largest_prime_leq_congruent_to_one(int_cast(b, ZZi64, ZZbig), 422144).map(|x| int_cast(x, ZZbig, ZZi64))).unwrap();
258    assert_eq!(result.len(), 2);
259    let prod = ZZbig.prod(result.iter().map(|n| ZZbig.clone_el(n)));
260    assert!(ZZbig.abs_log2_floor(&prod).unwrap() >= 60);
261    assert!(ZZbig.abs_log2_ceil(&prod).unwrap() <= 62);
262    assert!(result.iter().all(|n| ZZbig.is_one(&ZZbig.euclidean_rem(ZZbig.clone_el(n), &int_cast(422144, ZZbig, StaticRing::<i64>::RING)))));
263
264    let ZZbig = BigIntRing::RING;
265    let result = sample_primes(135, 138, 58, |b| largest_prime_leq_congruent_to_one(int_cast(b, ZZi64, ZZbig), 422144).map(|x| int_cast(x, ZZbig, ZZi64))).unwrap();
266    assert_eq!(result.len(), 3);
267    let prod = ZZbig.prod(result.iter().map(|n| ZZbig.clone_el(n)));
268    assert!(ZZbig.abs_log2_floor(&prod).unwrap() >= 135);
269    assert!(ZZbig.abs_log2_ceil(&prod).unwrap() <= 138);
270    assert!(result.iter().all(|n| ZZbig.is_one(&ZZbig.euclidean_rem(ZZbig.clone_el(n), &int_cast(422144, ZZbig, StaticRing::<i64>::RING)))));
271
272    let ZZbig = BigIntRing::RING;
273    let result = sample_primes(115, 118, 58, |b| largest_prime_leq_congruent_to_one(int_cast(b, ZZi64, ZZbig), 422144).map(|x| int_cast(x, ZZbig, ZZi64))).unwrap();
274    assert_eq!(result.len(), 2);
275    let prod = ZZbig.prod(result.iter().map(|n| ZZbig.clone_el(n)));
276    assert!(ZZbig.abs_log2_floor(&prod).unwrap() >= 115);
277    assert!(ZZbig.abs_log2_ceil(&prod).unwrap() <= 118);
278    assert!(result.iter().all(|n| ZZbig.is_one(&ZZbig.euclidean_rem(ZZbig.clone_el(n), &int_cast(422144, ZZbig, StaticRing::<i64>::RING)))));
279}