arnaucube
arnaucube/phantom-zonePublic Self-hosted repository
phantom-zone/src/decomposer.rs
src/decomposer.rsRaw
  1use itertools::Itertools;
  2use num_traits::{AsPrimitive, One, PrimInt, ToPrimitive, WrappingSub, Zero};
  3use std::{fmt::Debug, marker::PhantomData, ops::Rem};
  4
  5use crate::backend::{ArithmeticOps, ModularOpsU64};
  6
  7pub fn 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    //FIXME(Jay): there's no reason why it returns a vec instead of an iterator
 19    fn decompose(&self, v: &Self::Element) -> Vec<Self::Element>;
 20    fn d(&self) -> usize;
 21}
 22
 23pub struct DefaultDecomposer<T> {
 24    q: T,
 25    logq: usize,
 26    logb: usize,
 27    d: usize,
 28    ignore_bits: usize,
 29    ignore_limbs: usize,
 30}
 31
 32pub trait NumInfo {
 33    const BITS: u32;
 34}
 35
 36impl NumInfo for u64 {
 37    const BITS: u32 = u64::BITS;
 38}
 39impl NumInfo for u32 {
 40    const BITS: u32 = u32::BITS;
 41}
 42impl NumInfo for u128 {
 43    const BITS: u32 = u128::BITS;
 44}
 45
 46impl<T: PrimInt + NumInfo + Debug> DefaultDecomposer<T> {
 47    pub fn new(q: T, logb: usize, d: usize) -> DefaultDecomposer<T> {
 48        // if q is power of 2, then BITS - leading zeros outputs logq + 1.
 49        let logq = if q & (q - T::one()) == T::zero() {
 50            (T::BITS - q.leading_zeros() - 1) as usize
 51        } else {
 52            (T::BITS - q.leading_zeros()) as usize
 53        };
 54
 55        let d_ideal = (logq as f64 / logb as f64).ceil().to_usize().unwrap();
 56        let ignore_limbs = (d_ideal - d);
 57        let ignore_bits = (d_ideal - d) * logb;
 58
 59        DefaultDecomposer {
 60            q,
 61            logq,
 62            logb,
 63            d,
 64            ignore_bits,
 65            ignore_limbs,
 66        }
 67    }
 68
 69    fn recompose<Op>(&self, limbs: &[T], modq_op: &Op) -> T
 70    where
 71        Op: ArithmeticOps<Element = T>,
 72    {
 73        let mut value = T::zero();
 74        for i in self.ignore_limbs..self.ignore_limbs + self.d {
 75            value = modq_op.add(
 76                &value,
 77                &(modq_op.mul(&limbs[i], &(T::one() << (self.logb * i)))),
 78            )
 79        }
 80        value
 81    }
 82}
 83
 84impl<T: PrimInt + WrappingSub + Debug> Decomposer for DefaultDecomposer<T> {
 85    type Element = T;
 86    fn decompose(&self, value: &T) -> Vec<T> {
 87        let value = round_value(*value, self.ignore_bits);
 88
 89        let q = self.q;
 90        let logb = self.logb;
 91        // let b = T::one() << logb; // base
 92        let b_by2 = T::one() << (logb - 1);
 93        // let neg_b_by2_modq = q - b_by2;
 94        let full_mask = (T::one() << logb) - T::one();
 95        // let half_mask = b_by2 - T::one();
 96        let mut carry = T::zero();
 97        let mut out = Vec::<T>::with_capacity(self.d);
 98        for i in 0..self.d {
 99            let mut limb = ((value >> (logb * i)) & full_mask) + carry;
100
101            carry = limb & b_by2;
102            limb = (q + limb) - (carry << 1);
103            if limb > q {
104                limb = limb - q;
105            }
106            out.push(limb);
107
108            carry = carry >> (logb - 1);
109        }
110
111        return out;
112    }
113
114    fn d(&self) -> usize {
115        self.d
116    }
117}
118
119fn round_value<T: PrimInt>(value: T, ignore_bits: usize) -> T {
120    if ignore_bits == 0 {
121        return value;
122    }
123
124    let ignored_msb = (value & ((T::one() << ignore_bits) - T::one())) >> (ignore_bits - 1);
125    (value >> ignore_bits) + ignored_msb
126}
127
128#[cfg(test)]
129mod tests {
130    use rand::{thread_rng, Rng};
131
132    use crate::{backend::ModularOpsU64, decomposer::round_value, utils::generate_prime};
133
134    use super::{Decomposer, DefaultDecomposer};
135
136    #[test]
137    fn decomposition_works() {
138        let logq = 50;
139        let logb = 5;
140        let d = 10;
141
142        // q is prime of bits logq and i is true, other q = 1<<logq
143        for i in [true, false] {
144            let q = if i {
145                generate_prime(logq, 1 << 4, 1u64 << logq).unwrap()
146            } else {
147                1u64 << 50
148            };
149
150            let decomposer = DefaultDecomposer::new(q, logb, d);
151            let modq_op = ModularOpsU64::new(q);
152            for _ in 0..100 {
153                let value = 1000000;
154                let limbs = decomposer.decompose(&value);
155                let value_back = decomposer.recompose(&limbs, &modq_op);
156                let rounded_value = round_value(value, decomposer.ignore_bits);
157                assert_eq!(
158                    rounded_value, value_back,
159                    "Expected {rounded_value} got {value_back} for q={q}"
160                );
161            }
162        }
163    }
164}