poulpy-core 0.5.0

A backend agnostic crate implementing RLWE-based encryption & arithmetic.
Documentation
//! Ciphertext, key, plaintext, and secret-key layout types.
//!
//! This module defines the in-memory representations for every
//! cryptographic object manipulated by `poulpy-core`. Types are
//! organised into three sub-layers:
//!
//! * **Standard** (this module) -- serialisable, platform-independent
//!   byte layouts backed by [`poulpy_hal::layouts::VecZnx`] /
//!   [`poulpy_hal::layouts::MatZnx`].
//! * **[`compressed`]** -- seed-compressed variants that store only
//!   the body; the mask is regenerated from a 32-byte PRNG seed.
//! * **[`prepared`]** -- DFT-domain representations tied to a
//!   specific [`poulpy_hal::layouts::Backend`], used for fast
//!   polynomial multiplication.
//!
//! All layout structs are generic over `D: Data`, enabling owned
//! (`Vec<u8>`), shared (`&[u8]`), and mutable (`&mut [u8]`) backing
//! storage without copying. Conversion between ownership modes is
//! provided by `*ToRef` / `*ToMut` traits.
//!
//! # Parameter newtypes
//!
//! Domain-specific quantities are wrapped in [`u32`]-backed newtypes
//! generated by the `newtype_u32!` macro:
//!
//! | Type | Meaning |
//! |---|---|
//! | [`Degree`] | Ring polynomial degree *N* (always a power of two) |
//! | [`Base2K`] | Base-2 logarithm of the limb radix used in the CRT/digit decomposition |
//! | [`TorusPrecision`] | Total number of precision bits over the torus *T = R/Z* |
//! | [`Rank`] | GLWE rank (number of mask polynomials; 0 for plain LWE) |
//! | [`Dnum`] | Number of gadget-decomposition digits |
//! | [`Dsize`] | Size (in limbs) of each gadget digit |

mod gglwe;
mod gglwe_to_ggsw_key;
mod ggsw;
mod glwe;
mod glwe_automorphism_key;
mod glwe_plaintext;
mod glwe_public_key;
mod glwe_secret;
mod glwe_secret_tensor;
mod glwe_switching_key;
mod glwe_tensor;
mod glwe_tensor_key;
mod glwe_to_lwe_key;
mod lwe;
mod lwe_plaintext;
mod lwe_secret;
mod lwe_switching_key;
mod lwe_to_glwe_key;

pub mod compressed;
pub mod prepared;

pub use compressed::*;
pub use gglwe::*;
pub use gglwe_to_ggsw_key::*;
pub use ggsw::*;
pub use glwe::*;
pub use glwe_automorphism_key::*;
pub use glwe_plaintext::*;
pub use glwe_public_key::*;
pub use glwe_secret::*;
pub use glwe_secret_tensor::*;
pub use glwe_switching_key::*;
pub use glwe_tensor::*;
pub use glwe_tensor_key::*;
pub use glwe_to_lwe_key::*;
pub use lwe::*;
pub use lwe_plaintext::*;
pub use lwe_secret::*;
pub use lwe_switching_key::*;
pub use lwe_to_glwe_key::*;
pub use prepared::*;

use poulpy_hal::layouts::{Backend, Module};

/// Provides access to the ring polynomial degree *N*.
pub trait GetDegree {
    /// Returns the ring degree *N* as a [`Degree`].
    fn ring_degree(&self) -> Degree;
}

impl<B: Backend> GetDegree for Module<B> {
    fn ring_degree(&self) -> Degree {
        Self::n(self).into()
    }
}

