Skip to main content

poulpy_ckks/
lib.rs

1//! # poulpy-ckks
2//!
3//! Backend-agnostic implementation of the CKKS (Cheon-Kim-Kim-Song)
4//! homomorphic encryption scheme, built on top of the low-level primitives
5//! provided by `poulpy-core`, `poulpy-hal`, and the available compute
6//! backends (`poulpy-cpu-ref`, `poulpy-cpu-avx`).
7//!
8//! The crate uses a bivariate polynomial representation over the Torus
9//! (base-`2^{base2k}` digits) instead of the RNS representation used by
10//! most other CKKS libraries. Public precision management is exposed through
11//! [`CKKSMeta`]:
12//!
13//! - `log_delta`: base-2 logarithm of the encoded plaintext scaling factor
14//! - `log_budget`: remaining homomorphic headroom, also tracked in bits
15//!
16//! Together they define the semantic torus width of a value:
17//! `effective_k() = log_delta + log_budget`.
18//! Storage is rounded up to the next multiple of `base2k`, so the allocated
19//! width `max_k()` may exceed `effective_k()`. Arithmetic APIs update this
20//! metadata for you, while maintenance helpers let you compact or resize owned
21//! buffers without violating those invariants.
22//!
23//! Safe add/sub operations return K-normalized ciphertexts. The paired
24//! unnormalized traits ([`api::CKKSAddOpsUnnormalized`] and
25//! [`api::CKKSSubOpsUnnormalized`]) write into an
26//! [`layouts::UnnormalizedCKKSCiphertext`] for callers who want to fuse
27//! several linear steps before normalizing explicitly. Limb digits in that
28//! wrapper may hold un-propagated carries (wider than `base2k` bits), so
29//! passing it to any DFT-domain primitive (keyswitching, convolution,
30//! automorphisms) would produce incorrect decryptions. The wrapper does not
31//! implement [`GLWEToBackendRef`] or [`GLWEToBackendMut`], making such misuse
32//! a compile error. Call [`layouts::UnnormalizedCKKSCiphertext::normalize`]
33//! before the next keyswitching or convolution step.
34//!
35//! ## Modules
36//!
37//! | Module | Role |
38//! |--------|------|
39//! | [`encoding`] | CKKS encoders/decoders, including slot-wise real/imaginary packing |
40//! | [`layouts`] | CKKS ciphertext/plaintext wrappers and metadata-aware allocation helpers |
41//! | [`leveled`] | Leveled arithmetic (add, sub, mul, neg, rotate, conjugate), encryption, decryption, and rescale |
42//! | bootstrapping | Planned CKKS bootstrapping |
43
44use poulpy_core::layouts::{Base2K, GLWEInfos, GLWEToBackendMut, GLWEToBackendRef, LWEInfos, TorusPrecision};
45use poulpy_hal::layouts::Backend;
46
47pub mod api;
48pub mod default;
49pub(crate) mod delegates;
50pub mod encoding;
51mod error;
52pub mod layouts;
53pub mod leveled;
54pub mod oep;
55pub mod test_suite;
56pub use error::CKKSCompositionError;
57pub(crate) use error::{
58    checked_log_budget_sub, checked_mul_ct_log_budget, checked_mul_pt_log_budget, ensure_base2k_match,
59    ensure_plaintext_alignment, ensure_plaintext_coeff_in_range, ensure_plaintext_degree_match,
60};
61
62pub type CKKSCiphertextRef<'a, BE> = layouts::CKKSCiphertext<<BE as Backend>::BufRef<'a>>;
63pub type CKKSCiphertextMut<'a, BE> = layouts::CKKSCiphertext<<BE as Backend>::BufMut<'a>>;
64
65pub trait CKKSPlaintextToBackendRef<BE: Backend>: GLWEToBackendRef<BE> + GLWEInfos + LWEInfos {}
66
67impl<BE: Backend, T> CKKSPlaintextToBackendRef<BE> for T where T: GLWEToBackendRef<BE> + GLWEInfos + LWEInfos {}
68
69/// Marker bound for CKKS ciphertext type parameters.
70///
71/// Combines [`GLWEInfos`] (which already implies [`poulpy_core::layouts::LWEInfos`])
72/// with [`CKKSInfos`] to collapse the repeated `GLWEInfos + CKKSInfos` pair found
73/// throughout the API into a single, named constraint.
74pub trait CKKSCtBounds: GLWEInfos + CKKSInfos {}
75
76impl<T: GLWEInfos + CKKSInfos> CKKSCtBounds for T {}
77
78#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
79/// CKKS semantic precision metadata carried by ciphertexts and plaintexts.
80///
81/// `log_delta` is the scaling precision of the encoded value and
82/// `log_budget` is the remaining homomorphic headroom available above `log_delta`.
83pub struct CKKSMeta {
84    /// Base 2 logarithm of the decimal precision.
85    pub log_delta: usize,
86    /// Base 2 logarithm of the remaining homomorphic capacity.
87    pub log_budget: usize,
88}
89
90/// Common metadata accessors for CKKS ciphertext and plaintext containers.
91///
92/// This trait exposes the semantic precision of a value independently from the
93/// raw limb storage used by the underlying torus representation.
94pub trait CKKSInfos {
95    /// Returns the complete metadata pair.
96    fn meta(&self) -> CKKSMeta;
97
98    /// Returns the base-2 logarithm of the encoded decimal scaling factor.
99    fn log_delta(&self) -> usize;
100
101    /// Returns the base-2 logarithm of the remaining homomorphic capacity.
102    fn log_budget(&self) -> usize;
103
104    /// Returns the next multiple of [`Base2K`] greater than [`Self::log_delta`] + [`Self::log_budget`].
105    fn min_k(&self, base2k: Base2K) -> TorusPrecision {
106        ((self.log_delta() + self.log_budget()).next_multiple_of(base2k.as_usize())).into()
107    }
108
109    /// Returns the semantic torus width carried by the value.
110    ///
111    /// This is `log_delta + log_budget` and may differ from the rounded
112    /// storage capacity `max_k()`.
113    fn effective_k(&self) -> usize {
114        self.log_delta() + self.log_budget()
115    }
116}
117
118impl CKKSInfos for CKKSMeta {
119    fn meta(&self) -> CKKSMeta {
120        *self
121    }
122
123    fn log_delta(&self) -> usize {
124        self.log_delta
125    }
126
127    fn log_budget(&self) -> usize {
128        self.log_budget
129    }
130}
131
132/// Mutable CKKS metadata access for ciphertext/plaintext containers.
133pub trait SetCKKSInfos: CKKSInfos {
134    /// Replaces the semantic CKKS metadata.
135    fn set_meta(&mut self, meta: CKKSMeta);
136
137    /// Updates only the base-2 logarithm of the encoded scaling factor.
138    fn set_log_delta(&mut self, log_delta: usize) {
139        let mut meta = self.meta();
140        meta.log_delta = log_delta;
141        self.set_meta(meta);
142    }
143
144    /// Updates only the base-2 logarithm of the remaining homomorphic budget.
145    fn set_log_budget(&mut self, log_budget: usize) {
146        let mut meta = self.meta();
147        meta.log_budget = log_budget;
148        self.set_meta(meta);
149    }
150}
151
152pub(crate) fn ckks_offset_binary<R, A, B>(res: &R, a: &A, b: &B) -> usize
153where
154    R: LWEInfos + CKKSInfos + ?Sized,
155    A: LWEInfos + CKKSInfos + ?Sized,
156    B: LWEInfos + CKKSInfos + ?Sized,
157{
158    a.effective_k().min(b.effective_k()).saturating_sub(res.max_k().as_usize())
159}
160
161pub(crate) fn ckks_offset_unary<R, A>(res: &R, a: &A) -> usize
162where
163    R: LWEInfos + CKKSInfos + ?Sized,
164    A: LWEInfos + CKKSInfos + ?Sized,
165{
166    a.effective_k().saturating_sub(res.max_k().as_usize())
167}