arnaucube
arnaucube/phantom-zonePublic Self-hosted repository
phantom-zone/src/decomposer.rs
src/decomposer.rsRaw
  1use itertools::Itertools;
  2use num_traits::{AsPrimitive, Num, One, PrimInt, ToPrimitive, WrappingSub, Zero};
  3use std::{fmt::Debug, marker::PhantomData, ops::Rem};
  4
  5use crate::backend::{ArithmeticOps, ModularOpsU64};
  6
  7fn gadget_vector<T: PrimInt>(logq: usize, logb: usize, d: usize) -> Vec<T> {
  8    let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
  9    let ignored_limbs = d_ideal - d;
 10    (ignored_limbs..ignored_limbs + d)
 11        .into_iter()
 12        .map(|i| T::one() << (logb * i))
 13        .collect_vec()
 14}
 15
 16pub trait RlweDecomposer {
 17    type Element;
 18    type D: Decomposer<Element = Self::Element>;
 19
 20    /// Decomposer for RLWE Part A
 21    fn a(&self) -> &Self::D;
 22    /// Decomposer for RLWE Part B
 23    fn b(&self) -> &Self::D;
 24}
 25
 26impl<D> RlweDecomposer for (D, D)
 27where
 28    D: Decomposer,
 29{
 30    type D = D;
 31    type Element = D::Element;
 32    fn a(&self) -> &Self::D {
 33        &self.0
 34    }
 35    fn b(&self) -> &Self::D {
 36        &self.1
 37    }
 38}
 39
 40pub trait Decomposer {
 41    type Element;
 42    fn new(q: Self::Element, logb: usize, d: usize) -> Self;
 43    //FIXME(Jay): there's no reason why it returns a vec instead of an iterator
 44    fn decompose(&self, v: &Self::Element) -> Vec<Self::Element>;
 45    fn decomposition_count(&self) -> usize;
 46}
 47
 48// TODO(Jay): Shouldn't Decompose also return corresponding gadget vector ?
 49pub struct DefaultDecomposer<T> {
 50    q: T,
 51    logq: usize,
 52    logb: usize,
 53    d: usize,
 54    ignore_bits: usize,
 55    ignore_limbs: usize,
 56}
 57
 58pub trait NumInfo {
 59    const BITS: u32;
 60}
 61
 62impl NumInfo for u64 {
 63    const BITS: u32 = u64::BITS;
 64}
 65impl NumInfo for u32 {
 66    const BITS: u32 = u32::BITS;
 67}
 68impl NumInfo for u128 {
 69    const BITS: u32 = u128::BITS;
 70}
 71
 72impl<T: PrimInt + NumInfo + Debug> DefaultDecomposer<T> {
 73    fn recompose<Op>(&self, limbs: &[T], modq_op: &Op) -> T
 74    where
 75        Op: ArithmeticOps<Element = T>,
 76    {
 77        let mut value = T::zero();
 78        for i in 0..self.d {
 79            value = modq_op.add(
 80                &value,
 81                &(modq_op.mul(
 82                    &limbs[i],
 83                    &(T::one() << (self.logb * (i + self.ignore_limbs))),
 84                )),
 85            )
 86        }
 87        value
 88    }
 89
 90    pub(crate) fn gadget_vector(&self) -> Vec<T> {
 91        return gadget_vector(self.logq, self.logb, self.d);
 92    }
 93}
 94
 95impl<T: PrimInt + WrappingSub + Debug + NumInfo> Decomposer for DefaultDecomposer<T> {
 96    type Element = T;
 97
 98    fn new(q: T, logb: usize, d: usize) -> DefaultDecomposer<T> {
 99        // if q is power of 2, then `BITS - leading_zeros` outputs logq + 1.
100        let logq = if q & (q - T::one()) == T::zero() {
101            (T::BITS - q.leading_zeros() - 1) as usize
102        } else {
103            (T::BITS - q.leading_zeros()) as usize
104        };
105
106        let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
107        let ignore_limbs = (d_ideal - d);
108        let ignore_bits = (d_ideal - d) * logb;
109
110        DefaultDecomposer {
111            q,
112            logq,
113            logb,
114            d,
115            ignore_bits,
116            ignore_limbs,
117        }
118    }
119
120    fn decompose(&self, value: &T) -> Vec<T> {
121        let value = round_value(*value, self.ignore_bits);
122
123        let q = self.q;
124        // if value >= (q >> 1) {
125        //     value = value.wrapping_sub(&q);
126        // }
127
128        let logb = self.logb;
129        let b = T::one() << logb; // base
130        let b_by2 = T::one() << (logb - 1);
131        // let neg_b_by2_modq = q - b_by2;
132        let full_mask = (T::one() << logb) - T::one();
133        // let half_mask = b_by2 - T::one();
134        let mut carry = T::zero();
135        let mut out = Vec::<T>::with_capacity(self.d);
136        for i in 0..self.d {
137            let mut limb = ((value >> (logb * i)) & full_mask) + carry;
138            carry = T::zero();
139            if limb >= b_by2 {
140                limb = (q + limb) - b;
141                carry = T::one();
142            }
143
144            // carry = ((q + g - limb) % q) >> logb;
145
146            // carry = limb & b_by2;
147            // limb = (q + limb) - (carry << 1);
148            // if limb > q {
149            //     limb = limb - q;
150            // }
151            out.push(limb);
152
153            // carry = carry >> (logb - 1);
154        }
155
156        out[self.d - 1] = out[self.d - 1] + (carry << logb);
157        if out[self.d - 1] > q {
158            out[self.d - 1] = out[self.d - 1] - q;
159        }
160
161        return out;
162    }
163
164    fn decomposition_count(&self) -> usize {
165        self.d
166    }
167}
168
169// impl<T> Decomposer for dyn AsRef<DefaultDecomposer<T>>
170// where
171//     DefaultDecomposer<T>: Decomposer<Element = T>,
172// {
173//     type Element = T;
174
175//     fn new(q: Self::Element, logb: usize, d: usize) -> Self {
176//         DefaultDecomposer::<T>::new(q, logb, d)
177//     }
178
179//     fn decompose(&self, v: &Self::Element) -> Vec<Self::Element> {
180//         todo!()
181//     }
182
183//     fn decomposition_count(&self) -> usize {
184//         todo!()
185//     }
186// }
187
188// impl<U: AsRef<DefaultDecomposer<T>>> Decomposer for U
189// where
190//     DefaultDecomposer<T>: Decomposer,
191// {
192//     type Element = T;
193
194//     fn new(q: Self::Element, logb: usize, d: usize) -> Self {
195//         todo!()
196//     }
197
198//     fn decompose(&self, v: &Self::Element) -> Vec<Self::Element> {
199//         todo!()
200//     }
201
202//     fn decomposition_count(&self) -> usize {
203//         todo!()
204//     }
205// }
206
207fn round_value<T: PrimInt>(value: T, ignore_bits: usize) -> T {
208    if ignore_bits == 0 {
209        return value;
210    }
211
212    let ignored_msb = (value & ((T::one() << ignore_bits) - T::one())) >> (ignore_bits - 1);
213    (value >> ignore_bits) + ignored_msb
214}
215
216#[cfg(test)]
217mod tests {
218    use rand::{thread_rng, Rng};
219
220    use crate::{
221        backend::{ModInit, ModularOpsU64},
222        decomposer::round_value,
223        utils::generate_prime,
224    };
225
226    use super::{Decomposer, DefaultDecomposer};
227
228    #[test]
229    fn decomposition_works() {
230        let logq = 55;
231        let logb = 9;
232        let d = 6;
233
234        let mut rng = thread_rng();
235
236        // q is prime of bits logq and i is true, other q = 1<<logq
237        // FIXME: Test fails when q is prime, albeit the difference is minute
238        for i in [true, false] {
239            let q = if i {
240                generate_prime(logq, 1 << 4, 1u64 << logq).unwrap()
241            } else {
242                1u64 << logq
243            };
244            let decomposer = DefaultDecomposer::new(q, logb, d);
245            let modq_op = ModularOpsU64::new(q);
246            for _ in 0..1000 {
247                let value = rng.gen_range(0..q);
248                let limbs = decomposer.decompose(&value);
249                let value_back = decomposer.recompose(&limbs, &modq_op);
250                let rounded_value =
251                    round_value(value, decomposer.ignore_bits) << decomposer.ignore_bits;
252                // dbg!(&limbs, q);
253                assert_eq!(
254                    rounded_value, value_back,
255                    "Expected {rounded_value} got {value_back} for q={q}"
256                );
257            }
258        }
259    }
260}