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}