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 Decomposer {
 17    type Element;
 18    fn new(q: Self::Element, logb: usize, d: usize) -> Self;
 19    //FIXME(Jay): there's no reason why it returns a vec instead of an iterator
 20    fn decompose(&self, v: &Self::Element) -> Vec<Self::Element>;
 21    fn decomposition_count(&self) -> usize;
 22}
 23
 24// TODO(Jay): Shouldn't Decompose also return corresponding gadget vector ?
 25pub struct DefaultDecomposer<T> {
 26    q: T,
 27    logq: usize,
 28    logb: usize,
 29    d: usize,
 30    ignore_bits: usize,
 31    ignore_limbs: usize,
 32}
 33
 34pub trait NumInfo {
 35    const BITS: u32;
 36}
 37
 38impl NumInfo for u64 {
 39    const BITS: u32 = u64::BITS;
 40}
 41impl NumInfo for u32 {
 42    const BITS: u32 = u32::BITS;
 43}
 44impl NumInfo for u128 {
 45    const BITS: u32 = u128::BITS;
 46}
 47
 48impl<T: PrimInt + NumInfo + Debug> DefaultDecomposer<T> {
 49    fn recompose<Op>(&self, limbs: &[T], modq_op: &Op) -> T
 50    where
 51        Op: ArithmeticOps<Element = T>,
 52    {
 53        let mut value = T::zero();
 54        for i in 0..self.d {
 55            value = modq_op.add(
 56                &value,
 57                &(modq_op.mul(
 58                    &limbs[i],
 59                    &(T::one() << (self.logb * (i + self.ignore_limbs))),
 60                )),
 61            )
 62        }
 63        value
 64    }
 65
 66    pub(crate) fn gadget_vector(&self) -> Vec<T> {
 67        return gadget_vector(self.logq, self.logb, self.d);
 68    }
 69}
 70
 71impl<T: PrimInt + WrappingSub + Debug + NumInfo> Decomposer for DefaultDecomposer<T> {
 72    type Element = T;
 73
 74    fn new(q: T, logb: usize, d: usize) -> DefaultDecomposer<T> {
 75        // if q is power of 2, then `BITS - leading_zeros` outputs logq + 1.
 76        let logq = if q & (q - T::one()) == T::zero() {
 77            (T::BITS - q.leading_zeros() - 1) as usize
 78        } else {
 79            (T::BITS - q.leading_zeros()) as usize
 80        };
 81
 82        let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
 83        let ignore_limbs = (d_ideal - d);
 84        let ignore_bits = (d_ideal - d) * logb;
 85
 86        DefaultDecomposer {
 87            q,
 88            logq,
 89            logb,
 90            d,
 91            ignore_bits,
 92            ignore_limbs,
 93        }
 94    }
 95
 96    fn decompose(&self, value: &T) -> Vec<T> {
 97        let value = round_value(*value, self.ignore_bits);
 98
 99        let q = self.q;
100        // if value >= (q >> 1) {
101        //     value = value.wrapping_sub(&q);
102        // }
103
104        let logb = self.logb;
105        let b = T::one() << logb; // base
106        let b_by2 = T::one() << (logb - 1);
107        // let neg_b_by2_modq = q - b_by2;
108        let full_mask = (T::one() << logb) - T::one();
109        // let half_mask = b_by2 - T::one();
110        let mut carry = T::zero();
111        let mut out = Vec::<T>::with_capacity(self.d);
112        for i in 0..self.d {
113            let mut limb = ((value >> (logb * i)) & full_mask) + carry;
114            carry = T::zero();
115            if limb >= b_by2 {
116                limb = (q + limb) - b;
117                carry = T::one();
118            }
119
120            // carry = ((q + g - limb) % q) >> logb;
121
122            // carry = limb & b_by2;
123            // limb = (q + limb) - (carry << 1);
124            // if limb > q {
125            //     limb = limb - q;
126            // }
127            out.push(limb);
128
129            // carry = carry >> (logb - 1);
130        }
131
132        out[self.d - 1] = out[self.d - 1] + (carry << logb);
133        if out[self.d - 1] > q {
134            out[self.d - 1] = out[self.d - 1] - q;
135        }
136
137        return out;
138    }
139
140    fn decomposition_count(&self) -> usize {
141        self.d
142    }
143}
144
145fn round_value<T: PrimInt>(value: T, ignore_bits: usize) -> T {
146    if ignore_bits == 0 {
147        return value;
148    }
149
150    let ignored_msb = (value & ((T::one() << ignore_bits) - T::one())) >> (ignore_bits - 1);
151    (value >> ignore_bits) + ignored_msb
152}
153
154#[cfg(test)]
155mod tests {
156    use rand::{thread_rng, Rng};
157
158    use crate::{
159        backend::{ModInit, ModularOpsU64},
160        decomposer::round_value,
161        utils::generate_prime,
162    };
163
164    use super::{Decomposer, DefaultDecomposer};
165
166    #[test]
167    fn decomposition_works() {
168        let logq = 55;
169        let logb = 9;
170        let d = 6;
171
172        let mut rng = thread_rng();
173
174        // q is prime of bits logq and i is true, other q = 1<<logq
175        // FIXME: Test fails when q is prime, albeit the difference is minute
176        for i in [true, false] {
177            let q = if i {
178                generate_prime(logq, 1 << 4, 1u64 << logq).unwrap()
179            } else {
180                1u64 << logq
181            };
182            let decomposer = DefaultDecomposer::new(q, logb, d);
183            let modq_op = ModularOpsU64::new(q);
184            for _ in 0..1000 {
185                let value = rng.gen_range(0..q);
186                let limbs = decomposer.decompose(&value);
187                let value_back = decomposer.recompose(&limbs, &modq_op);
188                let rounded_value =
189                    round_value(value, decomposer.ignore_bits) << decomposer.ignore_bits;
190                // dbg!(&limbs, q);
191                assert_eq!(
192                    rounded_value, value_back,
193                    "Expected {rounded_value} got {value_back} for q={q}"
194                );
195            }
196        }
197    }
198}