/// Newtype over `u32` with arithmetic and comparisons against same type and `u32`.
/// Arithmetic is **saturating** (add/sub/mul) to avoid debug-overflow panics.
macro_rules! newtype_u32 {
    ($(#[$meta:meta])* $name:ident) => {
        $(#[$meta])*
        #[repr(transparent)]
        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
        pub struct $name(pub u32);

        // ----- Conversions -----
        impl From<$name> for u32 {
            #[inline]
            fn from(v: $name) -> u32 {
                v.0
            }
        }
        impl From<$name> for usize {
            #[inline]
            fn from(v: $name) -> usize {
                v.0 as usize
            }
        }

        impl From<u32> for $name {
            #[inline]
            fn from(v: u32) -> $name {
                $name(v)
            }
        }
        impl From<usize> for $name {
            #[inline]
            fn from(v: usize) -> $name {
                debug_assert!(v <= u32::MAX as usize, "{} overflow: {v} > u32::MAX", stringify!($name));
                $name(v as u32)
            }
        }

        // ----- Display -----
        impl ::core::fmt::Display for $name {
            #[inline]
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                write!(f, "{}", self.0)
            }
        }

        // ===== Arithmetic (same type) =====
        impl ::core::ops::Add for $name {
            type Output = $name;
            #[inline]
            fn add(self, rhs: $name) -> $name {
                $name(self.0.saturating_add(rhs.0))
            }
        }
        impl ::core::ops::Sub for $name {
            type Output = $name;
            #[inline]
            fn sub(self, rhs: $name) -> $name {
                $name(self.0.saturating_sub(rhs.0))
            }
        }
        impl ::core::ops::Mul for $name {
            type Output = $name;
            #[inline]
            fn mul(self, rhs: $name) -> $name {
                $name(self.0.saturating_mul(rhs.0))
            }
        }

        // ===== Arithmetic (with u32) =====
        impl ::core::ops::Add<u32> for $name {
            type Output = $name;
            #[inline]
            fn add(self, rhs: u32) -> $name {
                $name(self.0.saturating_add(rhs))
            }
        }
        impl ::core::ops::Sub<u32> for $name {
            type Output = $name;
            #[inline]
            fn sub(self, rhs: u32) -> $name {
                $name(self.0.saturating_sub(rhs))
            }
        }
        impl ::core::ops::Mul<u32> for $name {
            type Output = $name;
            #[inline]
            fn mul(self, rhs: u32) -> $name {
                $name(self.0.saturating_mul(rhs))
            }
        }

        impl $name {
            #[inline]
            pub const fn as_u32(self) -> u32 {
                self.0
            }
            #[inline]
            pub const fn as_usize(self) -> usize {
                self.0 as usize
            }

            #[inline]
            pub fn div_ceil<T: Into<u32>>(self, rhs: T) -> u32 {
                self.0.div_ceil(rhs.into())
            }
        }

        // Optional symmetric forms: u32 (+|-|*) $name -> $name
        impl ::core::ops::Add<$name> for u32 {
            type Output = $name;
            #[inline]
            fn add(self, rhs: $name) -> $name {
                $name(self.saturating_add(rhs.0))
            }
        }
        impl ::core::ops::Sub<$name> for u32 {
            type Output = $name;
            #[inline]
            fn sub(self, rhs: $name) -> $name {
                $name(self.saturating_sub(rhs.0))
            }
        }
        impl ::core::ops::Mul<$name> for u32 {
            type Output = $name;
            #[inline]
            fn mul(self, rhs: $name) -> $name {
                $name(self.saturating_mul(rhs.0))
            }
        }

        // ===== Cross-type comparisons with u32 (both directions) =====
        impl ::core::cmp::PartialEq<u32> for $name {
            #[inline]
            fn eq(&self, other: &u32) -> bool {
                self.0 == *other
            }
        }
        impl ::core::cmp::PartialEq<$name> for u32 {
            #[inline]
            fn eq(&self, other: &$name) -> bool {
                *self == other.0
            }
        }

        impl ::core::cmp::PartialOrd<u32> for $name {
            #[inline]
            fn partial_cmp(&self, other: &u32) -> Option<::core::cmp::Ordering> {
                self.0.partial_cmp(other)
            }
        }
        impl ::core::cmp::PartialOrd<$name> for u32 {
            #[inline]
            fn partial_cmp(&self, other: &$name) -> Option<::core::cmp::Ordering> {
                self.partial_cmp(&other.0)
            }
        }
    };
}

newtype_u32!(
    /// Ring polynomial degree *N* (always a power of two).
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    Degree
);

newtype_u32!(
    /// Torus precision in bits — the total number of significant bits
    /// used to represent elements of the discretised torus *T = R/Z*.
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    TorusPrecision
);

newtype_u32!(
    /// Base-2 logarithm of the limb radix in the digit (CRT)
    /// decomposition of ciphertext coefficients.
    ///
    /// Coefficients are stored as `ceil(k / base2k)` limbs, each
    /// carrying `base2k` bits of precision.
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    Base2K
);

newtype_u32!(
    /// Number of gadget-decomposition digits used in GGLWE / GGSW
    /// ciphertexts.
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    Dnum
);

newtype_u32!(
    /// GLWE rank — the number of mask polynomials in a GLWE ciphertext.
    ///
    /// A rank-0 GLWE is equivalent to a plain LWE ciphertext.
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    Rank
);

newtype_u32!(
    /// Size (in limbs) of each gadget-decomposition digit.
    ///
    /// Wraps a [`u32`] with saturating arithmetic.
    Dsize
);

impl Degree {
    /// Returns log2(n). Assumes n is a positive power of two.
    pub fn log2(&self) -> usize {
        debug_assert!(
            self.0 > 0 && self.0.is_power_of_two(),
            "Degree::log2 requires a positive power of two, got {}",
            self.0
        );
        self.0.trailing_zeros() as usize
    }
}