phantom-zone/src/decomposer.rs
src/decomposer.rsRaw
1use itertools::Itertools;
2use num_traits::{AsPrimitive, FromPrimitive, Num, One, PrimInt, ToPrimitive, WrappingSub, Zero};
3use std::{
4 fmt::{Debug, Display},
5 marker::PhantomData,
6 ops::Rem,
7};
8
9use crate::backend::{ArithmeticOps, ModularOpsU64};
10
11fn gadget_vector<T: PrimInt>(logq: usize, logb: usize, d: usize) -> Vec<T> {
12 let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
13 let ignored_limbs = d_ideal - d;
14 (ignored_limbs..ignored_limbs + d)
15 .into_iter()
16 .map(|i| T::one() << (logb * i))
17 .collect_vec()
18}
19
20pub trait RlweDecomposer {
21 type Element;
22 type D: Decomposer<Element = Self::Element>;
23
24 /// Decomposer for RLWE Part A
25 fn a(&self) -> &Self::D;
26 /// Decomposer for RLWE Part B
27 fn b(&self) -> &Self::D;
28}
29
30impl<D> RlweDecomposer for (D, D)
31where
32 D: Decomposer,
33{
34 type D = D;
35 type Element = D::Element;
36 fn a(&self) -> &Self::D {
37 &self.0
38 }
39 fn b(&self) -> &Self::D {
40 &self.1
41 }
42}
43
44pub trait Decomposer {
45 type Element;
46 type Iter: Iterator<Item = Self::Element>;
47 fn new(q: Self::Element, logb: usize, d: usize) -> Self;
48
49 fn decompose_to_vec(&self, v: &Self::Element) -> Vec<Self::Element>;
50 fn decompose_iter(&self, v: &Self::Element) -> Self::Iter;
51 fn decomposition_count(&self) -> usize;
52}
53
54pub struct DefaultDecomposer<T> {
55 /// Ciphertext modulus
56 q: T,
57 /// Log of ciphertext modulus
58 logq: usize,
59 /// Log of base B
60 logb: usize,
61 /// base B
62 b: T,
63 /// (B - 1). To simulate (% B) as &(B-1), that is extract least significant
64 /// logb bits
65 b_mask: T,
66 /// B/2
67 bby2: T,
68 /// Decomposition count
69 d: usize,
70 /// No. of bits to ignore in rounding
71 ignore_bits: usize,
72 /// No. of limbs to ignore in rounding. Set to ceil(logq / logb) - d
73 ignore_limbs: usize,
74}
75
76pub trait NumInfo {
77 const BITS: u32;
78}
79
80impl NumInfo for u64 {
81 const BITS: u32 = u64::BITS;
82}
83impl NumInfo for u32 {
84 const BITS: u32 = u32::BITS;
85}
86impl NumInfo for u128 {
87 const BITS: u32 = u128::BITS;
88}
89
90impl<T: PrimInt + NumInfo + Debug> DefaultDecomposer<T> {
91 fn recompose<Op>(&self, limbs: &[T], modq_op: &Op) -> T
92 where
93 Op: ArithmeticOps<Element = T>,
94 {
95 let mut value = T::zero();
96 for i in 0..self.d {
97 value = modq_op.add(
98 &value,
99 &(modq_op.mul(
100 &limbs[i],
101 &(T::one() << (self.logb * (i + self.ignore_limbs))),
102 )),
103 )
104 }
105 value
106 }
107
108 pub(crate) fn gadget_vector(&self) -> Vec<T> {
109 return gadget_vector(self.logq, self.logb, self.d);
110 }
111}
112
113impl<T: PrimInt + ToPrimitive + FromPrimitive + WrappingSub + NumInfo + From<bool> + Display>
114 Decomposer for DefaultDecomposer<T>
115{
116 type Element = T;
117 type Iter = DecomposerIter<T>;
118
119 fn new(q: T, logb: usize, d: usize) -> DefaultDecomposer<T> {
120 // if q is power of 2, then `BITS - leading_zeros` outputs logq + 1.
121 let logq = if q & (q - T::one()) == T::zero() {
122 (T::BITS - q.leading_zeros() - 1) as usize
123 } else {
124 (T::BITS - q.leading_zeros()) as usize
125 };
126
127 let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
128 let ignore_limbs = (d_ideal - d);
129 let ignore_bits = (d_ideal - d) * logb;
130
131 DefaultDecomposer {
132 q,
133 logq,
134 logb,
135 b: T::one() << logb,
136 b_mask: (T::one() << logb) - T::one(),
137 bby2: T::one() << (logb - 1),
138 d,
139 ignore_bits,
140 ignore_limbs,
141 }
142 }
143
144 // TODO(Jay): Outline the caveat
145 fn decompose_to_vec(&self, value: &T) -> Vec<T> {
146 let mut value = round_value(*value, self.ignore_bits);
147
148 let q = self.q;
149 let logb = self.logb;
150 let b = T::one() << logb;
151 let full_mask = b - T::one();
152 let bby2 = b >> 1;
153
154 if value >= (q >> 1) {
155 value = !(q - value) + T::one()
156 }
157
158 let mut out = Vec::with_capacity(self.d);
159 for _ in 0..self.d {
160 let k_i = value & full_mask;
161
162 value = (value - k_i) >> logb;
163
164 if k_i > bby2 || (k_i == bby2 && ((value & T::one()) == T::one())) {
165 out.push(q - (b - k_i));
166 value = value + T::one();
167 } else {
168 out.push(k_i);
169 }
170 }
171
172 return out;
173 }
174
175 fn decomposition_count(&self) -> usize {
176 self.d
177 }
178
179 fn decompose_iter(&self, value: &T) -> DecomposerIter<T> {
180 let mut value = round_value(*value, self.ignore_bits);
181
182 if value >= (self.q >> 1) {
183 value = !(self.q - value) + T::one()
184 }
185
186 DecomposerIter {
187 value,
188 q: self.q,
189 logq: self.logq,
190 logb: self.logb,
191 b: self.b,
192 bby2: self.bby2,
193 b_mask: self.b_mask,
194 steps_left: self.d,
195 }
196 }
197}
198
199impl<T: PrimInt> DefaultDecomposer<T> {}
200
201pub struct DecomposerIter<T> {
202 /// Value to decompose
203 value: T,
204 steps_left: usize,
205 /// (1 << logb) - 1 (for % (1<<logb); i.e. to extract least signiciant logb
206 /// bits)
207 b_mask: T,
208 logb: usize,
209 // b/2 = 1 << (logb-1)
210 bby2: T,
211 /// Ciphertext modulus
212 q: T,
213 /// Log of ciphertext modulus
214 logq: usize,
215 /// b = 1 << logb
216 b: T,
217}
218
219impl<T: PrimInt + From<bool> + WrappingSub + Display> Iterator for DecomposerIter<T> {
220 type Item = T;
221
222 fn next(&mut self) -> Option<Self::Item> {
223 if self.steps_left != 0 {
224 self.steps_left -= 1;
225 let k_i = self.value & self.b_mask;
226
227 self.value = (self.value - k_i) >> self.logb;
228
229 // if k_i > self.bby2 || (k_i == self.bby2 && ((self.value &
230 // T::one()) == T::one())) { self.value = self.value
231 // + T::one(); Some(self.q + k_i - self.b)
232 // } else {
233 // Some(k_i)
234 // }
235
236 // Following is without branching impl of the commented version above. It
237 // happens to speed up bootstrapping for `SMALL_MP_BOOL_PARAMS` (& other
238 // parameters as well but I haven't tested) by roughly 15ms.
239 // Suprisingly the improvement does not show up when I benchmark
240 // `decomposer_iter` in isolation. Putting this remark here as a
241 // future task to investiage (TODO).
242 let carry_bool =
243 k_i > self.bby2 || (k_i == self.bby2 && ((self.value & T::one()) == T::one()));
244 let carry = <T as From<bool>>::from(carry_bool);
245 let neg_carry = (T::zero().wrapping_sub(&carry));
246 self.value = self.value + carry;
247 Some((neg_carry & self.q) + k_i - (carry << self.logb))
248
249 // Some(
250 // (self.q & ((carry << self.logq) - (T::one() & carry))) + k_i
251 // - (carry << self.logb), )
252
253 // Some(k_i)
254 } else {
255 None
256 }
257 }
258}
259
260fn round_value<T: PrimInt>(value: T, ignore_bits: usize) -> T {
261 if ignore_bits == 0 {
262 return value;
263 }
264
265 let ignored_msb = (value & ((T::one() << ignore_bits) - T::one())) >> (ignore_bits - 1);
266 (value >> ignore_bits) + ignored_msb
267}
268
269#[cfg(test)]
270mod tests {
271
272 use itertools::Itertools;
273 use rand::{thread_rng, Rng};
274
275 use crate::{
276 backend::{ModInit, ModularOpsU64},
277 decomposer::round_value,
278 utils::{generate_prime, tests::Stats, TryConvertFrom1},
279 };
280
281 use super::{Decomposer, DefaultDecomposer};
282
283 #[test]
284 fn decomposition_works() {
285 let logq = 55;
286 let logb = 11;
287 let d = 5;
288 let ring_size = 1 << 11;
289
290 let mut rng = thread_rng();
291 let mut stats = Stats::new();
292
293 for i in [true] {
294 let q = if i {
295 generate_prime(logq, 2 * ring_size, 1u64 << logq).unwrap()
296 } else {
297 1u64 << logq
298 };
299 let decomposer = DefaultDecomposer::new(q, logb, d);
300 let modq_op = ModularOpsU64::new(q);
301 for _ in 0..100000 {
302 let value = rng.gen_range(0..q);
303 let limbs = decomposer.decompose_to_vec(&value);
304 let limbs_from_iter = decomposer.decompose_iter(&value).collect_vec();
305 assert_eq!(limbs, limbs_from_iter);
306 let value_back = round_value(
307 decomposer.recompose(&limbs, &modq_op),
308 decomposer.ignore_bits,
309 );
310 let rounded_value = round_value(value, decomposer.ignore_bits);
311 assert_eq!(
312 rounded_value, value_back,
313 "Expected {rounded_value} got {value_back} for q={q}"
314 );
315
316 stats.add_more(&Vec::<i64>::try_convert_from(&limbs, &q));
317 }
318 }
319 println!("Mean: {}", stats.mean());
320 println!("Std: {}", stats.std_dev().abs().log2());
321 }
322}