<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ckks.org/feed.xml" rel="self" type="application/atom+xml"/><link href="https://ckks.org/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-08-24T09:45:03+00:00</updated><id>https://ckks.org/feed.xml</id><title type="html">CKKS.org</title><subtitle></subtitle><entry><title type="html">Accelerating CKKS on GPUs with Cheddar and Theodosian</title><link href="https://ckks.org/blog/2026/CKKS-on-GPU/" rel="alternate" type="text/html" title="Accelerating CKKS on GPUs with Cheddar and Theodosian"/><published>2026-08-24T00:00:00+00:00</published><updated>2026-08-24T00:00:00+00:00</updated><id>https://ckks.org/blog/2026/CKKS-on-GPU</id><content type="html" xml:base="https://ckks.org/blog/2026/CKKS-on-GPU/"><![CDATA[<ul> <li>Written by <a href="https://scholar.google.com/citations?user=vnnXMLQAAAAJ">Jongmin Kim</a> (Seoul National University)</li> <li>Based on <a href="https://doi.org/10.1145/3760250.3762223">Cheddar</a> (ASPLOS 2026) and <a href="https://doi.org/10.1109/ISPASS69572.2026.00037">Theodosian</a> (ISPASS 2026)</li> </ul> <p><em>TL;DR: Modern GPUs provide enormous parallel computation capability, making them an attractive platform for accelerating CKKS. However, achieving high performance requires redesigning both the cryptographic algorithms and the GPU implementation together rather than simply porting CPU code. In this post, I introduce Cheddar, a GPU-native CKKS library based on a 32-bit RNS construction that achieves state-of-the-art performance, and Theodosian, which shows that modern GPU implementations are no longer compute-bound but instead limited by on-chip L2 cache bandwidth. Together, these works illustrate both how far GPU acceleration has come and where the next performance barriers lie.</em></p> <h2 id="introduction">Introduction</h2> <p>GPUs are promising hardware platforms for accelerating CKKS because they can execute thousands of parallel operations simultaneously. Based on our profiling, NVIDIA’s recent consumer GPU, the RTX 5090, can perform up to 14.95 trillion 32-bit integer multiply-and-add (IMAD) operations per second. Fully exploiting this computational throughput can dramatically reduce CKKS execution time compared to CPU-based FHE libraries such as SEAL, HElib, and OpenFHE. Indeed, numerous prior studies, such as Jung et al. [1], have explored GPU acceleration for FHE, reporting over 100$\times$ lower CKKS bootstrapping latency compared to previous CPU implementations.</p> <p>Despite this progress, existing GPU libraries still leave considerable performance on the table because they do not co-optimize CKKS for GPUs at both the algorithmic level (word size, RNS representation, and key structure) and the microarchitectural level (L2 cache hierarchy, memory bandwidth, and warp scheduling). Simply adapting CPU-oriented 64-bit arithmetic to GPUs is not enough; we instead need to design the software stack around the GPU’s native execution units.</p> <p>To bridge this performance gap, our research group at Seoul National University (led by Prof. Jung Ho Ahn) recently presented two works:</p> <ol> <li><strong>Cheddar</strong> introduces a systematic 32-bit RNS framework designed natively around GPU hardware primitives. We open-sourced Cheddar at <a href="https://github.com/scale-snu/cheddar-fhe">https://github.com/scale-snu/cheddar-fhe</a>.</li> <li><strong>Theodosian</strong> builds on Cheddar with a detailed microarchitectural study of GPU memory systems. It shows that once the compute bottlenecks are removed, execution encounters an “inner memory wall” at the on-chip L2 cache, and proposes memory-aware optimizations to overcome it.</li> </ol> <h2 id="gpu-friendly-32-bit-rns">GPU-friendly 32-bit RNS</h2> <p>Conventional CPU-based HE libraries use RNS primes as large as $2^{62}$, matching 64-bit CPU registers and SIMD extensions. GPUs, however, are optimized for 32-bit integer (INT32) execution, while 64-bit integer (INT64) arithmetic is implemented through software emulation using multiple INT32 operations. By choosing RNS primes that fit within INT32, HE computations can directly utilize the GPU’s native integer units, significantly improving throughput and efficiency.</p> <p>The tradeoff is that smaller RNS primes increase the number of RNS primes, $L$. With RNS, a polynomial is decomposed into $L$ <em>limbs</em>, where each limb contains $N$ (polynomial degree determined as a CKKS parameter) INT32 or INT64 coefficients. Decomposing the same modulus into INT32 RNS primes will roughly double $L$. Consequently, operations with $\mathcal{O}(L)$ complexity may nearly double, while those with $\mathcal{O}(L^2)$ complexity can increase by up to four times. Despite this overhead, the lower cost of native INT32 arithmetic—often less than half that of emulated INT64 operations—largely compensates for the increase. Furthermore, the larger number of data elements exposes additional parallelism, allowing work to be distributed across more GPU streaming multiprocessors (SMs) and ultimately yielding higher overall throughput.</p> <p>Previous CKKS.org blog posts already discussed a similar issue and introduced several approaches for constructing RNS using smaller word sizes such as INT32: <a href="https://ckks.org/blog/2026/heaan2-moduli-chain/">Modern Construction of Moduli Chain in HEaaN2</a> and <a href="https://ckks.org/blog/2025/grafting/">Grafting: Improving Performance and Usability of Homomorphic Encryption</a>. Here, I focus only on Cheddar’s <strong>25-30 prime system</strong>, which is specifically designed for GPU execution.</p> <p>The 25-30 prime system constructs the RNS using only primes close to $2^{25}$ (<em>Pr~25</em>) and $2^{30}$ (<em>Pr~30</em>). It supports only scales ($\Delta$) that are powers of $2^5$, such as $2^{30}$, $2^{35}$, $2^{40}$, and $2^{45}$. In practice, this restriction is rarely limiting; supporting every possible scale value is unnecessary for most CKKS applications.</p> <p>In this system, we attempt to find a <em>cycle</em>. For example, when $\Delta = 2^{40}$, we can rescale a polynomial in either of the following two ways:</p> <ul style="list-style-type: '① ';"> <li>Add two new <i>Pr~25</i> (inverse rescaling, which multiplies the polynomial by these two primes), then discard three <i>Pr~30</i> (ordinary rescaling). The resulting scale adjustment is 25 + 25 - 30 - 30 - 30 = -40 bits.</li> </ul> <ul style="list-style-type: '② ';"> <li>Add two new <i>Pr~30</i> primes and discard four <i>Pr~25</i> primes. The resulting scale adjustment is 30 + 30 - 25 - 25 - 25 - 25 = -40 bits.</li> </ul> <p>We then mix the use of these two methods in a cyclical manner with the number of <em>Pr~25</em> evolving as</p> \[0 \xrightarrow{①} 2 \xrightarrow{①} 4 \xrightarrow{②} 0 \xrightarrow{①} 2 \xrightarrow{①} 4 \xrightarrow{②} \cdots,\] <p>while always reusing the same four <em>Pr~25</em> in a predetermined order. The resulting RNS construction is illustrated below:</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2608_Jongmin/prime_system.svg" sizes="95vw"/> <img src="/assets/img/blog/2608_Jongmin/prime_system.svg" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 1. 25-30 Prime System. </div> <p>The motivation for this construction is to limit the number of <em>Pr~25</em> used, which correspond to the terminal primes introduced in BitPacker [2]. In BitPacker, terminal primes are selected greedily at each level, causing different levels to use largely different sets of primes. This becomes problematic when data structures such as evaluation keys must be shared across multiple levels.</p> <p>In contrast, the 25-30 prime system ensures that every level draws its <em>Pr~25</em> from the same small, fixed set. As a result, we can simply precompute and share the required data (e.g., evaluation keys) for every prime in the set, which is practical because the set is small. Despite this additional restriction, we observe almost no scale fluctuation in practice: when the target scale is $2^{40}$, the actual scale remains between $2^{39.9}$ and $2^{40.1}$ throughout execution.</p> <p>There are additional details regarding the levels dedicated to CKKS bootstrapping, which are discussed in the Cheddar paper.</p> <h3 id="inverted-terminal-data-layout">Inverted-Terminal Data Layout</h3> <p>Because we use the primes in a fixed order, the set of primes used at a level would be</p> \[\{ \mathcal{T}_0, \mathcal{T}_1, \cdots, \mathcal{T}_{A-1}, \mathcal{Q}_0, \mathcal{Q}_1, \cdots, \mathcal{Q}_{B-1} \}\] <p>where $A$ is the number of <em>Pr~25</em> ($\mathcal{T}_i$) and $B$ is the number of <em>Pr~30</em> ($\mathcal{Q}_i$). A polynomial at this level therefore contains $A+B$ limbs.</p> <p>To simplify indexing while keeping the data contiguous, we store the limbs in the following order, with the terminal primes reversed:</p> \[\mathcal{T}_{A-1}, \mathcal{T}_{A-2}, \cdots, \mathcal{T}_{0}, \mathcal{Q}_0, \mathcal{Q}_1, \cdots, \mathcal{Q}_{B-1}.\] <p>Suppose the limb corresponding to $\mathcal{Q}_0$ is assigned index 0. Then negative indices naturally refer to the terminal <em>Pr~25</em>, while non-negative indices refer to the main <em>Pr~30</em>. This inverted-terminal layout simplifies index computation and enables straightforward parallelization across the thousands of execution units available on modern GPUs.</p> <h2 id="cheddar-gpu-library">Cheddar GPU Library</h2> <p>Along with the 25-30 prime system, Cheddar incorporates several GPU-specific optimizations:</p> <ul> <li><strong>Optimized INT32 kernels:</strong> Core primitives such as the number-theoretic transform (NTT) and base conversion (BConv) are implemented using optimized INT32 arithmetic. In particular, Cheddar employs a signed Montgomery reduction that minimizes the number of integer instructions.</li> <li><strong>Extensive kernel fusion:</strong> Computational sequences are fused and reordered to avoid unnecessarily writing intermediate polynomial limbs back to global memory.</li> </ul> <p>Beyond these optimizations, Cheddar is a full-fledged CKKS library supporting the complete evaluation pipeline, from basic arithmetic to bootstrapping. The example below demonstrates how a CKKS computation can be implemented with only a few API calls.</p> <pre><code class="language-C++">using word = uint32_t;
Ciphertext&lt;word&gt; tmp;

// tmp = (ct1 - ct2) * ct3;
context-&gt;Sub(tmp, ct1, ct2);
context-&gt;Mult(tmp, tmp, ct3);
context-&gt;Relinearize(tmp, tmp, interface-&gt;GetMultiplicationKey());

