he_ring/ciphertext_ring/
mod.rs

1use std::alloc::Allocator;
2
3use feanor_math::integer::BigIntRing;
4use feanor_math::matrix::*;
5use feanor_math::ring::*;
6use feanor_math::rings::extension::{FreeAlgebra, FreeAlgebraStore};
7use feanor_math::rings::zn::zn_64::{ZnEl, Zn, ZnBase};
8use feanor_math::rings::zn::zn_rns;
9use feanor_math::seq::{VectorView, VectorFn};
10use tracing::instrument;
11
12use crate::number_ring::quotient::{NumberRingQuotient, NumberRingQuotientEl};
13use crate::number_ring::HECyclotomicNumberRing;
14use crate::rnsconv::RNSOperation;
15
16///
17/// Code for fast polynomial division by a cyclotomic polynomial.
18/// Used within [`single_rns_ring`].
19/// 
20pub mod poly_remainder;
21
22pub mod serialization;
23
24///
25/// Contains [`double_rns_ring::DoubleRNSRing`], an implementation of the ring `R/qR` for suitable `q`.
26///  
27pub mod double_rns_ring;
28
29///
30/// Contains [`single_rns_ring::SingleRNSRing`], an implementation of the ring `R/qR` for suitable `q`.
31///  
32pub mod single_rns_ring;
33
34///
35/// Contains [`double_rns_managed::ManagedDoubleRNSRing`], an implementation of the ring `R/qR` for suitable `q`
36/// that is based on [`double_rns_ring::DoubleRNSRing`].
37///  
38pub mod double_rns_managed;
39
40pub trait PreparedMultiplicationRing: RingBase {
41
42    type PreparedMultiplicant;
43
44    ///
45    /// Converts an element of the ring into a `PreparedMultiplicant`, which can then be used
46    /// to compute multiplications by this element faster.
47    /// 
48    fn prepare_multiplicant(&self, x: &Self::Element) -> Self::PreparedMultiplicant;
49
50    ///
51    /// Computes the product of two elements that have previously been "prepared" via
52    /// [`PreparedMultiplicationRing::prepare_multiplicant()`].
53    /// 
54    fn mul_prepared(&self, lhs: &Self::PreparedMultiplicant, rhs: &Self::PreparedMultiplicant) -> Self::Element;
55
56    ///
57    /// Computes the inner product of two vectors over this ring, whose elements have previously
58    /// been "prepared" via [`PreparedMultiplicationRing::prepare_multiplicant()`].
59    /// 
60    fn inner_product_prepared<'a, I>(&self, parts: I) -> Self::Element
61        where I: IntoIterator<Item = (&'a Self::PreparedMultiplicant, &'a Self::PreparedMultiplicant)>,
62            Self: 'a
63    {
64        parts.into_iter().fold(self.zero(), |current, (lhs, rhs)| self.add(current, self.mul_prepared(lhs, rhs)))
65    }
66}
67
68///
69/// Trait for rings `R/qR` with a number ring `R` and modulus `q = p1 ... pr` represented as 
70/// RNS basis, which provide all necessary operations for use as ciphertext ring in BFV/BGV-style
71/// HE schemes.
72/// 
73pub trait BGFVCiphertextRing: PreparedMultiplicationRing + FreeAlgebra + RingExtension<BaseRing = zn_rns::Zn<Zn, BigIntRing>> {
74
75    type NumberRing: HECyclotomicNumberRing;
76
77    fn number_ring(&self) -> &Self::NumberRing;
78
79    ///
80    /// Computes the ring `R_q'`, where `q'` is the product of all RNS factors of `q`,
81    /// except those whose indices are mentioned in `drop_rns_factors`.
82    /// 
83    fn drop_rns_factor(&self, drop_rns_factors: &[usize]) -> Self;
84
85    ///
86    /// Reduces an element of `from` modulo `q`, where `q` must divide the modulus of `from`.
87    /// 
88    /// More concretely, the RNS factors of `q` must be exactly the RNS factors of `from.rns_base()`,
89    /// except for the RNS factors whose indices occur in `dropped_rns_factors`.
90    /// 
91    fn drop_rns_factor_element(&self, from: &Self, dropped_rns_factors: &[usize], value: Self::Element) -> Self::Element;
92
93    ///
94    /// Reduces a `PreparedMultiplicant` of `from` modulo `q`, where `q` must divide the modulus of `from`.
95    /// 
96    /// More concretely, the RNS factors of `q` must be exactly the RNS factors of `from.rns_base()`,
97    /// except for the RNS factors whose indices occur in `dropped_rns_factors`.
98    /// 
99    fn drop_rns_factor_prepared(&self, from: &Self, dropped_rns_factors: &[usize], value: Self::PreparedMultiplicant) -> Self::PreparedMultiplicant;
100
101    ///
102    /// Returns the length of the small generating set used for [`BGFVCiphertextRing::as_representation_wrt_small_generating_set()`]
103    /// and [`BGFVCiphertextRing::from_representation_wrt_small_generating_set()`].
104    /// 
105    fn small_generating_set_len(&self) -> usize;
106
107    ///
108    /// Returns a view on the underlying representation of `x`. 
109    /// 
110    /// This is the counterpart of [`BGFVCiphertextRing::from_representation_wrt_small_generating_set()`].
111    /// 
112    /// More concretely, for some `Zq`-linear generating set `{ a_i | i }` consisting
113    /// of ring elements of small canonical norm, each column of the returned matrix contains
114    /// the RNS representation of some `x_i`, satisfying `x = sum_i a_i x_i`. The actual choice
115    /// of the `a_i` is left to the ring implementation, and may change in future releases.
116    /// The order of the rows (corresponding to the RNS factors of `Zq`) is the same as the
117    /// order of the RNS factors in `self.base_ring()`.
118    /// 
119    /// This function is a compromise between encapsulating the storage of ring elements
120    /// and exposing it (which is sometimes necessary for performance). 
121    /// Hence, it is recommended to instead use [`FreeAlgebra::wrt_canonical_basis()`] and
122    /// [`FreeAlgebra::from_canonical_basis()`], whose result is uniquely defined. However, note
123    /// that these may incur costs for internal representation conversion, which may not always
124    /// be acceptable.
125    /// 
126    /// Concrete representations:
127    ///  - [`single_rns_ring::SingleRNSRing`] will currently return the coefficients of a polynomial
128    ///    of degree `< n` (not necessarily `< phi(n)`) that gives the element when evaluated at `𝝵`
129    ///  - [`double_rns_managed::ManagedDoubleRNSRing`] will currently return the coefficients w.r.t.
130    ///    the powerful basis representation
131    /// 
132    fn as_representation_wrt_small_generating_set<V>(&self, x: &Self::Element, output: SubmatrixMut<V, ZnEl>)
133        where V: AsPointerToSlice<ZnEl>;
134
135    ///
136    /// Computes a subset of the rows of the representation that would be returned by
137    /// [`BGFVCiphertextRing::as_representation_wrt_small_generating_set()`]. Since not all rows
138    /// have to be computed, this may be faster than `as_representation_wrt_small_generating_set()`.
139    /// 
140    /// This function is a compromise between encapsulating the storage of ring elements and exposing 
141    /// it (which is sometimes necessary for performance).
142    /// Hence, it is recommended to instead use [`FreeAlgebra::wrt_canonical_basis()`] and
143    /// [`FreeAlgebra::from_canonical_basis()`], whose result is uniquely defined. However, note
144    /// that these may incur costs for internal representation conversion, which may not always
145    /// be acceptable.
146    /// 
147    fn partial_representation_wrt_small_generating_set<V>(&self, x: &Self::Element, row_indices: &[usize], output: SubmatrixMut<V, ZnEl>)
148        where V: AsPointerToSlice<ZnEl>;
149
150    ///
151    /// Creates a ring element from its underlying representation.
152    /// 
153    /// This is the counterpart of [`BGFVCiphertextRing::as_representation_wrt_small_generating_set()`],
154    /// which contains a more detailed documentation.
155    /// 
156    /// This function is a compromise between encapsulating the storage of ring elements
157    /// and exposing it (which is sometimes necessary for performance). 
158    /// Hence, it is recommended to instead use [`FreeAlgebra::wrt_canonical_basis()`] and
159    /// [`FreeAlgebra::from_canonical_basis()`], whose result is uniquely defined. However, note
160    /// that these may incur costs for internal representation conversion, which may not always
161    /// be acceptable.
162    /// 
163    fn from_representation_wrt_small_generating_set<V>(&self, data: Submatrix<V, ZnEl>) -> Self::Element
164        where V: AsPointerToSlice<ZnEl>;
165
166    ///
167    /// Computes `[lhs[0] * rhs[0], lhs[0] * rhs[1] + lhs[1] * rhs[0], lhs[1] * rhs[1]]`, but might be
168    /// faster than the naive way of evaluating this.
169    /// 
170    #[instrument(skip_all)]
171    fn two_by_two_convolution(&self, lhs: [&Self::Element; 2], rhs: [&Self::Element; 2]) -> [Self::Element; 3] {
172        let mut lhs_it = lhs.into_iter();
173        let mut rhs_it = rhs.into_iter();
174        let lhs: [_; 2] = std::array::from_fn(|_| self.prepare_multiplicant(lhs_it.next().unwrap()));
175        let rhs: [_; 2] = std::array::from_fn(|_| self.prepare_multiplicant(rhs_it.next().unwrap()));
176        [
177            self.mul_prepared(&lhs[0], &rhs[0]),
178            self.inner_product_prepared([(&lhs[0], &rhs[1]), (&lhs[1], &rhs[0])]),
179            self.mul_prepared(&lhs[1], &rhs[1])
180        ]
181    }
182}
183
184#[instrument(skip_all)]
185pub fn perform_rns_op<R, Op>(to: &R, from: &R, el: &R::Element, op: &Op) -> R::Element
186    where R: BGFVCiphertextRing,
187        Op: RNSOperation<RingType = ZnBase>
188{
189    assert!(from.number_ring() == to.number_ring());
190    assert_eq!(op.input_rings().len(), from.base_ring().len());
191    assert_eq!(op.output_rings().len(), to.base_ring().len());
192    assert!(op.input_rings().iter().zip(from.base_ring().as_iter()).all(|(l, r)| l.get_ring() == r.get_ring()));
193    assert!(op.output_rings().iter().zip(to.base_ring().as_iter()).all(|(l, r)| l.get_ring() == r.get_ring()));
194
195    let mut el_repr = OwnedMatrix::zero(from.base_ring().len(), from.small_generating_set_len(), from.base_ring().at(0));
196    from.as_representation_wrt_small_generating_set(el, el_repr.data_mut());
197    let mut res_repr = Vec::with_capacity(el_repr.col_count() * to.base_ring().len());
198    res_repr.resize(el_repr.col_count() * to.base_ring().len(), to.base_ring().at(0).zero());
199    let mut res_repr = SubmatrixMut::from_1d(&mut res_repr, to.base_ring().len(), el_repr.col_count());
200    op.apply(el_repr.data(), res_repr.reborrow());
201    return to.from_representation_wrt_small_generating_set(res_repr.as_const());
202}
203
204#[instrument(skip_all)]
205pub fn perform_rns_op_to_plaintext_ring<R, Op, A>(to: &NumberRingQuotient<R::NumberRing, Zn, A>, from: &R, el: &R::Element, op: &Op) -> NumberRingQuotientEl<R::NumberRing, Zn, A>
206    where R: BGFVCiphertextRing,
207        Op: RNSOperation<RingType = ZnBase>,
208        A: Allocator + Clone
209{
210    assert!(from.number_ring() == to.get_ring().number_ring());
211    assert_eq!(op.input_rings().len(), from.base_ring().len());
212    assert_eq!(op.output_rings().len(), 1);
213    assert!(op.input_rings().iter().zip(from.base_ring().as_iter()).all(|(l, r)| l.get_ring() == r.get_ring()));
214    assert!(op.output_rings().at(0).get_ring() == to.base_ring().get_ring());
215
216    let mut el_repr = Vec::with_capacity(from.rank() * from.base_ring().len());
217    el_repr.resize(from.rank() * from.base_ring().len(), from.base_ring().at(0).zero());
218    let mut el_repr = SubmatrixMut::from_1d(&mut el_repr, from.base_ring().len(), from.rank());
219    for (j, c) in from.wrt_canonical_basis(el).iter().enumerate() {
220        for (i, x) in from.base_ring().get_congruence(&c).as_iter().enumerate() {
221            *el_repr.at_mut(i, j) = *x;
222        }
223    }
224
225    let mut res_repr = Vec::with_capacity(el_repr.col_count());
226    res_repr.resize(el_repr.col_count(), to.base_ring().zero());
227    let mut res_repr = SubmatrixMut::from_1d(&mut res_repr, 1, el_repr.col_count());
228    op.apply(el_repr.as_const(), res_repr.reborrow());
229    return to.from_canonical_basis(res_repr.row_at(0).copy_els().iter());
230}