// perform bootstrapping
context-&gt;Boot(tmp, tmp, interface-&gt;GetEvkMap());
</code></pre> <p>Cheddar is open-sourced under the MIT license and is available at <a href="https://github.com/scale-snu/cheddar-fhe">https://github.com/scale-snu/cheddar-fhe</a>.</p> <h3 id="cheddar-performance">Cheddar Performance</h3> <p>The following table compares Cheddar against Jung et al. [1], TensorFHE [3], HEaaN-GPU [4], and WarpDrive [5].</p> <div style="overflow-x: auto; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid; min-width: 100%;"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Implementation (Hardware)</th> <th style="border: 1px solid; padding: 6px 12px; text-align: center;">Boot (ms)</th> <th style="border: 1px solid; padding: 6px 12px; text-align: center;">HELR (ms/it)</th> <th style="border: 1px solid; padding: 6px 12px; text-align: center;">ResNet-20 (s)</th> </tr> </thead> <tbody> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Jung et al. (V100)</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">328</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">775</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">-</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">TensorFHE (A100 40GB)</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">250</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">1007</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">4.94</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">HEaaN-GPU (A100 80GB)</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">171</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">-</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">8.58</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">WarpDrive (A100 80GB)</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">121</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">113</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">5.88</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>Cheddar (A100 80GB)</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>40.0</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>51.9</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>1.32</strong></td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>Cheddar (H100)</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>31.2</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>40.7</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>1.05</strong></td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>Cheddar (RTX 5090)</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>22.1</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>25.9</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>0.72</strong></td> </tr> </tbody> </table> </div> <div class="caption"> Table 1. Cheddar performance compared with prior GPU implementations. </div> <p>Compared to WarpDrive, the previous state of the art, Cheddar achieves 2.18–4.45$\times$ faster execution on the same NVIDIA A100 80GB GPU. On an RTX 5090, a ResNet-20 inference completes in just 0.72 seconds, which is more than 3,000$\times$ faster than the original CPU implementation by Lee et al. [6].</p> <h2 id="theodosian-were-approaching-a-performance-limit">Theodosian: We’re Approaching a Performance Limit</h2> <p>Having achieved highly competitive performance with Cheddar, we wanted to understand what ultimately limits further speedups. We therefore profiled Cheddar in detail with a particular focus on the GPU memory hierarchy.</p> <p>Prior work largely assumed that off-chip DRAM bandwidth was the primary bottleneck. However, our microarchitectural analysis in <strong>Theodosian</strong> reveals that on modern high-end GPUs such as the RTX 5090, highly optimized 32-bit kernels instead become limited by the on-chip L2 cache bandwidth—the “inner memory wall.”</p> <p>Even Cheddar’s most compute-intensive kernels, such as NTT and BConv, become L2-bandwidth bound after optimization. This indicates that nearly all GPU kernels are ultimately constrained by memory bandwidth (either L2 cache or DRAM) rather than raw computational throughput.</p> <p>Based on the total amount of L2 traffic required for CKKS bootstrapping, we estimate an <strong>absolute latency wall of 8.8ms</strong> on the RTX 5090. Cheddar’s measured latency of 22.1ms is therefore already within only 2.5$\times$ of this architectural limit.</p> <h3 id="optimizations-in-theodosian">Optimizations in Theodosian</h3> <p>Based on this analysis, Theodosian introduces several microarchitecture-aware optimizations:</p> <ul> <li><strong>L2-aware multi-polynomial batching:</strong> Batch multiple polynomials together to improve L2 cache utilization while keeping the working set small enough to avoid spilling into DRAM.</li> <li><strong>Resource co-scheduling:</strong> Execute L2-bound and DRAM-bound kernels together so they utilize complementary hardware resources and hide each other’s latency.</li> <li><strong>CUDA graphs:</strong> Reduce the overhead of increasingly complex execution schedules and kernel launches.</li> </ul> <p>Theodosian also includes additional kernel-level optimizations and further kernel fusion, which are described in detail in the paper.</p> <h3 id="theodosian-performance">Theodosian Performance</h3> <div style="overflow-x: auto; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid; min-width: 100%;"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Implementation</th> <th style="border: 1px solid; padding: 6px 12px;">Boot (ms)</th> <th style="border: 1px solid; padding: 6px 12px;">HELR (ms/it)</th> <th style="border: 1px solid; padding: 6px 12px;">ResNet-20 (s)</th> </tr> </thead> <tbody> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">FIDESlib [7]</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">147</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">-</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">-</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Cheddar</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">22.1</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">25.9</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">0.720</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>Theodosian</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>15.2</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>14.1</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;"><strong>0.467</strong></td> </tr> </tbody> </table> </div> <div class="caption"> Table 2. Theodosian performance on the RTX 5090. The scale target is log Δ = 40 for all the workloads. For each workload, Cheddar and Theodosian use the same parameters (refer to <a href="https://github.com/scale-snu/cheddar-ae/tree/main/parameters">GitHub link</a> for detailed parameters). </div> <p>Thanks to these optimizations, Theodosian achieves an additional 1.45–1.83$\times$ speedup over Cheddar on the RTX 5090. Notably, Theodosian is less than 2$\times$ away from the aforementioned absolute latency wall of 8.8ms, suggesting that relatively little performance remains to be gained through GPU optimization alone.</p> <div style="overflow-x: auto; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid; min-width: 100%;"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Implementation</th> <th style="border: 1px solid; padding: 6px 12px;">$\log N$</th> <th style="border: 1px solid; padding: 6px 12px;">$\log PQ$</th> <th style="border: 1px solid; padding: 6px 12px;">$\log Q$ after boot</th> <th style="border: 1px solid; padding: 6px 12px;">Boot precision</th> <th style="border: 1px solid; padding: 6px 12px;">Boot latency</th> </tr> </thead> <tbody> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Cheddar</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">16</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">1711</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">575</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">18.57 bits</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">23.18ms</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Theodosian</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">16</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">1711</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">575</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">18.57 bits</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">16.21ms</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>Theodosian (with CKKS bootstrapping algorithm enhancements)</strong></td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">16</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">1711</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">575</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">18.70 bits</td> <td style="border: 1px solid; padding: 6px 12px; text-align: center;">12.75ms</td> </tr> </tbody> </table> </div> <div class="caption"> Table 3. Comparing bootstrapping performance of Cheddar and Theodosian with and without additional algorithmic enhancements. Note that the scale target is log Δ = 35 here, contrary to what is used in Table 2. </div> <p>Algorithmic advances can still push this limit further, however. For example, by incorporating several recent CKKS bootstrapping improvements, we further reduced the bootstrapping latency to 12.75ms while maintaining similar precision and modulus budget after bootstrapping.</p> <h2 id="historical-trend-and-future-work">Historical Trend and Future Work</h2> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2608_Jongmin/historic_trend.svg" sizes="95vw"/> <img src="/assets/img/blog/2608_Jongmin/historic_trend.svg" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 2. Historical Trend. </div> <p>The figure above summarizes an interesting trend. FHE performance has steadily improved over time, but the rate of improvement is clearly slowing.</p> <p>Until around 2021, rapid progress of roughly 7.9$\times$ per year was sustained, driven by both algorithmic advances and the first large-scale use of GPUs for FHE by Jung et al. [1]. Since then, however, improvements have slowed to roughly 1.8$\times$ per year. This trend is not unique to GPUs: recent FPGA, TPU, and multi-GPU implementations all report similar performance levels, with no leap in performance attributable solely to hardware acceleration.</p> <p>This suggests that future breakthroughs are unlikely to come from hardware acceleration alone. Instead, continued progress will require cryptographic algorithms and hardware architectures to be designed together—the central theme behind both Cheddar and Theodosian.</p> <p>An even more ambitious question is whether we can build hardware specifically for FHE, with substantially higher computational throughput and memory bandwidth than today’s GPUs. If so, the architectural performance wall itself could be pushed much further away. That question remains open.</p> <h2 id="references">References</h2> <p>[1] Wonkyung Jung, Sangpyo Kim, Jung Ho Ahn, Jung Hee Cheon, and Younho Lee, “Over 100x Faster Bootstrapping in Fully Homomorphic Encryption through Memory-centric Optimization with GPUs.” IACR CHES 2021.</p> <p>[2] Nikola Samardzic and Daniel Sanchez, “BitPacker: Enabling High Arithmetic Efficiency in Fully Homomorphic Encryption Accelerators.” ASPLOS 2024.</p> <p>[3] Shengyu Fan, Zhiwei Wang, Weizhi Xu, Rui Hou, Dan Meng, and Mingzhe Zhang, “TensorFHE: Achieving Practical Computation on Encrypted Data Using GPGPU.” IEEE HPCA 2023.</p> <p>[4] Jaiyoung Park, Donghwan Kim, Jongmin Kim, Sangpyo Kim, Wonkyung Jung, Jung Hee Cheon, and Jung Ho Ahn, “Toward Practical Privacy-Preserving Convolutional Neural Networks Exploiting Fully Homomorphic Encryption.” DISCC 2023.</p> <p>[5] Guang Fan, Mingzhe Zhang, Fangyu Zheng, Shengyu Fan, Tian Zhou, Xianglong Deng, Wenxu Tang, Liang Kong, Yixuan Song, and Shoumeng Yan, “WarpDrive: GPU-Based Fully Homomorphic Encryption Acceleration Leveraging Tensor and CUDA Cores.” IEEE HPCA 2025.</p> <p>[6] Eunsang Lee, Joon-Woo Lee, Junghyun Lee, Young-Sik Kim, Yongjune Kim, Jong-Seon No, and Woosuk Choi, “Low-Complexity Deep Convolutional Neural Networks on Fully Homomorphic Encryption Using Multiplexed Parallel Convolutions.” ICML 2022.</p> <p>[7] Carlos Agulló-Domingo, Óscar Vera-López, Seyda Guzelhan, Lohit Daksha, Aymane El Jerari, and Kaustubh Shivdikar, “FIDESlib: A Fully-Fledged Open-Source FHE Library for Efficient CKKS on GPUs.” IEEE ISPASS 2025.</p>]]></content><author><name>Jongmin Kim</name></author><summary type="html"><![CDATA[TL;DR: Modern GPUs provide enormous parallel computation capability, making them an attractive platform for accelerating CKKS. However, achieving high performance requires redesigning both the cryptographic algorithms and the GPU implementation together rather than simply porting CPU code. In this post, I introduce Cheddar, a GPU-native CKKS library based on a 32-bit RNS construction that achieves state-of-the-art performance, and Theodosian, which shows that modern GPU implementations are no longer compute-bound but instead limited by on-chip L2 cache bandwidth. Together, these works illustrate both how far GPU acceleration has come and where the next performance barriers lie.]]></summary></entry><entry><title type="html">FHE for SIMD Arithmetic Logic Units with Amortized O(1) Bootstrapping per Ciphertext</title><link href="https://ckks.org/blog/2026/fhe-simd-alu/" rel="alternate" type="text/html" title="FHE for SIMD Arithmetic Logic Units with Amortized O(1) Bootstrapping per Ciphertext"/><published>2026-07-27T00:00:00+00:00</published><updated>2026-07-27T00:00:00+00:00</updated><id>https://ckks.org/blog/2026/fhe-simd-alu</id><content type="html" xml:base="https://ckks.org/blog/2026/fhe-simd-alu/"><![CDATA[<ul> <li>Written by <a href="https://hongrenzhe.ng">Hongren Zheng</a> (Tsinghua University)</li> <li>Based on <a href="https://eprint.iacr.org/2026/233">https://ia.cr/2026/233</a> (Crypto 2026)</li> </ul> <p><em>TL;DR: We propose a new CKKS-compatible encoding framework that supports both arithmetic and Boolean operations for a vector of, for example, 64-bit integers. The key idea is using multiple complex slots to represent one integer, with a special ring isomorphism to maintain the desired integer arithmetic. For arithmetic-only workloads, each refreshing requires only two bootstrapping operations for one ciphertext. For Boolean operations, the arithmetic-to-Boolean conversion can batch $O(n)$ ciphertexts, resulting in amortized $O(1)$ bootstrapping per ciphertext. The prototype is available at <a href="https://github.com/tsinghua-ideal/fhe-simd-alu">https://github.com/tsinghua-ideal/fhe-simd-alu</a>.</em></p> <hr/> <h2 id="arithmetic-and-boolean-operations">Arithmetic and Boolean Operations</h2> <p>In software, we usually need both arithmetic and Boolean operations. One example is computing a value and then using it to condition execution. The computation itself mostly involves arithmetic operations, while the logical step often involves Boolean operations.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="nf">func</span><span class="p">(</span><span class="kt">long</span> <span class="n">a</span><span class="p">,</span> <span class="kt">long</span> <span class="n">b</span><span class="p">,</span> <span class="kt">long</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">long</span> <span class="n">res</span> <span class="o">=</span> <span class="mi">42</span> <span class="o">*</span> <span class="n">a</span> <span class="o">+</span> <span class="mi">100</span> <span class="o">*</span> <span class="n">b</span> <span class="o">-</span> <span class="n">c</span><span class="p">;</span> <span class="c1">// Arithmetic Add/Mult</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">&lt;</span> <span class="mi">1024</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Comparison</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div> <p>In this article, we focus on computer integers, also known as “machine words”. They are among the most basic types in programming.</p> <p>For arithmetic operations, considering only addition, subtraction and multiplication (side note: division can be understood as a mixture of arithmetic and Boolean operations), $n$-bit machine words can be understood as elements in the ring $\mathbb{Z}_{2^{n}}$. We refer to this representation as the “whole” paradigm.</p> <p>On the other hand, Boolean operations require the <em>bit representation</em> of machine words. For example, the “less than” comparison operation for integers $a$ and $b$ requires the most significant bit (MSB) of $(a - b)$. We refer to this representation as the “bit-vector” paradigm.</p> <p>In plaintext computation, it is relatively cheap to convert between the two paradigms. However, in homomorphic encryption, the situation is different. For the “whole” paradigm, we have the following limitations:</p> <ul> <li>BGV/BFV: They typically support $\mathbb{Z}_{p}$ arithmetic with prime $p$ (directly supporting $\mathbb{Z}_{2^n}$ leads to limited SIMD capability). Applications can only use $\mathbb{Z}_{p}$ to simulate $\mathbb{Z}_{2^n}$ semantics. However, unless we know the exact range of all operands, we can only use worst-case range analysis and $p$ is often large.</li> <li>CKKS: It is possible to directly embed 64-bit integers in CKKS complex slots. However, after one multiplication, we obtain 128-bit integers in complex slots, and we need more-than-128-bit-precision bootstrapping to keep the lower bits (resulting message) and truncate the higher bits (overflows) to allow further computation.</li> <li>“Whole” paradigm are hard to convert to the “bit vector” paradigm: they either need polynomial evaluation to extract all bits, or use a series of bootstrappings.</li> </ul> <p>In this regard, the “bit-vector” paradigm is more natural for existing solutions, such as TFHE and RadixCKKS (Eurocrypt’26) [6]. However, these schemes then need to manage carries and overflows <em>iteratively</em>. A typical TFHE multiplication circuit uses $O(n^2)$ programmable bootstrapping operations. RadixCKKS requires $O(\log n)$ CKKS bootstrapping operations after each multiplication.</p> <div align="center"> <div style="overflow-x: auto; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid; min-width: 100%;"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Scheme</th> <th style="border: 1px solid; padding: 6px 12px;">Arithmetic</th> <th style="border: 1px solid; padding: 6px 12px;">Boolean</th> </tr> </thead> <tbody> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">BGV/BFV with $\mathbb{Z}_{2^n}$</td> <td style="border: 1px solid; padding: 6px 12px;">No SIMD</td> <td style="border: 1px solid; padding: 6px 12px;">Hard</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">BGV/BFV with $\mathbb{Z}_{p}$</td> <td style="border: 1px solid; padding: 6px 12px;">Good but range issue</td> <td style="border: 1px solid; padding: 6px 12px;">Hard</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">CKKS</td> <td style="border: 1px solid; padding: 6px 12px;">Precision issue</td> <td style="border: 1px solid; padding: 6px 12px;">Hard</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">TFHE</td> <td style="border: 1px solid; padding: 6px 12px;">Expensive carry logic</td> <td style="border: 1px solid; padding: 6px 12px;">Natural</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">RadixCKKS [6]</td> <td style="border: 1px solid; padding: 6px 12px;">$O(\log n)$ BTS</td> <td style="border: 1px solid; padding: 6px 12px;">$O(1)$ BTS</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">REFHE [5]</td> <td style="border: 1px solid; padding: 6px 12px;">"Leveled" with $O(n)$ PBS</td> <td style="border: 1px solid; padding: 6px 12px;">$O(n)$ PBS</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>This work</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>"Leveled" with $O(1)$ BTS</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>Amortized $O(1)$ BTS</strong></td> </tr> </tbody> </table> </div> </div> <div class="caption"> Table 1. Comparison of Schemes. </div> <h2 id="triangle-encoding">Triangle Encoding</h2> <p>We depart from existing paradigms and introduce a middle ground called “triangle encoding”. It addresses the <em>iterative bootstrapping</em> issue by representing messages, carries, and overflows in a form that allows efficient extraction and cleanup.</p> <p>This work follows the line of “polynomial plaintext modulus” technique. This technique dates all the way back to 2000 from Hoffstein and Silverman in the NTRU literature [1]. In the FHE literature, this idea appears as early as CLPX (CT-RSA’18) [2], and recent works like GBFV (Eurocrypt’25, S&amp;P’25) [3,4] and REFHE (Eurocrypt’26) [5] quickly gain gravity in the FHE community. Their bootstrapping methods are constantly evolving: from leveled HE, to FHE with BFV-style bootstrapping, to FHE with TFHE-style bootstrapping. Technically, we tweak the flattened polynomial further into a triangular format, making it friendly to CKKS-style bootstrapping and obtaining (amortized) O(1) bootstrapping.</p> <p>Let’s directly jump into REFHE (Eurocrypt’26). It used a ring isomorphism from the “whole” paradigm to a polynomial quotient ring:</p> \[\mathbb{Z}_{2^n} \cong \mathbb{Z}[X]/\langle X^n-X+2, X-2 \rangle\] <p>The ring $\mathbb{Z}[X]$ contains all polynomials $f(X)=\sum_{k=0}^{d}f_k X^k$ with integer coefficients of any polynomial degree $d$. The quotient structure means the polynomial addition and multiplication are carried out modulo both \(X^n-X+2\) and \(X-2\). Concretely, this can be understood as replacing every occurrence of $X^n$ with $X-2$ and then replacing every occurrence of $X-2$ with 0.</p> <p>To instantiate the ring isomorphism, there is a corresponding “flatten operation” that expands a scalar message into a polynomial while preserving its arithmetic properties. It can be understood as modulo by a polynomial, therefore it uses the notation \([\cdot]_t\). Here we use \(t=X-2\). Formally, for each element \(m\in \mathbb{Z}_{2^n}\), there is a corresponding <em>flattened</em> polynomial in the ring \(\mathbb{Z}[X]/\langle X^n-X+2 \rangle\):</p> \[[m]_t = b_0 + b_1 X + \cdots + b_{n-1} X^{n-1}\] <p>The $k$-th coefficient \(b_k\in \{0, 1\}\) is the $k$-th bit of $m$. Another way to understand it is \(m=\sum_{k=0}^{n-1}b_k 2^k=\sum_{k=0}^{n-1}b_k X^k\). Note that as we modulo by $X-2$, which means we can replace every occurence of $X$ with 2, the modulo-by-$(X^n-2+2)$ structure can be understood as a modulo-by-$2^n$ operation. Therefore, every polynomial addition and multiplication of flattened polynomials modulo both \(X^n-X+2\) and \(X-2\) correspond to addition and multiplication in \(\mathbb{Z}_{2^n}\), respectively.</p> <p>However, maintaining the flattened structure homomorphically is difficult, as it involves two quotient relations. The usual strategy in existing works is to use the RLWE ring to induce the first relation, while using “plaintext modulus” to induce the second relation. However, this makes the arithmetic tightly coupled with the RLWE ring, like those in CLPX and GBFV. REFHE chose to change the RLWE ring, but it then became inconvenient for implementation.</p> <p>Our strategy is to simulate the two quotient relations step by step. Assume now that we can obtain $\mathbb{R}[X]/\langle X^n-X+2 \rangle$ arithmetic which enforces the first relation. To simulate the second modulo structure, we then separate any polynomial $f(X)$ in the polynomial ring $\mathbb{Z}[X]/\langle X^n-X+2 \rangle$ into two parts:</p> \[f(X) = [m]_t + (X-2) \cdot I(X)\] <p>Here, $[m]_t$ is the remainder and $I(X)$ is the quotient, a polynomial with integer coefficients. Equivalently, $(X-2) I(X)$ lives in the $\langle X-2\rangle$ polynomial ideal in \(\mathbb{Z}[X]/\langle X^n-X+2 \rangle\), and quotienting by this ideal gives us the flattened polynomial.</p> <p>However, the polynomial ideal is hard to deal with using homomorphic operations. REFHE needs to manage it iteratively. Our key observation is that we can turn the polynomial ideal into an integer ideal by multiplying by $t^{-1}$, so that the $I(X)$ can be managed in a coefficient-wise manner. We have the inverse of $t$ in the the ring $\mathbb{R}[X]/\langle X^n-X+2\rangle$. A direct computation in this ring gives:</p> \[I(X) + \frac{[m]_t}{t}= \sum_{k=0}^{n-1}I_kX^k + \left(\textcolor{gray}{\frac{m}{2^n}} - \sum_{k=0}^{n-1}\frac{1}{2^{k+1}}{\underset{0\leq i\leq k}{[m]}}X^{k}\right)\] <p>where $\underset{0\leq i\leq k}{[m]} = \sum_{i=0}^{k}b_i2^i$ is the lower $k+1$ bits of $m$. After scaling by $2^{k+1}$, these prefix sums form a triangular pattern.</p> <p>We now take homomorphic encryption concerns into account. In RLWE, homomorphic encryption will introduce noise. To actually simulate the \(\mathbb{R}[X]/\langle X^n-X+2\rangle\) arithmetic with noise, we follow the idea of the CKKS scaling factor. The triangle encoding has the following form, where $e$ denotes the noise:</p> \[\Delta \left( I + \frac{[m]_t}{t}\right) + e\] <p>Figure 1 illustrates the encoding. Here, each column represents one polynomial coefficient. The first column is the constant coefficient, the second column is the coefficient for $X^1$, and so on. The column is displayed from low bits to high bits. The extra term $m/2^n$ in the constant coefficient is not shown.</p> <div class="row mt-3"> <div class="col-sm-8 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2607_Hongren/triangle-480.webp 480w,/assets/img/blog/2607_Hongren/triangle-800.webp 800w,/assets/img/blog/2607_Hongren/triangle-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2607_Hongren/triangle.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 1. Triangle Encoding. </div> <p>This representation has the following useful properties:</p> <ul> <li>(Exact Correctness) The noise $e$ can contaminate part of the triangle, as long as the “top row” (in purple) of the triangle is not contaminated. As long as \(\lVert te \rVert &lt; \Delta /2\), we can recover the integer <em>exactly</em>.</li> <li>(Arithmetic Refreshing) If we can efficiently reset the noise $e$ (and the overflow $I$), we allow further arithmetic operations.</li> <li>(Arithmetic-To-Boolean Conversion) If we extract the “top row” of the triangle, we get a Boolean representation of the number.</li> </ul> <h2 id="simd-homomorphism-chain">SIMD Homomorphism Chain</h2> <p>Before discussing these management operations, we first explain how to obtain the \(\mathbb{R}[X]/\langle X^n-X+2\rangle\) arithmetic. We also explain how to obtain SIMD, which is crucial for application efficiency.</p> <p>Our starting point is the celebrated RLWE ring \(\mathcal{R}_Q=\mathbb{Z}[X]/\langle X^N+1, Q\rangle\) (e.g., \(N=2^{16}\)) that is widely studied and accelerated. It can simulate complex slots using CKKS.</p> <p>The key observation is that we can use some complex slots to simulate the \(\mathbb{R}[X]/\langle X^n-X+2\rangle\) arithmetic because of a ring isomorphism. Here we assume $n$ is a power-of-two, then $X^n-X+2$ has $n/2$ pairs of conjugate roots in the complex plane with no repeated roots.</p> \[\mathbb{C}^{n/2} \cong \mathbb{R}[X]/\langle X^n-X+2\rangle\] <p>The whole picture is Figure 2, where we obtain a <em>chain</em> of homomorphisms. We first connect the RLWE ring to complex slots. Then, we group the complex slots, with each group containing $n/2$ complex slots, and connect the complex slots to the polynomial ring \(\mathbb{R}[X]/\langle X^n-X+2\rangle\). We then use the triangle encoding to embed \(\mathbb{Z}_{2^n}\) into the polynomial ring structure. In the end, we obtain $N/n$ number of \(\mathbb{Z}_{2^n}\) slots from the usual RLWE ring. In a typical parameterization where $N=2^{16}$ and $n=64$, we obtain 1024 slots.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2607_Hongren/simd-480.webp 480w,/assets/img/blog/2607_Hongren/simd-800.webp 800w,/assets/img/blog/2607_Hongren/simd-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2607_Hongren/simd.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 2. SIMD Homomorphism Chain. </div> <p>To clarify, the homomorphisms in this chain are evaluated in plaintext during encoding/decoding. Encryption and homomorphic computation always take place in the ring \(\mathbb{Z}[X]/\langle X^N+1, Q\rangle\).</p> <h2 id="o1-bootstrapping-for-arithmetic-refreshing">O(1) Bootstrapping for Arithmetic Refreshing</h2> <p>The goal of arithmetic refreshing is to reset the noise $e$ (and the overflow $I$) so that arithmetic computations can continue.</p> <p>The reason for managing the overflow $I$ is that, it contributes to the noise growth in multiplication. Suppose we have two triangles in the form of \(\Delta I + e\) and \(\Delta I' + e'\) (just two terms to simplify the discussion), after multiplication and rescaling, we can find that the new noise contains cross terms like \(Ie'\) and \(I'e\), and the new overflow contains $II’$ term. Therefore, smaller overflow $I$ will lead to smaller noise growth.</p> <p>In the triangle encoding section, we have made the overflow $I$ into an expression that can be managed in a coefficient-wise manner in the ring \(\mathbb{R}[X]/\langle X^n-X+2\rangle\). On the other hand, the encoding process through the homomorphism chain makes the encoded polynomial appear “irregular” in the ring \(\mathbb{Z}[X]/\langle X^N+1, Q\rangle\). Note that in the RLWE ring, there is a modulo-by-Q structure that <em>natively</em> modulo every coeffcient, and we want to exploit that.</p> <p>The idea is to move the “regular” coefficients from one polynomial ring to another. In CKKS, there are linear transformations that move data between coefficients and slots. Here, we use linear transformations to move polynomial coefficients from \((\mathbb{R}[X]/\langle X^n-X+2\rangle)^{N/n}\) to \(\mathcal{R}_Q=\mathbb{Z}[X]/\langle X^N+1, Q\rangle\). More technically, we first move from the coefficients from the first polynomial rings to \(\mathbb{C}^{N/2}\) using linear transformation that costs \(O(\sqrt{n})\) key-switching, then use CKKS-style SlotsToCoeffs to move them into the RLWE ring.</p> <p>After applying linear transformations, the ciphertext encrypts the \(\Delta (I + [m]_t/t) + e\) coefficient structure in the RLWE ring \(\mathcal{R}\). We can then reset the overflow $I$ using the “Truncate and ModRaise” operation. The “Truncate” operation is inspired by [7] which multiplies by \(Q_{\ell}/\Delta\) to remove the higher bits, and then modulus switching back to \(q_0\). The ModRaise operation then introduces another $I’$ while raising the modulus back to \(Q_{L}\). By using a sparse key encapsulation technique, the norm of the coefficients of the new $I’$ can be very small.</p> <div class="row mt-3"> <div class="col-sm-8 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2607_Hongren/truncate-480.webp 480w,/assets/img/blog/2607_Hongren/truncate-800.webp 800w,/assets/img/blog/2607_Hongren/truncate-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2607_Hongren/truncate.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 3. The "Truncate and ModRaise" Operation. </div> <p>After the “Truncate and ModRaise” operation, we again use linear transformations to move \(\Delta(I' + [m]_t/t) + e\) back to the original representation. The “linear transformations + Truncate and modRaise + linear transformations” structure has a cost similar to one CKKS bootstrapping, dominated by the linear transformations.</p> <p>To manage the noise $e$, we follow the idea of bootstrapping the noise, like in META-BTS [8]. We first multiply by $t$ in the original ring to obtain \(\Delta (tI + [m]_t) + te\), so that \(\Delta (tI + [m]_t)\) can be truncated away using “Truncate and ModRaise” as $(tI + [m]_t)$ has integer coefficients. We then bootstrap $te$, recover $e$, and subtract it from the input ciphertext.</p> <p>Therefore, we only need two CKKS bootstrapping operations for arithmetic-to-arithmetic refreshing. We use $O(1)$ notation as the CKKS bootstrapping number does not change with the bit-width $n$, unlike previous works. We see CoeffsToSlots and SlotsToCoeffs as the major cost while seeing the \(O(\sqrt{n})\) cost for linear transformation as minor because $n$ is moderate. We further remark that refreshing is needed only when $I$ or $e$ becomes large enough, so several multiplications can be performed before one refreshing (“leveled”), and both bootstrapping operations can be executed independently.</p> <p>As for the concrete number of multiplications that can be performed, it depends on the noise and overflow size of the operands, the remaining CKKS levels, and the scaling factor. In our parameter setting, we estimate 2 to 3 multiplications could be performed before a refreshing, depending on the whether the input operands are plaintext, fresh encryption or refreshed ciphertexts.</p> <h2 id="amortized-o1-bootstrapping-for-arithmetic-to-boolean-conversion">Amortized O(1) Bootstrapping for Arithmetic-To-Boolean Conversion</h2> <p>To support Boolean operations, we need a representation where the bits are placed in the complex slots</p> \[\Delta (b_0 , b_1, \cdots, b_{n-1}) \in \mathbb{C}^{n}\] <p>In this representation, bitwise AND/OR can be simulated as $(ab)$ and $(a + b - ab)$ over complex slots. Bit shifting can be simulated using CKKS rotation.</p> <p>On the other hand, this representation resembles the “top row” (purple line) in Figure 1. Therefore, the central task of Arithmetic-to-Boolean conversion is to extract the bits of the triangle efficiently. We omit some details here and focus on the central idea.</p> <p>The observation is that, because the representation is a triangle, we can extract $b_0$ using one bootstrapping (we need bootstrapping due to the overflow $I$) and subtract the $b_0$ from other coefficients (in concrete execution, we remove some of them and treat other lower bits as noise). Then, $b_1$ becomes exposed to us and we can continue the extraction. Therefore, after $O(n)$ bootstrapping operations, we obtain all the bits.</p> <div class="row mt-3"> <div class="col-sm-8 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2607_Hongren/bit-remove-480.webp 480w,/assets/img/blog/2607_Hongren/bit-remove-800.webp 800w,/assets/img/blog/2607_Hongren/bit-remove-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2607_Hongren/bit-remove.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 4. Extract $b_0$ then remove $b_0$ in other coefficients. </div> <p>Then we observe that, in the bootstrapping for each bit $b_i$, we actually use one real slot out of $n/2$ complex slots occupied by the triangle. This allows us to combine the $b_i$’s of $O(n)$ ciphertexts into one input ciphertext for one bootstrapping, and we can use $O(n)$ bootstrapping to process $O(n)$ ciphertexts, resulting in amortized O(1) bootstrapping per ciphertext.</p> <div class="row mt-3"> <div class="col-sm-8 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2607_Hongren/batch-480.webp 480w,/assets/img/blog/2607_Hongren/batch-800.webp 800w,/assets/img/blog/2607_Hongren/batch-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2607_Hongren/batch.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 5. Batch the bootstrapping of $b_0$ in $O(n)$ ciphertexts. </div> <p>In our concrete execution, the bit extraction is carried out in blocks (4 bits per bootstrapping). We need $n/4$ bootstrapping to process $n/4$ ciphertexts, therefore amortized to 1 bootstrapping per ciphertext.</p> <h2 id="summary">Summary</h2> <p>In summary, we introduce the triangle encoding, a new encoding for machine words. In arithmetic mode, each refresh uses two bootstrapping-like operations, independent of the word size. When we want to mix in Boolean operations, we can achieve amortized $O(1)$ bootstrapping when there are a sufficient number of ciphertexts.</p> <p>Our scheme is most suitable for applications with heavy arithmetic and lightweight Boolean operations. The recommended word sizes for applications are $n=32$ and $n=64$, as these are common word sizes in modern computer. We also demonstrated the use of $n=256$ for high-precision arithmetic in the paper. In principle, we can support arbitrary bit-width $n$, but then the linear transformation cost between \(\mathbb{C}^{n/2}\) and \(\mathbb{R}[X]/\langle X^n-X+2\rangle\) grows in the order of \(O(\sqrt{n})\), unlike in RadixCKKS where a DFT-like structure can be used.</p> <p>This post focuses on the amortized conversion strategy presented in the paper. More direct arithmetic-to-Boolean conversion methods, without relying on the same amortization regime, will be discussed separately.</p> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <p>[1] Jeffrey Hoffstein and Joseph Silverman, “Optimizations for NTRU.” Proc. the Conf. on Public Key Cryptography and Computational Number Theory, Warsaw. pp. 77–88 (2000)</p> <p>[2] Hao Chen, Kim Laine, Rachel Player, and Yuhou Xia, “High-Precision Arithmetic in Homomorphic Encryption.” CT-RSA 2018</p> <p>[3] Robin Geelen and Frederik Vercauteren, “Fully Homomorphic Encryption for Cyclotomic Prime Moduli.” EUROCRYPT 2025</p> <p>[4] Hyunho Cha, Intak Hwang, Seonhong Min, Jinyeong Seo, and Yongsoo Song, “MatriGear: Accelerating Authenticated Matrix Triple Generation with Scalable Prime Fields via Optimized HE Packing.” IEEE S&amp;P 2025</p> <p>[5] Zvika Brakerski, Offir Friedman, Daniel Golan, Alon Gurny, Dolev Mutzari, and Ohad Sheinfeld, “REFHE: Fully Homomorphic ALU.” EUROCRYPT 2026</p> <p>[6] Gyeongwon Cha, Dongjin Park, and Joon-Woo Lee, “Improved Radix-based Approximate Homomorphic Encryption for Large Integers via Lightweight Bootstrapped Digit Carry.” EUROCRYPT 2026</p> <p>[7] Jaehyung Kim and Taeyeong Noh, “Modular Reduction in CKKS.” IACR CIC 2025 (2)</p> <p>[8] Youngjin Bae, Jung Hee Cheon, Wonhee Cho, Jaehyung Kim, and Taekyung Kim, “META-BTS: Bootstrapping Precision Beyond the Limit.” ACM CCS 2022</p>]]></content><author><name>Hongren Zheng</name></author><summary type="html"><![CDATA[TL;DR: We propose a new CKKS-compatible encoding framework that supports both arithmetic and Boolean operations for a vector of, for example, 64-bit integers. The key idea is using multiple complex slots to represent one integer, with a special ring isomorphism to maintain the desired integer arithmetic. For arithmetic-only workloads, each refreshing requires only two bootstrapping operations for one ciphertext. For Boolean operations, the arithmetic-to-Boolean conversion can batch $O(n)$ ciphertexts, resulting in amortized $O(1)$ bootstrapping per ciphertext. The prototype is available at .]]></summary></entry><entry><title type="html">Faster Bootstrapping for CKKS with Less Modulus Consumption</title><link href="https://ckks.org/blog/2026/less-mod-ckks/" rel="alternate" type="text/html" title="Faster Bootstrapping for CKKS with Less Modulus Consumption"/><published>2026-06-29T04:00:00+00:00</published><updated>2026-06-29T04:00:00+00:00</updated><id>https://ckks.org/blog/2026/less-mod-ckks</id><content type="html" xml:base="https://ckks.org/blog/2026/less-mod-ckks/"><![CDATA[<ul> <li>Written by <a href="https://orcid.org/0009-0000-8378-9446">Lianglin Yan</a> (Institute of Information Engineering, Chinese Academy of Sciences)</li> <li>Based on <a href="https://eprint.iacr.org/2025/1403">https://ia.cr/2025/1403</a> (PKC 2026)</li> </ul> <p><em>TL;DR: To improve efficiency and reduce the modulus consumption in standard CKKS bootstrapping, we propose two novel core techniques: level-conserving rescaling (LCR) and aggregated key-switching (AKS), which act on the matrix-vector multiplications in linear transformations and can be further combined into the lossless LCR+AKS. The contributions enable bootstrapping that consumes one fewer modulus level, improves throughput by 20%–35%, and reduces CtS rotation key size by 11.9%–15.2%, while preserving identical precision and failure probability.</em></p> <hr/> <p><br/></p> <h2 id="introduction">Introduction</h2> <div style="margin-top: 1.5em;"></div> <p>The standard CKKS bootstrapping comprises four steps: ModRaise, CtS, EvalMod, and StC. Many works have optimized these subroutines to improve the bootstrapping process. However, bootstrapping is still the performance bottleneck of the whole scheme, manifested mainly in the large computational overhead of CtS/StC and excessive modulus consumption. For example, in a 25-level CKKS scheme, as shown in [1], only 10 levels remain after bootstrapping for subsequent homomorphic computations. Here we focus on optimizing the linear transformations: CtS and StC (the technical descriptions mainly target CtS below, yet AKS is also applicable to StC).</p> <p>In CKKS bootstrapping, homomorphic linear transformations are essentially homomorphic matrix-vector multiplications, and the state-of-the-art linear transformations were introduced in [1]. The authors employ matrix factorization from [2,3], which decomposes a DFT/iDFT matrix into several sparse diagonal matrices; thus, a matrix-vector multiplication becomes several sparse diagonal matrix-vector multiplications. This improves efficiency but consumes more depth. Furthermore, the work [1] put forward the double-hoisting Baby-Step Giant-Step (BSGS) algorithm. The BSGS splits $r$ rotations into two parts: baby-step rotations and giant-step rotations, reducing the number of homomorphic rotations to $O(\sqrt{r})$. Nevertheless, we found that for small $r$ (i.e., for sparse diagonal matrices), the performance gain of BSGS is limited. We aim to design a faster matrix-vector multiplication method specifically for sparse diagonal matrices. Our second goal is to reduce modulus consumption in CKKS bootstrapping.</p> <p><strong>Notation.</strong> We first provide some necessary notations. Let $N$ be a power-of-two integer, $q_0,q_1,\cdots, q_L,p_0,p_1,\cdots,p_{k-1}$ be $L+k+1$ distinct primes, and $Q_\ell=\prod_{i=0}^{\ell}q_i$, $P_j=\prod_{i=0}^j p_i$ for $0\le \ell \le L$ and $0\le j&lt;k$. For simplicity, we write $P = P_{k−1}$. We define $\mathcal{R}_Q=\mathbb{Z}[X]/(X^N+1,Q)$, the cyclotomic polynomial ring over the integers modulo $Q$. <br/></p> <h2 id="key-ideas">Key ideas</h2> <div style="margin-top: 1.5em;"></div> <p>Our work is built upon two core ideas: one is LCR, which saves one level of moduli, and the other is AKS, which accelerates sparse diagonal matrix-vector multiplication. We elaborate on the rationale behind these two methods as follows: <br/></p> <h3 id="level-conserving-rescaling">Level-Conserving Rescaling</h3> <div style="margin-top: 1.5em;"></div> <p>Rescaling is a fundamental operation in CKKS. It is usually performed after homomorphic multiplication to reduce the errors and restore the encoding factor in the ciphertext. It also reduces the modulus level of the ciphertext by one. We introduce a new rescaling operation that does not reduce the ciphertext level.</p> <blockquote> <p><strong>Core idea of LCR</strong>: For ciphertext \(\mathtt{ct}\in\mathcal{R}_{Q_L}^2\) encrypting plaintext \(\mathtt{pt}\) under secret key \(\mathtt{sk}\), if the coefficients of \(\mathtt{ct}\) are small enough, specifically, \(\Vert\langle \mathtt{ct},\mathtt{sk}\rangle\Vert_{\infty}&lt; Q_L /2\), then the output ciphertext of the rescaling on \(\mathtt{ct}\) can still be a valid ciphertext of \(\mathtt{pt}/q_L\) in \(\mathcal{R}_{Q_L}^2\).</p> </blockquote> <p><strong>Why?</strong> Recalling the CKKS decryption process, we know \(\langle \mathtt{ct},\mathtt{sk}\rangle = \mathtt{pt} + e + kQ_L\) where \(\Vert\mathtt{pt} + e\Vert_{\infty} &lt; Q_L/2\), $e$ is an error and $k$ is a polynomial with integer coefficients. If \(\Vert\langle \mathtt{ct},\mathtt{sk}\rangle\Vert_{\infty}&lt; Q_L /2,\) then $k = 0$, i.e.,\(\langle \mathtt{ct},\mathtt{sk}\rangle=\mathtt{pt} + e\). After rescaling $\mathtt{ct}’=\lfloor \frac{\mathtt{ct}}{q_L}\rceil$, we have \(\langle \mathtt{ct}',\mathtt{sk}\rangle \approx \frac{\mathtt{pt}+e}{q_L},\) thus \(\left[ \langle \mathtt{ct}',\mathtt{sk}\rangle \right]_{Q_{L}}=\left[ \langle \mathtt{ct}',\mathtt{sk}\rangle \right]_{Q_{L-1}}.\)</p> <p><strong>Where does LCR apply?</strong> LCR is only valid when \(\Vert\langle \mathtt{ct}, \mathtt{sk}\rangle\Vert_\infty &lt; Q_L/2\). In general, this condition is <strong>not</strong> satisfied because ciphertext coefficients look typically uniformly distributed over \(\mathbb{Z}_{Q_L}\). However, an exception is the ciphertext immediately after applying ModRaise. ModRaise lifts the modulus from $q_0$ to $Q_L$ without changing the ciphertext polynomials; coefficients stay bounded by \(\Vert\mathtt{ct}\Vert_\infty \le q_0/2 \ll Q_L\), hence \(\Vert\langle \mathtt{ct}, \mathtt{sk}\rangle\Vert_\infty \ll Q_L/2\). LCR can therefore be applied once, right after ModRaise and before the first rotation/key-switching in CtS. <br/></p> <h3 id="aggregated-key-switching">Aggregated Key-Switching</h3> <div style="margin-top: 1.5em;"></div> <p>Matrix–vector multiplication mainly involves homomorphic rotations (including key-switchings) and plaintext–ciphertext multiplications. The main idea of AKS is to re-encode the key-switching keys so that plaintext–ciphertext multiplication and rescaling are merged into the key-switching operation, reducing overall computation.</p> <blockquote> <p><strong>Core idea of AKS</strong> (Merging plaintext–ciphertext multiplication and rescaling into key-switching): For a ciphertext $(c_0,c_1)$ with secret $s_1$, a standard switching key (from $s_1$ to $s_2$) encrypts $P s_1$ under $s_2$, and key-switching multiplies $P s_1$ by $c_1$, where $P$ is the auxiliary modulus. We instead encrypt $P m s_1 / q_L$ where $m$ is the plaintext used for plaintext-ciphertext multiplication, so that, after key-switching (including ModDown), the term $m s_1 c_1 / q_L$ is produced, which means that the key-switching also automatically completes scalar multiplication and rescaling operations on $c_1$. As a result, only scalar multiplication and rescaling on $c_0$ need to be evaluated separately, reducing the overall time complexity.</p> </blockquote> <p>Note that merging plaintext-ciphertext multiplication into key-switching conflicts with the BSGS method. Therefore, the AKS method applies to sparse diagonal matrix-vector multiplication scenarios, and discarding BSGS causes no notable loss in efficiency. <br/></p> <h2 id="methods-lcr-aks-lcraks">Methods: LCR, AKS, LCR+AKS</h2> <div style="margin-top: 1.5em;"></div> <p>Below we provide the concrete algorithms, including the combined LCR+AKS, and explain how to use them in CKKS bootstrapping. <br/></p> <h3 id="lcr-algorithm-for-the-first-matrixvector-multiplication-in-cts">LCR Algorithm for the First Matrix–Vector Multiplication in CtS</h3> <div style="margin-top: 1.5em;"></div> <p>The algorithm of level-conserving rescaling is the same as the ordinary CKKS rescaling except that the output $\mathtt{ct}’$ is treated as a ciphertext at level $Q_L$.</p> \[\textbf{LCRescale}(\mathtt{ct}):\quad \mathtt{ct}' = \Bigl\lfloor \frac{\mathtt{ct}}{q_L} \Bigr\rceil \in \mathcal{R}_{Q_L}^2.\] <p><strong>Correctness:</strong> if $\Vert\langle \mathtt{ct}, \mathtt{sk}\rangle\Vert_\infty &lt; Q_L/2$, then $\mathtt{ct}’$ encrypts $\mathtt{pt}/q_L$ under $\mathtt{sk}$ at modulus $Q_L$.</p> <p><strong>Using LCR in CtS.</strong> Standard CtS performs key-switching during every rotation. Key-switching involves an inner product with the switching key and spreads the ciphertext coefficients uniformly over $\mathbb{Z}_{Q_L}$, so LCR <strong>cannot</strong> be used after the first rotation. To apply LCR on the first sparse matrix–vector multiplication after ModRaise, we postpone rotations: for each non-zero diagonal $j$, encode the shifted diagonal, compute plaintext–ciphertext multiplication, apply LCRescale, and only then homomorphically rotate and add—this is a <em>level-conserving</em> matrix-vector multiplication. It saves one modulus level in bootstrapping but has two structural costs on that matrix:</p> <ol> <li><strong>BSGS must be dropped</strong> in the first step, because scalar multiplication and rescaling must happen <em>before</em> key-switching/rotation; the usual baby-step/giant-step loop is incompatible with LCR.</li> <li>The step requires $r$ rotations (one per non-zero diagonal) instead of the $O(\sqrt{r})$ rotations of BSGS, and LCRescale is repeated $r$ times instead of a single rescaling at the end.</li> </ol> <p><strong>Redistributing non-zero diagonals.</strong> CtS relies on matrix factorization [2,3]: the DFT/iDFT matrix is split into several sparse diagonal factors. When BSGS is removed from the first factor, efficiency is most sensitive to the number of non-zero diagonals $r$. Discarding BSGS is essentially optimal when $r \le 16$, and still nearly optimal for $r = 32$; for $r = 64$ the overhead grows but can be offset by AKS. In practice, one chooses the factorization depth and diagonal budget so that the <strong>first</strong> matrix (the LCR step) keeps a modest $r$ (e.g., $8$ or $16$), while later matrices—evaluated with ordinary BSGS—may use the remaining diagonal budget. This allocation alleviates the efficiency loss caused by dropping BSGS. <br/></p> <h3 id="aks-algorithm-for-sparse-diagonal-matrixvector-multiplications">AKS Algorithm for Sparse Diagonal Matrix–Vector Multiplications</h3> <div style="margin-top: 1.5em;"></div> <p>As discussed earlier, we first design the AKS keys and then use them to construct the matrix-vector multiplication algorithm.</p> <p><strong>AKS keys.</strong> Let \(L+1=\alpha \cdot \beta\), \(D_j=\prod_{i=j\alpha}^{(j+1)\alpha-1}q_i\). For hybrid key-switching with decomposition bases \(\{D_j\}_{0\le j&lt; \beta}\), \(\hat{D}_j=Q/D_j\) and \(D_j^*=\Bigl[\hat{D}_j^{-1}\Bigr]_{D_j}\), the AKS key from \(s_1\) to $s_2$ is defined as (plaintext $m$ from the DFT/iDFT diagonal encoding, modulus $Q_L$):</p> \[\mathtt{evk}_j = \Biggl(\Biggl[-a_j s_2 + \Bigg\lfloor\frac{\bigl[P m s_1 \hat{D}_j D_j^*\bigr]_{P Q_L}}{q_L}\Bigg\rceil + e_j\Biggr]_{P Q_L},\; [a_j]_{P Q_L}\Biggr),\] <p>where $a_j \leftarrow U(\mathcal{R}_{P Q_L})$ and $e_j$ is sampled from the error distribution.</p> <p><strong>AKS operation.</strong> Given \(\mathtt{ct} = (c_0, c_1) \in \mathcal{R}_{Q_L}^2\) that encrypts \(\mathtt{pt}\) under \(s_1\) at modulus $Q_{L}$, the AKS operation proceeds in three steps:</p> <ol> <li><strong>Key-switching:</strong> \((d_0, d_1) = \left\lfloor \frac{1}{P} \left( \sum_{j=0}^{\beta-1} [c_1]_{D_j} \cdot \text{evk}_j \pmod {P Q_{L-1}} \right) \right\rceil \in \mathcal{R}_{Q_{L-1}}^2.\)</li> <li><strong>ScalarMult+Rescale on $c_0$:</strong> \(c_0' = \left\lfloor \frac{m \cdot c_0}{q_L} \right\rceil\in \mathcal{R}_{Q_{L-1}}\).</li> <li><strong>Addition:</strong> $\mathtt{ct}’ = (d_0 + c_0’, d_1)$.</li> </ol> <p>The output encrypts \(m \cdot \mathtt{pt} / q_L\) under $s_2$ at modulus $Q_{L-1}$.</p> <p><strong>New matrix-vector multiplication.</strong> Applying the AKS operation, we design a new matrix-vector multiplication algorithm, which adopts the hoisting approach but drops the BSGS method. The improved algorithm is:</p> <ol> <li><strong>Decompose:</strong> Compute \([c_1]_{D_j}\) for $0\le j&lt;\beta$.</li> <li><strong>AKS operation:</strong> For each $i\in\left[0,r\right)$, perform inner-product using \([c_1]_{D_j}\) and the aggregated rotation keys (KS+ScalarMult+Rescale on \(c_1\)), followed by the ModDown procedure (divided by $P$).</li> <li><strong>ScalarMult+Rescale</strong>: Multiply $c_0$ by $m$ then rescale by $q_L$.</li> <li><strong>Addition:</strong> Rotate all $r$ ciphertexts accordingly and add them together.</li> </ol> <p>Compared with the matrix-vector multiplication with $O(\sqrt{r})$ rotations based on the BSGS method, the new algorithm requires $O(r)$ rotations, but the process is much simpler: it cuts down scalar multiplication and rescaling operations on $c_1$, and eliminates extra NTT/iNTT computations between baby-step and giant-step loops. Theoretically, the AKS-based algorithm is more efficient when the number of non-zero diagonals is small ($r \le 64$). The space overhead increases due to the larger number of rotation keys. <br/></p> <h3 id="lcr--aks-algorithm-for-the-first-matrixvector-multiplication-in-cts">LCR + AKS Algorithm for the First Matrix–Vector Multiplication in CtS</h3> <div style="margin-top: 1.5em;"></div> <p>Overall, LCR saves modulus consumption at the cost of higher computational overhead, while AKS improves efficiency at the cost of increased space overhead. Here we combine LCR and AKS for the first sparse matrix multiplication in CtS, yielding a <strong>lossless</strong> improvement. Compared with the AKS-based matrix-vector multiplication, LCR+AKS involves three changes:</p> <ol> <li><strong>Modulus level is preserved:</strong> The output of the AKS operation stays in $\mathcal{R}_{Q_L}^2$.</li> <li><strong>LCRescale:</strong> The regular Rescale is replaced by LCRescale on the $c_0$ branch.</li> <li><strong>GHS-type key-switching:</strong> In key-switching, the GHS-type key-switching is used instead of hybrid key-switching, i.e., the rotation key is \(\mathtt{evk} = \Biggl(\Biggl[-a s_2 + \Bigg\lfloor\frac{\bigl[P m s_1 \bigr]_{P Q_L}}{q_L}\Bigg\rceil + e\Biggr]_{P Q_L},\; [a]_{P Q_L}\Biggr).\)</li> </ol> <p><strong>Why GHS-type?</strong> Hybrid key-switching involves the CRT composition \(\sum_{j=0}^{\beta-1} [c_1]_{D_j} \cdot \hat{D}_j D_j^* = c_1 + k_2 Q_L\), where \(k_2\) is a polynomial with integer coefficients. After rescaling by $q_L$, the term $k_2 Q_L$ becomes \(k_2 Q_{L-1}\), which cannot be eliminated by modulo $Q_L$, making LCR inapplicable. Therefore, we use the GHS-type key-switching. We <strong>keep the same auxiliary modulus $P$</strong> as in the hybrid key-switching because post-ModRaise ciphertexts have small coefficients and thus introduce small key-switching noise.</p> <p>Two other advantages of GHS-type key-switching are:</p> <ul> <li>It reduces the time complexity of the Decompose and MultSum steps by a factor of $\beta$ since the ciphertext $c_1$ no longer needs to be decomposed;</li> <li>It reduces the size of each $\mathtt{evk}$ by a factor of $\beta$.</li> </ul> <p>Therefore, the LCR+AKS method achieves comprehensive improvements in modulus consumption, computational efficiency, and storage space, realizing a lossless performance enhancement. <br/></p> <h3 id="application-to-bootstrapping">Application to Bootstrapping</h3> <div style="margin-top: 1.5em;"></div> <p>We apply LCR+AKS to the first matrix–vector multiplication in CtS to save one modulus level. With sparse-secret encapsulation [4], the key-switching for decapsulation is merged into the aggregated key-switching in the first matrix-vector multiplication, which accelerates the ModRaise step.</p> <p>For the remaining matrices in CtS (and StC), one can use the AKS-based algorithm or the double-hoisting BSGS method according to the available storage resources. The more AKS is used, the more rotation keys are required. This gives rise to a lossless bootstrapping strategy and some time–memory trade-off bootstrapping strategies (illustrated in Fig. 1).</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2606_Lianglin/fig1-480.webp 480w,/assets/img/blog/2606_Lianglin/fig1-800.webp 800w,/assets/img/blog/2606_Lianglin/fig1-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2606_Lianglin/fig1.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <p><strong>Figure 1.</strong> Flowchart of the CtS procedure in prior works [1, 4] (top), lossless strategy (middle), and time–memory trade-off strategy (bottom). </p> <p><br/></p> <h2 id="results">Results</h2> <div style="margin-top: 1.5em;"></div> <p>We employ the lossless strategy based on the sparse-secret encapsulation bootstrapping [4]. All experiments were conducted on an Intel Core i7-9700 (3.00 GHz) running Windows 10 with single-thread execution and 256 GB storage space. Our code is developed upon the Lattigo library. As the baseline, we reproduced the sparse-secret encapsulation bootstrapping in [4] on the same machine. The experimental results, shown in Table 1, can be summarized as follows:</p> <ol> <li><strong>Improved Computational Efficiency</strong>: LCR+AKS reduces the running time of ModRaise and the first matrix-vector multiplication in CtS. Specifically, ModRaise, including sparse secret en/decapsulations, achieves roughly 9.4–9.8× speedup, and the first matrix-vector multiplication gains a 3.0–3.5× speed boost.</li> <li><strong>Enhanced Throughput</strong>: The overall bootstrapping throughput rises by 20%–35% against the baseline, with bootstrapping precision and failure probability fully preserved.</li> <li><strong>Saved Modulus Resource</strong>: One modulus level is saved.</li> <li><strong>Optimized Key Storage</strong>: Despite the increased number of rotation keys, the total size of CtS rotation keys drops by around 12%–15%. This benefit stems from the smaller individual key size under the LCR+AKS scheme.</li> </ol> <p><strong>Table 1.</strong> Bootstrapping performance comparison against baseline scheme from [4]. Parameter sets are from [4]. rtk means rotation keys.</p> <div style="overflow-x: auto; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid; min-width: 100%;"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px; min-width: 80px;">Set</th> <th style="border: 1px solid; padding: 6px 12px;">ModRaise time(s)</th> <th style="border: 1px solid; padding: 6px 12px;">Mat1 (32)</th> <th style="border: 1px solid; padding: 6px 12px;">Mat2 (15)</th> <th style="border: 1px solid; padding: 6px 12px;">Mat3 (15)</th> <th style="border: 1px solid; padding: 6px 12px;">Mat4 (31)</th> <th style="border: 1px solid; padding: 6px 12px;">BTS time(s)</th> <th style="border: 1px solid; padding: 6px 12px;">Left Levels</th> <th style="border: 1px solid; padding: 6px 12px;">BTS Precision</th> <th style="border: 1px solid; padding: 6px 12px;">$\log(\textbf{Throughput})$</th> <th style="border: 1px solid; padding: 6px 12px;">CtS rtk size (GB)</th> </tr> </thead> <tbody> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">I-[4]</td> <td style="border: 1px solid; padding: 6px 12px;">1.14</td> <td style="border: 1px solid; padding: 6px 12px;">3.62</td> <td style="border: 1px solid; padding: 6px 12px;">1.85</td> <td style="border: 1px solid; padding: 6px 12px;">1.79</td> <td style="border: 1px solid; padding: 6px 12px;">3.34</td> <td style="border: 1px solid; padding: 6px 12px;">25.98</td> <td style="border: 1px solid; padding: 6px 12px;">14</td> <td style="border: 1px solid; padding: 6px 12px;">27.8</td> <td style="border: 1px solid; padding: 6px 12px;">24.28</td> <td style="border: 1px solid; padding: 6px 12px;">5.47</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>I′-our</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>0.12</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>1.19</strong></td> <td style="border: 1px solid; padding: 6px 12px;">1.88</td> <td style="border: 1px solid; padding: 6px 12px;">1.81</td> <td style="border: 1px solid; padding: 6px 12px;">3.29</td> <td style="border: 1px solid; padding: 6px 12px;">23.20</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>15</strong></td> <td style="border: 1px solid; padding: 6px 12px;">27.8</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>24.54</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>4.82</strong></td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">II-[4]</td> <td style="border: 1px solid; padding: 6px 12px;">1.18</td> <td style="border: 1px solid; padding: 6px 12px;">3.87</td> <td style="border: 1px solid; padding: 6px 12px;">1.98</td> <td style="border: 1px solid; padding: 6px 12px;">1.98</td> <td style="border: 1px solid; padding: 6px 12px;">3.13</td> <td style="border: 1px solid; padding: 6px 12px;">27.99</td> <td style="border: 1px solid; padding: 6px 12px;">10</td> <td style="border: 1px solid; padding: 6px 12px;">32.9</td> <td style="border: 1px solid; padding: 6px 12px;">24.09</td> <td style="border: 1px solid; padding: 6px 12px;">6.19</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>II′-our</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>0.12</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>1.10</strong></td> <td style="border: 1px solid; padding: 6px 12px;">1.81</td> <td style="border: 1px solid; padding: 6px 12px;">1.73</td> <td style="border: 1px solid; padding: 6px 12px;">3.24</td> <td style="border: 1px solid; padding: 6px 12px;">22.91</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>11</strong></td> <td style="border: 1px solid; padding: 6px 12px;">32.9</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>24.52</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>5.25</strong></td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;">III-[4]</td> <td style="border: 1px solid; padding: 6px 12px;">1.03</td> <td style="border: 1px solid; padding: 6px 12px;">3.45</td> <td style="border: 1px solid; padding: 6px 12px;">1.82</td> <td style="border: 1px solid; padding: 6px 12px;">1.52</td> <td style="border: 1px solid; padding: 6px 12px;">2.78</td> <td style="border: 1px solid; padding: 6px 12px;">23.37</td> <td style="border: 1px solid; padding: 6px 12px;">12.5</td> <td style="border: 1px solid; padding: 6px 12px;">19.7</td> <td style="border: 1px solid; padding: 6px 12px;">24.29</td> <td style="border: 1px solid; padding: 6px 12px;">5.81</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px; min-width: 80px;"><strong>III′-our</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>0.11</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>1.03</strong></td> <td style="border: 1px solid; padding: 6px 12px;">1.73</td> <td style="border: 1px solid; padding: 6px 12px;">1.74</td> <td style="border: 1px solid; padding: 6px 12px;">2.69</td> <td style="border: 1px solid; padding: 6px 12px;">20.21</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>13.5</strong></td> <td style="border: 1px solid; padding: 6px 12px;">19.7</td> <td style="border: 1px solid; padding: 6px 12px;"><strong>24.62</strong></td> <td style="border: 1px solid; padding: 6px 12px;"><strong>4.93</strong></td> </tr> </tbody> </table> </div> <p>The time–memory trade-off strategy can reach up to 40% higher throughput at the cost of doubling CtS rotation key size. <br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <p>[1] Bossuat, J.P., Mouchet, C., Troncoso-Pastoriza, J., and Hubaux, J.P. “Efficient Bootstrapping for Approximate Homomorphic Encryption with Non-sparse Keys.” EUROCRYPT 2021.</p> <p>[2] Chen, H., Chillotti, I., and Song, Y. “Improved Bootstrapping for Approximate Homomorphic Encryption.” EUROCRYPT 2019.</p> <p>[3] Han, K., Hhan, M., and Cheon, J.H. “Improved Homomorphic Discrete Fourier Transforms and FHE Bootstrapping.” IEEE Access 2019.</p> <p>[4] Bossuat, J.P., Troncoso-Pastoriza, J.R., and Hubaux, J.P. “Bootstrapping for Approximate Homomorphic Encryption with Negligible Failure-probability by using Sparse-secret Encapsulation.” ACNS 2022.</p>]]></content><author><name>Lianglin Yan</name></author><summary type="html"><![CDATA[TL;DR: To improve efficiency and reduce the modulus consumption in standard CKKS bootstrapping, we propose two novel core techniques: level-conserving rescaling (LCR) and aggregated key-switching (AKS), which act on the matrix-vector multiplications in linear transformations and can be further combined into the lossless LCR+AKS. The contributions enable bootstrapping that consumes one fewer modulus level, improves throughput by 20%–35%, and reduces CtS rotation key size by 11.9%–15.2%, while preserving identical precision and failure probability.]]></summary></entry><entry><title type="html">On the (In)security of Approximate Computation Protocols from CKKS</title><link href="https://ckks.org/blog/2026/Insecurity-ckks-protocol/" rel="alternate" type="text/html" title="On the (In)security of Approximate Computation Protocols from CKKS"/><published>2026-06-15T04:00:00+00:00</published><updated>2026-06-15T04:00:00+00:00</updated><id>https://ckks.org/blog/2026/Insecurity-ckks-protocol</id><content type="html" xml:base="https://ckks.org/blog/2026/Insecurity-ckks-protocol/"><![CDATA[<ul> <li>Written by <a href="https://scholar.google.com/citations?user=-9Vi89QAAAAJ">Dongwon Lee</a> (Seoul National University)</li> <li>Based on <a href="https://eprint.iacr.org/2025/395">https://ia.cr/2025/395</a></li> </ul> <p><em>TL;DR: Recent advances in approximate HE, particularly CKKS, have significantly advanced the practicality of secure computation involving approximate arithmetic. However, the inherent errors introduced by CKKS pose substantial challenges in the security analysis and protocol design. We investigate the correctness and security of existing CKKS-based protocols relying on the noise smudging technique, in which each party independently samples exponentially large noise. We show that these constructions fail to achieve standard simulation-based security. To address this issue, we propose a collaborative sampling approach in which parties jointly generate additive shares of the smudging noise. We present concrete constructions for both asymmetric two-party and symmetric multiparty settings, together with formal ideal functionalities. Furthermore, we provide concrete implementations of round-efficient collaborative sampling protocols. As an alternative perspective, we show that existing protocols satisfy a weaker security notion called liberal security.</em></p> <hr/> <p><br/></p> <h1 id="introduction">Introduction</h1> <div style="margin-top: 1.5em;"></div> <p>The improvement in the practicality of HE makes it highly effective for secure outsourcing in cloud environments. It is widely used in both two-party computation (2PC) and multi-party computation (MPC) protocols, such as secure inference or federated learning, due to its low communication overhead and round complexity compared to generic MPC. While translating exact HE schemes (like BFV or BGV) into simulation-based secure MPC protocols is well-established by combining $\text{IND-CPA}$ security with circuit privacy or secure distributed decryption, the security of the underlying HE scheme does not automatically guarantee overall protocol security.</p> <p>Among various HE schemes, the CKKS scheme has gained significant traction for privacy-preserving machine learning because it supports approximate arithmetic on real numbers. A key distinguishing feature of CKKS compared to exact HE schemes is that it interprets noise, whether intentionally added for security or incurred through homomorphic operations, as a part of the plaintext. Therefore, decrypting a CKKS ciphertext produces an approximate value, which inherently contains the accumulated error. While this approximate nature enhances computational efficiency, it introduces critical vulnerabilities; notably, revealing raw decryptions can lead to key-recovery attacks, which motivated the creation of the $\text{IND-CPA}^\text{D}$ security model [1] and subsequent research into countermeasures like noise smudging.</p> <p>We also emphasize that analyzing these security elements, such as $\text{IND-CPA}^\text{D}$ security or circuit privacy, in isolation is fundamentally insufficient for designing end-to-end protocols. Existing security enhancement techniques fail to construct secure (CKKS-based) approximate computation protocols that achieve standard simulation-based security. Since security countermeasures in CKKS typically rely on adding extra noise that directly alters the underlying plaintext values, protocol designers cannot treat security and correctness as independent properties; instead, they require an integrated methodology that analyzes both jointly. Based on these observations, we raise the following questions:</p> <blockquote> <p>Is it possible to implement a CKKS-based protocol that achieves standard simulation-based security?</p> </blockquote> <p><strong>Note</strong> Throughout this post, we only discuss 2PC case between a client and a server for the security analysis and construction of approximate computation protocol for the sake of explanation.</p> <p><br/></p> <h2 id="preliminaries-standard-simulation-based-security">Preliminaries: Standard Simulation-based Security</h2> <div style="margin-top: 1.5em;"></div> <p>We first introduce the standard simulation-based security notion for the computation protocol.</p> <ul> <li><strong>$n$:</strong> the number of parties.</li> <li><strong>\(f:(\{0,1\}^*)^n \rightarrow (\{0,1\}^*)^n\):</strong> a probabilistic polynomial-time (PPT) functionality.</li> <li><strong>\(\text{view}_i^\Pi(\mathbf{x})\):</strong> a view of party $P_i,$ consisting of its input, random tape, and all messages received during the protocol.</li> <li><strong>\(\text{output}_i^\Pi(\mathbf{x})\):</strong> an output of the protocol.</li> </ul> <p><strong>Definition</strong> An $n$-party protocol $\Pi$ securely realizes a functionality $f$ if there exists a PPT simulator $\text{Sim}^\Pi$ such that for every corrupted subset of parties \(A \subseteq \{1, \dots, n\}\) and every input vector $\mathbf{x},$ the following holds:</p> \[\{(\text{Sim}^\Pi(A, (x_i)_{i\in A}, f_A(\mathbf{x})), f(\mathbf{x}))\} \approx_c \{(\text{view}_A^\Pi(\mathbf{x}), \text{output}^\Pi(\mathbf{x}))\}\] <p>The above privacy requirement asserts that the <strong>joint distribution</strong> of the simulator’s output and the outputs of functionality should be indistinguishable from the view of the corrupted party and the outputs of parties. This requirement is particularly meaningful when the functionality is probabilistic, as it guarantees that the adversary does not obtain any additional information about the output.</p> <p><br/></p> <h1 id="system-model-asymmetric-2pc-protocol">System Model: Asymmetric 2PC protocol</h1> <div style="margin-top: 1.5em;"></div> <p>Asymmetric two-party protocols constitute one of the most common applications of HE such as secure inference. In this setting, the <strong>client</strong> acts as a key owner and the <strong>server</strong> as an evaluator, collaboratively computing a public circuit $C$ on their respective private inputs $x$ and $y$ as follows.</p> <p>1) The client generates a key pair, encrypts its private input, and sends the ciphertext and the evaluation key to the server.</p> <p>2) The server homomorphically evaluates the circuit on the received ciphertext and its private input, and sends the resulting ciphertext back to the client.</p> <p>3) The client decrypts the output ciphertext to obtain the final result and shares it with the server.</p> <p>This baseline solution raises several security concerns. First, the decrypted value of a CKKS ciphertext may leak more information than the client intends. On the other hand, the server also needs to ensure its input privacy, which is often referred to as circuit privacy in the HE context. Therefore, the two aforementioned issues must be considered together for constructing a secure CKKS-based 2PC protocol. Since a noise smudging technique is widely regarded as a standard countermeasure for achieving $\text{IND-CPA}^\text{D}$ security [2] and circuit privacy [3], existing CKKS-based 2PC protocols typically adopt noise smudging in both sides. In this case, the functionality output is $f(x,y) = C(x,y) + e_1 + e_2$ where $e_1$ and $e_2$ are independently sampled from the client ($P_1$) and the server ($P_2$), respectively.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2606_Dongwon/exist2PC.PNG" sizes="95vw"/> <img src="/assets/img/blog/2606_Dongwon/exist2PC.PNG" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 1. Existing approximate 2PC protocol. </div> <p><br/></p> <h1 id="security-issue-insecurity-of-existing-approximate-2pc-protocol">Security Issue: Insecurity of existing approximate 2PC protocol</h1> <div style="margin-top: 1.5em;"></div> <p>We demonstrate that this protocol does not satisfy the simulation-based security, especially for the circuit $C(x,y) = x + y$. Assume the client ($P_1$) is corrupted. We claim that there does not exist a PPT algorithm $\text{Sim}^{\Pi}$ that satisfies the standard simulation-based security condition.</p> \[\{(\text{Sim}^\Pi(P_1, x, f(x,y)), f(x,y))\} \approx_c \{(\text{view}_{P_1}^\Pi(x,y), \text{output}^\Pi(x,y))\}\] <p>In a real execution of the protocol, the client naturally knows its own input $x$ and its locally sampled smudging noise $e_1$. Since the final protocol output is approximately the sum of all inputs and smudging noises ($z = x + y + e_1 + e_2$), a real-world adversary can easily calculate $z - x - e_1 = y + e_2$.</p> <p>In the ideal world, however, the simulator only receives the client’s input $x$ and the aggregated ideal output $z$. To satisfy simulation-based security, the simulator must generate a fake view containing a simulated noise $e’_1$ such that $z - x - e’_1$ is indistinguishable from $y + e_2$. Because the simulator has no knowledge of the server’s private input $y,$ it faces an impossible task. It cannot correctly decompose the aggregated sum to isolate a convincing $e’_1$ for all possible values of $y,$ making the simulated view easily distinguishable from the real execution.</p> <p>Similarly, it can be readily shown that no simulator exists for a corrupted server. When the server is corrupted, the smudging error $e_2$ becomes part of the adversary’s internal state. Ultimately, because a valid simulator cannot be constructed for either party, the baseline protocol fails to satisfy standard simulation-based security requirements.</p> <p><strong>Note</strong> In contrast to the approximate HE setting, in an exact HE setting, the final outputs of both the real-world protocol and the ideal-world functionality ($z=x+y$) are completely independent of the smudging errors ($e_1$ and $e_2$). Because the noise is entirely separated from the actual plaintext computation, the final decrypted result is not affected by the smudging noise. As a result, even if a corrupted party’s simulator is given the specific smudging noise associated with that adversary, it is impossible for the simulator to derive any information more accurate than what is already provided by the ideal functionality. Therefore, we can construct the ideal-world simulation which is (computationally) indistinguishable from the real-world execution, thereby satisfying the requirements of standard simulation-based security.</p> <p><br/></p> <h1 id="our-construction-secure-approximate-2pc-protocol">Our Construction: Secure approximate 2PC protocol</h1> <div style="margin-top: 1.5em;"></div> <p>As discussed in the previous section, existing formulations of $\text{IND-CPA}^\text{D}$ security and circuit privacy for CKKS do not adequately capture the security requirements of CKKS-based approximate protocols. A key insight from our analysis is that security cannot be achieved if any party retains non-negligible information about the smudging error, as no simulator can reconstruct the view of that party using only the ideal functionality. As a result, we address this issue from the following key idea:</p> <blockquote> <p>If each party is independent of the smudging error, the protocol achieves the simulation-based security.</p> </blockquote> <p>In order to achieve this property, we propose an alternative error generation procedure, called <strong>collaborative sampling</strong>. The functionality of the collaborative sampling is generating a single smudging error $e$ from a smudging distribution and dividing it into two random additive shares, $r_1$ and $r_2,$ such that $r_1 + r_2 = e$. Because each individual share is statistically indistinguishable from a uniformly random element, neither party obtains any partial knowledge about the total smudging error $e$ in isolation.</p> <p>By utilizing the collaborative sampling, we can construct the secure approximate 2PC protocol. During the protocol, the client encrypts its input, and the server homomorphically evaluates the circuit with their private input. To maintain circuit privacy, the server adds its secret noise share $r_2$ directly to the evaluated ciphertext before sending it to the client. The client then decrypts the received ciphertext while simultaneously factoring in its own noise share $r_1$. This reconstructs the final output, which is statistically identical to $z=C(x,y) + e$.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2606_Dongwon/secure2PC.PNG" sizes="95vw"/> <img src="/assets/img/blog/2606_Dongwon/secure2PC.PNG" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> Figure 2. Secure approximate 2PC protocol. </div> <p>In the previous (insecure) protocol, knowing one’s own noise allowed an adversary to extract a more precise approximation than the ideal functionality intended to reveal. In this secure protocol, because the individual noise shares are entirely decoupled from the actual total error $e,$ neither the client nor the server can derive additional information than the ideal functionality. More precisely, we can construct the simulator against the adversarial client ($P_1$) as below.</p> <p>1) Generate key pair $(sk, pk)$ and encrypt the client’s private input \(\text{ct}_\text{in} \leftarrow \text{Enc}_{pk}(x)\).</p> <p>2) Sample $r_1 \leftarrow R_q$ and let $u’:= z-r_1$ for the ideal functionality $z$.</p> <p>3) Sample \(a_\text{out} \leftarrow R_q\) and let \(\text{ct}'_\text{out} := (a_\text{out} \cdot s + u', a_\text{out})\).</p> <p>4) Output \((x, r_1, sk, pk, \text{ct}_\text{in}, \text{ct}'_\text{out})\).</p> <p>We show that the output of the simulator $\text{Sim}^{P_1}_{\text{secure2PC}}(x, z)$ is computationally indistinguishable from the joint distribution of $P_1$’s view and the real execution output.</p> \[\begin{array}{cl} &amp; \{\text{view}^{\Pi_{\text{secure2PC}}}_{P_1}(x,y), \text{Out}^{\Pi_{\text{secure2PC}}}(x,y)\} \\ \equiv\,\, &amp; \{(..., \text{ct}_{\text{out}}':= \text{ct}_{\text{out}}+\text{ct}_{\text{zero}} + (r_2, 0)), C(x,y) + e'_{\text{out}} + r_1 + r_2 \} \\ \approx_s &amp; \{(..., \text{ct}_{\text{out}}':= \text{ct}_{\text{out}}+\text{ct}_{\text{zero}} + (e - e'_{\text{out}} - r_1, 0)), C(x,y)+e'_{\text{out}} + e - e'_{\text{out}}\} \\ \equiv\,\, &amp; \{(..., \text{ct}_{\text{out}}'= ((a_{\text{out}} + a_{\text{zero}}) s + C(x,y) + e - r_1, a_{\text{out}}+a_{\text{zero}})), C(x,y) + e \} \\ \approx_c &amp; \{(..., \text{ct}_{\text{out}}'= (as + z - r_1, a)), z \} \\ \equiv\,\, &amp; \{(\text{Sim}^{P_1}_{\text{secure2PC}}(x, z), z )\}, \end{array}\] <p>where \(e'_{\text{out}} = e_{\text{out}} + e_{\text{zero}}\).</p> <p>The statistical indistinguishability follows from the assumption that $e$ is a smudging error of \(e'_{\text{out}}\) , while the computational indistinguishability is derived from the fact that the distribution of \(a_\text{zero}\) obtained from \(\text{ct}_\text{zero}=(b_\text{zero}, a_\text{zero}) \leftarrow \text{Enc}_{pk}(0)\) is indistinguishable from a uniform distribution over $R_q$ under the RLWE assumption.</p> <p><br/></p> <h1 id="implementation-collaborative-sampling">Implementation: Collaborative Sampling</h1> <div style="margin-top: 1.5em;"></div> <p>Now, we describe efficient collaborative sampling protocols that securely generate the shared smudging noise required for standard simulation-based security in CKKS-based systems. Instead of using generic MPC frameworks that suffer from high communication overhead, we provide protocols offering round-efficient implementations optimized for both two-party (2PC) and multi-party (MPC) settings.</p> <p><strong>Note</strong> In the case of security analysis and protocol construction, 2PC naturally extends to MPC; however, in the case of collaborative sampling, we will explain both scenarios because they are constructed by applying distinct cryptographic primitives.</p> <p><br/></p> <h2 id="unified-mechanism-for-uniform-error-distribution">Unified Mechanism for Uniform Error Distribution</h2> <div style="margin-top: 1.5em;"></div> <p>The uniform distribution is the most widely applied distribution for noise smudging. To generate a collaborative smudging error from a uniform distribution $\mathcal{D}$:</p> <ul> <li><strong>Shared Random Bits:</strong> The process consumes $k = \log(E) + \lambda$ additive shares of random bits ($b_j \in {0,1}$), where $k$ is determined by the upper bound of the smudging error $E$ and the security parameter $\lambda$.</li> <li><strong>Local Reconstitution:</strong> Each party locally scales and combines their bit shares to derive their final additive error share: \(r_i=\sum_{j=0}^{k-1} 2^j r_{i, j}\) where $r_{i,j}$ denotes party $i$’s additive share of the random bit $b_j$.</li> </ul> <p>This technique can be extended across polynomials ($R$) by evaluating each coefficient independently.</p> <p><br/></p> <h2 id="two-party-bit-sharing-via-oblivious-transfer">Two-Party Bit-Sharing via Oblivious Transfer</h2> <div style="margin-top: 1.5em;"></div> <p>In a two-party environment, the required random bit shares are generated efficiently via a 1-out-of-2 Oblivious Transfer (OT) protocol:</p> <ul> <li><strong>The Process:</strong> The sender ($P_1$) samples a random bit $b$ and a local mask $r_1,$ preparing masked values $(m_0, m_1)=(b-r_1, 1-b-r_1)$ for the transfer. The receiver ($P_2$) samples a selection bit $\sigma$ and fetches the corresponding value $m_\sigma$ through the OT mechanism.</li> <li><strong>Optimization:</strong> The protocol instantiates this using a random OT baseline combined with Beaver’s OT derandomization technique. This approach turns a random OT into a sender-chosen OT with only one additional round of communication. Because all instances can run in parallel, the round complexity remains constant.</li> </ul> <p><br/></p> <h2 id="multi-party-bit-sharing-via-discrete-ckks">Multi-Party Bit-Sharing via Discrete CKKS</h2> <div style="margin-top: 1.5em;"></div> <p>For multi-party settings ($n &gt; 2$), the protocol leverages the cryptographic properties of <strong>Discrete CKKS</strong> [4] alongside specialized bootstrapping to minimize network communication:</p> <ul> <li><strong>Initial Encryption:</strong> Each party generates a key pair, samples a polynomial with binary coefficients, and broadcasts an encrypted version under a small modulus ($q_0$).</li> <li><strong>Aggregating and Scaling:</strong> The individual ciphertexts are aggregated linearly to create a encryption containing a uniformly random bit string. The parties then apply <strong>Binary Bits Bootstrapping</strong> [4] and a cleaning operation to securely expand the plaintext.</li> <li><strong>Distributed Decryption:</strong> The parties obtain additive shares of random bits after decrypting the aggregated ciphertext. During the decryption, we use the masking technique [5], generating an additive share of zero by securely exchanging PRF keys, to hide the information about the random bit.</li> </ul> <p>As described in [6,7], secure distributed decryption in both synchronous and asynchronous setting can be achieved by having each party exchange pairwise (key homomorphic) PRF seeds. Since both the PRF and the corresponding seeds are already necessary components in the synchronous and asynchronous settings, respectively, the proposed masking technique introduces essentially no additional overhead to our protocol.</p> <p><br/></p> <h2 id="benchmark">Benchmark</h2> <div style="margin-top: 1.5em;"></div> <p>We implement and benchmark the protocols for collaborative sampling in both two-party and multi-party cases. All experiments are performed with a single thread on a machine with an Intel(R) Xeon(R) Platinum 8268 CPU running at 2.90GHz and 384GB of RAM.</p> <div style="display: flex; justify-content: center; margin-bottom: 1.0em;"> <table style="border-collapse: collapse; border: 1px solid"> <thead> <tr style="border-bottom: 2px solid "> <th style="border: 1px solid; padding: 6px 12px;">#(bit shares)</th> <th style="border: 1px solid; padding: 6px 12px;">System Model</th> <th style="border: 1px solid; padding: 6px 12px;">Time(s)</th> </tr> </thead> <tbody> <tr> <td rowspan="2" style="border: 1px solid; padding: 6px 12px; text-align: center;">$2^{16}$</td> <td style="border: 1px solid; padding: 6px 12px;">2 party</td> <td style="border: 1px solid; padding: 6px 12px;">0.12</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px;">multi-party</td> <td style="border: 1px solid; padding: 6px 12px;">33.96</td> </tr> <tr> <td rowspan="2" style="border: 1px solid; padding: 6px 12px; text-align: center;">$2^{18}$</td> <td style="border: 1px solid; padding: 6px 12px;">2 party</td> <td style="border: 1px solid; padding: 6px 12px;">0.41</td> </tr> <tr> <td style="border: 1px solid; padding: 6px 12px;">multi-party</td> <td style="border: 1px solid; padding: 6px 12px;">135.84</td> </tr> </tbody> </table> </div> <p>For the two-party case, we use LibOTe library with the OT extension to generate bit shares for $\log q \approx 128$. For the multi-party case, we use Lattigo library to implement discrete CKKS. We use the parameters $N = 2^{16}, \log QP \approx 1200,$ so that a single protocol creates $2^{16}$ bit shares. We also set the parameters so that after the protocol $157$ bits of modulus is left with $\log \Delta \approx 60$ and $\approx 2^{-47}$ bits of precision. Hence, we can set $\log q \approx 90$ and support up to $2^7 = 128$ parties with 40 bits of statistical security. We also note that we only implement the bootstrapping and the cleaning part, as it is the main bottleneck of the protocol.</p> <p><br/></p> <h1 id="liberally-secure-approximate-2pc-protocol">Liberally Secure approximate 2PC protocol</h1> <div style="margin-top: 1.5em;"></div> <p>We also investigate a relaxed, alternative security framework termed <strong>liberal security</strong>, inspired by [8]. Unlike the standard definition, in which the simulator relies solely on the ideal functionality’s output, the liberal definition grants the simulator access to auxiliary information.</p> <p><strong>Definition</strong> Let $\Pi$ be an $n$-party protocol realizing a functionality $f$. We also define auxiliary information $\hat{f}$ for the functionality $f$. We say $\Pi$ is liberally secure if there exists a PPT simulator $\text{Sim}^\Pi$ such that for every corrupted subset of parties \(A \subseteq \{1, \dots, n\}\) and every input vector $\mathbf{x},$ the following holds:</p> \[\{ (\text{Sim}^{\Pi}(A, (x_i)_{i \in A}, f_A(\mathbf{x}), \hat{f}_A(\mathbf{x})), f(\mathbf{x})) \} \approx_c \{ (\text{view}_A^{\Pi}(\mathbf{x}), \text{output}^{\Pi}(\mathbf{x})) \}\] <p>Liberal security guarantees that the adversary cannot obtain more information than the adversary that is given access to both the ideal functionality $f$ and the auxiliary information $\hat{f}$. It is the main difference between liberal security and standard MPC security notion. More precisely, liberal security guarantees that the protocol reveals no additional private information beyond that already disclosed by the pair $(f, \hat{f}),$ whereas the standard simulation-based definition restricts leakage solely to the information revealed by the ideal functionality $f$.</p> <p><br/></p> <h3 id="security-analysis-of-existing-2pc-protocol">Security Analysis of Existing 2PC protocol</h3> <div style="margin-top: 1.5em;"></div> <p>Under this definition, we prove that existing CKKS-based protocols (which utilize party-wise noise smudging and fail to achieve standard security) formally satisfy this relaxed security notion where the auxiliary information is an exact computation result ($\hat{z} = C(x,y)$). To prove the security of the existing 2PC protocol, we assume the case where the client is corrupted. Then, we can construct a simulator for the adversarial client as below.</p> <p>1) Generate key pair $(sk, pk)$ and encrypt the client’s private input \(\mathsf{ct}_\mathsf{in} \leftarrow \mathtt{Enc}_{pk}(x)\).</p> <p>2) Let \(e := z - \hat{z}\) and sample \((e_1, e_2) \leftarrow \{(e_1, e_2) \in \mathcal{D} \times \mathcal{D} \mid e_1 + e_2 = e\}\).</p> <p>3) Sample \(a_\text{out} \leftarrow R_q\) and let \(\text{ct}'_\text{out} := (a_\text{out} \cdot s + e_2, a_\text{out})\).</p> <p>4) Output \((x, r_1, sk, pk, \text{ct}_\text{in}, \text{ct}'_\text{out})\).</p> <p>Unlike the definition of standard security, the simulator also obtains the exact computation result as auxiliary information for the inputs. Therefore, the simulator can extract approximation error by subtracting $\hat{z}$ from $z,$ and it enables to generate the adversary’s view satisfying the desired condition of the security notion. This security proof guarantees that existing 2PC protocol leaks no more information than can be derived from $(f, \hat{f})$ where $\hat{f}$ returns the exact evaluation result for a certain circuit $C$.</p> <p>As such, the satisfaction of existing protocols with liberal security provides a theoretical justification for widely deployed protocols, showing that they are not completely insecure but rather offer a minimum security guarantee.</p> <p><br/></p> <h1 id="conclusion">Conclusion</h1> <div style="margin-top: 1.5em;"></div> <p>In this work, we show that existing CKKS-based 2PC and MPC protocols fail to achieve the standard simulation-based notion of MPC security. To address this limitation, we introduce a collaborative sampling technique for noise smudging and construct CKKS-based protocols that achieve the standard security definition by integrating this technique. In addition, we provide the notion of liberal security as a relaxed security definition, and demonstrate that existing CKKS-based protocols for approximate computation satisfy this liberal security.</p> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <p>[1] Baiyu Li and Daniele Micciancio. “On the Security of Homomorphic Encryption on Approximate Numbers.” EUROCRYPT 2021.</p> <p>[2] Baiyu Li, Daniele Micciancio, Mark Schultz, and Jessica Sorrell. “Securing Approximate Homomorphic Encryption using Differential Privacy.” CRYPTO 2022.</p> <p>[3] Kamil Kluczniak and Giacomo Santato. “On Circuit Private, Multikey and Threshold Approximate Homomorphic Encryption.” IACR CiC 2025 (2).</p> <p>[4] Youngjin Bae, Jung Hee Cheon, Jaehyung Kim, and Damien Stehlé. “Bootstrapping Bits with CKKS.” EUROCRYPT 2024.</p> <p>[5] Rafael del Pino, Shuichi Katsumata, Mary Maller, Fabrice Mouhartem, Thomas Prest, and Markku-Juhani Saarinen. “Threshold Raccoon: Practical Threshold Signatures from Standard Lattice Assumptions.” EUROCRYPT 2024.</p> <p>[6] François Colin de Verdière, Alain Passelègue, and Damien Stehlé. “On Threshold Fully Homomorphic Encryption with Synchronized Decryptors.” IACR ePrint 2026.</p> <p>[7] Ehsan Ebrahimi and Anshu Yadav. “Strongly Secure Universal Thresholdizer.” ASIACRYPT 2024.</p> <p>[8] Joan Feigenbaum, Yuval Ishai, Tal Malkin, Kobbi Nissim, Martin J. Strauss, and Rebecca N. Wright. “Secure Multiparty Computation of Approximations.” ICALP 2001.</p>]]></content><author><name>Dongwon Lee</name></author><summary type="html"><![CDATA[TL;DR: Recent advances in approximate HE, particularly CKKS, have significantly advanced the practicality of secure computation involving approximate arithmetic. However, the inherent errors introduced by CKKS pose substantial challenges in the security analysis and protocol design. We investigate the correctness and security of existing CKKS-based protocols relying on the noise smudging technique, in which each party independently samples exponentially large noise. We show that these constructions fail to achieve standard simulation-based security. To address this issue, we propose a collaborative sampling approach in which parties jointly generate additive shares of the smudging noise. We present concrete constructions for both asymmetric two-party and symmetric multiparty settings, together with formal ideal functionalities. Furthermore, we provide concrete implementations of round-efficient collaborative sampling protocols. As an alternative perspective, we show that existing protocols satisfy a weaker security notion called liberal security.]]></summary></entry><entry><title type="html">Modern Construction of Moduli Chain in HEaaN2</title><link href="https://ckks.org/blog/2026/heaan2-moduli-chain/" rel="alternate" type="text/html" title="Modern Construction of Moduli Chain in HEaaN2"/><published>2026-05-18T04:00:00+00:00</published><updated>2026-05-18T04:00:00+00:00</updated><id>https://ckks.org/blog/2026/heaan2-moduli-chain</id><content type="html" xml:base="https://ckks.org/blog/2026/heaan2-moduli-chain/"><![CDATA[<ul> <li>Written by Seonghak Kim (CryptoLab Inc.)</li> <li>About HEaaN2 (Available at <a href="https://heaan.io/">CODE.HEAAN</a>)</li> </ul> <p><em>TL;DR: Every CKKS computation is built upon a sequence of moduli that predetermines the rescaling amount after each multiplication. A new CKKS library, HEaaN2, generalizes the construction of this parameter with a carefully designed scheme and API set. In this article, we break down the traditional construction of the moduli chain to derive the new one.</em></p> <hr/> <p>A CKKS circuit essentially requires a sequence of moduli. Every CKKS entity is scaled up with its own scaling factor, and multiplying scaled inputs yields a doubly-scaled result. A rescaling operation is required to restore the original value of the scaling factor, and this operation reduces the ciphertext modulus. Continued multiplication and rescaling done on a ciphertext consumes the modulus until the available modulus is exhausted, and a bootstrapping operation restores the modulus back.</p> <p>A <em>moduli chain</em> is this sequence of moduli, governing the entire lifecycle of a ciphertext. Notably in many CKKS implementations the moduli chain is treated as a global object in a “CKKS Parameter”, meaning it affects not just one ciphertext but every ciphertext in the program, making it critical for overall performance and memory usage.</p> <p>In this article, we first review existing models of the moduli chain, then present a modern construction as implemented in HEaaN2. Finally, we outline an extended, chain-free CKKS API relying on ciphertext-based modulus management.</p> <p><br/></p> <h2 id="traditional-construction-of-moduli-chain-and-its-limits">Traditional Construction of Moduli Chain and Its Limits</h2> <div style="margin-top: 1.5em;"></div> <p>An early-stage model of the moduli chain is presented in the original RNS-CKKS paper<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. Throughout this article, let <em>level</em> denote multiplicative depth.</p> <ul> <li>Let $q_i$ be primes which are close to a common value.</li> <li>Let the initial modulus $Q = \prod_{i = 1}^\ell q_i$.</li> <li>Let the modulus for level $l$ to be $Q_l = \prod_{i = 1}^l q_i$. In other words, rescaling scales down the modulus by $q_i$ at each level.</li> <li>During the procedures, the scale is kept roughly stable by setting $\Delta \simeq q_i$ so that ${\Delta^2}/{q_i} \approx \Delta.$</li> </ul> <p>Note that this model explicitly chooses an approximate management of the scaling factor $\Delta$, trading precision for ease of use. A more accurate variant can be obtained by modifying the scale management as follows.</p> <ul> <li>Let the scale differ for each level, following the recurrence ${\Delta_i^2}/{q_i} = \Delta_{i-1}$.</li> <li>Choose $q_i$ appropriately to make $\Delta_{i-1}$ close to the desired value.</li> </ul> <p>This simple, user-friendly model was adopted in the initial designs of many CKKS libraries, including Lattigo<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>, OpenFHE<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>, and HEaaN<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>. Over time, each implementation improved the model independently to overcome its natural limitations.</p> <p>As recognized in prior works (BitPacker<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>, Grafting<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>), the core limitation is the coupling between the RNS system and the rescaling amount (roughly, the scaling factor). The rescaling amount, whose value is $q_i$, cannot be freely chosen because $q_i$ is a single prime and primes are not conveniently distributed.</p> <p>This coarse choice leads to the following restrictions.</p> <ul> <li>The rescaling amount is tied to $q_i$, and thus cannot exceed a machine word.</li> <li>As NTT is required for efficient polynomial arithmetic, the primes $q_i$ must be NTT-friendly, hence $\equiv 1 \pmod{2N}$, requiring $q_i$ to be at least $2N+1$ (and often significantly more).</li> <li>The suitable primes that fit into a 32-bit machine word are scarce, so it is hard to construct a stable implementation based on a 32-bit architecture.</li> <li>If the chain grows long enough, the scale factor can diverge.</li> </ul> <p><br/></p> <h2 id="attempts-for-generalized-constructions">Attempts for Generalized Constructions</h2> <div style="margin-top: 1.5em;"></div> <p>Several attempts have been made to generalize the system and overcome the problems above. They share a common abstraction that redefines the moduli chain in a more general way. A short formalization of the model is as follows.</p> <ul> <li>Define $Q_0 &lt; Q_1 &lt; \cdots &lt; Q_L$ as the modulus for each level. $Q_{i-1} \mid Q_i$ is not required.</li> <li>Rescaling divides by the ratio $Q_i / Q_{i-1}$.</li> <li>The scale follows the recurrence \({\Delta_i^2}/{(Q_i / Q_{i-1})} = \Delta_{i-1} \text{.}\)</li> </ul> <p>Since $Q_i / Q_{i-1}$ is no longer restricted to a single NTT-friendly prime, the rescaling amount can be chosen much more freely. The model is suggested and instantiated in distinct forms by different implementations.</p> <p>HElib<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup> suggests and utilizes a generalized construction, doing so from the very beginning of its design. It manages <em>small primes</em> alongside the normal primes, which are primes dedicated to handle fine-grained adjustment of modulus. These <em>small primes</em> are precomputed for a target bit resolution, enabling construction of $Q_i$ at an arbitrary multiple of that resolution.</p> <p>BitPacker<sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> conceptualizes the CKKS adjusting and rescaling operations and proposes a greedy algorithm to construct a modulus of arbitrary desired bit size. Like HElib, BitPacker separates <em>terminal residues</em> from non-terminal residues, but allows variable-length <em>terminal residues</em>, whereas HElib’s <em>small primes</em> always occupy up to two machine words.</p> <p>Grafting<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup> formalizes the correctness of CKKS adjusting and rescaling, and identifies a key-switching problem in generalized moduli chains: a switching key must be constructed over the LCM of all chain entries, unless multiple switching keys are instantiated at additional memory cost. To address this, Grafting introduces the <em>sprout</em>, whose divisor can have an arbitrary bit size, thereby limiting the LCM of the moduli and reducing key size. See the <a href="https://ckks.org/blog/2025/grafting/">article on Grafting</a> for more details.</p> <p>Cheddar<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup> utilizes a middle ground between HElib and BitPacker to maximize performance. In its <em>25-30 prime system</em>, the RNS system consists of many 30-bit primes and a few 25-bit primes. The 25-bit primes are precomputed to provide a predefined resolution as in HElib, while occupying a variable number of words as in BitPacker.</p> <p>We note that the security of the corresponding CKKS scheme relies on the same RLWE problem over a ring modulo a large modulus, regardless of the underlying arithmetic description of how moduli change during homomorphic operations.</p> <p><br/></p> <h2 id="the-construction-in-heaan2">The Construction in HEaaN2</h2> <div style="margin-top: 1.5em;"></div> <p>The moduli chain construction in HEaaN2 tries to combine the best features of these various implementations, though the library internally adopts the terminology of Grafting.</p> <p>Like the prior works, HEaaN2 lets a few designated RNS words absorb the fine-grained bit adjustment while the remaining words stay at full machine-word size. Following Grafting, these designated words are called the <em>sprout</em>. We formalize how HEaaN2 uses <em>sprout</em> as below.</p> <blockquote> <p><strong>Definition (Sprout).</strong> The <em>sprout</em> associated with $i$-th modulus (i.e. $Q_i$), $S_i$ is a designated pair of words $(q_{30}, q_X)$ such that</p> <ul> <li>$q_{30}$ is either 1 or a 30-bit NTT-friendly prime.</li> <li>$q_X$ is a flexible-bit prime with $30 \le \lfloor\log_2 q_X\rceil \le 59$.</li> </ul> </blockquote> <p>Note that we can find a sprout for any bit size in $\left[30, 89\right]$. By adding as many 60-bit primes as needed, we can thus obtain moduli with any bit-size $\ge 30$ bits.</p> <p>Now that a single modulus $Q_i$ can be constructed as desired, building the whole moduli chain from a sequence of desired scale factors is straightforward.</p> <ul> <li>Start from $Q_0$, sized to meet the base-level bits.</li> <li>Repeatedly build $Q_{i+1}$ on top of $Q_i$ by updating the sprout and, if needed, appending a full-sized word.</li> <li>Pick $q_X$ at each level so that $\Delta_i$ stays close to the desired scale. The value of $q_X$ is allowed to differ across $S_i$’s even for the same target bit size.</li> </ul> <p>The resulting construction offers several advantages over its predecessors. Its overall structure resembles HElib, successfully providing a 1-bit resolution as HElib does. Unlike HElib, however, it chooses $q_X$ dynamically at construction time, making scale divergence far less likely. Compared to BitPacker, the scheme keeps the number of <em>terminal values</em> small, so the modulus-switching overhead between adjacent moduli stays low. A drawback is that the construction requires a 64-bit RNS system and cannot be applied to 32-bit systems, where NTT-friendly primes in the required range are too scarce.</p> <p>A remaining issue is the switching-key problem raised by Grafting<sup id="fnref:6:2"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>. HEaaN2 adopts a pragmatic solution suggested in the same paper: it first switches the ciphertext modulus up to the key’s modulus, performs the key switch, then switches the modulus back down to the ciphertext’s original modulus. This yields a simple solution, at the expense of a small performance overhead on key-switching.</p> <p>In HEaaN2, the concept of moduli chain is represented by a class named <code class="language-plaintext highlighter-rouge">Levels</code>. This class contains two fields, one being the sequence of moduli (the moduli chain), the other one being the corresponding sequence of scales. The class can be handed to computing modules (e.g. <code class="language-plaintext highlighter-rouge">HomEval</code>) to inform which moduli chain to use during a computation. Notably, the API separates a moduli chain from a full CKKS parameter set, allowing a CKKS program to have multiple moduli chains. More details can be found at <a href="https://cryptolab.gitbook.io/heaan2/api-reference/metadata/levels">the HEaaN2 documentation</a>.</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">HEAAN2_API</span> <span class="n">Levels</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">PolyMod</span><span class="o">&gt;</span> <span class="n">mods</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Real128</span><span class="o">&gt;</span> <span class="n">scales</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div> <p>HEaaN2 also offers an utility to automatically construct a moduli chain, named <code class="language-plaintext highlighter-rouge">LevelsBuilder</code>. A notable point on the utility is that it accepts the bit-size of a modulus as an argument, thanks to the <em>sprout</em>’s capability to express any bits. Details of the class and an example usage can be found at <a href="https://cryptolab.gitbook.io/heaan2/basics/custom-parameters#levelsbuilder">the corresponding documentation</a>.</p> <p><br/></p> <h2 id="towards-a-moduli-chain-free-ckks">Towards a Moduli-Chain Free CKKS</h2> <div style="margin-top: 1.5em;"></div> <p>Though the moduli chain is a natural structure for CKKS computations, it also creates rigidity in complex circuits. As an example, when the required precision per multiplication varies, the rescaling amount can be finely adjusted to reduce modulus consumption. A natural idea is to try to get rid of the global moduli chain and move to a ciphertext-based modulus management.</p> <p>Recall that the moduli chain was derived from an alternating sequence of multiplication and rescaling. To support non-standard operation sequences—such as consecutive multiplications without rescaling —a more general system for managing modulus and scale is needed. In this section, we specify such a system. Note that this is not a new idea; CKKS has never restricted its operation sequence, and what follows is one point in its vast design space.</p> <p>We begin by letting the domain be all ciphertexts and plaintexts encoded under an arbitrary modulus and scale. Write $\mathrm{Enc}_s(m, Q, \Delta)$ for the set of ciphertexts encrypting message $m$ under secret $s$ with modulus $Q$ and scale $\Delta$. The elementary CKKS operations are then defined as follows.</p> <ul> <li> <p><strong>Addition.</strong> For $ct_1 \in \mathrm{Enc}_s(m_1, Q, \Delta)$ and $ct_2 \in \mathrm{Enc}_s(m_2, Q, \Delta)$,</p> \[\mathrm{Add}(ct_1, ct_2) \in \mathrm{Enc}_s(m_1 + m_2,\; Q,\; \Delta).\] </li> <li> <p><strong>Multiplication.</strong> For $ct_1 \in \mathrm{Enc}_s(m_1, Q, \Delta_1)$ and $ct_2 \in \mathrm{Enc}_s(m_2, Q, \Delta_2)$,</p> \[\mathrm{Mul}(ct_1, ct_2) \in \mathrm{Enc}_s(m_1 \cdot m_2,\; Q,\; \Delta_1 \cdot \Delta_2).\] </li> <li> <p><strong>Scalar-Multiplication.</strong> For $ct_1 \in \mathrm{Enc}_s(m_1, Q, \Delta_1)$, $c_2 \in \mathbb{C}$ and $\Delta_2$,</p> \[\mathrm{SMul}(ct_1, c_2, \Delta_2) \in \mathrm{Enc}_s(m_1 \cdot c_2,\; Q,\; \Delta_1 \cdot \Delta_2).\] </li> <li> <p><strong>Rescaling.</strong> For \(ct \in \mathrm{Enc}_s(m, Q, \Delta)\) and a modulus \(Q_{to}\),</p> \[\mathrm{Rescale}(ct, Q_{to}) \in \mathrm{Enc}_s(m, Q_{to}, \frac{\Delta}{Q / Q_{to}}).\] </li> </ul> <p>Note that multiplication can be decomposed into tensor product and relinearization, but we omit the details here. Likewise, scalar-multiplication is a concatenation of encoding $c_2$ at scale $\Delta_2$ and multiplying.</p> <p>The usual, moduli-chain based CKKS behaviour can be emulated using these operations. However, additional primitives exist that enable CKKS circuits beyond what a fixed chain can express.</p> <ul> <li><strong>Setting Scale.</strong> For \(ct \in \mathrm{Enc}_s(m, Q, \Delta)\) and a new scale \(\Delta_{to}\), \(\mathrm{SetScale}(ct, \Delta_{to}) \in \mathrm{Enc}_s(m \cdot \Delta / \Delta_{to},\; Q,\; \Delta_{to}).\) This does not change the polynomial, but merely sets the scale to a different value, with the effect of multiplying the underlying message by a factor $\Delta / \Delta_{to}$.</li> <li><strong>Adjusting.</strong> For \(ct \in \mathrm{Enc}_s(m, Q, \Delta)\), a target modulus \(Q_{to}\) and target scale \(\Delta_{to}\), \(\mathrm{Adjust}(ct, Q_{to}, \Delta_{to}) \in \mathrm{Enc}_s(m,\; Q_{to},\; \Delta_{to}).\) This switches the modulus and scale while preserving the message.</li> </ul> <p>Notably, these two operations together can align the moduli and scales of multiple ciphertexts, so that they become compatible operands for subsequent additions or multiplications.</p> <p>Adjust may seem unfamiliar, but it is essentially a scalar-multiplication that compensates the modulus ratio, followed by a rescaling. Although the operation offers considerable flexibility, it remains precise only when $Q_{to}$ is sufficiently smaller than $Q$.</p> <p>The deconstructed system opens a vast design space for fine-grained optimizations, but in return, the user bears the burden of navigating that space—manually tracking every modulus and scale that a moduli chain would otherwise manage. In practice, the chain-free primitives are most useful alongside the ordinary ones: run standard leveled operations within a chain, and invoke non-leveled operations only to transfer between chains or to execute a specially optimized sub-circuit.</p> <div class="row mt-3"> <div class="col-sm-10 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2605_Seonghak/HPBootCircuit-480.webp 480w,/assets/img/blog/2605_Seonghak/HPBootCircuit-800.webp 800w,/assets/img/blog/2605_Seonghak/HPBootCircuit-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2605_Seonghak/HPBootCircuit.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <div class="caption"> The Computation Circuit of High-precision Bootstrapping. <br/> $Q_{stc}$, $Q_0$, $Q_{lpcts}$, $Q_{em}$, $Q_{hpcts}$, $Q_{bts}$ stand for the moduli of the ciphertexts on respective steps. <br/> $\Delta_{stc}$, $\Delta_0$, $\Delta_{lpcts}$, $\Delta_{em}$, $\Delta_{hpcts}$, $\Delta_{bts}$ are corresponding scale factors. </div> <p>An eager usage of chain-free operations can be found in the high-precision bootstrapping circuit from CKSS25<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>. The circuit uses the standard moduli chain piecewise, inside each module (e.g. SlotToCoeff, CoeffToSlot), and two chains do diverge, then converge back through various chain-free operations.</p> <p>HEaaN2 provides a subset of these chain-free primitives and illustrates them through <a href="https://cryptolab.gitbook.io/heaan2/advanced/cleaning">a <code class="language-plaintext highlighter-rouge">Cleaning</code> example</a>: iterative cubic multiplication whose rescaling amount differs at every iteration. The example compares the execution with the naive chain-full variant, to emphasize the practical gain of managing modulus and scale outside a fixed chain.</p> <p><br/></p> <h2 id="conclusion">Conclusion</h2> <div style="margin-top: 1.5em;"></div> <p>The moduli chain is a fundamental parameter of CKKS, but its traditional construction—a single fixed sequence baked into the program—is unnecessarily rigid for aggressive optimizations. Improvements by HElib, BitPacker, and Grafting have refined the chain construction, but they still operate within the framework of a predetermined sequence. HEaaN2 deconstructs the chain into independent modulus and scale management, and this approach gives a very flexible way to express complex CKKS circuits (diverging chains, variable rescaling, cross-chain transfers) and allow expert users to perform fine-grained optimization of modulus usage.</p> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1"> <p>J. H. Cheon, K. Han, A. Kim, M. Kim, and Y. Song. <a href="https://ia.cr/2018/931">“A Full RNS Variant of Approximate Homomorphic Encryption.”</a> SAC 2018. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:2"> <p>EPFL-LDS, Tune Insight SA. <a href="https://github.com/tuneinsight/lattigo">“A Library for Lattice-based Homomorphic Encryption in Go.”</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:3"> <p>A. Al Badawi, J. Bates, F. Bergamaschi, et al. <a href="https://ia.cr/2022/915">“OpenFHE: Open-Source Fully Homomorphic Encryption Library.”</a> WAHC 2022. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:4"> <p>CryptoLab Inc. <a href="https://heaan.it/">“HEaaN Library.”</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:5"> <p>N. Samardzic and D, Sanchez. <a href="https://dl.acm.org/doi/10.1145/3620665.3640397">“Bitpacker: Enabling High Arithmetic Efficiency in Fully Homomorphic Encryption Accelerators.”</a> ASPLOS 2024. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p> </li> <li id="fn:6"> <p>J. H. Cheon, H. Choe, M. Kang, J. Kim, S. Kim, J. Mono, and T. Noh. <a href="https://ia.cr/2024/1014">“Grafting: Decoupled Scale Factors and Modulus in RNS-CKKS.”</a> ACM CCS 2025. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:6:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p> </li> <li id="fn:7"> <p>S. Halevi and V. Shoup. <a href="https://ia.cr/2020/1481">“Design and Implementation of HElib: A Homomorphic Encryption Library.”</a> IACR ePrint Archive. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:8"> <p>W. Choi, J. Kim and J. Ahn <a href="https://dl.acm.org/doi/abs/10.1145/3760250.3762223">“Cheddar: A Swift Fully Homomorphic Encryption Library Designed for GPU Architectures.”</a> ASPLOS 2026. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:9"> <p>H. Choe, J. Kim, D. Stehlé, E. Suvanto <a href="https://ia.cr/2025/1786">“Leveraging Discrete CKKS to Bootstrap in High Precision.”</a> ACM CCS 2025. <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>]]></content><author><name>Seonghak Kim</name></author><summary type="html"><![CDATA[TL;DR: Every CKKS computation is built upon a sequence of moduli that predetermines the rescaling amount after each multiplication. A new CKKS library, HEaaN2, generalizes the construction of this parameter with a carefully designed scheme and API set. In this article, we break down the traditional construction of the moduli chain to derive the new one.]]></summary></entry><entry><title type="html">RadixCKKS: A General Framework for Integer Computation over CKKS</title><link href="https://ckks.org/blog/2026/radix-ckks/" rel="alternate" type="text/html" title="RadixCKKS: A General Framework for Integer Computation over CKKS"/><published>2026-04-07T04:01:00+00:00</published><updated>2026-04-07T04:01:00+00:00</updated><id>https://ckks.org/blog/2026/radix-ckks</id><content type="html" xml:base="https://ckks.org/blog/2026/radix-ckks/"><![CDATA[<ul> <li>Written by <a href="https://sites.google.com/view/cau-paiclab/home">Gyeongwon Cha</a> (Chung-ang university)</li> <li>Based on <a href="https://eprint.iacr.org/2025/1740">https://ia.cr/2025/1740</a> (Eurocrypt 2026)</li> </ul> <p><em>TL;DR: To handle large integers in FHE, a common approach is to decompose an integer into several small pieces, called digits, and perform computations based on them. One such approach is the radix-based approach, which decomposes a large integer into digits in base $B$ and carries out arithmetic on those digits via polynomial operations. However, after arithmetic operations, the resulting representation is no longer unique, which makes it difficult to directly perform non-arithmetic operations such as comparison or bitwise operations. The process of restoring such a disturbed digit representation back to its unique form is commonly called digit carry, and this step inherently requires non-arithmetic processing. In this post, we introduce a two-step homomorphic digit carry algorithm over CKKS. Our algorithm restores the digit representation to its unique form using $O(\log k)$ bootstrappings.</em></p> <hr/> <p><br/></p> <h2 id="introduction">Introduction</h2> <div style="margin-top: 1.5em;"></div> <p>Recently, the FHE community has shown growing interest in homomorphically evaluating integers of various sizes, ranging from machine-word-sized integers to RSA moduli. Surprisingly, 64-bit arithmetic appears quite simple from the plaintext perspective, much like multiplying two <code class="language-plaintext highlighter-rouge">long long</code> variables. In FHE, however, this is not straightforward, because the noise growth typically scales with the message size. Consequently, most existing approaches either maintain a sufficiently large gap between the message and the noise or decompose the message into smaller pieces and express the overall arithmetic through those pieces.</p> <p>The radix-based approach represents a plaintext integer in base $B$. Once decomposed in this way, an integer can be viewed as a polynomial whose coefficients are its digits, and the original integer is recovered by evaluating that polynomial at $X=B$. As a result, arithmetic on radix-decomposed integers can naturally be carried out through polynomial arithmetic. The key advantage of this approach is that the digit representation still preserves much of the structure of the underlying integer. For example, to compare two integers, one can examine their digits sequentially from the most significant digit to the least significant one. In this sense, radix representations are highly flexible, since they leave room for extending arithmetic to non-arithmetic operations such as comparison.</p> <p>The main challenge is that while polynomial arithmetic preserves the value of the represented integer, it does not preserve the uniqueness of its digit representation. After arithmetic operations, digit values may exceed their normal range, which in turn causes additional noise growth. More importantly, once the digit representation is no longer unique, non-arithmetic operations such as comparison can no longer be performed directly. Therefore, radix-based approaches must restore the non-unique digit representation to its unique form through digit carry.</p> <p>This carry procedure, however, has remained a major bottleneck, since it inherently requires remainder computation and must process the digits sequentially. Although [1, 2] achieved homomorphic digit carry using discrete CKKS, the number of required bootstrappings remained linear in the plaintext bit-length $k$. This naturally leads to the following question: can this linear cost be reduced further?</p> <p><strong>Notation.</strong> Throughout this post, we adopt the following notation for clarity. We denote homomorphic arithmetic operations between ciphertexts simply as $+,-,\times$. In addition, we denote the rotation operation by $\rho_r$, which rotates a ciphertext to the left by $r$ positions. If $r$ is negative, this corresponds to a right rotation.</p> <p><br/></p> <h2 id="key-idea-reducing-carry-in-base-b-to-carry-in-base-2">Key idea: reducing carry in base $B$ to carry in base $2$.</h2> <div style="margin-top: 1.5em;"></div> <p>Our key idea for reducing the complexity of the carry algorithm is the following:</p> <blockquote> <p>If every digit is smaller than $2B-1$, then carry propagation on a radix-$B$ digit vector behaves exactly like carry propagation in binary.</p> </blockquote> <p>To see why, recall that in base-$B$ carry, if the $i$-th digit $z_i$ is at least $B$, we propagate \(y_i = Q_B(z_i)\) to the next digit, where $Q_B(z_i)$ denotes the quotient of \(z_i\) divided by $B$. The difficulty is that once \(y_i\) is added to the $(i+1)$-th digit, the carry propagated to the $(i+2)$-th digit becomes $ Q_B(z_{i+1} + y_i), $ so the carry process becomes inherently sequential.</p> <p>However, if every digit is smaller than $2B-1$, then a much simpler structure emerges. In this case, $Q_B(z_i)$ can be at most $1$. Consequently, $z_{i+1}+y_i$ is also at most $2B-1$, and therefore the carry propagated to the next digit is again at most $1$. Intuitively, this makes radix-$B$ carry behave like binary carry, where each position propagates only a single carry bit.</p> <p>At this point, our problem boils down to the following two tasks:</p> <ol> <li>Homomorphically reducing each digit so that it is smaller than \(2B-1\).</li> <li>Designing a carry algorithm for digit vectors whose entries are all smaller than \(2B-1\).</li> </ol> <p><br/></p> <h2 id="2-step-carry-algorithm">2-step carry algorithm</h2> <div style="margin-top: 1.5em;"></div> <p><strong>Polynomial arithmetic.</strong> Since CKKS supports SIMD-style parallel operations across slots, a different method is needed to realize polynomial arithmetic. We simply use the DFT to evaluate polynomials in Fourier form, and then apply the iDFT to recover the result in coefficient form.</p> <p>When the plaintext modulus is \(\mathbb{Z}_{B^k}\), a single integer is packed into \(2k\) slots: the first \(k\) slots contain its radix-\(B\) digits, while the remaining \(k\) slots are padded with zeros. This zero-padding is introduced to avoid cyclic shifts. Accordingly, after the iDFT step, the lower \(k\) slots are masked out and reset to zero.</p> <p>Let the number of CKKS slots be \(N/2\), and assume that \(2k \mid (N/2)\). Then a single ciphertext can store a total of \(N/4k\) integers, where the packing is obtained by simply concatenating digit vectors of length \(2k\).</p> <p><strong>Modular reduction in CKKS.</strong> Modular reduction, as discussed in [3, 4], is a key operation in our construction. At a high level, this algorithm first transforms a slot-encoded CKKS ciphertext into coefficient encoding, reduces it modulo the base modulus \(q_0\), and then applies bootstrapping to recover a slot-encoded CKKS ciphertext.</p> <p>The interesting point is that if the ciphertext scaling at level \(q_0\) is adjusted to \(q_0/B\), then the resulting ciphertext has exactly the same form as a coefficient-encoded BFV ciphertext with plaintext modulus \(B\). Consequently, the message naturally undergoes the operation \([\cdot]_B\), while requiring only a single bootstrapping. For convenience, we denote the modular reduction algorithm with respect to \(B\) by \(\mathsf{Mod}_B\).</p> <p><strong>Functional bootstrapping in CKKS.</strong> Functional bootstrapping [3, 5] in discrete CKKS enables the evaluation of a look-up table (LUT) function, denoted as $\mathsf{LUT}$, simultaneously with the bootstrapping of a ciphertext.</p> <p>We denote the functional bootstrapping operation in CKKS by $\mathsf{CKKS.FBT}(\cdot,~\mathsf{LUT} = \cdot)$, where the first argument is the ciphertext to be bootstrapped and the second argument specifies the LUT function.</p> <p><br/></p> <h3 id="homomorphic-digit-reduction">Homomorphic digit reduction</h3> <div style="margin-top: 1.5em;"></div> <p>After arithmetic operations, we repeatedly invoke \(\mathsf{Mod}_B\) to reduce the digit size. Let the input digit vector be</p> \[\mathsf{ct}\longleftrightarrow \vec z = (z_1,z_2,\dots,z_k)\in\mathbb{Z}^k.\] <p>Here, we omit the last \(k\) slots for simplicity.</p> <p>Applying \(\mathsf{Mod}_B\) yields</p> \[\mathsf{ct}_{\mathsf{mod}}\longleftrightarrow \vec z_{\mathsf{mod}} = ([z_1]_B,[z_2]_B,\dots,[z_k]_B)\in\mathbb{Z}^k.\] <p>Moreover, since \(Q_B = \frac{\mathsf{id}-\mathsf{Mod}_B}{B},\) we can additionally obtain, at the cost of one extra multiplication level,</p> \[\mathsf{ct}_{Q}\longleftrightarrow \vec z_{Q} = (Q_B(z_1),Q_B(z_2),\dots,Q_B(z_k))\in\mathbb{Z}^k.\] <p>Now, by rotating \(\mathsf{ct}_{Q}\) one position to the right and adding it to \(\mathsf{ct}_{\mathsf{mod}}\), we obtain another digit vector representing the same integer. If \(\|\vec z\|_{\infty}=U\), then the infinity norm of the resulting digit vector is bounded by \(B + Q_B(U) = B + O(U/B).\)</p> <p>We call this entire procedure <strong>LazyCarry</strong>.</p> <p>For the unique digit representations of two integers, no digit reduction is needed after addition. In contrast, after multiplication, invoking LazyCarry \(O(\log k)\) times suffices to reduce all digit values below \(2B-1\).</p> <p>In practice, for \(B=16\), multiplying two integers in unique digit representation requires only 2 to 4 bootstrappings to reduce all digit values below \(2B-1\), for bit-lengths ranging from 16 bits to 2048 bits. If a larger base \(B\) is chosen, this number can be reduced even further.</p> <p>Repeated iterations of LazyCarry reduce the digit size rapidly, making it possible to continue arithmetic operations once the digits become sufficiently small. This is in the same spirit as [2].</p> <p><br/></p> <h3 id="homomorphic-digit-carry">Homomorphic digit carry</h3> <div style="margin-top: 1.5em;"></div> <p>We now take a closer look at the carry behavior of digit vectors whose entries are all smaller than \(2B-1\). The figure illustrates the case \(B=16\) and \(k=4\): the top shows carry propagation in base \(B\), while the bottom shows the corresponding carry behavior after reduction to binary.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2604_Gyeongwon/c-480.webp 480w,/assets/img/blog/2604_Gyeongwon/c-800.webp 800w,/assets/img/blog/2604_Gyeongwon/c-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2604_Gyeongwon/c.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <p>Under this constraint, whenever the \(i\)-th digit propagates a value to the next digit, the \(i\)-th digit decreases by \(B\) and the \((i+1)\)-th digit increases by \(1\). This is exactly the same behavior observed in the binary-reduced representation.</p> <p>Therefore, if we can obtain a ciphertext encrypting a vector $\vec c$ whose entries indicate whether each digit propagates a carry to the next position—encoded as $1$ for true and $0$ for false—then the unique carried representation of the original digit vector $\vec z$ can be computed as follows :</p> \[z_{\mathsf{carried}} = \vec z - B\cdot\vec c + \rho_{-1}(\vec c) \tag{1}\] <p>For example, in the figure, $\vec c = (1,1,1,0)$.</p> <p>Reduction from a radix-\(B\) representation to a binary representation is performed via a LUT operation. The binary reduction is determined by the range of each digit; more precisely, we define</p> \[\phi(x)= \begin{cases} 0, &amp; \text{if } 0 \le x &lt; B-1,\\ 1, &amp; \text{if } x = B-1,\\ 2, &amp; \text{if } B \le x &lt; 2B-1. \end{cases}\] <p>It remains to design a function that determines, in the binary-reduced representation, whether a given digit propagates a value to the next position.</p> <p>We begin with the two-digit case \(z_1,z_2\), and design a function that determines whether \(z_2\) propagates a carry to the next digit.</p> <p>If \(z_2=0\), then even if \(z_1=2\) and propagates a carry into \(z_2\), the resulting value is still smaller than \(2\), and hence the output is \(0\). If \(z_2=2\), then for the same reason the output is always \(1\), regardless of the value of \(z_1\). The only nontrivial case is \(z_2=1\). In that case, the output depends on \(z_1\): if \(z_1=2\), the output should be \(1\); if \(z_1=0\), it should be \(0\); and if \(z_1=1\), the output depends on the previous digit. In the two-digit example, this case evaluates to \(0\).</p> <p>Motivated by this observation, we design a bivariate function \(f(x,y)\) that returns \(0\) if carry propagation is false, \(2\) if it is true, and \(1\) if the result is not yet determined:</p> \[f:\{0,1,2\}^2 \to \{0,1,2\}, \qquad (x,y)\mapsto \begin{cases} 0, &amp; \text{if } y=0,\\ x, &amp; \text{if } y=1,\\ 2, &amp; \text{if } y=2. \end{cases}\] <p>We then extend this to a \(k\)-digit vector \((z_1,\dots,z_k)\). The question of whether \(z_k\) propagates a carry to the next position (the case \(i&lt;k\) will be discussed shortly) can be expressed recursively using rotations via the function \(f_k\):</p> \[f_2 = f, \quad f_k(z_1,\dots,z_k) = f\bigl(z_1, f_{k-1}(z_2,\dots,z_k)\bigr).\] <p>At first glance, evaluating \(f_k\) appears to require \(k\) sequential calls to \(f\). However, observing that the structure of \(f\) is determined by its second argument, one can show that</p> \[f_k(z_1,\dots,z_k) = f\bigl(f_{k/2}(z_1,\dots,z_{k/2}),\, f_{k/2}(z_{k/2+1},\dots,z_k)\bigr).\] <p>Since the digits \(z_1,\dots,z_k\) are stored across multiple slots, this allows us to evaluate \(f_k\) using only \(\log k\) invocations of \(f\).</p> <p>For the \(i\)-th digit with \(i\le k\), the first \(k-i\) slots correspond to zero-padded slots from the previous integer. Hence it suffices to evaluate</p> \[f_k(0,\dots,0,m_1,\dots,m_i),\] <p>since zeros do not affect the carry behavior at all. The figure below illustrates the evaluation of $f_k$ for $k = 4$.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2604_Gyeongwon/lc2c-480.webp 480w,/assets/img/blog/2604_Gyeongwon/lc2c-800.webp 800w,/assets/img/blog/2604_Gyeongwon/lc2c-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2604_Gyeongwon/lc2c.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <p>After evaluating \(f_k\), the positions that propagate a carry contain the value \(2\), while the remaining positions contain \(0\) or \(1\). We then evaluate a mapping function \(\tau\) that converts these values into \(1\) and \(0\), respectively. Finally, by applying \(\tau\) and evaluating Equation (1), the carry procedure is completed. We call this overall procedure <strong>LazyCarry-to-Carry</strong>.</p> <p><br/></p> <h3 id="whole-algorithm-description">Whole algorithm description</h3> <div style="margin-top: 1.5em;"></div> <p>The overall procedure of our two-step homomorphic carry algorithm is as follows.<br/> Let $\mathsf{ct}$ be a ciphertext that requires digit carry propagation.<br/> We assume that an upper bound on the corresponding digit vector of $\mathsf{ct}$ can be estimated.<br/> Given this bound, let $t$ denote the number of times the <strong>LazyCarry</strong> algorithm is applied to ensure that each digit becomes smaller than $2B - 1$.</p> <p>For example, consider a 64-bit multiplication with parameters $(B,k) = (2^4, 16)$.<br/> When multiplying two unique digit representations, the resulting upper bound is given by \(k(B-1)^2 = 3600.\) In this case, two iterations of <strong>LazyCarry</strong> are sufficient.</p> <ol> <li> <p>Repeat <strong>LazyCarry</strong> $t$ times : \(\mathsf{ct}_{\mathsf{lazycarry}} \gets \mathsf{LazyCarry}^{(t)}(\mathsf{ct})\)</p> </li> <li> <p>Evaluate the look-up table $\phi$ via functional bootstrapping : \(\mathsf{ct}_{\mathsf{bin}} \gets \mathsf{CKKS.FBT}(\mathsf{ct}_{\mathsf{lazycarry}}, \mathsf{LUT}=\phi)\)</p> </li> <li> <p>Evaluate $f_k$ : For $i=0$ to $\log(k)$ do {\(\mathsf{ct}_{\mathsf{bin}} \gets \text{evaluate } f(\rho_{-2^i}(\mathsf{ct}_{\mathsf{bin}}), \mathsf{ct}_{\mathsf{bin}})\)}</p> </li> <li> <p>Evaluate $\tau$ : \(\mathsf{ct}' \gets \tau(\mathsf{ct}_{\mathsf{bin}})\)</p> </li> <li> <p>Return \(\mathsf{ct}_{\mathsf{out}} \gets \mathsf{ct}_{\mathsf{lazycarry}} - B\cdot \mathsf{ct}' + \rho_{-1}(\mathsf{ct}')\)</p> </li> </ol> <p>Our algorithm performs (t) bootstrappings in Step 1, one bootstrapping in Step 2, and additional bootstrappings in Step 3. The extra bootstrappings in Step 3 arise in two cases: (1) when the remaining multiplicative depth is insufficient to evaluate the function (f_k), or (2) when bootstrapping is required for noise management. Despite these additional calls, the total number of bootstrappings remains modest in practice. The exact number of bootstrapping calls is reported in the Conclusion section.</p> <p><br/></p> <h2 id="other-integer-operations">Other integer operations</h2> <div style="margin-top: 1.5em;"></div> <p>Because the digit vector can be restored to its unique representation, one can further design non-arithmetic operations such as comparison and conditional subtraction. Moreover, by using the bit-extraction technique of [5], each digit can be decomposed into bits, thereby enabling bitwise operations as well. These non-arithmetic operations can in turn be used to homomorphically implement reduction techniques such as folding and Montgomery reduction, thereby supporting computation not only over prime-power moduli but also over more general moduli. Although we would have liked to discuss these non-arithmetic operations in this post as well, we refer the interested reader to the paper for further details.</p> <p><br/></p> <h2 id="conclusion">Conclusion</h2> <div style="margin-top: 1.5em;"></div> <p>All experiments were conducted on an AMD Ryzen 9 7900X 12-Core Processor with 125 GB RAM, running Ubuntu 20.04, using a single thread. We used the Lattigo library, a representative RNS-CKKS library, for our implementation.</p> <p>We evaluated multiplication operations for bit-lengths ranging from 16 bits to 2048 bits. In the third column, (a+b) indicates that (a) bootstrappings were used in LazyCarry and (b) bootstrappings were used in LazyCarry-to-Carry.</p> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2604_Gyeongwon/2-480.webp 480w,/assets/img/blog/2604_Gyeongwon/2-800.webp 800w,/assets/img/blog/2604_Gyeongwon/2-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2604_Gyeongwon/2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <p>To be completely honest, a single multiplication still took a considerable amount of time. Even though our method processes many integers at once, the total runtime is by no means small.</p> <p>At first glance, BFV may appear more attractive, as homomorphic multiplication can be performed simply by invoking the scheme’s native multiplication primitive. However, the main bottleneck of BFV lies in bootstrapping, whose runtime may exceed that of our entire multiplication procedure depending on the plaintext modulus. Therefore, a fair comparison should not be based solely on raw multiplication speed, but rather should account for the cost of BFV bootstrapping.</p> <p>For instance, according to Table 4 in [6], BFV bootstrapping with a plaintext modulus of approximately 234 bits at $N = 2^{17}$ takes about 392 seconds. In contrast, our proposed framework enables subsequent computations after a single additional CKKS bootstrapping (approximately 12 seconds).</p> <p>From an amortized perspective, a more careful comparison is also needed, since the number of available BFV slots depends on the plaintext modulus.</p> <p>For DM/CGGI, by contrast, our method consistently shows better amortized performance. Interestingly, at the time of writing, our single-thread benchmark on the same machine showed that TFHE-rs required 77.5 seconds for 128-bit multiplication, whereas our method took 57.4 seconds. This indicates that from 128 bits onward, our method outperforms TFHE-rs not only in amortized throughput but also in latency.</p> <p>Finally, we conclude by introducing a paper for readers interested in related work [7]. While our work focuses on algorithms for performing carry homomorphically, the work introduced below is particularly impressive in that it avoids performing carry altogether.</p> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <p>[1] Kim, J. “Efficient Homomorphic Integer Computer from CKKS.” TCHES 2025.</p> <p>[2] Kim, J. “Faster Homomorphic Integer Computer.” Cryptology ePrint Archive, Paper 2025/1440, 2025.</p> <p>[3] Alexandru, A., Kim, A., and Polyakov, Y. “General Functional Bootstrapping using CKKS.” CRYPTO 2025.</p> <p>[4] Kim, J. and Noh, T. “Modular Reduction in CKKS.” CIC 2025.</p> <p>[5] Bae, Y., Kim, J., Stehlé, D., and Suvanto, E. “Bootstrapping Small Integers with CKKS.” ASIACRYPT 2024.</p> <p>[6] Kim, J., Seo, J., and Song, Y. “Simpler and Faster BFV Bootstrapping for Arbitrary Plaintext Modulus from CKKS.” CCS 2024.</p> <p>[7] Brakerski, Z., Friedman, O., Golan, D., Gurny, A., Mutzari, D., and Sheinfeld, O. “REFHE: Fully Homomorphic ALU.” EUROCRYPT 2026.</p>]]></content><author><name>Gyeongwon Cha</name></author><summary type="html"><![CDATA[TL;DR: To handle large integers in FHE, a common approach is to decompose an integer into several small pieces, called digits, and perform computations based on them. One such approach is the radix-based approach, which decomposes a large integer into digits in base $B$ and carries out arithmetic on those digits via polynomial operations. However, after arithmetic operations, the resulting representation is no longer unique, which makes it difficult to directly perform non-arithmetic operations such as comparison or bitwise operations. The process of restoring such a disturbed digit representation back to its unique form is commonly called digit carry, and this step inherently requires non-arithmetic processing. In this post, we introduce a two-step homomorphic digit carry algorithm over CKKS. Our algorithm restores the digit representation to its unique form using $O(\log k)$ bootstrappings.]]></summary></entry><entry><title type="html">Verifiable Computation for CKKS</title><link href="https://ckks.org/blog/2026/verifiable-ckks/" rel="alternate" type="text/html" title="Verifiable Computation for CKKS"/><published>2026-03-03T04:01:00+00:00</published><updated>2026-03-03T04:01:00+00:00</updated><id>https://ckks.org/blog/2026/verifiable-ckks</id><content type="html" xml:base="https://ckks.org/blog/2026/verifiable-ckks/"><![CDATA[<style>.math-wide,.math-narrow{display:none}@media(min-width:661px){.math-wide{display:block}}@media(max-width:660px){.math-narrow{display:block}}</style> <ul> <li>Written by Ignacio Cascudo (IMDEA Software Institute), Anamaria Costache (École Polytechnique), Daniele Cozzo (IMDEA Software Institute), Dario Fiore (IMDEA Software Institute), Antonio Guimarães (IMDEA Software Institute), Eduardo Soria-Vazquez (Technology Innovation Institute)</li> <li>Based on <a href="https://ia.cr/2025/286">https://ia.cr/2025/286</a> (Crypto 2025)</li> </ul> <p><em>TL;DR: Homomorphic Encryption (HE) enables computing over encrypted data but, by itself, provides no guarantees that the computation was honestly executed. One can build “Verifiable HE” (vHE) using SNARKs, but efficiently combining HE and SNARKs in practice is a major challenge. This work introduces a blueprint for building verifiable HE schemes and its efficient instantiation for CKKS. Our first step is to introduce a “proof-friendly” version of CKKS, which is more amenable to proof systems, while being only slightly slower than typical RNS CKKS implementations. We then show how the problem of proving correctness of computations for such proof-friendly HE schemes can be reduced to just two sets of arithmetic relations (containing equalities and inequalities). We show that if these are satisfied, it implies the correct execution of the HE evaluation. We design Polynomial Interactive Oracle Proofs (PIOPs) for efficiently proving these relations, and we show how they can be instantiated using standard proof components. Our final construction demonstrates the feasibility of building SNARKs for proving computation of full-fledged HE schemes, opening the path for building practical verifiable HE schemes.</em></p> <hr/> <p><br/></p> <h2 id="context">Context</h2> <div style="margin-top: 1.5em;"></div> <p><strong>Homomorphic Encryption.</strong> Homomorphic Encryption (HE) is a type of encryption that allows to compute on encrypted data. This allows, amongst others, to outsource computations without compromising on privacy. It is a very powerful primitive, which enables many applications such as secure outsourced medical analysis, private set intersection, and so on. In recent years, Machine Learning (ML) applications have become ubiquitous. While this has opened up the door to many novel and powerful applications, this has also introduced privacy concerns. Indeed, ML applications inherently and very heavily rely on data - but this data can turn out to be sensitive, and users may rightly be weary of sharing their private data with companies. To the rescue comes HE, in the form of Privacy-Preserving Machine Learning (PPML)! With HE, we can now encrypt the data, and evaluate an ML model directly on encrypted data. The result can now be recovered in plaintext, but only by whoever holds the secret key.</p> <p><strong>Verifiable Homomorphic Encryption.</strong> A significant limitation of homomorphic encryption (HE) is that it works in the trusted model, where the entity that computes over the ciphertexts is assumed to behave honestly. In particular, we must assume that the computing party (from here on, we refer to this party as the <em>server</em>) performs exactly the operation it says it does, which in practice is a rather strong assumption. In reality, without an integrity mechanism on top of the computation, the server could follow any malicious strategy: it could for example bias the result, or perform a different computation entirely. Even more concerning, without integrity mechanisms in place, the server could take advantage of the malleability of HE ciphertexts and mount a key recovery attack. Therefore, a lack of integrity can potentially lead to serious privacy leaks!</p> <h3 id="snarks">SNARKs</h3> <p><em>Succinct proof systems</em> (commonly referred to as SNARKs) are an important cryptographic primitive that allow to add integrity to computations. Informally, these are cryptographic proofs that allow a prover to convince a verifier that a statement is true. This is a very rich research field in and of itself, so for now, we can think of SNARKs as tools that allow us to prove a statement of the following kind: “Given a public circuit $C$ and an output $y$, I know an input $x$ such that $y=C(x)$.”</p> <p>What makes SNARKs useful in practice are the following properties:</p> <ul> <li><strong>Correctness</strong>: if the statement is true, then the verifier will accept the proof;</li> <li><strong>Soundness</strong>: a cheating prover is not able to convince the verifier about a false statement, except with negligible probability;</li> <li><strong>Succinctness</strong>: the proof is very short and verifying it should be fast, e.g. sublinear in the size of the computation.</li> </ul> <h3 id="verifiable-he">Verifiable HE</h3> <p>It is natural then to combine SNARKs and HE to achieve both privacy and integrity in outsourcing computations. This approach, also known as <em>Verifiable Homomorphic Encryption (vHE)</em> does work in theory, because SNARKs can prove NP statements. Unfortunately, in practice this approach has several limitations for the prover efficiency.</p> <p>1) HE ciphertexts are typically pairs of elements of the polynomial ring $R_q = \mathbb{Z}_q[X]/{(X^{N}+1)}$, whereas SNARKs typically work best on computations over large finite fields;</p> <p>2) Virtually all HE schemes require so-called <em>ciphertexts maintenance</em> operations (such as rescaling). These operations entail non-algebraic operations, for instance real division and rounding. In contrast, SNARKs shine at proving algebraic statements. Even worse, operations like rescaling cause the underlying algebraic structure to change during the computation, something that cannot be easily processed by traditional SNARKs.</p> <p>Naively, general-purpose SNARKs can prove ciphertext arithmetic and maintenance operations by emulating them. This is prohibitively expensive for the prover, as shown by a recent survey by Knabenhans, Viand, and Hithnawi [4].</p> <p>This motivates the research line of designing SNARKs <em>specifically tailored to HE operations</em>.</p> <p>In this blogpost, we introduce our recent result [1] that represents the state-of-the-art in this direction. Our work is a blueprint for constructing vHE schemes that can scale with large computations and have practical proving times.</p> <p><br/></p> <h2 id="the-blueprint">The blueprint</h2> <div style="margin-top: 1.5em;"></div> <p>The core contributions of our work are a blueprint for constructing practical vHE schemes in a modular way and its instantiation to the CKKS scheme. Such a framework consists of several building blocks that are carefully designed to be combined together to yield the final vHE scheme.</p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2603_DanieleCozzo/image-480.webp 480w,/assets/img/blog/2603_DanieleCozzo/image-800.webp 800w,/assets/img/blog/2603_DanieleCozzo/image-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2603_DanieleCozzo/image.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 1: The Blueprint. </figcaption> </figure> <p>The first step in our blueprint is to take an HE scheme, and modify it in such a way that it becomes more “SNARK-friendly”. The aim is to make arithmetization easier – that is to say, we aim to make the operations of the HE scheme easily expressed in a language that can then be processed by the SNARK with small overhead. We call such a scheme “proof-friendly”. In practice, such a scheme must work over a <strong>single</strong> ring with <strong>specific algebraic structures</strong> that are necessary for the SNARKs to achieve soundness. In this work, we instantiate our blueprint using the CKKS scheme, introducing a proof-friendly variant of CKKS.</p> <p>Once we have a proof-friendly HE scheme, a computation over its ciphertexts is processed according to the following steps:</p> <ul> <li><strong>Translation</strong>: The arithmetization process translates the computation into an arithmetic circuit satisfiability relation over the ring, and a finite number of range check relations over the ring. This is the language our SNARK “speaks”.</li> <li><strong>Proofs</strong>: These relations will be proved by specialized proof systems, in the form of <em>Polynomial Interactive Oracle Proofs (PIOPs)</em>. PIOPs are proof systems where the interaction between the prover and verifier happens via polynomials: the statements are encoded by long polynomials and the proof consists in evaluating these over points chosen uniformly at random by the verifier. Importantly, PIOPs differ from SNARKs in that they are interactive, are not succinct (meaning that the proof is typically long, of the size of the statement) and soundness holds information-theoretically. A popular and practical way of realizing SNARKs, which we follow, consists in first designing PIOPs, and then compiling them into full-fledged SNARKs through cryptographic tools (we will talk about this in a moment). In our case, we need two PIOPs: one that is specialized to prove arithmetic statements over rings, and one specialized to prove range checks over rings.</li> <li><strong>Cryptographic compiler</strong>: Finally, in order to compile these information-theoretic protocols into a SNARK we need one more tool, called a <em>Polynomial commitment</em> (PC) scheme. This is a primitive that allows the prover to commit to a large polynomial resulting in a short string, and to later prove evaluations of the committed polynomial. Polynomial commitment schemes are used to compile PIOPs into SNARKs: the long polynomials resulting from the prover PIOP are replaced by the short commitments.</li> </ul> <p><br/></p> <h2 id="proof-friendly-ckks">Proof-friendly CKKS</h2> <div style="margin-top: 1.5em;"></div> <p>We start by showing how we can build a proof-friendly version of CKKS which presents the characteristics described above while maintaining near state-of-the-art performance levels.</p> <h3 id="setting-up-the-ring">Setting up the ring</h3> <p>CKKS works over polynomial rings of the form $R_q := \mathbb{Z}_q[X]/(X^N+1)$. The first step is to set up the ring $R_q$ such that it will be easier to construct a SNARK over it. Remember, one of the properties a SNARK must satisfy is soundness: this says that if the statement is false, then the prover is only able to convince the verifier with negligible probability.</p> <p>Naively, a way to achieve this is to just repeat the proof system as many times as needed, to amplify soundness error. However, this makes the proof much larger, and one might need many repetitions to achieve negligible soundness error. The alternative is to choose the ring $R_q$ in an appropriate way, so that no repetitions are needed.</p> <p>The second requirement is, of course, efficiency. We want the prover to be fast while performing the CKKS operations. For that reason, as is typical in HE, we invoke the Chinese Remainder Theorem (CRT). Let $N$ be a power of two and $q:= \prod_{i=0}^L p_i$ for some integer $L$, such that each $p_i$ is a prime of the form $2aN/d + 1$ for some odd integer $a$ and suitable power-of-two value $d$. Then, for each $p_i$, the cyclotomic polynomial $X^N+1$ splits in \(\mathbb{Z}_{p_i}[X]\) into $k = N/d$ irreducible factors \(X^N + 1 = \prod_{j=0}^{k-1}(X^d - \zeta^{(2j+1)}),\) where \(\zeta\in\mathbb{Z}_{p_i}\) is a \(2N/d\)-th primitive root of unity. By the CRT, the ring \(R_{q}\) splits as</p> \[R_{q} = \prod_{i=0}^L R_{p_i} = \prod_{i=0}^L \left( \prod_{j=0}^{k-1} R_{i,j} \right), \tag{1}\] <p>with each \(R_{i,j} = \mathbb{Z}_{p_i}[X]/(X^d - \zeta^{(2j+1)})\) being a field of size $p_i^d$.</p> <p>The value $d$ will be chosen appropriately so as to guarantee soundness security. For brevity, we omit a detailed discussion on choosing the value $d$, but note that typical choices for $d$ would be $d=2$, or $d=4$. We refer to [1] for details on efficient arithmetic over the rings these choices entail.</p> <h3 id="algorithms">Algorithms</h3> <p>Now that we have set up the underlying ring $R_q$, we are ready to describe the main algorithms for our proof-friendly CKKS. In more detail, we present a modified version of the CKKS scheme. At a high level, these modifications aim to get around of the hurdles which makes CKKS incompatible with SNARKs, namely the fact that the ring changes during the computation. In our version of CKKS, the algorithms will always work on the same underlying ring $R_q$. The trick that makes this possible is a modification of the CRT map underlying the RNS version of CKKS, which we show now.</p> <p>For all $0 \leq j \leq L$ define $q_j = \prod_{i=0}^{j} p_{i}$, and in particular $q_L = q$. Then we define \(R := \mathbb{Z}[X]/(X^N+1)\) and \(R_{q_j} := \mathbb{Z}_{q_j}[X]/(X^N+1)\) for all $0 \leq j \leq L.$ Let $\omega_{j} = (p_0, p_1, \dots, p_{j})$ be a CRT base for $q_j$ and given $a \in R_{q}$, we define the inverse CRT map as follows:</p> <div class="math-wide"> $$CRT^{-1}_{\omega_{j}}(a) := \left( \left[ a\right]_{p_0}, \left[ a \right]_{p_1}, \dots, \left[ a \right]_{p_{j}} \right) \in R_{q}^{j+1}.$$ </div> <div class="math-narrow"> $$\begin{aligned} &amp;CRT^{-1}_{\omega_{j}}(a) := \\ &amp;~~ \left( \left[ a\right]_{p_0}, \left[ a \right]_{p_1}, \dots, \left[ a \right]_{p_{j}} \right) \in R_{q}^{j+1}. \end{aligned}$$ </div> <p>Note that, in general, the inverse CRT for $\omega_j$ would be defined as a map $R_{q} \rightarrow R_{p_0} \times \ldots \times R_{p_j}$. We slightly modify this, by adding an embedding from $R_{p_0} \times \ldots \times R_{p_{j}}$ to $R_{q}^{j+1}$. More precisely, our mapping looks like:</p> <div class="math-wide"> $$\begin{aligned} R_{q_0} ~~ &amp;\longrightarrow \qquad R_{p_0} \times \ldots \times R_{p_j} \qquad \longrightarrow \quad R_{q}^{j+1} \\ a ~~~ &amp;\mapsto ~ \mathbf{a} := \left( \left[ a\right]_{p_0}, \left[ a\right]_{p_1}, \dots,\left[ a\right]_{p_{j}}\right) \rightarrow \left(\mathbf{a}'_0, \ldots, \mathbf{a}'_j \right)\end{aligned}$$ </div> <div class="math-narrow"> $$\begin{aligned} R_{q_0} &amp;\longrightarrow \prod_{i=0}^{j} R_{p_i} \longrightarrow R_{q}^{j+1} \\ a &amp;\mapsto \mathbf{a} := ([a]_{p_i})_{i \in [0,j]} \\ &amp;~~~~~~~~~~~\rightarrow (\mathbf{a}'_0, \ldots, \mathbf{a}'_j) \end{aligned}$$ </div> <p>where, using RNS representation,</p> <div class="math-wide"> $$\mathbf{a}'_i = \left( \left[ \left[ a\right]_{p_i} \right]_{p_0}, \left[ \left[ a\right]_{p_i}\right]_{p_1}, \dots,\left[ \left[ a\right]_{p_i} \right]_{p_{j}}, \underbrace{0 \ldots, 0}_{L-j ~\text{times}}\right) \in R_q.$$ </div> <div class="math-narrow"> $$\begin{aligned} \mathbf{a}'_i = \bigg( &amp;\left[ \left[ a\right]_{p_i} \right]_{p_0}, \left[ \left[ a\right]_{p_i}\right]_{p_1}, \dots,\\ &amp; ~~~~~ \left[ \left[ a\right]_{p_i} \right]_{p_{j}}, \underbrace{0 \ldots, 0}_{L-j ~\text{times}} \bigg) \in R_q. \end{aligned}$$ </div> <p>Letting $Q_{i} = q_L/p_i$ and \(\hat{Q}_{i} = \left[(q_L/p_i)^{-1}\right]_{p_i}\) be the usual constants for CRT recomposition, we define</p> <div class="math-wide"> $$z_j := \sum_{i=0}^{j} Q_i\hat{Q}_i = CRT(\underbrace{1, \dots, j}_{j+1~\text{times}}, 0, \dots, 0) \in R_{q}.$$ </div> <div class="math-narrow"> $$\begin{aligned} z_j :=&amp; \sum_{i=0}^{j} Q_i\hat{Q}_i \\ =&amp; ~CRT(\underbrace{1, \dots, j}_{j+1~\text{times}}, 0, \dots, 0) \in R_{q}. \end{aligned}$$ </div> <p>For each $0 \leq j \leq L$, the CRT recomposition vector for a given $a \in R_{q}$ is:</p> <div class="math-wide"> $$ PW_{\omega_{j}}(a) := \left(\left[ aQ_{0}\hat{Q}_{0} \right]_{q}, \left[ aQ_{1}\hat{Q}_{1} \right]_{q}, \dots, \left[ aQ_{j}\hat{Q}_{j} \right]_{q} \right) \in R_q^{j+1}.$$ </div> <div class="math-narrow"> $$\begin{aligned} PW_{\omega_{j}}(a) := \bigg( &amp;\left[ aQ_{0}\hat{Q}_{0} \right]_{q}, \left[ aQ_{1}\hat{Q}_{1} \right]_{q},\\ &amp;\dots, \left[ aQ_{j}\hat{Q}_{j} \right]_{q} \bigg) \in R_q^{j+1}. \end{aligned}$$ </div> <p>For any $a, b \in R_{q}$, for any $j$, the following holds</p> <div class="math-wide"> $$ a \cdot b \cdot z_j \equiv \langle PW_{\omega_{j}}(a), CRT^{-1}_{\omega_{j}}(b) \rangle \cdot z_j \pmod{q_0}. $$ </div> <div class="math-narrow"> $$\begin{aligned} a \cdot b \cdot z_j \equiv \langle PW_{\omega_{j}}(a),~&amp; CRT^{-1}_{\omega_{j}}(b) \rangle \cdot z_j \\ &amp;~~~\pmod{q_0}. \end{aligned}$$ </div> <p>This follows from a direct application of the CRT in the ideal defined by $z_j$.</p> <p>We are ready to describe the algorithms defining our proof-friendly version of CKKS. Let $q_{0} &lt; q_{1} &lt; \dots &lt; q_{D-1}$ be a chain of moduli for a circuit of depth $D$ and $(\omega_{0}, \omega_{1}, \dots, \omega_{D-1})$ their respective CRT bases, $\chi_\text{key}$ be the secret key distribution over $R$, and $\chi_\text{err}, \chi_\text{enc}$ be discrete Gaussian distributions over $R_{q}$. Below we present our version of the CKKS scheme. For brevity, we only present the algorithms which are different from “regular CKKS”.</p> <ul> <li>$\mathsf{EvalKeyGen}:$ Let the secret key be $s$, and assume we are performing a key switching from $s^2$ to $s$. Sample $a_i \leftarrow R_{q}$, sample $e_i \leftarrow \chi_\text{err}$ for $i= 0, \dots, L$. Compute $b_i = - a_i \cdot s + e_i + PW_{\omega_{L}}(s^2)[i]$ $\bmod q$. For each level $l \in { 0, \dots, D - 1}$, compute</li> </ul> <div class="math-wide"> $$\mathfrak{evk}_l := (\mathfrak{evk}_{l,0}, \mathfrak{evk}_{l,1}) \leftarrow \left( (z_l b_i)_{i=0, \dots, l}, (z_l a_i)_{i=0, \dots, l} \right) \in \left( R_{q}^2 \right)^{l+1}.$$ </div> <div class="math-narrow"> $$\begin{aligned} \mathfrak{evk}_l &amp;:= (\mathfrak{evk}_{l,0}, \mathfrak{evk}_{l,1}) \\ \leftarrow&amp; \left( (z_l b_i)_{i=0, \dots, l}, (z_l a_i)_{i=0, \dots, l} \right) \in \left( R_{q}^2 \right)^{l+1}. \end{aligned}$$ </div> <p>Notice that all key switching keys are generated in $R_{q}$ for all levels, but we <em>manually</em> change levels by moving them to the ideals defined by $z_l$. In practice, zeroed RNS components do not need to be processed, yielding similar performance as typical RNS implementations.</p> <ul> <li> <p>$\mathsf{Mult}(ct_0, ct_1, l):$ Multiplication of two ciphertexts $ct_0 := (ct_0[0], ct_0[1])$, $ct_1 := (ct_1[0], ct_1[1])$ at the same multiplicative level $l$ goes as follows. First a <em>pre-multiplication</em> performs a polynomial multiplication</p> <div class="math-wide"> $$ (d_0, d_1, d_2) := (ct_0[0] \cdot ct_1[0], ct_0[0] \cdot ct_1[1] + ct_1[0] \cdot ct_0[1], ct_0[1] \cdot ct_1[1]) \in R_q^3. $$ </div> <div class="math-narrow"> $$\begin{aligned} (d_0, d_1, d_2) := \big(ct_0[0] \cdot ct_1[0], ~~~~~~~~~~~&amp;\\ ct_0[0] \cdot ct_1[1] + ct_1[0] \cdot ct_0[1], &amp;\\ ct_0[1] \cdot ct_1[1] \big) \in R_q^3. &amp; \end{aligned}$$ </div> <p>Then, we perform the <em>key switching</em> as follows.</p> <div class="math-wide"> $$ D_i := d_i + \langle CRT_{\omega_l}^{-1}(d_2), \mathfrak{evk}_{l,i} \rangle \in R_{q}, \quad i=0,1. $$ </div> <div class="math-narrow"> $$\begin{aligned} D_i := d_i + \langle CRT_{\omega_l}^{-1}(d_2), &amp;\mathfrak{evk}_{l,i} \rangle \in R_{q},\\ \text{ for } i=0,1.~&amp; \end{aligned}$$ </div> <p>Then we <em>re-scale</em> $D_0, D_1$ as follows. Let \(p_l^{-1} = \left[q_{l+1}/q_{l}\right]_{q_{j+1} } \cdot z_l \in R_{q}\). Then compute and output the final ciphertext</p> <div class="math-wide"> $$ c_i := \left( D_i - \left[D_i\right]_{p_l}\right) p^{-1}_l \in R_{q},\quad i=0,1. $$ </div> <div class="math-narrow"> $$\begin{aligned} c_i := \left( D_i - \left[D_i\right]_{p_l}\right) p^{-1}_l \in R_{q},&amp;\\ \text{ for } i=0,1.~~~~~~~~~~~~~~~&amp; \end{aligned}$$ </div> </li> </ul> <p>A detailed noise analysis can be found in the full version of our paper. We omit it here for brevity, but we show that our CKKS incurs <em>at most</em> one additional bit of noise.</p> <p><br/></p> <h2 id="arithmetization">Arithmetization</h2> <div style="margin-top: 1.5em;"></div> <p>Now that we have a proof-friendly HE scheme, we describe how we can translate it into a finite set of relations over $R_q$, which, if satisfied, imply the correct execution of the scheme. Although we focus specifically on our (proof-friendly) CKKS additions and multiplications, we note that this process could be extended to any operations or schemes satisfying our desirable “proof-friendly” characteristics.</p> <h3 id="additions">Additions</h3> <p>Let two ciphertexts \(a = (a_0, a_1)\), \(b=(b_0, b_1) \in R_q^2\) be at the same level $l$. Adding $a$ and $b$ can be readily expressed as an arithmetic circuit satisfiability relation over $R_q$, since the resulting ciphertext \(c = (c_0, c_1) := (a_0 + b_0, a_1 + b_1)\) is simply the component-wise addition of $a$ and $b$ over $R_q^2$.</p> <h3 id="multiplications">Multiplications</h3> <p>Suppose that now we want to multiply the ciphertexts $a$ and $b$. That means that the ciphertexts $a, b$ are actually elements in $R_{q_l}$, although our CKKS treats them as elements in $R_q$. We know that a CKKS multiplication is composed of a pre-multiplication, followed by a key switching and finally by a rescaling. Let’s analyze each of these operations in turn.</p> <p>Pre-multiplication can be easily expressed as a sequence of arithmetic operations over $R_q$:</p> <div class="math-wide"> $$ (d_0, d_1, d_2):= (a_0 b_0, a_0b_1 + a_1b_0, a_1b_1). \tag{2} $$ </div> <div class="math-narrow"> $$\begin{aligned} (d_0, d_1, d_2&amp;):= \\ (a_0 b_0, &amp;a_0b_1 + a_1b_0, a_1b_1). \end{aligned}\tag{2}$$ </div> <p>For the key switching</p> <div class="math-wide"> $$ D_0 = d_0 + \langle \mathfrak{evk}_0, CRT^{-1}_{\omega_l}(d_2) \rangle, \quad D_1 = d_1 + \langle \mathfrak{evk}_1, CRT^{-1}_{\omega_l}(d_2) \rangle, \tag{3} $$ </div> <div class="math-narrow"> $$\begin{aligned} D_0 &amp;= d_0 + \langle \mathfrak{evk}_0, CRT^{-1}_{\omega_l}(d_2) \rangle, \\ D_1 &amp;= d_1 + \langle \mathfrak{evk}_1, CRT^{-1}_{\omega_l}(d_2) \rangle, \end{aligned}\tag{3}$$ </div> <p>we come across the first obstacle. The term \(\langle \mathfrak{evk}_0\), \(CRT^{-1}_{\omega_l}(d_2) \rangle\) involves expressing $d_2$ with respect to the RNS basis \(\omega_l\), which is not an arithmetic operation. Instead of proving the decomposition, we let the prover give the verifier the outcome of the decomposition. In other words, the prover sends inputs \(w_{ks, 0}, \dots, w_{ks, l} \in R_q\) satisfying</p> \[d_2 = \sum_{i=0}^l PW_{\omega_l}(1)[i] \cdot w_{ks, i}, \tag{4}\] <p>that is to say, they recompose to $d_2$ under the CRT map, and</p> \[\Vert w_{ks, i} \Vert &lt; p_i, \quad i = 0, \dots l, \tag{5}\] <p>which means that they are bounded by the RNS primes of the basis $\omega_l$ (remember we are at level $l$). In other words, (4) and (5) prove that the new inputs are indeed the CRT decomposition of $d_2$, and thus they can be used in (2) to compute the values $D_i$’s and continue the computation. Next is modulus switching</p> <div class="math-wide"> $$ c_0 = (D_0 + [D_0]_{p_l})\cdot p_l^{-1}, \quad c_1 = (D_1 + [D_1]_{p_l})\cdot p_l^{-1}. $$ </div> <div class="math-narrow"> $$\begin{aligned} c_0 &amp;= (D_0 + [D_0]_{p_l})\cdot p_l^{-1},\\ c_1 &amp;= (D_1 + [D_1]_{p_l})\cdot p_l^{-1}. \end{aligned}$$ </div> <p>Note that this is just a component-wise Euclidean division of $D_i$ by $p_l$. Using the same strategy as above, we let the prover introduce values $w_{quo, 0}, w_{quo, 1}$ and $w_{rmd, 0}, w_{rmd, 1}$ and prove that these are the quotients and remainders for the above equations. Specifically, the prover shows that</p> <div class="math-wide"> $$ D_i = p_l \cdot w_{quo, i} + w_{rmd, i}, \quad i = 0,1, \tag{6} $$ </div> <div class="math-narrow"> $$\begin{aligned} D_i = p_l \cdot w_{quo, i} &amp;+ w_{rmd, i}, \\ i = 0,&amp; 1, \end{aligned}\tag{6}$$ </div> <p>and</p> \[\Vert w_{quo, i} \Vert &lt; q/p_l, \quad i = 0, 1, \tag{7}\] <p>and</p> \[\Vert w_{rmd,i} \Vert &lt; p_l, \quad i=0,1. \tag{8}\] <p>Putting (2), (3), (4) and (6) together, these are equivalent to the following arithmetic circuit satisfiability relation over $R_q$:</p> <div class="math-wide"> $$ \begin{cases} p_l \cdot w_{quo, 0} + w_{rmd, 0} - a_0b_0 - \sum_{i=0}^l \mathfrak{evk}_0[i]\cdot w_{ks, i} = 0,\\ p_l \cdot w_{quo, 1} + w_{rmd, 1} - a_0b_1 - a_1b_0 - \sum_{i=0}^l \mathfrak{evk}_1[i]\cdot w_{ks, i} = 0,\\ d_2 - \sum_{i=0}^l PW_{\omega_l}(1)[i] \cdot w_{ks, i} = 0, \end{cases} $$ </div> <div class="math-narrow"> $$ \begin{cases} \begin{aligned} p_l \cdot w_{quo, 0} &amp;+ w_{rmd, 0} - a_0b_0 \\ &amp;~~ - \sum_{i=0}^l \mathfrak{evk}_0[i]\cdot w_{ks, i} = 0, \end{aligned}\\ \begin{aligned} p_l \cdot w_{quo, 1} &amp;+ w_{rmd, 1} - a_0b_1 - a_1b_0 \\ &amp;~~ -\sum_{i=0}^l \mathfrak{evk}_1[i]\cdot w_{ks, i} = 0, \end{aligned}\\ d_2 - \sum_{i=0}^l PW_{\omega_l}(1)[i] \cdot w_{ks, i} = 0, \end{cases} $$ </div> <p>and $l+5$ range check relations over $R_q$:</p> \[\Vert w_{ks, 0} \Vert &lt; p_0, \dots, \Vert w_{ks, l} \Vert &lt; p_l,\] \[\Vert w_{rmd,0} \Vert, \Vert w_{rmd,1} \Vert &lt; p_l,\] \[\Vert w_{quo, 0} \Vert, \Vert w_{quo, 1} \Vert &lt; q/p_l.\] <p>We have shown how to arithmetize a single addition and multiplication. A similar but much more involved argument can be done for a general circuit made of CKKS additions and multiplications. The strategy is to organize the CKKS circuit into mutiplicative layers, where consecutive additions are grouped together and are followed by the multiplication gate. Now instead of single $R_q$ values, one has to reason with $R_q$ vectors. For the details, we refer to Sec. 4 of our paper.</p> <h3 id="to-summarize">To summarize</h3> <p>To recap, our proof-friendly CKKS has the following properties that make it particularly suitable for proof systems:</p> <p>1) A carefully designed underlying ring which gives large enough exceptional sets while keeping arithmetic fast;</p> <p>2) This ring does not change during the computation, thanks to our re-design of the rescaling algorithm;</p> <p>3) A scheme design for which a noise analysis proves it allows looser bounds, which in turn enables batching of range proofs.</p> <p>At this point, one might ask: do these modifications have an impact on efficiency? After all, we do not change rings in our proof-friendly CKKS so, in particular, the ciphertext size does not decrease as we go through homomorphic computations. As it turns out, staying in the same ring <em>does not</em> impact the efficiency of CKKS operations! Intuitively, this is because we re-embed the ciphertexts into the initial ring $R_q$ by zero-ing the relevant slots in the RNS representation. The prover sees those zeros and simply ignores them: after all, operations involving zeros do not change the result.</p> <p>We implemented and benchmarked our proof-friendly version of CKKS. We show that our proof-friendly CKKS implementation only introduces an overhead of up to 20% for ciphertext multiplications over a “regular” instantiation of the scheme, while still being faster than commonly used libraries such as HELib. This slowdown is mainly due to the use of incomplete NTT. The source code is available on <a href="https://github.com/vfhe/proof-friendly-CKKS">GitHub</a>.</p> <p><br/></p> <h2 id="a-first-instantiation">A first instantiation</h2> <div style="margin-top: 1.5em;"></div> <p>The framework we propose reduces the problem of proving CKKS operations to the problem of designing PIOPs for two specific relations: arithmetic circuit satisfiability over $R_q$ and range checks for $R_q$ vectors. This problem can now be solved with the use of black-box components, making our solution highly modular. In the paper, we make some specific choices for providing a first instantiation that could concretely demonstrate practical feasibility. By themselves, some of these components are also independent contributions, as we designed them to exploit the particular characteristics of our arithmetic structures. Ultimately, however, one could pick and choose different instantiations, and in doing so, optimise for different efficiency metrics.</p> <p>Our instantiation of the framework consists of the following components:</p> <ul> <li>Arithmetic circuit relations are proven with a “custom” version of the GKR protocol [2] over rings. This variant crucially takes advantage of the particular structure of the circuit induced by our previous arithmetization, which results in a GKR circuit of constant depth (consisting of only $4$ layers), independent of the size or depth of the HE circuit.</li> <li>Range checks are proven using lookup arguments, i.e., a proof that convinces the verifier that a value belongs to a table of values $T_B$. For example, this table may include values within a certain range $B$. However, since CKKS requires to check large bounds (e.g., $B$ can have between $50$ and $300$ bits, depending on the level), and the ring dimension is concretely large (typically \(N \in \{2^{13}, 2^{17}\}\)) the public table $T_B$ would be too large to even represent. To overcome this issue, we rely on the recent decomposition technique of Lasso [5], that consists in splitting a large table into smaller ones, so that one can efficiently perform look-ups into them.</li> <li>The last component for building a succinct argument is a polynomial commitment for <em>multilinear</em> polynomials over $R_q$, since those are used to encode the messages sent by the prover in our PIOPs. First, we use the splitting (1) of $R_q$ into the product of finite fields to reduce the problem of designing a PC for multilinear polynomials over $R_q$ into that of designing PCs for multilinear polynomials over finite fields. The second contribution here is to use a field-agnostic PC, Brakedown [3], and modify it in such a way that we can use the limited algebraic structure of our fields to gain in efficiency.</li> </ul> <p><br/></p> <h2 id="future-work">Future work</h2> <div style="margin-top: 1.5em;"></div> <p>The relevance of our framework is mainly on the abstraction level. We are able to provide a blueprint for constructing vHE schemes where verification is asymptotically fast and the prover can potentially scale up well with the size of the circuit. Our blueprint is realized in a modular way by combining specific building blocks. We instantiate these by modifying and optimizing recent constructions from proof systems literature. We demonstrate that our building blocks can be practically instantiated. Compared to previous literature, our results for small (depth-1) circuits indicate similar performance levels as [4], which was the state of the art on concrete performance for verifiable RNS HE schemes. However, contrary to [4], we verify full-featured RNS-based leveled HE schemes, including key switching and rescaling operations, which enables our solution to scale to larger circuits. In contrast, the performance in the previous approach [4] would deteriorate exponentially with the circuit depth.</p> <p>The interested reader can also check online the recordings of our talks:</p> <ul> <li>For an FHE introduction check the <a href="https://www.youtube.com/watch?v=nAdAs56TxvE">talk at FHE.org</a></li> <li>For a more SNARK-y perspective check the <a href="https://www.youtube.com/watch?v=QskB0USXFrU">talk at ZKProof</a></li> </ul> <p>The next challenge is of course practical: to show that our framework is really able to scale up with large circuits. So stay tuned!</p> <hr/> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <p>[1] I. Cascudo, A. Costache, D. Cozzo, D. Fiore, A. Guimaraes and E. Soria-Vazquez. “Verifiable Computation for Approximate Homomorphic Encryption Schemes.” CRYPTO 2025.</p> <p>[2] S. Goldwasser, Y. T. Kalai, and G. N. Rothblum. “Delegating Computation: Interactive Proofs for Muggles.” Journal of the ACM 2015.</p> <p>[3] A. Golovnev, J. Lee, S. T. V. Setty, J. Thaler, and R. S. Wahby. “Brakedown: Linear-time and Field-agnostic SNARKs for R1CS.” CRYPTO 2023.</p> <p>[4] C. Knabenhans, A. Viand, and A. Hithnawi. “Towards Robust FHE for the Real World.” Real World Crypto 2024.</p> <p>[5] S. T. V. Setty, J. Thaler, and R. S. Wahby. “Unlocking the Lookup Singularity with Lasso.” EUROCRYPT 2024.</p>]]></content><author><name>Ignacio Cascudo, Anamaria Costache, Daniele Cozzo, Dario Fiore, Antonio Guimarães, Eduardo Soria-Vazquez</name></author><summary type="html"><![CDATA[TL;DR: Homomorphic Encryption (HE) enables computing over encrypted data but, by itself, provides no guarantees that the computation was honestly executed. One can build "Verifiable HE" (vHE) using SNARKs, but efficiently combining HE and SNARKs in practice is a major challenge. This work introduces a blueprint for building verifiable HE schemes and its efficient instantiation for CKKS. Our first step is to introduce a "proof-friendly" version of CKKS, which is more amenable to proof systems, while being only slightly slower than typical RNS CKKS implementations. We then show how the problem of proving correctness of computations for such proof-friendly HE schemes can be reduced to just two sets of arithmetic relations (containing equalities and inequalities). We show that if these are satisfied, it implies the correct execution of the HE evaluation. We design Polynomial Interactive Oracle Proofs (PIOPs) for efficiently proving these relations, and we show how they can be instantiated using standard proof components. Our final construction demonstrates the feasibility of building SNARKs for proving computation of full-fledged HE schemes, opening the path for building practical verifiable HE schemes.]]></summary></entry><entry><title type="html">Orion: A Fully Homomorphic Encryption Framework for Deep Learning</title><link href="https://ckks.org/blog/2026/orion/" rel="alternate" type="text/html" title="Orion: A Fully Homomorphic Encryption Framework for Deep Learning"/><published>2026-02-02T04:00:00+00:00</published><updated>2026-02-02T04:00:00+00:00</updated><id>https://ckks.org/blog/2026/orion</id><content type="html" xml:base="https://ckks.org/blog/2026/orion/"><![CDATA[<ul> <li>Written by <a href="https://austinebel.net/">Austin Ebel</a>, <a href="https://kvgarimella.github.io/">Karthik Garimella</a>, <a href="https://brandonreagen.com/">Brandon Reagen</a> (New York University)</li> <li>Based on <a href="https://arxiv.org/pdf/2311.03470">https://arxiv.org/pdf/2311.03470</a> (ASPLOS 2025)</li> </ul> <p><em>TL;DR: Orion is a framework that compiles PyTorch neural network models into efficient CKKS FHE programs for encrypted inference. Orion automatically handles low-level FHE details such as data packing, bootstrap placement, and precision management. Orion is open-sourced at: <a href="https://github.com/baahl-nyu/orion">https://github.com/baahl-nyu/orion</a>.</em></p> <hr/> <p><br/></p> <h2 id="1-introduction">1. Introduction</h2> <div style="margin-top: 1.5em;"></div> <p>The CKKS FHE scheme enables cryptographically secure outsourced computing, which has broad implications for areas such as health or finance. However, writing FHE programs, especially FHE neural networks, remains a challenge given the low-level primitives that CKKS exposes: addition, multiplication, and rotation of encrypted vectors. Furthermore, auxiliary FHE operations that are necessary for deep computations (such as bootstrapping and scale management) only make this harder.</p> <p>A user of FHE might ask: How and when should bootstraps be placed during inference? What FHE algorithm should be used for computing convolutions? How can we approximate non-linear activations? In this blog, we introduce our framework Orion, which handles each of these issues automatically, making it easier to write FHE neural network programs. The figure below describes three key aspects we had in mind when building Orion.</p> <div style="width: 95%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/orion_three_pillars.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/orion_three_pillars.svg" class="img-fluid" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 1: Orion's high-level goals: (left) vector packing for fast linear transforms, (middle) automated bootstrap placement and level management, and (right) an end-user workflow that stays close to PyTorch. </div> </div> <p>First, we automatically handle FHE packing in order to leverage fast CKKS linear transformation algorithms. Our packing strategy ensures that each linear layer consumes <em>only one multiplicative level</em> regardless of the linear layer configuration. Second, we entirely automate both bootstrap and scale management, which enables Orion to run <em>deep</em> neural networks such as ResNet-50 while still maintaining precision. Finally, we wanted Orion to be <em>accessible</em>; writing FHE neural networks in Orion requires only knowledge of PyTorch.</p> <p>Below, we’ll cover the details of our convolution algorithm and bootstrap placement strategy.</p> <p><br/></p> <h2 id="2-efficient-linear-transform-algorithms">2. Efficient Linear Transform Algorithms</h2> <div style="margin-top: 1.5em;"></div> <p>Linear transformations are a core building block in neural networks, from convolutional neural networks (convolutions, average pooling, and final head layers) to modern-day transformer architectures (QKV projections, feed-forward MLPs, and even RoPE). Under CKKS, it helps to think about linear transforms in terms of three constraints: how well we use ciphertext slots, how many multiplicative levels we consume, and how many expensive homomorphic operations we require (in particular, key-switches induced by ciphertext rotations).</p> <p>As a concrete example, consider outsourced neural inference where we want to compute a matrix–vector product between a plaintext weight matrix and an encrypted ciphertext vector. Since the weight matrix is unencrypted, it can be <em>packed</em> into CKKS plaintext vectors in different ways, and that packing choice largely determines how many rotations and levels we pay for.</p> <p>A natural first attempt is to pack each row of the matrix into a CKKS vector and compute a dot product against the ciphertext. In practice, row-based packing tends to be a poor fit for CKKS: it requires padding dimensions to powers of two, it computes dot products <em>within</em> ciphertexts (often leading to low slot utilization), and it typically consumes extra multiplicative depth to consolidate partial sums. It also scales poorly in rotations for dense matrix–vector products.</p> <div style="width: 85%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/diagonal_method.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/diagonal_method.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 2: The diagonal method for plaintext-ciphertext matrix–vector products. </div> </div> <p>Orion instead targets <strong>diagonal-based</strong> matrix–vector product algorithms in which the <em>diagonals</em> of the unencrypted matrix are packed into CKKS vectors. These algorithms have been well-optimized by the cryptographic community, and they align well with how CKKS bootstrapping itself is implemented (which relies on large linear transforms). Rather than computing dot products within intermediate ciphertexts, diagonal-based approaches compute dot products <em>across</em> ciphertexts and then aggregate them into a single packed ciphertext output.</p> <p>A standard way to reduce the rotation count further is the baby-step giant-step (BSGS) strategy. Informally, BSGS reuses ciphertext rotations by reorganizing which shifts happen on ciphertexts and which shifts can be absorbed into plaintext preprocessing of packed diagonals. This reduces rotation counts from $O(n)$ down to $O(\sqrt{n})$ while still consuming only <strong>one</strong> multiplicative level.</p> <div style="width: 85%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/bsgs_method.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/bsgs_method.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 3: BSGS reduces ciphertext rotations by leveraging the fact that matrix diagonals can be cheaply rotated before being encoded. </div> </div> <p>In Orion, we combine BSGS with low-level cryptographic optimizations (in particular, <em>double hoisting</em>) to reduce the amortized cost per rotation. Once we treat “fast matrix–vector products” as a first-class compiler target, we can build higher-level neural network layers on top of a kernel that is both level-efficient (one level per MV) and rotation-efficient (BSGS + hoisting).</p> <p><br/></p> <h2 id="3-convolutions-as-matrix-vector-products">3. Convolutions as Matrix-Vector Products</h2> <div style="margin-top: 1.5em;"></div> <p>Now that we have a blueprint for fast matrix-vector products, a natural question is: can we apply it to convolutions? If so, then the same principles would extend to the much wider class of <em>convolutional</em> neural nets.</p> <p>The standard approach for this is known as the Toeplitz formulation. Here, the input image is flattened into a vector and the kernel expands into a matrix. Each row of this Toeplitz matrix corresponds to one filter multiplication with the input image as it slides over all of its positions.</p> <div style="width: 80%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/toeplitz_animation.webp" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/toeplitz_animation.webp" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 4: The Toeplitz formulation of a single-input, single-output (SISO) convolution. </div> </div> <p>The animation in Figure 4 shows this for single-input single-output (SISO) convolutions, although the approach naturally extends to multi-input, multi-output convolutions as well. We use the following code block in Orion to perform the initial packing of <strong>arbitrary</strong> convolutions (e.g., any stride, padding, dilation, etc.) directly in PyTorch.</p> <details> <summary>Click to expand code</summary> <div style="font-size: 0.9em;"> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">construct_toeplitz_matrix</span><span class="p">(</span><span class="n">input_shape</span><span class="p">,</span> <span class="n">output_shape</span><span class="p">,</span> <span class="n">conv_layer</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Construct the Toeplitz representation of a convolutional layer.
    
    The Toeplitz matrix reformulates convolution as matrix-vector multiplication:
        output_flat = toep_matrix @ input_flat
    
    Args:
        input_shape (tuple): Shape of input tensor (N, Ci, Hi, Wi)
        output_shape (tuple): Shape of output tensor (N, Co, Ho, Wo)
        conv_layer (nn.Conv2d): PyTorch convolutional layer
    
    Returns:
        torch.Tensor: Toeplitz matrix of shape (Co * Ho * Wo, Ci * Hi * Wi)
    </span><span class="sh">"""</span>
    <span class="c1"># Unpack shapes
</span>    <span class="n">N</span><span class="p">,</span> <span class="n">Ci</span><span class="p">,</span> <span class="n">Hi</span><span class="p">,</span> <span class="n">Wi</span> <span class="o">=</span> <span class="n">input_shape</span>
    <span class="n">N</span><span class="p">,</span> <span class="n">Co</span><span class="p">,</span> <span class="n">Ho</span><span class="p">,</span> <span class="n">Wo</span> <span class="o">=</span> <span class="n">output_shape</span>
    
    <span class="c1"># Extract convolution parameters
</span>    <span class="n">kernel_weights</span> <span class="o">=</span> <span class="n">conv_layer</span><span class="p">.</span><span class="n">weight</span><span class="p">.</span><span class="n">data</span>  <span class="c1"># shape: (Co, Ci, kH, kW)
</span>    <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">kH</span><span class="p">,</span> <span class="n">kW</span> <span class="o">=</span> <span class="n">kernel_weights</span><span class="p">.</span><span class="n">shape</span>
    <span class="n">padding</span> <span class="o">=</span> <span class="n">conv_layer</span><span class="p">.</span><span class="n">padding</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">stride</span> <span class="o">=</span> <span class="n">conv_layer</span><span class="p">.</span><span class="n">stride</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">dilation</span> <span class="o">=</span> <span class="n">conv_layer</span><span class="p">.</span><span class="n">dilation</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    
    <span class="c1"># Compute padded input dimensions
</span>    <span class="n">Hi_padded</span> <span class="o">=</span> <span class="n">Hi</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">padding</span>
    <span class="n">Wi_padded</span> <span class="o">=</span> <span class="n">Wi</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">padding</span>
    
    <span class="c1"># Initialize Toeplitz matrix
</span>    <span class="n">num_output_elements</span> <span class="o">=</span> <span class="n">Co</span> <span class="o">*</span> <span class="n">Ho</span> <span class="o">*</span> <span class="n">Wo</span>
    <span class="n">num_padded_input_elements</span> <span class="o">=</span> <span class="n">Ci</span> <span class="o">*</span> <span class="n">Hi_padded</span> <span class="o">*</span> <span class="n">Wi_padded</span>
    <span class="n">toep_matrix</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">num_output_elements</span><span class="p">,</span> <span class="n">num_padded_input_elements</span><span class="p">)</span>
    
    <span class="c1"># Create index grid for the padded input (Ci, Hi_padded, Wi_padded)
</span>    <span class="n">padded_indices</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span>
        <span class="n">Ci</span> <span class="o">*</span> <span class="n">Hi_padded</span> <span class="o">*</span> <span class="n">Wi_padded</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">int32</span>
    <span class="p">).</span><span class="nf">reshape</span><span class="p">(</span><span class="n">Ci</span><span class="p">,</span> <span class="n">Hi_padded</span><span class="p">,</span> <span class="n">Wi_padded</span><span class="p">)</span>
    
    <span class="c1"># Indices within a single kernel window (accounts for dilation)
</span>    <span class="n">kernel_window_indices</span> <span class="o">=</span> <span class="n">padded_indices</span><span class="p">[</span>
        <span class="p">:</span><span class="n">Ci</span><span class="p">,</span>                      <span class="c1"># all input channels
</span>        <span class="p">:</span><span class="n">kH</span> <span class="o">*</span> <span class="n">dilation</span><span class="p">:</span><span class="n">dilation</span><span class="p">,</span>  <span class="c1"># kernel height with dilation
</span>        <span class="p">:</span><span class="n">kW</span> <span class="o">*</span> <span class="n">dilation</span><span class="p">:</span><span class="n">dilation</span>   <span class="c1"># kernel width with dilation
</span>    <span class="p">].</span><span class="nf">flatten</span><span class="p">()</span>
    
    <span class="c1"># Top-left corner positions where the kernel is applied (accounts for stride)
</span>    <span class="n">kernel_start_positions</span> <span class="o">=</span> <span class="n">padded_indices</span><span class="p">[</span>
        <span class="mi">0</span><span class="p">,</span>                  <span class="c1"># first channel only (others offset by kernel_window_indices)
</span>        <span class="mi">0</span><span class="p">:</span><span class="n">Ho</span> <span class="o">*</span> <span class="n">stride</span><span class="p">:</span><span class="n">stride</span><span class="p">,</span>  <span class="c1"># vertical positions
</span>        <span class="mi">0</span><span class="p">:</span><span class="n">Wo</span> <span class="o">*</span> <span class="n">stride</span><span class="p">:</span><span class="n">stride</span>   <span class="c1"># horizontal positions
</span>    <span class="p">].</span><span class="nf">flatten</span><span class="p">()</span>
    
    <span class="c1"># Fill Toeplitz matrix: each output channel block gets the kernel weights
</span>    <span class="n">output_channel_offsets</span> <span class="o">=</span> <span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="n">Co</span><span class="p">)</span> <span class="o">*</span> <span class="n">Ho</span> <span class="o">*</span> <span class="n">Wo</span><span class="p">).</span><span class="nf">reshape</span><span class="p">(</span><span class="n">Co</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    
    <span class="k">for</span> <span class="n">spatial_idx</span><span class="p">,</span> <span class="n">start_pos</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">kernel_start_positions</span><span class="p">):</span>
        <span class="n">row_indices</span> <span class="o">=</span> <span class="n">spatial_idx</span> <span class="o">+</span> <span class="n">output_channel_offsets</span>
        <span class="n">col_indices</span> <span class="o">=</span> <span class="n">kernel_window_indices</span> <span class="o">+</span> <span class="n">start_pos</span>
        <span class="n">toep_matrix</span><span class="p">[</span><span class="n">row_indices</span><span class="p">,</span> <span class="n">col_indices</span><span class="p">]</span> <span class="o">=</span> <span class="n">kernel_weights</span><span class="p">.</span><span class="nf">reshape</span><span class="p">(</span><span class="n">Co</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
    
    <span class="c1"># Extract only the columns corresponding to the original (unpadded) input
</span>    <span class="n">original_input_indices</span> <span class="o">=</span> <span class="n">padded_indices</span><span class="p">[</span>
        <span class="p">:,</span>                      <span class="c1"># all channels
</span>        <span class="n">padding</span><span class="p">:</span><span class="n">Hi</span> <span class="o">+</span> <span class="n">padding</span><span class="p">,</span>   <span class="c1"># remove top/bottom padding
</span>        <span class="n">padding</span><span class="p">:</span><span class="n">Wi</span> <span class="o">+</span> <span class="n">padding</span>    <span class="c1"># remove left/right padding
</span>    <span class="p">].</span><span class="nf">flatten</span><span class="p">()</span>
    
    <span class="n">toep_matrix</span> <span class="o">=</span> <span class="n">toep_matrix</span><span class="p">[:,</span> <span class="n">original_input_indices</span><span class="p">]</span>
    
    <span class="k">return</span> <span class="n">toep_matrix</span></code></pre></figure> </div> </details> <div style="margin-top: 1em;"></div> <p>The core issue with this method is that it does not efficiently extend to strided convolutions under FHE. In particular, the nice <em>diagonal</em> pattern we see in the unit-stride case is no longer present. Without that pattern, the number of non-zero diagonals depends on the input’s spatial dimensions, which scales poorly to larger image sizes.</p> <div style="width: 95%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/toeplitz_strided.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/toeplitz_strided.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 5: Why strided Toeplitz convolutions are harder. With stride $s &gt; 1$, the Toeplitz matrix loses the clean diagonal structure of the unit-stride case. </div> </div> <p>For the Toeplitz representation to be useful, we need better slot utilization for strided convolutions. Our key observation is that we can permute the matrix rows without changing the computation. All that changes is the order of the output feature map. Figure 6 shows this approach for a convolution with stride $s=2$.</p> <div style="width: 95%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/multiplexed_convs.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/multiplexed_convs.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 6: Single-shot multiplexing. Permuting rows of the Toeplitz matrix packs the diagonals more densely, which reduces expensive ciphertext rotations. </div> </div> <p>We call this technique “single-shot multiplexing” as it mirrors the work from Lee et al. <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, but consumes just a single multiplicative level. Like their work, this method produces outputs with a gap $g$. Future layers need to account for this gap, but Orion handles it automatically during compilation.</p> <p>The result is fewer rotations, roughly half of which are hoisted. On CIFAR-10, we see 1.65$\times$ fewer rotations on ResNet-20 and up to 6.41$\times$ fewer rotations on AlexNet.</p> <p><br/></p> <h2 id="4-automating-bootstrap-placement">4. Automating Bootstrap Placement</h2> <div style="margin-top: 1.5em;"></div> <p>At this point, it’s worth zooming in on how Orion automates bootstrap placement, because bootstrapping is what makes deep encrypted inference possible in the first place. Unlike data packing, <em>good</em> bootstrap placement often requires a deeper understanding of the underlying cryptography. In our experience, this creates the largest barrier to entry for practitioners, making automated solutions all the more valuable.</p> <p>Our approach involves reformulating things as a shortest-path problem. We create what we call a “level DAG” to enumerate the behavior of all possible network states and their transitions.</p> <p>Figure 7 <em>(left)</em> visualizes this DAG for a simple 3-layer MLP without intermediate activation functions. Here, nodes within a row represent possible choices of level for each linear layer, weighted by the latency of performing that linear layer at the given level. Each row also excludes invalid states. For instance, there is no node for <code class="language-plaintext highlighter-rouge">fc2</code> at level <code class="language-plaintext highlighter-rouge">l=0</code> because linear layers consume a level, and we could lose decoding correctness if there were.</p> <div style="width: 90%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/simple_level_dag.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/simple_level_dag.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 7: Bootstrap placement as shortest-path search. Nodes are level choices (weighted by latency), edges are transitions that may require a bootstrap. The shortest path gives us where to bootstrap and at what level to run each layer. </div> </div> <p>We can then weigh the edges between layers by whether a bootstrap operation is required. For instance, the edges highlighted in red in Figure 7 <em>(middle)</em> each increase the level of the ciphertext (after the layer itself is performed), and therefore must be bootstrapped.</p> <p>With these two constraints, we can apply a shortest path algorithm to find the path that minimizes end-to-end latency in Figure 7 <em>(right)</em>. With respect to our heuristics, this shortest path gives us both the optimal <strong>locations</strong> to bootstrap and the optimal <strong>levels</strong> at which to perform each layer.</p> <p><br/></p> <h3 id="4a-beyond-simple-mlps">4a. Beyond Simple MLPs</h3> <div style="margin-top: 1.5em;"></div> <p>This method extends almost directly to more complex networks, especially those with <em>residual</em> connections. The main obstacle is that residual connections create multiple paths through the graph, each sharing a common fork and join node. This makes directly applying our shortest path approach challenging.</p> <p>We solve this by creating two level DAGs around each residual connection: one for the backbone, one for the residual itself. Then, we can (i) sum the shortest paths for all possible <em>pairs</em> of input and output nodes, and (ii) insert that black-boxed solution back into the original level DAG. This reduces the problem to our original shortest path approach.</p> <div style="width: 100%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/bootstrap_placement_animation.webp" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/bootstrap_placement_animation.webp" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 8: Bootstrap placement over residual connections by solving and black-boxing single-entry, single-exit regions (the backbone and residual paths) and inserting the aggregated solution back into the global level DAG. </div> </div> <p>Interestingly, this approach extends recursively to graphs with any number of fork/join pairs. Figure 9 gives the high-level solution for attention blocks within transformers.</p> <div style="width: 100%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/extending_auto_bootstrap.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/extending_auto_bootstrap.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: center; margin-top: -0.5em;"> Figure 9: Bootstrap placement for more complex networks and attention blocks with multiple fork/join pairs. </div> </div> <p><br/></p> <h2 id="5-addressing-the-sharp-bits">5. Addressing the Sharp Bits</h2> <div style="margin-top: 1.5em;"></div> <p>There’s still more to running deep FHE inference beyond packing and bootstrap placement. Orion includes several automation layers that remove the “sharp edges” that typically trip up non-experts.</p> <p>First, to conform to the CKKS programming model, all non-linear functions (e.g., ReLU or GELU) must be replaced by high-degree polynomials over the correct <em>range</em>. Orion automates this with <code class="language-plaintext highlighter-rouge">orion.fit()</code> which iterates over the training data and (i) replaces non-linearities with Chebyshev polynomials, and (ii) automatically determines the input ranges those approximations need.</p> <p>Second, Orion automates scale management. We leverage the fact that our compilation phase has already determined the level at which to perform any linear layer (call it level $j$). We encode the weights with scale factor $q_j$ (the last RNS modulus at level $j$) rather than $\Delta$. This way, performing a convolution with a ciphertext at scale $\Delta$ produces an output at scale $\Delta \cdot q_j$. Rescaling divides by $q_j$, resetting the scale back to exactly $\Delta$. This is what we call “errorless” neural network evaluation.</p> <p>Finally, Orion handles the large data structures that come with FHE inference. Server-side packed vectors and evaluation keys can reach tens to hundreds of gigabytes. Orion optionally reduces memory pressure by dynamically loading per-layer plaintext vectors during linear transforms.</p> <div style="width: 100%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/orion_sharp_bits.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/orion_sharp_bits.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 10: The sharp bits Orion handles automatically: (left) fitting activation functions, (middle) scale management, (right) large data structures. </div> </div> <p>From the user’s perspective, Orion feels like PyTorch: you write a model as usual, fit/compile it for FHE, and run encrypted inference.</p> <details> <summary>Click to expand: Model definition (ResNet-20)</summary> <div style="font-size: 0.9em;"> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>
<span class="kn">import</span> <span class="n">orion.nn</span> <span class="k">as</span> <span class="n">on</span>


<span class="k">class</span> <span class="nc">BasicBlock</span><span class="p">(</span><span class="n">on</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="n">expansion</span> <span class="o">=</span> <span class="mi">1</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">Ci</span><span class="p">,</span> <span class="n">Co</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">conv1</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Conv2d</span><span class="p">(</span><span class="n">Ci</span><span class="p">,</span> <span class="n">Co</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="n">stride</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">bn1</span>   <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">BatchNorm2d</span><span class="p">(</span><span class="n">Co</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">act1</span>  <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">ReLU</span><span class="p">()</span>

        <span class="n">self</span><span class="p">.</span><span class="n">conv2</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Conv2d</span><span class="p">(</span><span class="n">Co</span><span class="p">,</span> <span class="n">Co</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">bn2</span>   <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">BatchNorm2d</span><span class="p">(</span><span class="n">Co</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">act2</span>  <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">ReLU</span><span class="p">()</span>
       
        <span class="n">self</span><span class="p">.</span><span class="n">add</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Add</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">shortcut</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Sequential</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">stride</span> <span class="o">!=</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">Ci</span> <span class="o">!=</span> <span class="n">self</span><span class="p">.</span><span class="n">expansion</span><span class="o">*</span><span class="n">Co</span><span class="p">:</span>
            <span class="n">self</span><span class="p">.</span><span class="n">shortcut</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Sequential</span><span class="p">(</span>
                <span class="n">on</span><span class="p">.</span><span class="nc">Conv2d</span><span class="p">(</span><span class="n">Ci</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">expansion</span><span class="o">*</span><span class="n">Co</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="n">stride</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">),</span>
                <span class="n">on</span><span class="p">.</span><span class="nc">BatchNorm2d</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">expansion</span><span class="o">*</span><span class="n">Co</span><span class="p">))</span>
  
    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">act1</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">bn1</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">conv1</span><span class="p">(</span><span class="n">x</span><span class="p">)))</span>
        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">bn2</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">conv2</span><span class="p">(</span><span class="n">out</span><span class="p">))</span>
        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">out</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="nf">shortcut</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">act2</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">ResNet</span><span class="p">(</span><span class="n">on</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">dataset</span><span class="p">,</span> <span class="n">block</span><span class="p">,</span> <span class="n">num_blocks</span><span class="p">,</span> <span class="n">num_chans</span><span class="p">,</span> <span class="n">conv1_params</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">in_chans</span> <span class="o">=</span> <span class="n">num_chans</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">self</span><span class="p">.</span><span class="n">last_chans</span> <span class="o">=</span> <span class="n">num_chans</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

        <span class="n">self</span><span class="p">.</span><span class="n">conv1</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Conv2d</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">in_chans</span><span class="p">,</span> <span class="o">**</span><span class="n">conv1_params</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">bn1</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">BatchNorm2d</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">in_chans</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">act</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">ReLU</span><span class="p">()</span>
        
        <span class="n">self</span><span class="p">.</span><span class="n">layers</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">ModuleList</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">num_blocks</span><span class="p">)):</span>
            <span class="n">stride</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">2</span>
            <span class="n">self</span><span class="p">.</span><span class="n">layers</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">layer</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="n">num_chans</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">num_blocks</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">stride</span><span class="p">))</span>

        <span class="n">self</span><span class="p">.</span><span class="n">avgpool</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">AdaptiveAvgPool2d</span><span class="p">(</span><span class="n">output_size</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">))</span> 
        <span class="n">self</span><span class="p">.</span><span class="n">flatten</span> <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Flatten</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">linear</span>  <span class="o">=</span> <span class="n">on</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">last_chans</span> <span class="o">*</span> <span class="n">block</span><span class="p">.</span><span class="n">expansion</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">layer</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">block</span><span class="p">,</span> <span class="n">chans</span><span class="p">,</span> <span class="n">num_blocks</span><span class="p">,</span> <span class="n">stride</span><span class="p">):</span>
        <span class="n">strides</span> <span class="o">=</span> <span class="p">[</span><span class="n">stride</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="n">num_blocks</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">layers</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">stride</span> <span class="ow">in</span> <span class="n">strides</span><span class="p">:</span>
            <span class="n">layers</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">block</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">in_chans</span><span class="p">,</span> <span class="n">chans</span><span class="p">,</span> <span class="n">stride</span><span class="p">))</span>
            <span class="n">self</span><span class="p">.</span><span class="n">in_chans</span> <span class="o">=</span> <span class="n">chans</span> <span class="o">*</span> <span class="n">block</span><span class="p">.</span><span class="n">expansion</span>
        <span class="k">return</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Sequential</span><span class="p">(</span><span class="o">*</span><span class="n">layers</span><span class="p">)</span>
    
    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">act</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">bn1</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">conv1</span><span class="p">(</span><span class="n">x</span><span class="p">)))</span>
        <span class="k">for</span> <span class="n">layer</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">layers</span><span class="p">:</span>
            <span class="n">out</span> <span class="o">=</span> <span class="nf">layer</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>

        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">avgpool</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
        <span class="n">out</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">flatten</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">linear</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">ResNet20</span><span class="p">(</span><span class="n">dataset</span><span class="o">=</span><span class="sh">'</span><span class="s">cifar10</span><span class="sh">'</span><span class="p">):</span>
    <span class="n">configs</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">"</span><span class="s">cifar10</span><span class="sh">"</span><span class="p">:</span>  <span class="p">{</span><span class="sh">"</span><span class="s">kernel_size</span><span class="sh">"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="sh">"</span><span class="s">stride</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">padding</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">num_classes</span><span class="sh">"</span><span class="p">:</span> <span class="mi">10</span><span class="p">},</span>
        <span class="sh">"</span><span class="s">cifar100</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">kernel_size</span><span class="sh">"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="sh">"</span><span class="s">stride</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">padding</span><span class="sh">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">num_classes</span><span class="sh">"</span><span class="p">:</span> <span class="mi">100</span><span class="p">},</span>
    <span class="p">}</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">configs</span><span class="p">[</span><span class="n">dataset</span><span class="p">]</span>
    <span class="n">conv1_params</span> <span class="o">=</span> <span class="p">{</span>
        <span class="sh">'</span><span class="s">kernel_size</span><span class="sh">'</span><span class="p">:</span> <span class="n">config</span><span class="p">[</span><span class="sh">"</span><span class="s">kernel_size</span><span class="sh">"</span><span class="p">],</span>
        <span class="sh">'</span><span class="s">stride</span><span class="sh">'</span><span class="p">:</span> <span class="n">config</span><span class="p">[</span><span class="sh">"</span><span class="s">stride</span><span class="sh">"</span><span class="p">],</span>
        <span class="sh">'</span><span class="s">padding</span><span class="sh">'</span><span class="p">:</span> <span class="n">config</span><span class="p">[</span><span class="sh">"</span><span class="s">padding</span><span class="sh">"</span><span class="p">]</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nc">ResNet</span><span class="p">(</span><span class="n">dataset</span><span class="p">,</span> <span class="n">BasicBlock</span><span class="p">,</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">16</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">64</span><span class="p">],</span> <span class="n">conv1_params</span><span class="p">,</span> <span class="n">config</span><span class="p">[</span><span class="sh">"</span><span class="s">num_classes</span><span class="sh">"</span><span class="p">])</span></code></pre></figure> </div> </details> <details> <summary>Click to expand: FHE compilation workflow</summary> <div style="font-size: 0.9em;"> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1"># High-level workflow (sketch)
</span><span class="n">net</span> <span class="o">=</span> <span class="nc">ResNet20</span><span class="p">()</span>
<span class="n">orion</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">trainloader</span><span class="p">)</span>   <span class="c1"># range discovery + polynomial activation replacement
</span><span class="n">orion</span><span class="p">.</span><span class="nf">compile</span><span class="p">(</span><span class="n">net</span><span class="p">)</span>            <span class="c1"># packing + bootstrap placement + key/material generation
</span>
<span class="n">net</span><span class="p">.</span><span class="nf">he</span><span class="p">()</span>                      <span class="c1"># switch model into FHE execution mode
</span><span class="n">ct_out</span> <span class="o">=</span> <span class="nf">net</span><span class="p">(</span><span class="n">ct_in</span><span class="p">)</span>           <span class="c1"># run encrypted inference</span></code></pre></figure> </div> </details> <p><br/></p> <h2 id="6-results">6. Results</h2> <div style="margin-top: 1.5em;"></div> <p>To highlight Orion’s scalability, we ran ResNet-34 and ResNet-50 on ImageNet end-to-end under FHE with single-threaded inference times of 3.98 hours and 8.98 hours, respectively. To show that Orion handles more than classification, we also ran object detection using a 139 million parameter YOLO-v1 model on the PASCAL-VOC dataset (images of size 448 $\times$ 448 $\times$ 3). An encrypted inference took 17.5 hours and produced bounding boxes and confidence scores entirely under FHE, matching the cleartext PyTorch output to 8 bits of precision.</p> <div style="margin-top: 1em;"></div> <div style="width: 80%; margin: 0 auto;"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2602_Austin/object_detection.svg" sizes="95vw"/> <img src="/assets/img/blog/2602_Austin/object_detection.svg" class="img-fluid rounded" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> <div class="caption" style="text-align: left; margin-top: -0.5em;"> Figure 11: Homomorphic object detection and localization results. Labels show predicted class and confidence score. Outputs match cleartext PyTorch to 8 bits of precision. </div> </div> <p><em>If you want to reproduce results or try Orion on your own models, the repository is available at <a href="https://github.com/baahl-nyu/orion">https://github.com/baahl-nyu/orion</a>.</em></p> <hr/> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1"> <p>Eunsang Lee, Joon-Woo Lee, Junghyun Lee, Young-Sik Kim, Yongjune Kim, Jong-Seon No, and Woosuk Choi. “Low-Complexity Deep Convolutional Neural Networks on Fully Homomorphic Encryption Using Multiplexed Parallel Convolutions.” ICML 2022. <a href="https://proceedings.mlr.press/v162/lee22e.html">https://proceedings.mlr.press/v162/lee22e.html</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>]]></content><author><name>Austin Ebel, Karthik Garimella, Brandon Reagen</name></author><summary type="html"><![CDATA[TL;DR: Orion is a framework that compiles PyTorch neural network models into efficient CKKS FHE programs for encrypted inference. Orion automatically handles low-level FHE details such as data packing, bootstrap placement, and precision management. Orion is open-sourced at: https://github.com/baahl-nyu/orion.]]></summary></entry><entry><title type="html">DPHE: Protecting Server Privacy in CKKS-based Protocols</title><link href="https://ckks.org/blog/2026/DPHE/" rel="alternate" type="text/html" title="DPHE: Protecting Server Privacy in CKKS-based Protocols"/><published>2026-01-05T09:12:00+00:00</published><updated>2026-01-05T09:12:00+00:00</updated><id>https://ckks.org/blog/2026/DPHE</id><content type="html" xml:base="https://ckks.org/blog/2026/DPHE/"><![CDATA[<ul> <li>Written by <a href="https://jin-yeong-seo.github.io/">Jinyeong Seo</a> (Seoul National University)</li> <li>Based on <a href="https://ia.cr/2025/382">https://ia.cr/2025/382</a> (Asiacrypt 2025)</li> </ul> <p><em>TL;DR: We investigate methods for protecting server privacy in CKKS-based protocols. Unlike exact homomorphic encryption schemes, formally defining security notions for the server is challenging in CKKS-based protocols due to the approximate nature of CKKS. We address this by introducing a new security notion called Differentially Private Homomorphic Encryption, which is motivated by differential privacy. Based on this notion, we construct a general compiler that transforms CKKS-based protocols into DPHE protocols. We also present the first zero-knowledge argument of knowledge for CKKS ciphertexts to protect server privacy against malicious clients.</em></p> <hr/> <p><br/></p> <h2 id="1introduction">1.Introduction</h2> <div style="margin-top: 1.5em;"></div> <p>In recent years, CKKS has become a popular choice for building privacy-preserving machine learning (PPML) protocols. The primary reason for its popularity is its support for efficient real and complex arithmetic. This feature enables straightforward design of machine learning as a service (MLaaS) protocols that protect user privacy. However, in most CKKS-based protocols, server privacy is not guaranteed. Specifically, a client may learn more than just the inference result, potentially gaining access to sensitive information such as model weights or training data. In delegated computation scenarios where the server’s model is public, such as in open-source large language models, this is not a major concern. However, in settings where the service provider aims to keep its model private—due to high training costs, risks of model jailbreaking, or legal and regulatory issues involving sensitive data such as health or legal information—protecting server privacy becomes critical. Thus, in this paper, we aim to address the following question in CKKS-based protocols.</p> <blockquote> <p>How can we protect the server’s privacy in CKKS-based MLaaS protocols?</p> </blockquote> <p><br/></p> <h2 id="2-server-privacy-in-homomorphic-evaluation-protocols">2. Server Privacy in Homomorphic Evaluation Protocols</h2> <div style="margin-top: 1.5em;"></div> <h3 id="circuit-privacy-is-insufficient">Circuit Privacy is Insufficient</h3> <p>For other homomorphic encryption (HE)-based protocols, protecting server privacy has been studied in the context of standard two-party computation (2PC) protocol security, which is often referred to as circuit privacy. The circuit privacy framework is suitable for two-party cryptographic protocols, such as oblivious pseudo-random function (OPRF) protocols <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, where the output reveals no information at all about the server’s input. However, for encrypted MLaaS protocols, where the output often contains too much information about the server’s input, circuit privacy does not guarantee server privacy, as it does not prevent leakage from the output itself.</p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-12 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2601_Jinyeong/oprf-480.webp 480w,/assets/img/blog/2601_Jinyeong/oprf-800.webp 800w,/assets/img/blog/2601_Jinyeong/oprf-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2601_Jinyeong/oprf.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 1: OPRF protocol </figcaption> </figure> <h3 id="estimating-privacy-leakage-via-differential-privacy">Estimating Privacy Leakage via Differential Privacy</h3> <p>To estimate server privacy leakage in CKKS-based MLaaS protocols, we utilize a differential privacy (DP)-based analysis beyond the circuit privacy framework. In plain MLaaS protocols, server privacy leakage is usually measured through the lens of differential privacy. In particular, it can be formalized through the notion of DP-prediction<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>, which is defined as follows.</p> <p><strong>Definition (DP Prediction).</strong> Let $M : \Theta \times \mathcal{X} \rightarrow \mathcal{Y}$ be a random algorithm. We say that $M$ is an $\epsilon$-DP prediction algorithm if, for every $x \in \mathcal{X}$, the output $M(\theta, x)$ is $\epsilon$-DP with respect to $\theta \in \Theta$. In other words, for all adjacent $\theta, \theta’ \in \Theta$ and all PPT algorithms $\mathcal{A}$, the following holds.</p> \[\Pr[ \mathcal{A}( M(\theta, x) ) = 1] \lesssim e^{\epsilon} \cdot \Pr[\mathcal{A}( M(\theta', x) ) = 1]\] <p>The above definition models an MLaaS protocol where the client’s query is $x$ and the server’s model weight or training data is $\theta$. Then, the above definition essentially says that server privacy is maintained regardless of the client’s query. Then, we model the ideal functionality of encrypted MLaaS protocols as evaluating some DP-prediction algorithm $M$, which can be described as follows.</p> <p><strong>Ideal Functionality.</strong> The ideal functionality $\mathcal{F}$ for encrypted MLaaS protocols is defined as follows.</p> <ul> <li><em>Client’s input</em>: $x \in \mathcal{X}$</li> <li><em>Server’s input</em>: $\theta \in \Theta$</li> <li><em>Client’s output</em>: $M(\theta, x)$</li> <li><em>Server’s output</em>: $\bot$</li> </ul> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-10 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2601_Jinyeong/ideal-480.webp 480w,/assets/img/blog/2601_Jinyeong/ideal-800.webp 800w,/assets/img/blog/2601_Jinyeong/ideal-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2601_Jinyeong/ideal.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 2: Ideal functionality </figcaption> </figure> <p>Based on the above ideal functionality, we define the server’s privacy in encrypted MLaaS protocols as follows.</p> <p><strong>Definition (Server Privacy).</strong> Let $\Pi$ be a two-party protocol that implements the ideal functionality $\mathcal{F}$. We say $\Pi$ achieves server privacy with parameter $\epsilon$ if an execution of $\Pi$ is an $\epsilon$-DP prediction with respect to the server’s input $\theta$. In other words, the following holds for all PPT adversaries $\mathcal{A}$ that manipulate the client, and all PPT environments $\mathcal{Z}$.</p> \[\Pr[\mathsf{Exec}[\Pi_{\theta}, \mathcal{A}, \mathcal{Z}] = 1] \lesssim e^{\epsilon} \cdot \Pr[\mathsf{Exec}[\Pi_{\theta'}, \mathcal{A}, \mathcal{Z}] = 1]\] <p>The above definition can be interpreted as a natural extension of DP-prediction in a two-party computation scenario. In other words, the above definition ensures that the protocol $\Pi$ protects the server’s input $\theta$ in terms of differential privacy against all adversarial clients’ inputs $x$.</p> <h3 id="circuit-privacy-implies-server-privacy">Circuit Privacy implies Server Privacy</h3> <p>One interesting corollary is that circuit privacy remains meaningful within our new definition of server privacy. To present more details, we recall the definition of circuit privacy below.</p> <p><strong>Definition (Circuit Privacy).</strong> Let $\Pi$ be a two-party protocol that implements the ideal functionality $\mathcal{F}$. We say $\Pi$ achieves circuit privacy if there exists a PPT simulator $\mathcal{S}$ such that the following holds for all PPT adversaries $\mathcal{A}$ that manipulate the client, and all PPT environments $\mathcal{Z}$.</p> \[\Pr[\mathsf{Exec}[\Pi_{\theta}, \mathcal{A}, \mathcal{Z}] = 1] \approx \Pr[\mathsf{Exec}[\mathcal{F}_{\theta}, \mathcal{S}, \mathcal{Z}] = 1]\] <p>Then, we can derive the following result.</p> <p><strong>Theorem (Circuit Privacy).</strong> Let $\Pi$ be a two-party protocol that implements the ideal functionality $\mathcal{F}$. Suppose the target model $M$ in $\mathcal{F}$ is an $\epsilon$-DP prediction and $\Pi$ achieves circuit privacy, then $\Pi$ achieves server privacy with parameter $\epsilon$.</p> <p>The above theorem says that if the target model $M$ is a DP-prediction algorithm and $\Pi$ achieves circuit privacy, then $\Pi$ guarantees server privacy. Thus, we can conclude that achieving circuit privacy is still meaningful in the context of encrypted MLaaS protocols if the target models are set to DP prediction algorithms.</p> <p><br/></p> <h2 id="3-differentially-private-homomrphic-evaluation">3. Differentially Private Homomrphic Evaluation</h2> <div style="margin-top: 1.5em;"></div> <h3 id="motivation">Motivation</h3> <p>Within our new server privacy notion, the problem of achieving server privacy seems to essentially boil down to achieving circuit privacy, which naturally leads to the following questions in the case of CKKS-based protocols.</p> <blockquote> <p>Can we achieve circuit privacy in CKKS-based protocols?</p> </blockquote> <p>The answer to the above question is <strong>No</strong> in general due to the peculiar structure of CKKS ciphertexts. To demonstrate the reasons, we first compare the ciphertext structure of CKKS with that of other exact HE schemes, such as BFV. In a BFV ciphertext, a noise term $e$ and a plaintext $m$ are strictly separated, so they do not interfere with each other unless $e$ exceeds the decryption bound. However, in a CKKS ciphertext, the noise $e$ and the plaintext $m$ exist in a fused state $m + e$, and they cannot be separated once encryption is performed. Thus, the size of the noise affects the precision of the plaintext after decryption, as they interfere with each other.</p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-10 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2601_Jinyeong/ctxt-480.webp 480w,/assets/img/blog/2601_Jinyeong/ctxt-800.webp 800w,/assets/img/blog/2601_Jinyeong/ctxt-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2601_Jinyeong/ctxt.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 3: Ciphertext structure of BFV &amp; CKKS </figcaption> </figure> <p>To achieve circuit privacy, one frequently utilized technique is noise flooding, which introduces additional noise $e’$ to erase any circuit information remaining in the noise part $e$. To achieve the indistinguishability notion in circuit privacy, the size of $e’$ is set to be exponentially larger than $e$. This is acceptable for BFV ciphertexts if $e + e’$ remains below the decryption bound, as the additional noise does not alter the value $m$ of the plaintext. However, for CKKS ciphertexts, excessive noise corrupts the plaintext value because they are fused. To be precise, performing noise flooding results in a decryption result of $m + e + e’$. If $e’ \gg m$, then the decryption result becomes entirely unusable for the client, even though circuit privacy is achieved.</p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-10 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2601_Jinyeong/flooding-480.webp 480w,/assets/img/blog/2601_Jinyeong/flooding-800.webp 800w,/assets/img/blog/2601_Jinyeong/flooding-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2601_Jinyeong/flooding.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 4: Ciphertext structure of BFV &amp; CKKS after noise flooding </figcaption> </figure> <h3 id="definition">Definition</h3> <p>We observe that the main difficulty in achieving circuit privacy arises from the computational indistinguishability requirement between the ideal functionality and the real protocol execution. However, if our final goal is to achieve server privacy, which is estimated in terms of differential privacy, requiring computational indistinguishability can be an overkill. Thus, we define the concept of <em>differentially private homomorphic evaluation</em><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> (DPHE) as follows to resolve this issue.</p> <p><strong>Definition (DPHE).</strong> Let $\Pi$ be a homomorphic evaluation protocol that implements ideal functionality $\mathcal{F}$. We say $\Pi$ is an $\epsilon$-DPHE protocol for $F$ if there exists a PPT simulator $\mathcal{S}$ such that the following holds for all PPT adversaries $\mathcal{A}$ that manipulate the client, and all PPT environments $\mathcal{Z}$.</p> \[\Pr[\mathsf{Exec}[\Pi_{\theta}, \mathcal{A}, \mathcal{Z}] = 1] \lesssim e^{\epsilon} \cdot \Pr[\mathsf{Exec}[\mathcal{F}_{\theta}, \mathcal{S}, \mathcal{Z}] = 1]\] \[\Pr[\mathsf{Exec}[\mathcal{F}_{\theta}, \mathcal{S}, \mathcal{Z}] = 1] \lesssim e^{\epsilon} \cdot \Pr[\mathsf{Exec}[\Pi_{\theta}, \mathcal{A}, \mathcal{Z}] = 1]\] <p>The above definition can be viewed as a relaxation of the indistinguishability notion in circuit privacy into something analogous to differential privacy. Once a protocol satisfies the DPHE property, we can derive the following result.</p> <p><strong>Theorem (DPHE).</strong> Let $\Pi$ be a two-party protocol that implements the ideal functionality $\mathcal{F}$. Suppose the target model $M$ in $\mathcal{F}$ is an $\epsilon$-DP prediction and $\Pi$ achieves $\epsilon’$-DPHE property, then $\Pi$ achieves server privacy with parameter $\epsilon+2\epsilon’$.</p> <p>Therefore, we can conclude that achieving the DPHE property is sufficient for server privacy instead of achieving circuit privacy.</p> <h3 id="instantiation">Instantiation</h3> <p>Once we verify that the DPHE property is sufficient, it naturally leads to the following next questions.</p> <blockquote> <p>Can we achieve the DPHE property in CKKS-based protocols?</p> </blockquote> <p>The answer is <strong>Yes</strong>, and we show how to instantiate a DPHE protocol by compiling existing CKKS-based protocols. The core idea is to utilize the Laplace mechanism<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> to achieve differential privacy. Suppose the ciphertext space is \(R_q = \mathbb{Z}_q[X]/(X^N + 1)\) and security parameter is given as $\mathbf{1}^{\lambda}$. We recall that to achieve circuit privacy, the noise flooding method introduces additional noise $e’$ whose norm is $O(2^{\lambda} \cdot B_e)$, where $B_e$ is an upper bound of the norm $\Vert e \Vert_{\infty}$ of the initial noise $e$. However, to achieve the DPHE property, it suffices to add additional noise $e’$ whose norm is $O(N B_e)$, which is significantly smaller than that of noise flooding.</p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-10 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2601_Jinyeong/dphe-480.webp 480w,/assets/img/blog/2601_Jinyeong/dphe-800.webp 800w,/assets/img/blog/2601_Jinyeong/dphe-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2601_Jinyeong/dphe.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption" style="text-align: center;"> Figure 5: Noise flooding vs. Laplace mechansim </figcaption> </figure> <p>The detailed procedure is as follows. Suppose a protocol $\Pi$ aims at evaluating a DP mechanism $M$ on the client’s encrypted input $\mathsf{ct}_{in} = \mathsf{Enc}(x)$. Let $\tau &gt; 0$ be an $L^1$-norm bound for the noise when evaluating $M$ in CKKS evaluation algorithms for $\theta \in \Theta$ and $x \in \mathcal{X}$. Then, the compilation is achieved as follows.</p> <ol> <li>$(c_0, c_1) \gets \mathsf{Eval} \big( M(\theta, \cdot), \mathsf{ct}_{in} \big) \pmod{q}$</li> <li>$(c’_0, c’_1) \gets (c_0, c_1) + (\lceil t \rfloor, 0) \pmod{q}$ for $t \gets \mathsf{Lap}(N \tau / \epsilon’)^N$</li> <li>\(\mathsf{ct}_{out} \gets q' \cdot (c'_0, c'_1) + \mathsf{Enc}_{\mathsf{pk}}(0) \pmod{qq'}\)  </li> </ol> <p>The second step is for achieving the differential privacy property on the client’s output plaintext, and the third step is to remove any remaining information in the ciphertext components by adding an encryption of zero. As a corollary, our compiler results in the following result.</p> <p><strong>Corollary (DPHE Compiler).</strong> Let $\mathcal{F}$ be the ideal functionality for homomorphic evaluation of an $\epsilon$-DP prediction algorithm $M$. Then, the DPHE compiler produces an $\epsilon’$-DPHE protocol $\Pi$ that implements $\mathcal{F}$, and $\Pi$ achieves server privacy with parameter. $\epsilon + 2\epsilon’$</p> <p>Therefore, by relaxing the security notion for homomorphic evaluation protocols, we succeed in achieving server privacy in CKKS-based protocols.</p> <p><br/></p> <h2 id="4-zkaok-for-ckks-ciphertexts">4. ZKAoK for CKKS Ciphertexts</h2> <div style="margin-top: 1.5em;"></div> <p>Our DPHE compiler is based on the assumption that the input ciphertext is well-formed and the input message lies in the domain $\mathcal{X}$. However, for malicious clients, there is no guarantee that the input ciphertext satisfies these conditions. Thus, for the server to verify these conditions without compromising the client’s privacy, we need a zero-knowledge argument of knowledge (ZKAoK) for CKKS ciphertexts.</p> <p>However, designing ZKAoK for CKKS is nontrivial, and there have been no previous attempts for it. The main difficulty arises from the lack of techniques for verifying the validity of the message, i.e., $\vec{x} \in \mathcal{X} \subseteq \mathbb{R}^N$. To be precise, when encrypting a message $\vec{x} \in \mathbb{R}^N$, it is encoded into a polynomial $m(X) \in R_q$ that lies in the ciphertext space $R_q = \mathbb{Z}_q[X]/(X^N + 1)$. Current ZKAoK for HE ciphertexts<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> only support verification of arithmetic relations defined over $R_q$, whereas we need to verify the condition $x \in \mathcal{X}$, which is defined over $\mathbb{R}^N$.</p> <h3 id="solution">Solution</h3> <p>We address this issue by delegating the encoding procedure to the server, described below.</p> <ol> <li>For an input message $\vec{x} := (x_0, \dots, x_{N-1}) \in \mathbb{R}^N$, the client generates a plaintext $m’(X) \in R_q$ with scaled coefficient packing as follows. <ul> <li>$m’(X) = \lceil \Delta x_0 \rceil + \lceil \Delta x_1 \rceil X + \cdots + \lceil \Delta x_{N-1} \rceil X^{N-1}$</li> </ul> </li> <li>The client generates a ciphertext $\mathsf{ct}’ = (a’, b’) \in R_q^2$ from $m’(X)$ and proves the following relations hold through ZKAoK for HE ciphertexts. <ul> <li>$b’ - a’ s = m’ + e’ \pmod{q}$</li> <li>$\Vert s \Vert_{\infty} \le B_s$ and $\Vert e’ \Vert_{\infty} \le B_e$</li> <li>$\mathsf{Coeff}(m’) \in \lceil \Delta \mathcal{X} \rceil \subseteq \mathbb{Z}_q^N$</li> </ul> </li> <li>The server verifies the ZKAoK for the ciphertext $\mathsf{ct}’$, and obtains the actual input ciphertext $\mathsf{ct}$ as follows. <ul> <li>$\mathsf{ct} \gets \mathsf{CoeffToSlot}(\mathsf{ct}’)$</li> </ul> </li> </ol> <p>The scaled coefficient packing in the first step allows a client to generate a ZKAoK for verifying the input domain in $R_q$. After the server verifies this ZKAoK, it can generate an input ciphertext whose slot values correspond to these coefficient values by applying the coeff-to-slot operation. As a result, the server can ensure that input ciphertexts are well-formed, i.e., the input messages lie within the input domain.</p> <h3 id="benchmark">Benchmark</h3> <p>With the above technique, we instantiate the first ZKAoK for CKKS ciphertexts<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup> that can verify the input domain. Specifically, we construct the ZKAoK for CKKS, which proves that input messages of ciphertexts lie in $[-1, 1]^N$, together with well-formedness of public keys, including encryption, relinearization, rotation key, and conjugation key. In the following table, we provide the concrete benchmark results, where $k$ denotes the number of ciphertexts, $\Delta$ denotes the scaling factor, <strong>PK Size</strong> denotes the total size of public keys, and <strong>CT Size</strong> denotes the total size of ciphertexts. The performance is measured on an Intel Xeon Platinum 8268 CPU with a single thread.</p> <div style="margin-top: 1.5em;"></div> <table> <thead> <tr> <th>$k$</th> <th>$ \log\Delta $</th> <th>PK Size</th> <th>CT Size</th> <th>Proof Size</th> <th>Prover Time (s)</th> <th>Verifier Time (s)</th> </tr> </thead> <tbody> <tr> <td>2</td> <td>16</td> <td>39.5 MB</td> <td>1.57 MB</td> <td>17.9 MB</td> <td>324.35</td> <td>50.88</td> </tr> <tr> <td>4</td> <td>16</td> <td>39.5 MB</td> <td>3.14 MB</td> <td>18.9 MB</td> <td>365.46</td> <td>56.08</td> </tr> <tr> <td>8</td> <td>16</td> <td>39.5 MB</td> <td>6.28 MB</td> <td>21.0 MB</td> <td>442.86</td> <td>67.13</td> </tr> <tr> <td>2</td> <td>32</td> <td>39.5 MB</td> <td>1.57 MB</td> <td>18.7 MB</td> <td>356.90</td> <td>54.65</td> </tr> <tr> <td>4</td> <td>32</td> <td>39.5 MB</td> <td>3.14 MB</td> <td>20.4 MB</td> <td>425.26</td> <td>64.33</td> </tr> <tr> <td>8</td> <td>32</td> <td>39.5 MB</td> <td>6.28 MB</td> <td>24.0 MB</td> <td>561.28</td> <td>83.63</td> </tr> </tbody> </table> <p><br/></p> <h2 id="5-conclusion">5. Conclusion</h2> <div style="margin-top: 1.5em;"></div> <p>In summary, we examine the server privacy issues in CKKS-based protocols, particularly for encrypted MLaaS protocols. The key takeaways are as follows.</p> <ul> <li>We formalize the security notion for server privacy based on differential privacy.</li> <li>We achieve server privacy for CKKS without noise flooding.</li> <li>We construct the first zero-knowledge argument of knowledge for CKKS to handle malicious clients.</li> </ul> <hr/> <p><br/></p> <h2 id="references">References</h2> <div style="margin-top: 1.5em;"></div> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1"> <p>Martin R. Albrecht, Alex Davidson, Amit Deo, and Daniel Gardham. “Crypto Dark Matter on the Torus: Oblivious PRFs from shallow PRFs and TFHE.” Eurocrypt 2024. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:2"> <p>Cynthia Dwork and Vitaly Feldman. “Privacy-preserving prediction.” COLT 2018. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:3"> <p>Intak Hwang, Seonhong Min, Jinyeong Seo, and Yongsoo Song. “On the security and privacy of CKKS-based homomorphic evaluation protocols.” Asiacrypt 2025. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:4"> <p>Cynthia Dwork, Frank McSherry, Kobbi Nissim, and Adam Smith. “Calibrating noise to sensitivity in private data analysis.” TCC 2006. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:5"> <p>Intak Hwang, Hyeonbum Lee, Jinyeong Seo, and Yongsoo Song. “Practical zero-knowledge PIOP for maliciously secure multiparty homomorphic encryption.” ACM CCS 2025. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:6"> <p><a href="https://github.com/SNUCP/ckks-piop">https://github.com/SNUCP/ckks-piop</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>]]></content><author><name>Jinyeong Seo</name></author><summary type="html"><![CDATA[TL;DR: We investigate methods for protecting server privacy in CKKS-based protocols. Unlike exact homomorphic encryption schemes, formally defining security notions for the server is challenging in CKKS-based protocols due to the approximate nature of CKKS. We address this by introducing a new security notion called Differentially Private Homomorphic Encryption, which is motivated by differential privacy. Based on this notion, we construct a general compiler that transforms CKKS-based protocols into DPHE protocols. We also present the first zero-knowledge argument of knowledge for CKKS ciphertexts to protect server privacy against malicious clients.]]></summary></entry><entry><title type="html">Homomorphic Encryption for Data Science</title><link href="https://ckks.org/blog/2025/HE4DS/" rel="alternate" type="text/html" title="Homomorphic Encryption for Data Science"/><published>2025-12-08T09:12:00+00:00</published><updated>2025-12-08T09:12:00+00:00</updated><id>https://ckks.org/blog/2025/HE4DS</id><content type="html" xml:base="https://ckks.org/blog/2025/HE4DS/"><![CDATA[<ul> <li>Written by Allon Adir, Ehud Aharoni, Nir Drucker, Ronen Levy, Hayim Shaul, Omri Soceanu (IBM Research, Israel)</li> <li>Based on <a href="https://link.springer.com/book/10.1007/978-3-031-65494-7">https://link.springer.com/book/10.1007/978-3-031-65494-7</a> (Homomorphic Encryption for Data Science)</li> </ul> <p><em>TL;DR: FHE has advanced significantly since its introduction fifteen years ago, yet it remains challenging to use efficiently. We examine methods addressing three of the major challenges faced by cryptographers and data scientists face when using FHE: data packing; polynomial approximations and data traversal.</em></p> <hr/> <p><br/></p> <p>More than a decade and a half has passed since the publication of Gentry’s original paper [1] about Fully Homomorphic Encryption (FHE). Since then, real progress has been made transforming FHE from a theoretical tool accessible only to a few into a practical solution with rapidly improving usability, performance, and abstraction. Still, the development of practical applications and data science models remains challenging for both cryptographers and data scientists. The three major challenges are outlined below and discussed in subsequent sections:</p> <ul> <li>Efficient data packing</li> <li>Accurate polynomial approximation of analytical functions</li> <li>Efficient data traversal</li> </ul> <p><br/></p> <h2 id="efficient-data-packing-and-tile-tensors">Efficient Data Packing and Tile Tensors</h2> <p>Several FHE schemes (e.g. BGV [2], BFV [3], CKKS [4]) encode a vector of plaintext values as polynomial coefficients and operate on it element-wise. Thus, encrypted data operations in many FHE schemes can be viewed as Single Instruction, Multiple Data (SIMD). Efficiently mapping complex data to these coefficients (or vector slots) is often a pain-point for developers. Tile tensors [5] simplify this packing process by defining the layout of tensors and their tiling using high-level “tile tensor shapes”. Tile tensors extend known packing approaches, allowing researchers to focus on the algorithms rather than on the ciphertext internals.</p> <p>The idea behind tile tensors is to think of a ciphertext as a <em>tile</em> with a configurable shape and cover tensors with these tiles. This implies that the size of a tile equals the number of slots each ciphertext has. Changing the shape of the tiles changes how a tensor is packed into ciphertexts. For example, when covering a 2-dimensional matrix with a tile of shape 1x8 (assuming 8 slots in a ciphertext, for simplicity) we get row-based packing. With a tile of shape 8x1 we get column-based packing. Other shapes, such as 2x4 are also possible. See Figure 1 for an example.</p> <p><br/></p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-7 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2512_Hayim/image1-480.webp 480w,/assets/img/blog/2512_Hayim/image1-800.webp 800w,/assets/img/blog/2512_Hayim/image1-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2512_Hayim/image1.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption"> Figure 1: Packing a 5x6 matrix in two different ways, assuming a ciphertext has 8 slots. On the left, a tile has the shape of 2x4. In this case the matrix is partitioned into 6 ciphertexts (tiles), padding with 0s where needed. On the right, a tile has a shape of 1x8. In this case the matrix is partitioned into 5 ciphertexts. Switching between the packings is done simply by changing the tile shape. The image is taken from a tutorial given in CCS’22 and is available online [6]. </figcaption> </figure> <p><br/></p> <h2 id="efficient-and-accurate-polynomial-approximating">Efficient and Accurate Polynomial Approximating</h2> <p>Another fundamental FHE challenge is function evaluation. Most FHE schemes support a handful of arithmetic primitives, such as element-wise addition, multiplication and vector rotation. Any operation beyond these primitives, e.g. division, comparison, not to mention complex neural network activation functions, requires some approximation. One can construct polynomials using only additions and multiplications, and then use them to approximate almost any function to a desired degree of accuracy. However, since the use of any primitive comes with an inherent computational cost, every operation should be assessed with scrutiny, to minimize the amount and type of operations to only those that are necessary to meet accuracy goals. The art here is twofold: devising polynomial approximations that are both accurate and of low-enough degree to achieve acceptable performance and representing and evaluating these polynomials in ways optimized for FHE. Efficient polynomial evaluation, packing tricks (that can be implemented with Tile Tensors), and circuit crafting are all essential.</p> <p><br/></p> <h2 id="efficient-data-traversal">Efficient Data Traversal</h2> <p>Since algorithms under FHE deal with encrypted messages they are unable to make any decision based on the encrypted input. This includes branching dynamically, i.e. take a path of the code depending on encrypted input. See for example [7]. Instead, algorithms need to evaluate all possible branches and multiplex (“select”) their output. Even a simple algorithm such as searching a binary tree becomes exponentially more expensive in FHE because it needs to traverse every path of the tree and not just a single one as in the cleartext case. Algorithms such as those needed in private database queries or machine learning (decision trees) suffer from this limitation and are not efficient under FHE.</p> <p>Different methods have been proposed, among which is “Copy-and-Recurse” [8, 9]. Rather than branching, which is problematic under FHE, this method creates a copy (under FHE) of the branch that the algorithm needs to take and continues with this copy. Creating this copy requires a constant number of multiplications and additions for each node, but then any the number of expensive computations that are done at nodes (e.g., comparisons) is proportional to the cleartext algorithm.</p> <p><br/></p> <figure class="figure-class"> <div class="row mt-3"> <div class="col-sm-7 mt-3 mt-md-0 mx-auto d-block"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/blog/2512_Hayim/image2-480.webp 480w,/assets/img/blog/2512_Hayim/image2-800.webp 800w,/assets/img/blog/2512_Hayim/image2-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/blog/2512_Hayim/image2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> <figcaption class="figure-caption"> Figure 2: Traversing a search tree using copy-and-recurse. To reach the black leaf we multiplex both branches to create a single copy which we recurse into. The number of operations is still linear but the number of the (expensive) comparison is only logarithmic. </figcaption> </figure> <p><br/></p> <h2 id="a-book-for-the-practicing-cryptographer-and-data-scientist">A Book for the Practicing Cryptographer and Data Scientist</h2> <p>These challenges, alongside techniques and practical recipes to solve them, are important for algorithmic researchers attempting to utilize FHE. Each topic is explained further with concrete examples, code templates, and in-depth discussions in the book “<strong>Homomorphic Encryption for Data Science (HE4DS)</strong>” [10]. For cryptographers determined to bridge the theory-practice gap, and for data scientists eager to harness encrypted computation for real-world ML and analytics tasks, this text stands as an invitation: the road is still rugged, but the right tools and abstractions exist. The landscape of FHE is shifting from a field of delicate manual hacks to one supported by high-level libraries and reproducible patterns. Understanding these new abstractions will empower the next generation of privacy-preserving applications.</p> <hr/> <p><br/></p> <h2 id="references">References</h2> <p>[1] Craig Gentry. “Fully Homomorphic Encryption Using Ideal Lattices.” Proceedings of the 41st Annual ACM Symposium on Theory of Computing (STOC 2009), pp. 169–178. ACM, 2009. DOI: 10.1145/1536414.1536440</p> <p>[2] Zvika Brakerski, Craig Gentry, and Vinod Vaikuntanathan. “(Leveled) Fully Homomorphic Encryption without Bootstrapping.” Proceedings of the 3rd Innovations in Theoretical Computer Science Conference (ITCS 2012), pp. 309–325. ACM, 2012.</p> <p>[3] J. Fan and F. Vercauteren. Somewhat practical fully homomorphic encryption. IACR Cryptol. ePrint Arch., 2012:144.</p> <p>[4] Jung Hee Cheon, Andrey Kim, Miran Kim, and Yongsoo Song. “Homomorphic Encryption for Arithmetic of Approximate Numbers.” In Advances in Cryptology – ASIACRYPT 2017, Lecture Notes in Computer Science, vol. 10624, pp. 409–437. Springer, 2017.</p> <p>[5] Ehud Aharoni, Allon Adir, Moran Baruch, Nir Drucker, Gilad Ezov, Ariel Farkash, Lev Greenberg, Ramy Masalha, Guy Moshkowich, Dov Murik, Hayim Shaul, and Omri Soceanu. “HeLayers: A Tile Tensors Framework for Large Neural Networks on Encrypted Data.” Proceedings on Privacy Enhancing Technologies (PoPETs), 2023(1): 325–342. DOI: 10.56553/popets-2023-0020</p> <p>[6] Ehud Aharoni, Nir Drucker and Hayim Shaul, “Tutorial: Advanced HE packing methods with applications to ML”, ACM CCS 2022, https://research.ibm.com/haifa/dept/vst/tutorial_ccs2022.html.</p> <p>[7] Sunchul Jung, “Convergent Evolution: Why Secure Homomorphic Encryption Will Resemble High-Performance GPU Computing”, https://ckks.org/blog/2025/convergent-evolution/#21-the-fhe-security-model-and-the-turing-barrier</p> <p>[8] Eyal Kushnir, Guy Moshkowich, and Hayim Shaul. “Secure Range-Searching Using Copy-And-Recurse.” Proceedings on Privacy Enhancing Technologies (PoPETs), 2024(3).</p> <p>[9] Eyal Kushnir and Hayim Shaul. “Improved Range Searching and Range Emptiness Under FHE Using Copy-And-Recurse.” Cryptology ePrint Archive, Paper 2025/751, 2025. Available at: https://eprint.iacr.org/2025/751</p> <p>[10] Adir, A., Aharoni, E., Drucker, N., Levy, R., Shaul, H., and Soceanu, O. Homomorphic Encryption for Data Science (HE4DS). Springer Nature Switzerland, 2024. ISBN 9783031654947. Available at: https://link.springer.com/book/10.1007/978-3-031-65494-7</p>]]></content><author><name>Allon Adir, Ehud Aharoni, Nir Drucker, Ronen Levy, Hayim Shaul, Omri Soceanu</name></author><summary type="html"><![CDATA[TL;DR: FHE has advanced significantly since its introduction fifteen years ago, yet it remains challenging to use efficiently. We examine methods addressing three of the major challenges faced by cryptographers and data scientists face when using FHE: data packing; polynomial approximations and data traversal.]]></summary></entry></feed>