Repository navigation
Updates main to v1.6.0 - #1362
Merged
Merged
Conversation
…int8_t and removed their aliases from inttypes.h (#1168) * Remove usshort and uschar from inttypes.h and replaced their usage with uint16_t and uint8_t * Removed usint from inttypes.h and replaced its usage with uint32_t * Pass integer parameter by value to function instead of by constant reference --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
Added funding acknowledgment for ARPA-H research.
…v5 or v6) (#1182) * Changes for migration from Node.js 20 to 24 actions * Start using Doxyfile for the docs build * Revert "Start using Doxyfile for the docs build" This reverts commit 1c81c89. * Do not extract anonymous namespaces * Additional changes for migration from Node.js 20 to 24 and docs generation * More changes for docs * Suppress the anonymous namespace entries * Test changes * Revert "Test changes" This reverts commit 3a2af45. --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
…/cryptocontext-ser.h, so the functions from src/core/include/utils/serial.h can be used directly (#1191) Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
…rs of the same class, moved some trivial getters from .cpp to .h files, remove some "this->" for class data and function members, added m_ to a privateKey data member in Cryptocontext, changed a few error messages, removed PrintIntegerConstants (unused function), removed duplicate returns (#1194) Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
… to gen-cryptocontext-params-validation.cpp (#1197) Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Added SerializeEvalBootstrapKey()/DeserializeEvalBootstrapKey() and fixed some bugs. * Added DeserializeEvalBootstrapKey() overload with number of slots as argument, example file and unittests * Addressed code review comments --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Performance: cache paramsQl for HYBRID KeySwitchDown * Fix: use DCRTPoly::Params instead of private ParmType in helper * Removed the helper function and simplified the fix * Additional changes for the cached params * cached params optimizations --------- Co-authored-by: Dr. Amneek Singh <amneek@amneek.tech> Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
* Corrected exception handling for COMPOSITESCALING* * Corrected unittests --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* feat: expose ClearBootstrapPrecom to release cached bootstrap data (#533) FHECKKSRNS::m_bootPrecomMap is populated by EvalBootstrapSetup and EvalBootstrapPrecompute but never released by the existing ClearEval*Keys or ReleaseAllContexts APIs when a user still holds a CryptoContext reference, so the documented "clear" paths leave that memory alive for the lifetime of the process. Add a virtual FHEBase::ClearBootstrapPrecom() (default no-op), override it in FHECKKSRNS to clear m_bootPrecomMap, and expose matching forwarders on SchemeBase and CryptoContextImpl so callers can free the precomputations without destroying the context. * feat: release CKKS scheme caches and trim allocator (#533) Extends the initial ClearBootstrapPrecom work so callers can actually drop every piece of scheme-level memory a CKKS CryptoContext retains, without destroying the context itself. - Add per-slot ClearBootstrapPrecom(slots) and ClearBootstrapPrecomExcept(keep) on FHEBase / SchemeBase / CryptoContextImpl so callers can evict a single transient precomputation or keep only a hot entry. - Add HasBootstrapPrecom() inspectors (whole-map and per-slot) for tests and memory-diagnostic code. - Introduce FHEBase::ClearSchemeSwitchPrecom() overridden by SWITCHCKKSRNS to drop the intermediate CKKS/BinFHE contexts, CKKS<->FHEW switching keys, auxiliary ciphertext, and the precomputed decoding-matrix plaintexts that EvalCKKStoFHEWSetup / EvalSchemeSwitchingSetup populate and which the existing Clear*Keys paths never released. - Add CryptoContextFactory::ReleaseAllContextsAndTrim() which extends ReleaseAllContexts() by also invoking the scheme-level clears on every tracked context and asking the allocator to return pages to the OS via the new TrimAllocator() helper (malloc_trim on glibc, no-op elsewhere). - Mark the cache-clear APIs noexcept so they are safe to call from destructors and shutdown paths. Adds UnitTestCKKSCacheClear (15 tests) covering per-slot / keep-except / full-release semantics, scheme-switch precom release, inspector agreement, and the ReleaseAllContextsAndTrim path. Adds ckks-release-memory example demonstrating the three common usage patterns. Closes #533 * test: use 128-bit-compatible params in scheme-switch helper The test helper used modulus sizes and a scaling style that only work on 32/64-bit builds, so it threw on the 128-bit CI job. Pick larger moduli and FIXEDAUTO scaling when NATIVEINT == 128, matching what UnitTestSchemeSwitch.cpp already does. Production code is unchanged. * test: skip MemoryDropsAfterBootstrapClear when heap probe is unavailable When the build links an alternative allocator like TCMalloc (WITH_TCM=ON), mallinfo2() and malloc_zone_statistics() return 0 because they only see the system malloc that TCMalloc replaced. The relative-drop check is unmeasurable in that case, so call GTEST_SKIP() instead of failing the assertion. The TrimAllocator contract test (TrimAllocatorIsSafe) still runs and pins the no-throw + capability-flag behavior on every backend. * Removed TrimAllocator(), s_autoReleaseMode and unnecessary functions. * Simplified ckks-release-memory.cpp and some functions. * Simplified UnitTestCKKSCacheClear.cpp * Added debug statements * Corrected unittest conditions --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Modify default policy to keep large allocations on the heap and never auto-trim * update Best_Performance.md * Improve explanation of memory allocation policy Clarify memory allocation behavior and performance impact. --------- Co-authored-by: yspolyakov <89226542+yspolyakov@users.noreply.github.com>
…ep Horner BSGS and other LT optimizations (#1209) * Reduce CKKS bootstrapping rotation-key footprint in the homomorphic FFT Reformulate the baby-step/giant-step (BSGS) evaluation of the CoeffsToSlots and SlotsToCoeffs linear transforms so that the same slot permutations are realized with far fewer distinct rotation (automorphism) keys. The keys these transforms need dominate the memory cost of a bootstrapping context, and EvalBootstrapKeyGen now generates a smaller, more heavily shared set. The underlying math is unchanged. Core changes to EvalCoeffsToSlots / EvalSlotsToCoeffs and their precompute: - Horner single giant-step. Replace the forward outer sum sum_i Aut_{i*t}(block_i) with the nested Horner form block_0 + Aut_t(block_1 + Aut_t(...)). Both use the same rotation count, but Horner needs only one giant-step key per level (stride t = g*scale) instead of the b-1 distinct keys {t, 2t, ..., (b-1)t}. - Zero-based hoisted inner rotations. Replace the centered inner baby-step indices {(j-offset)*sigma} with zero-based {j*sigma}. The j=0 term is now always rotation 0 and is handled directly by KeySwitchExt (no key), and the per-level offset delta_s = offset*scale is pushed offline into the precomputed plaintexts as a pre-rotation. - Folded per-level corrections. The per-level zero-basing corrections (an O(levels) set of runtime EvalAtIndex calls in both transforms) are commuted forward and absorbed into the precomputed plaintexts, leaving a single accumulated rotation applied once at the end of SlotsToCoeffs. EvalMod is equivariant under slot rotations, so the CoeffsToSlots correction passes through it unchanged. - Split, not combined, accumulated correction. Apply Aut_{-(slots-1)} at the end of CoeffsToSlots so it always outputs correctly-ordered slots, rather than deferring a single combined correction to the end of SlotsToCoeffs. This fixes EvalFBTNoDecoding + EvalHomDecoding (FBT_CONSECLEV), where a user operation between the two transforms previously saw a residual rotation. - Sparse-packing index reduction. Reduce all BSGS rotation indices modulo min(2*slots, M/4). Under sparse packing the precomputed-plaintext vector has cyclic period 2*slots (the concatenated real/imaginary blocks), not M/4, so indices reduced only to [0, M/4) could otherwise be inconsistent with the period-2*slots plaintext pre-rotations. For full and half packing the modulus equals M/4 and behavior is unchanged. The single-level linear transform (EvalLinearTransform, used when the level budget is 1) is converted to the same single-giant-step Horner form, and FindLinearTransformRotationIndices no longer emits the giant-step keys {2g, 3g, ..., (h-1)g} that the forward form required. Supporting cleanups (no behavior change): - Inline the precomputed rot_in index tables in EvalCoeffsToSlots / EvalSlotsToCoeffs (compute per level, drop the 2D allocation and a redundant scale pass). - Fix the over-large reserve() in the Find*RotationIndices helpers (they reserved ~M entries for a list of a few hundred). - Hoist a redundant KeySwitchExt out of the EvalLinearTransform giant-step loop, and hoist repeated GetParams()/GetElementAtIndex() calls in ExtendCiphertext. - Take crypto parameters by const reference and compute the bootstrap scale factor with std::ldexp. - Add Doxygen for the transform functions and a note in CKKS_BOOTSTRAPPING.md. Not included: the SlotsToCoeffs decode-layer re-partition that moves the remainder group to scale 1 to mirror CoeffsToSlots. The StC remainder still sits at the last (largest-scale) position, so the additional key sharing that re-partition enables (e.g. dropping the StC-specific large-scale remainder key) is not realized here. * Apply single-giant-step Horner BSGS to CKKS/FHEW scheme-switching transforms * map-hoist + seed-peel of the Horner giant-step loop * updated comments and simplified scheme-switching LT rotation-index generation to closed form
…text, ConstPlaintext& plaintext) (#1217)
…he extended basis (#1225) * unification of EvalFastRotation/EvalRotate/EvalAutomorphism/Conjugate * lazy extended-basis accumulation (deferred ApproxModDown) for PartialSum, Horner CtS/StC, and scheme-switching linear transforms * generalize EvalPartialSumInPlace into a radix-parameterized rotation fold * add EvalPartialSumInPlace radix as a CMake option (CKKS_PARTIAL_SUM_RADIX, default 4) * extend the radix knob to EvalSum, dedupe multiparty keygen, and rework the *Many operations Renames CKKS_PARTIAL_SUM_RADIX to PARTIAL_SUM_RADIX: the knob is no longer CKKS-specific because it now also drives the scheme-generic EvalSum/EvalSumRows/EvalSumCols family. All four folds route through one EvalSumRadixFold that, under HYBRID key switching, accumulates lazily in the extended basis exactly like EvalPartialSumInPlace (one deferred ApproxModDown for element 0, per-level settle for element 1, one digit decomposition per level); a hoisted eager fallback covers non-HYBRID via a new SchemeBase::EvalAutomorphismCore wrapper, needed because EvalSum keys live in their own key map. EvalAddMany becomes a null-tolerant left fold with a single clone (addition is exact and associative, so the pairing tree bought nothing; ~1.5-2x vs dev, growing with tower count) and EvalAddManyInPlace an actually-in-place serial fold with zero allocations (~1.8-2.3x); its result lands in slot 0 per the in-place contract, and null-leading vectors work for the first time. Fixes a size-1 aliasing bug in the cryptocontext wrappers: EvalAddMany/EvalMultMany returned the caller's input handle for single-element vectors, so mutating the result silently corrupted the input; results never alias inputs now (EvalAddMany's early return is dropped, EvalMultMany's becomes a keyless Clone). Unifies EvalMultMany: the base tree picks levelsToDrop from GetCompositeDegree() (1 everywhere except composite-scaling CKKS), making the AdvancedSHECKKSRNS override redundant; it is removed. The unified fold keeps the three-phase structure, clones for size 1 (also fixing an empty-scratch-vector UB at the scheme layer), and frees each consumed partial immediately: live intermediates drop from n-1 to ~n/2, measured -46% peak scratch at n=32 with unchanged runtime and pairing order (bit-identical). * fix fully-packed EvalSum key coverage for smaller batches The fully-packed BFV/BGV branch kept the legacy doubling index set, which no longer covers EvalSum calls at smaller batch sizes once those fold at the configured radix (radix-2 sets were nested across all batch sizes; e.g. EvalInnerProduct with default keygen requested key 5^3). The fully-packed path now radix-folds its first phase (covering batchSize/2) with only the m-1 conjugation tail kept special, in both keygen and the fold, making the fully-packed key set a superset of every smaller batch's set again. * Update cmake_in_openfhe.rst
…1229) * made the multiparty limitation explicit in the scheme switching md * Update memory consumption note in documentation Clarified memory consumption explanation for scheme switching. --------- Co-authored-by: yspolyakov <89226542+yspolyakov@users.noreply.github.com>
Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
…composition (#1238) * issue #1226 Avoid UB signed left shifts in RingGSWAccumulator::SignedDigitDecompose * Decouple carry update from digit extraction in SignedDigitDecompose The carry (d0 - r0) >> gBits equals (d0 + gHalf) >> gBits since the digit r0 lies in [-gHalf, gHalf). The new form removes the extracted digit from the loop-carried dependency chain (4 -> 2 cycles per digit) and drops the now-unneeded digit extraction ahead of the loop. Output is bit-identical. * Vectorize SignedDigitDecompose via digit-major loop interchange Process one gadget digit at a time across the whole polynomial, carrying the running quotient per coefficient in a scratch array. The inner loops become unit-stride and auto-vectorize (AVX2/AVX-512) when built with native optimizations; output is bit-identical to the previous order. * Replace signed digit extraction with excess-half bias in SignedDigitDecompose Bias each coefficient once by H (gHalf in every digit position); every balanced digit then reads out as an independent unsigned window ((w >> k*gBits) & gMask) - gHalf. This removes the per-digit carry chain and all signed-shift arithmetic, drops the SignExtend helper, and speeds up the decomposition a further 1.2-1.8x over the digit-major form (up to 10.6x total vs the original). Output is bit-identical. Requires digitsG*gBits < MaxBits, satisfied by all supported parameter sets.
Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
…ies (#1244) Add a bulk NativeVector::BaseDecompose that extracts all base-2^digitSize digits at once as balanced digits in [-B/2, B/2) of the centered representative: the excess-half bias is applied once per coefficient and every digit is then an independent shift-and-mask window, with the top window absorbing the remaining quotient so reconstruction is exact mod q. Decompositions whose bias would not fit the native word fall back to unsigned windows. All predicates are computed as bit masks so the loops auto-vectorize at the baseline ISA as well as with native optimizations. Key material is unchanged; balanced digits halve the per-digit magnitude bound, matching the relinBase/2 factor already assumed by the BGV noise estimate, and reduce measured BV key-switching noise by ~0.9 bits (stddev) / ~0.6 bits (max).
* fixes larger noise for some scenarios of composite scaling * revised the comment * Fixed ExtendCiphertext * fixes the bug introduced in ExtendCiphertext * fixed the precision issue when firstModSize > scalingModSize + 1 * reduced CS error for a deep ct x pt circuit * reduced the drift in the scaling factor for deeper computations * added unit tests for fixed CS bugs * fixed deg > 5 bug * generalized the key switching fix for composite scaling modes to all scaling techniques + added it to EstimateLogP * Refactored ExtendCiphertext to move precomputations to CKKS Parameters RNS class * disabled DeepComputationWithFillers for MATHBACKEND 2 and removed cout statements from CS-vs-FA unit tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cleanup * composite scaling P margin: 4*d -> 6 + ceil(log2(d)) per the RNS-hybrid noise bound --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
- Guard right shifts and truncate tiny scalar magnitudes toward zero. Apply left shifts modulo each CRT tower to avoid overflow, and restore negative multiplication factors using modular subtraction. - Add regression coverage for shift boundaries, subnormal and large operands, complex scalars, and increased ciphertext scales. - Validated with 115 CKKS tests and shift sanitization. Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Remove the K = 768 overflow bound for composite scaling bootstrapping (#1310) The larger bound (K_UNIFORMEXT with the g_coefficientsUniformExt table) was selected for UNIFORM_TERNARY with composite degree > 2, or degree 2 at ring dimension >= 2^17. It compensated for the approximate RNS basis extension of the original ExtendCiphertext, which inflated the post-ModRaise overflow by ~40%. With the exact HPS basis extension the composite overflow statistics match the non-composite ones, so K = 512 suffices everywhere. - remove K_UNIFORMEXT, g_coefficientsUniformExt, and the degree/ring-dimension switches in EvalBootstrap and EvalBootstrapStCFirst - remove the mirrored UNIFORM_TERNARY composite restriction in EvalFEFuncBootstrapSetup - document the uniform K = 512 bound in COMPOSITE_SCALING.md - add FEFBT unit tests for composite degree 3 with UNIFORM_TERNARY Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Update ckksrns-fhe.cpp --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: pascoec <123595534+pascoec@users.noreply.github.com>
* Add a pluggable diagnostic output channel Route the library's diagnostic writes through OPENFHE_LOG_ERR / OPENFHE_LOG_OUT (each a std::ostream&) instead of std::cerr / std::cout directly, so an embedding application can redirect OpenFHE's diagnostics without editing call sites. Because the macros expand to std::ostream&, every operator<< overload and manipulator keeps working unchanged. The default sink (std::cerr / std::cout) lives in one translation unit, openfhe_log_default.cpp, compiled only when the new OPENFHE_DEFAULT_LOG_SINK CMake option is ON (the default). With the default sink ON the behavior is byte-for-byte identical to before. Embedders may redirect at runtime via SetOpenFHEErrStream / SetOpenFHEOutStream, or build with -DOPENFHE_DEFAULT_LOG_SINK=OFF and supply their own accessor definitions to keep std::cerr / std::cout out of the compiled archive entirely -- e.g. an R package, where CRAN forbids compiled code that writes to stdout/stderr. Converts the existing std::cerr / std::cout diagnostic sites in core, binfhe, and pke to the new macros. No functional change under the default configuration. * Clarify diagnostic output naming and simplify documentation * Complete the diagnostic output migration and fix the sink-off build Several diagnostic sites still bypassed the channel. ckksrns-fhe.cpp kept one std::cerr in the sparse-slots branch of EvalBootstrap, and the debug.h OPENFHE_DEBUG*/PROFILELOG* macros, ubintntl.h, cryptocontext.h and matrixstrassen-impl.h -- all headers compiled into the library or installed for downstream use -- were never converted. Routing them through the channel is what makes the stated goal measurable: with -DWITH_DEFAULT_DIAGNOSTIC_SINK=OFF, nm now reports zero cerr/cout references in libOPENFHEcore, libOPENFHEpke and libOPENFHEbinfhe -- the condition an embedder such as an R package on CRAN has to meet. That configuration also did not link. Nothing defines the accessors when the sink is off, so every executable failed at link time. The built-in sink is now compiled into OpenFHE's own executables through an INTERFACE target, OPENFHEdiagnosticsink, kept out of the library and not installed. INTERFACE rather than static, because ld will not extract an archive member to satisfy an undefined symbol that comes from a shared library. This also restores the note that OFF is meant for static builds and is rejected when the library is created on macOS and Windows. Output returns to its pre-refactor form. Two "Decoding time" messages had moved from the error to the output channel, splitting one BOOTSTRAPTIMING report across two differently buffered streams, and two "Encoding time" messages had gained a leading newline their third sibling did not have. A TIC(t) added at the tail of EvalBootstrapStCFirst is dropped: that function has already run SlotToCoeff and returns immediately after, so the timer was never read. WITH_DEFAULT_LOG_SINK becomes WITH_DEFAULT_DIAGNOSTIC_SINK to match the header, macros and namespace, and is added to the CMake summary and the documented option table. SetOpenFHEErrStream/SetOpenFHEOutStream now return the stream they replaced, so a caller can restore it. Adds UnitTestDiagnosticOutput covering per-channel capture, manipulators and the returned previous stream, and a pre-commit hook, scripts/maint/check-diagnostic-output.py, that rejects direct stdout/stderr writes in library sources and installed headers so the invariant cannot be reintroduced unnoticed. * update after rebase --------- Co-authored-by: Balasubramanian Narasimhan <b.naras@gmail.com> Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
- Casting through int64_t flipped the sign of positive centered coefficients above INT64_MAX and truncated anything wider. Use ConvertToDouble() instead. - The DCRTPoly constructor now checks its first-tower shortcut against the remaining towers and falls back to CRT interpolation if a coefficient does not fit. It also rejects an empty DCRTPoly instead of indexing into it. - Added regression tests. Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Replace the K = 25 sparse-secret approximations with K = 28 and select K by the first modulus size The mod-raise overflow bound of the sparse secret distributions is now chosen by the failure probability of the Irwin-Hall model (eprint 2022/024, eq. (1)), which depends only on K and the Hamming weight h of the sparse secret: - SPARSE_ENCAPSULATED uses K = 16 with h = 32 (first modulus of at most 60 bits; failure probability below 2^-137 for 2^16 slots) and the K = 28 tables of SPARSE_TERNARY with h = 64 (first modulus above 60 bits; below 2^-142 for 2^16 slots). The Hamming weight is fully determined by the size of the first modulus (CryptoParametersCKKSRNS::SparseKSHammingWeight) and no longer by the composite degree. - SPARSE_TERNARY functional bootstrapping moves from K = 25 to K = 28 (h = 192, about 2^-22 for 2^16 slots), the bound already used by regular bootstrapping. K_SPARSE_ALT = 25 and g_coefficientsSparseAlt are removed. The K = 25 FBT tables are replaced by K = 28 tables with the smallest degrees matching the previous precision: exp degree 64 (2^-20) and 72 (2^-32), cos degree 64 (2^-21); the degree-64 tables need one more level than their degree-58 predecessors. EvalFBTSetup now derives its depth from the actual Hamming weight of the sparse secret (AdjustDepthFBTInternal) instead of the public estimate, which could not know it; GetFBTDepth, GetFEFBTDepth and AdjustDepthFBT take the first modulus size instead of the composite degree. Tests: FBT rows for SPARSE_ENCAPSULATED with composite scaling cover both Hamming weights; a new FEFBT case covers the 64-weight secret; docs updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Fix the 14-bit LUT errors and the modular-reduction precision loss at K = 28 The exponential table of functional bootstrapping and the two approximate modular-reduction tables were selected by approximation error alone, which is the wrong criterion for the first and too weak for the other two. coeff_exp_28_double_72 becomes coeff_exp_28_double_69. The digit errors of the 14-bit LUTs are set by the Paterson-Stockmeyer decomposition rather than by the approximation error: ComputeDegreesPS maps degrees 63-69 to (k, m) = (10, 3) and degrees 70-76 to (11, 3), and GetDepthByDegree is 8 for both. At (11, 3), rows 22-24 of UTCKKSRNS_FBT (SPARSE_TERNARY, 14-bit LUT, 58-bit modulus, ring dimension 2^17) fail; row 22 misses 174 of its 131072 values by one digit at degree 72, and equally at degree 76, at degree 80 ((6, 4)), and at K = 25, although those tables are up to twelve bits more accurate. At (10, 3) the three rows are exact. Degree 69 also needs one power-basis element fewer than degree 72. g_coefficientsSparse goes from degree 44 to 48 and g_coefficientsSparseEncapsulated from degree 32 to 36. At degree 44 the K = 28 table approximates the modular reduction to 2^-30.8 and the K = 16 table at degree 32 to 2^-33.5, against 2^-38.5 for the K = 25 table they replace. Both new degrees stay within the depth-7 Paterson-Stockmeyer range, so the bootstrapping depth is unchanged. At ring dimension 2^16 with composite scaling (89-bit first modulus, 78-bit scaling factor) the bootstrapping precision improves from 16.4 to 24.1 bits for SPARSE_ENCAPSULATED and from 16.2 to 23.6 bits for SPARSE_TERNARY, for a bootstrapping time within 3% of the previous tables. * Use one exponential table for every LUT digit size and raise the cosine degree Both K = 28 tables of functional bootstrapping sat below the accuracy available at their depth. coeff_exp_28_double_64 is removed and the digitBitSize > 10 branch with it. It selected between two tables of the same Paterson-Stockmeyer depth (8), so the lower degree bought nothing: LUTs of at most 10 bits now approximate exp(2 Pi i x) to 2^-27.4 instead of 2^-20.0 after the double-angle iterations, with one table and one branch fewer. coeff_cos_28_double goes from degree 64 to 68, improving cos(Pi x) from 2^-21.0 to 2^-26.9 after its double-angle iteration. 68 is the largest even degree whose Paterson-Stockmeyer parameters are (k, m) = (10, 3). Both tables keep k = 10, so the power basis, the multiplication count and the depth are unchanged. Neither degree is the most accurate available at depth 8 -- degree 76 is, for both -- but 76 falls in the (11, 3) cluster that produces the 14-bit LUT digit errors described in the previous commit. The cosine tables gain the degree suffix the exponential tables already carry: coeff_cos_28_double_68, coeff_cos_16_double_50 and coeff_cos_512_double_92. The degree governs the Paterson-Stockmeyer parameters, so it belongs at the call site. --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
…1319) * Keep precomputed powers intact in linear series evaluation (#1312) Below degree 5, EvalPolyWithPrecomp and EvalChebyshevSeriesWithPrecomp evaluated the series straight from the power (or Chebyshev) basis and scaled the basis ciphertexts in place. Any caller sharing a precomputation across several series got a correct first result and silently wrong ones afterwards. EvalMVB/EvalMVBNoDecoding hit this for Hermite coefficient vectors of degree 3 (p = 4 at order 1, p = 2 at orders 2 and 3); the seriesPowers repackaging in EvalMVBNoDecodingInternal shares the same CiphertextImpl objects, so the corruption reached the caller's powers. Scale each lower-order term into a temporary instead. EvalMult on a scalar is Clone plus in-place multiply, so this costs one ciphertext copy per nonzero lower-order term (at most k-1, never the top term), fewer copies than guarding in the callers, and it also fixes the two public APIs. Measured at N = 2^15 the extra copies are under 1 ms per function against a 100-300 ms series evaluation. - take the basis by const reference in internalEvalPolyLinearWithPrecomp and internalEvalChebyshevSeriesLinearWithPrecomp - drop the now redundant deep copies in EvalFEFuncBootstrapWithPrecomp - document that EvalPolyWithPrecomp/EvalChebyshevSeriesWithPrecomp leave the precomputation unmodified - extend UnitTest_MVBReuse to evaluate f1, f2, f1 on one precomputation and add p = 4 rows (FIXEDMANUAL full/sparse, FLEXIBLEAUTO, COMPOSITESCALINGAUTO), which fail without the fix - add EVAL_PRECOMP_REUSE to UnitTestPoly for the generic power-basis and Chebyshev precomputation APIs * comments on reusing powers + tiny FIXEDMANUAL optimization * edits after rebase --------- Co-authored-by: Andreea Alexandru <aalexandru@dualitytech.com>
…sis conversions (#1322) * Request consistent OpenMP team widths at DCRTPoly format switches Consecutive parallel regions that ask for different team sizes make the OpenMP runtime reconfigure its team, and libgomp charges heavily for it (issue #1300). DCRTPolyImpl::SwitchFormat was the most frequent offender: it ran the per-tower loop at the full team count, while the regions around it in the same operation ran at the tower count or at one of the tuned caps on the ring-dimension loops. - ParallelControls::GetThreadLimit clamps to [1, limit] - SwitchFormat resolves thread_limit == 0 to the tower count, which is what the tower-parallel regions around it use. An explicit request is honoured as given rather than clamped to the tower count, so a caller can align with a wider neighbour. - The three measured widths for the loops over the ring dimension, written until now as the literals 8, 16 and 36 at fourteen sites, are named DCRTPolyImpl constants: THREADS_SCALE_TO_POLY 8 ScaleAndRound to a single NativePoly THREADS_CRT_BASIS_SWITCH 16 SwitchCRTBasis, ScaleAndRound, ApproxScaleAndRound, CRTInterpolate THREADS_APPROX_CRT_BASIS_SWITCH 36 ApproxSwitchCRTBasis The values are unchanged. - Format switches that sit beside one of those loops request its width: the internal switch in ExpandCRTBasis, ExpandCRTBasisReverseOrder and ApproxModUp; the two switches in KeySwitchHYBRID::EvalKeySwitchPrecomputeCore around ApproxSwitchCRTBasis; and BFV's HPS-family switches around ScaleAndRound, SwitchCRTBasis and the basis expansions in EvalMult, EvalSquare, EvalAutomorphism and EvalFastRotation. BEHZ sites keep the tower-count default because their neighbours are tower loops. - SwitchFormat runs its loop serially when called from inside an active parallel region (ParallelControls::InParallelRegion). CRTDecompose switches every digit from within its tower-parallel loop, which opened a one-thread nested team per digit per tower on every BV key switch. EvalMult on a 2^14 ring, icelake, time relative to dev (min of 3 medians; lower is faster, 0.5 = twice as fast): 36 threads, gcc-14/libgomp BFV 0.49 .. 0.60 BGV/CKKS with BV key switching 0.84 .. 0.95 HYBRID 0.99 .. 1.01 36 threads, clang-18/libomp BFV 0.84 .. 0.94 BGV/CKKS with BV 0.94 .. 1.00 HYBRID at parity within the run-to-run band 8 threads 0.97 .. 1.00 on every path 1 thread 1.00 everywhere EvalRotate moves with it: BV 0.80 .. 0.95 and BFV 0.68 .. 0.87 at 36 threads under gcc. On dev, BFV EvalMult under gcc ran slower at 36 threads than at 8; it no longer does. CKKS bootstrapping (benchmark configs 7, 15, 17 at 36 threads) is unchanged at 1.01 under gcc and 0.99 under clang. Capping the switches to the tower count without the aligned call sites cost HYBRID 10-23% and BFV at 8 threads 3-7%, so the alignment ships with the mechanism. The nested-region guard measures neutral and is kept for the regions it removes. * Accumulate the BV key switch over towers and digit groups KeySwitchBV::EvalFastKeySwitchCore ran its parallel region over the digits, cloning the key's towers for every digit, multiplying in place, and then summing the digit products serially in a loop the same size as the work it followed. The region is now over (tower, digit group) tasks: the digits are split into as many groups as fit the team beside the tower count, each task accumulates one tower over one group without any clone, and the groups are summed per tower over coefficient ranges so that the reduction also uses the whole team. With a team no wider than the tower count this degenerates to one thread per tower and no reduction, which is the shape KeySwitchHYBRID::EvalFastKeySwitchCoreExt already has. Modular accumulation is exact, so the result is bit-identical, and it carries the ciphertext's own parameter object rather than one cloned from the key. Two simpler forms were measured and rejected. A pure tower-major loop gains 3-20% at one thread but loses 3-12% at 8 and 36 threads on 2-4 towers, because for BV the digit count is all digits across towers (about 18 for three 60-bit towers at a 10-bit base) and was the wider axis there. Adding the digit groups but reducing per tower recovers 8 threads and leaves 2-6% on the 2-4 tower cells at 36 threads, where only tower-count threads sum the partials. The two format switches in the sparse-encapsulation key switch and modulus raise of CKKS bootstrapping that sit beside SwitchCRTBasis request THREADS_CRT_BASIS_SWITCH like their neighbours elsewhere. EvalMult on a 2^14 ring, icelake, time relative to the previous commit (min of 3 medians; lower is faster): 1 thread BV paths 0.80 .. 0.98 (both compilers) 8 threads BV paths 0.87 .. 1.01 36 threads, gcc-14/libgomp BV 6-7 towers 0.86 .. 0.92 BV 2-4 towers 0.99 .. 1.03 36 threads, clang-18/libomp BV 0.78 .. 1.04 HYBRID paths unchanged (not touched) EvalRotate moves further, to 0.71 at 36 threads on 7 towers. The one-thread gain is the removed clones and reduction passes. Two gcc cells at 36 threads with 3-4 towers remain 2-3% slower; there the 3-4 towers give too few tasks to hide the partial-sum traffic. CKKS bootstrapping (benchmark configs 7, 15, 17 at 36 threads) is unchanged with the aligned sparse key-switch switches: 0.99 .. 1.02 under gcc, 1.00 under clang. core_tests and pke_tests pass under both compilers. * Size the ApproxSwitchCRTBasis team from the work in its loop ApproxSwitchCRTBasis capped its loop over the ring dimension at 36 threads. That width was measured on bootstrapping, where the loop converts 20 to 30 towers of a 2^16 ring, and it is right there. On a 2^14 ring converting a 2-tower digit the same loop is 200 times shorter, and running it at 36 beside regions 4 to 8 wide costs HYBRID key switching more than the loop itself: the sweep over all caps in {4, 8, 16, 36} across rings 2^13 to 2^16 put the optimum at 4 to 8 for every small case and at 36 only for the bootstrapping-scale ones. The quantity that separates them is the work, ring dimension times input towers times output towers. DCRTPolyImpl::ApproxSwitchCRTBasisThreads(ringDim, sizeQ, sizeP) returns one thread per 2^17 multiply-accumulates, clamped to [8, 36]. ApproxSwitchCRTBasis uses it for its loop, and the format switches aligned to that loop (inside ApproxModUp and the two in KeySwitchHYBRID::EvalKeySwitchPrecomputeCore) ask for the same value, so the alignment holds at every size. The floor of 8 keeps behaviour with 8 or fewer threads unchanged. The divisor and the bounds were fitted on icelake at 36 threads; the other two caps (8 for the scale-and-round to a single polynomial, 16 for the exact basis switches) measured near-optimal in the same sweep and are unchanged. EvalMult at 36 threads relative to the fixed cap (min of 3 medians; lower is faster): gcc-14/libgomp HYBRID key switching 2^13 0.50 2^14 0.66 2^15 0.80 2^16 (7 towers) 0.95 BFV (via FastExpandCRTBasisPloverQ) 0.58 .. 0.85 0.72 .. 0.94 0.87 .. 0.97 clang-18/libomp HYBRID 2^13 0.83 2^14 0.91 2^15 ~1.0 8 threads 1.00 everywhere (same requests as before) EvalRotate with HYBRID keys moves further: 0.37 at 2^13, 0.51 at 2^14, 0.71 at 2^15 under gcc. BV paths, which never reach this loop, are unchanged. CKKS bootstrapping (benchmark configs 7, 15, 17 at 36 threads), where the large key switches still resolve to 36: 0.96 .. 0.99 under gcc, 1.00 under clang. core_tests and pke_tests pass under both compilers. * Cap the BV key-switch task count by the work available The (tower, digit group) split in KeySwitchBV::EvalFastKeySwitchCore sized its team from the thread limit alone, so a 2^13 ring with four towers at 72 threads spawned 72 tasks of a few microseconds each across two sockets and ran rotations 1.4x slower than the digit-major loop it replaced. The team is now also bounded by one task per 2^15 coefficient multiply-accumulates. EvalMult (rotate) relative to the uncapped form, icelake, BGV with BV keys and 4 towers (lower is faster): 2^13 gcc 36t 0.85 (0.76) gcc 72t 0.84 (0.59) clang 36t 0.92 (0.90) 2^14 gcc 36t 0.94 (0.92) gcc 72t 0.94 (0.79) clang 36t 0.89 (0.85) 2^15 1.00 everywhere; 18 threads and below unchanged Other paths within 0.93 .. 1.01; HYBRID paths do not use this code. core_tests and pke_tests pass under gcc-14 and clang-18. * Move the thread-width constants to DCRTPolyInterface and tighten the BV guard * remove unused DCRTPolyInterfaceType::THREADS_APPROX_CRT_BASIS_SWITCH
* Raise the mod-raise overflow bound K for uniform ternary secrets at unchanged depth The uniform ternary approximations now use K = 648 for regular bootstrapping, K = 672 for functional bootstrapping, and K = 696 for FE functional bootstrapping. The probabilities of failure for fully packed slots (normal approximation of the overflow, Hamming weight ~2N/3) are 2^-71 / 2^-77 / 2^-84 for N = 2^16 and 2^-28 / 2^-31 / 2^-34 for N = 2^17. Each K is the largest value whose Chebyshev approximation keeps its precision without an additional level of multiplicative depth. The regular and functional bootstrapping tables use degree 104: higher degrees within the same depth bracket (106 to 119, and exactly 105) fall into a noisier regime of the Paterson-Stockmeyer evaluation that costs about 3 bits of precision, so the cap is documented next to the constants. The FE functional bootstrapping table uses degree 27. - g_coefficientsUniform: K = 648, degree 104 (approximation error 2^-36) - coeff_exp_672_double_104 / coeff_cos_672_double_104: functional bootstrapping tables, six / five double-angle iterations (2^-37 / 2^-34) - coeff_exp_696_double_27 with R_func_696_double_27 = 9: FE functional bootstrapping table (2^-30) - K_UNIFORM, K_UNIFORM_FBT, K_UNIFORM_FEFBT select the bound per flavor Measured bootstrapping precision is unchanged or better (regular FIXEDAUTO at N = 64: 15.5 -> 16.8 bits; functional bootstrapping noise unchanged; FE functional bootstrapping within 0.5 bit). Docs and test comments updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Quote the Security Guidelines failure probabilities for the uniform ternary K values The probabilities of failure in the comments and docs are now the ones given by the estimator of the Security Guidelines for Implementing Homomorphic Encryption (Appendix A: Irwin-Hall model of the mod-raise overflow with the correction for the spread of the Hamming weight), which are slightly more conservative than the plain normal approximation quoted before: K = 648: 2^-67 (N = 2^16) / 2^-27 (N = 2^17); K = 672: 2^-73 / 2^-30; K = 696: 2^-79 / 2^-33. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Encode degree-2 constants at the scale of the dropped prime for the FIXED scaling techniques Closes #1333. With FIXEDMANUAL and FIXEDAUTO the rescale divides by the actual prime q_l of the dropped tower, not by the scaling factor Delta, so a constant added to a degree-2 ciphertext as c * Delta^2 came out as c * Delta / q_l. The residual (about 2^-33 for 50-bit primes) is a constant shift of T_2 = 2x^2 - 1 that the squaring chain of the Paterson-Stockmeyer giant steps amplifies by roughly the square of the degree. FIXEDAUTO adds every constant of the Chebyshev recursion before its automatic rescale and lost about 4 bits on the degree-119 bootstrapping polynomial and 5 bits on EvalMod compared with FIXEDMANUAL. The per-degree factor of a constant is now the modulus of the tower that the corresponding rescale drops (both NATIVEINT variants of GetElementForEvalAddOrSub, shared helper), and the FIXEDMANUAL plaintext degree raise in AdjustForAddOrSubInPlace uses the same moduli. AdjustLevelsAndDepthInPlace gets a FIXEDAUTO-specific path: the tracked scaling factor and GetModReduceFactor are exactly Delta for the FIXED techniques, so every FLEXIBLE-style "multiply by Delta, then rescale" maneuver left a Delta / q_l factor on one operand and spent a scalar multiplication and a rescale for nothing. A degree-1 ciphertext is now truncated to the towers of the other operand (as in FIXEDMANUAL), a degree-2 ciphertext is rescaled when the other operand has degree 1, and a degree raise multiplies by the target's last modulus. Measured at N = 2^12 with 50-bit primes (max error over 2048 slots): degree-119 series: FIXEDAUTO 2^-23.9 -> 2^-27.7 (FIXEDMANUAL 2^-27.6) series + 6 double-angle iterations: FIXEDAUTO 2^-14.6 -> 2^-19.7 (FIXEDMANUAL 2^-19.7) New regression test UTCKKSRNS_FIXED_SCALING.DegreeTwoConstantsChebyshev119 requires at least 26 bits for both techniques; it fails at 2^-24 without the fix. All 2064 pke unit tests pass; FLEXIBLE* and COMPOSITESCALING* are unchanged. Three pre-existing C-style casts in the 128-bit constant encoder are converted to static_cast to satisfy cpplint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * edits after rebase * Unify FIXEDMANUAL and FIXEDAUTO operand alignment in AdjustLevelsAndDepthInPlace Both FIXED scaling techniques use the scaling factor Delta at every level, so two operands of equal noise scale degree only have to agree on the number of towers: the one with fewer towers dropped is level-reduced, and nothing else is needed. The FIXEDAUTO branch now handles only the degree mismatches (a degree-2 operand is rescaled, a degree-1 operand is raised by the modulus of the target's last tower); a degree mismatch under FIXEDMANUAL keeps the general path. Previously FIXEDMANUAL fell through to the FLEXIBLE-style path for every ciphertext-ciphertext addition or subtraction with mismatched levels, which multiplied one operand by Delta/Delta/Delta and rescaled it to reach the level a truncation reaches directly, spending a scalar multiplication and a rescale and leaving a Delta/q_l residual on the operand. The four basis-alignment switches in ckksrns-advancedshe.cpp (the linear and Paterson-Stockmeyer power and Chebyshev bases) that special-cased FIXEDMANUAL with LevelReduceInPlace call AdjustLevelsAndDepthInPlace unconditionally. --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Review the lint configs and rerun pre-commit over the tree (#1170) Pin every tool version in .pre-commit-config.yaml and let pre-commit download them, so a contributor installs only pre-commit and everyone formats with the same clang-format and cpplint. Previously both ran as language: system, so the result depended on whatever happened to be installed. The hooks repository moves to v6.0.0, which renames check-byte-order-marker to fix-byte-order-marker. .clang-format keeps the existing style. The three pre-12 boolean spellings are replaced by the enum equivalents, which produce identical output, and Standard: c++17 is added to match CMAKE_CXX_STANDARD. CPPLINT.cfg stops filtering build/include_order, build/header_guard and build/include_what_you_use, sets linelength to match ColumnLimit, and filters three categories that are new in cpplint 2.x. Enabling those three checks reported 1780 findings, all fixed here: - 239 header guards renamed to the path-based form. 12 guards nested inside a WITH_BE2, WITH_BE4 or WITH_NTL check now wrap the whole file. - 174 files given the standard headers they use. - 428 include blocks regrouped as own header, C system, C++ system, other. - explicit added to six single-argument constructors in binfhe. Requiring each implementation file to include its own header first exposed a real defect. mubintvecfxd.h and mubintvecntl.h use lbcrypto::BigVectorInterface without including math/hal/vector.h, relying on their includer to pull in math/math-hal.h first, which broke the MATHBACKEND 2 and 6 builds with 120 errors. Both headers now include it. mubintvecdyn.h already did, which is why the default backend 4 build never showed the problem. external-prng.cpp was not a clang-format fixed point: a banner comment following a pragma with a trailing comment shifted right on every pass. Its comments now sit above the pragmas, and the headers both arms of the conditional use are hoisted out of it. The contributing documents drop the instructions to install clang-format and cpplint and the claim that the format was tested with clang-format 9.0.0. The style guide documents the guard naming rule and the include grouping that are now enforced. scripts/maint/apply-code-format.sh ran a bare find over the whole tree, including build directories, with a meaningless --assume-filename; it now runs the pinned hook. Verified with builds and the full unit test suite on MATHBACKEND 2, 4 and 6, under both g++ and clang. * Make the bootstrap cache-clear test independent of the host thread count UTCKKSCacheClear.FullBootstrapClear compares heap-in-use before EvalBootstrapSetup against heap-in-use after ClearBootstrapPrecom and ReleaseAllContexts, and expects the latter to be no larger. It took that baseline before any bootstrap setup had run, so the baseline excluded the per-thread allocator structures that the first setup creates and never returns. That growth is proportional to the OpenMP thread count: threads 8 16 32 72 growth 77 KB 136 KB 251 KB 543 KB Above roughly 32 threads it exceeds the memory ReleaseAllContexts gives back, so the comparison inverts and the test reports a leak that is not there. The test therefore passed on an 8 or 16 core machine and on CI, and failed on a 72 core one. It is not a regression; a build predating this branch fails the same way. Running one setup and clear before the baseline is taken leaves the allocator in its steady state. A second cycle then returns to within a kilobyte of its own baseline, and the test passes at 1, 8, 16, 32, 64, 72 and 128 threads. SchemeSwitchPrecomClear needs no equivalent change. It generates keys before taking its baseline, so its arenas are already warm; its measurements are byte-identical at 16 and 72 threads and it has about a megabyte of margin. * Sort includes with clang-format and make two headers self-contained .clang-format set SortIncludes: false, so include order was maintained by hand and checked only by cpplint. Setting SortIncludes: CaseSensitive with the Google default IncludeBlocks: Regroup lets the formatter maintain it instead. Regroup's categories are the order cpplint already requires, namely the header a file implements, C system headers, C++ system headers, then everything else, so the two tools agree and cpplint becomes a backstop rather than the thing a contributor has to satisfy by hand. 185 files are reordered as a result. Sorting exposed two headers that name a type they do not include and that happened to work only because a caller included something else first: - ilelement.h uses NativeInteger. It compiled because callers included lattice/lat-hal.h ahead of it; sorting puts lattice/ilelement.h first. It now includes math/hal/nativeintbackend.h. - gen-cryptocontext-params-defaults.h uses SecretKeyDist. It compiled because gen-cryptocontext-params.h was included ahead of it. It now includes lattice/constants-lattice.h. Without those two includes the build fails with 18 errors, so both are defects in their own right rather than fallout from the reordering. Verified with builds on MATHBACKEND 2, 4 and 6 and with clang, and with the full unit test suite. * Include the standard headers each file uses cpplint's build/include_what_you_use check works from a table of about thirty symbols. It never asks for <cstdint>, so satisfying it left most files still naming standard types they did not include and relying on a transitive path through a project header. A file like that breaks when an unrelated header changes what it includes, which is what happened to ilelement.h and mubintvecfxd.h earlier on this branch. This adds the missing headers across 348 files, found by scanning for a wider set of unambiguous symbols: <cstdint> 339 <initializer_list> 15 <fstream> 5 <cmath> 70 <type_traits> 14 <functional> 3 <complex> 50 <random> 12 <mutex> 2 <sstream> 38 <iomanip> 6 <cstddef> 2 <numeric> 6 <iterator> 2 <cstdlib> 1 Two related fixes: - scheme-utils.h had no includes at all while using uint32_t. - UnitTestTransform.cpp included "random" in quotes, which names a file in the project's include path rather than the standard header. The correct <random> is now present and the quoted form is removed. The vendored blake2 sources are left alone; they are built as C, where <cstdint> does not exist. Verified by building MATHBACKEND 2, 4 and 6, clang, and NATIVE_SIZE 128. * Check the pre-commit hooks in CI Nothing enforced the lint configuration: the hooks ran only for contributors who had installed them, which is how the tree came to need 1780 fixes. This adds a job that runs them on every pull request and on pushes to main and dev. It runs on a GitHub-hosted runner rather than the build machines, because it needs only Python and network access and finishes in about a minute. Change runs-on to use the RUNNER variable if the build machines are preferred, but note that pre-commit needs pip and outbound network to fetch the pinned tools. The job checks and does not push. On failure it prints the diff the hooks would apply, along with the command to reproduce it locally. Pushing fixes automatically is not possible for pull requests from organization-owned forks, where the token is read-only, so it would silently skip part of the traffic. --all-files rather than only the files a pull request touches, so drift anywhere in the tree is reported rather than accumulating until someone reruns the hooks over everything. * Pin the lint runner image ubuntu-latest tracks whichever Ubuntu release GitHub currently points it at, and that target moves. Naming the image keeps the job on one release until it is changed deliberately. * Indent wrapped parameter lists instead of aligning them AlignAfterOpenBracket was Align, which aligns a wrapped parameter list under the open bracket when that fits inside the column limit and falls back to breaking after the bracket when it does not. The shape of a declaration therefore depended on how long the function name happened to be, and neighbouring functions in the same file were laid out differently. DontAlign with ContinuationIndentWidth 8 gives every wrapped list the same shape: the first parameter stays on the declaration line and the rest indent by a fixed amount. The setting applies to all brackets, so calls and conditions wrap the same way; clang-format has no way to distinguish declarations from calls here. Three cpplint findings surfaced because re-wrapping moved code onto different lines: - UnitTestFixedScalingConstants.cpp uses std::max without <algorithm>. - UnitTestMatrix.cpp uses std::make_shared without <memory>. - In ubintntl.h a // NOLINT sat on a line carrying two is_same checks. Wrapping split them, leaving the second without its suppression, so the comment is now on the line it applies to. Verified by building MATHBACKEND 2, 4 and 6, clang, and NATIVE_SIZE 128. * Fix a heap overflow when serializing NTL integers myZZ::save took its byte count from _ntl_ALLOC, which returns NTL's packed alloc_ field rather than a limb count: the low two bits are flags and the capacity is that field shifted right by two. Multiplying the packed value by a pointer size and reading from the start of the body ran past the allocation by roughly a factor of four. AddressSanitizer reports a 256-byte read off the end of an 80-byte block. Every binary serialization of an NTL integer does this, so the overflow is constant. Whether it faults depends on whether the bytes after the block happen to be mapped, which is why MATHBACKEND 6 fails intermittently in CI with a segmentation fault that moves between test cases and sometimes does not appear at all. The code dates to 2022; only its visibility has changed. The magnitude is now written through NTL's public byte interface instead of as a copy of NTL's internal memory. That removes the overflow and the dependency on a layout that was never portable between platforms or NTL builds. This changes the MATHBACKEND 6 binary archive format. It cannot be preserved, because the length field the old format records is the defect itself. Verified under AddressSanitizer with GCC 14: the serialization suite reports no errors where the same build previously reported the overflow. * more tweaks * Handle unsupported heap checks and fix skipped tests on MinGW - Skip heap-usage tests when allocator statistics are unavailable. - Report skipped tests separately and include them in the test summary. --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
* Recommend SPARSE_ENCAPSULATED for CKKS bootstrapping in the documentation Add a secret key distribution section to CKKS_BOOTSTRAPPING.md with the bootstrapping failure probabilities for fully packed N = 2^16 and 2^17, recommend SPARSE_ENCAPSULATED (failure probability below 2^-128) for all flavors of CKKS bootstrapping, discourage SPARSE_TERNARY, and state the UNIFORM_TERNARY failure probabilities for users who need uniform ternary secrets for compliance with security guidelines. Update the corresponding notes in the functional bootstrapping and composite scaling docs, the parameter READMEs, and the secret key distribution comments in the bootstrapping examples. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Use SPARSE_ENCAPSULATED in the CKKS functional bootstrapping examples Switch the multi-value functional bootstrapping example and the Fourier extension functional bootstrapping example from SPARSE_TERNARY to the recommended SPARSE_ENCAPSULATED secret key distribution, and update the comments describing the secret key distribution choice accordingly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Make doxygen comments match the documented declarations Audit of every doxygen block in the core, pke, and binfhe headers (and the two library sources that carry doxygen tags) against the declaration it documents. Most @PARAM tags were written with the reference or pointer sigil in the name, so doxygen never matched them to an argument; they are normalized, missing parameters are documented, tags for parameters that do not exist are removed or renamed, tags are put in signature order, and @return tags on void functions are dropped. Descriptions copied from other functions are corrected, including swapped EvalPowers/EvalPoly and BootstrapFuncCore/BootstrapFunc texts, PackedEncoding text on the CKKS encoding, DCRTPoly text on PolyImpl methods, and misnamed or nonexistent parameters in the CryptoContext API. Comments only; no code changes. Doxygen with WARN_NO_PARAMDOC now reports no parameter warnings for the library, down from 480. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Fix the Sphinx documentation build with current Breathe Expand the BLAKE2_PACKED macro of the bundled BLAKE2 header to its body in the Exhale doxygen configuration. With the packed attribute left in place, Doxygen emits the struct members without a source location, and Breathe 5 aborts the build when it reads them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Document the key switching classes Add doxygen comments for all public methods of KeySwitchBase, KeySwitchRNS, KeySwitchBV, and KeySwitchHYBRID, and class descriptions explaining how the BV, GHS, and hybrid methods control the key switching noise, how the RNS digit decomposition and the extended basis QP are used, and how the exposed core operations (digit decomposition, inner product with the key, division by P) are composed for hoisted rotations and accumulation in the extended basis. The descriptions follow Appendix B of https://eprint.iacr.org/2021/204. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add doxygen comments for the public methods in pke/include Document the non-trivial public methods of the pke headers: the algorithm interfaces in schemebase, the RNS, BFV, BGV, and CKKS implementations (including the bootstrapping pipeline, functional bootstrapping entry points, and scheme switching), the precomputation tables with their index semantics, parameter generation, encodings, keys, ciphertexts, constants, and the remaining CryptoContext methods. SchemeBase gets a class-level description of its dispatch to the algorithm objects instead of per-method blocks for its forwarders. Plain getters and setters, serialization boilerplate, and defaulted constructors are left undocumented. A few existing comments that contradicted the code are corrected. Comments only. Doxygen now reports no parameter, return-type, or compound warnings for pke/include. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add doxygen comments for the public methods in binfhe/include Document the non-trivial public methods of the binfhe headers: the key and ciphertext containers with the layout of their data (LWE switching key indices, RingGSW evaluation key rows, accumulator key dimensions per bootstrapping method), the 32-bit internal key formats and their conversions, the RingGSW and LWE crypto parameters with their precomputations and lazy caches, the BinFHE context parameter struct, and the 32-bit key generation, accumulation, and key switching entry points. Plain getters and setters, serialization boilerplate, and member-copying constructors are left undocumented. Comments only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add doxygen comments for the public methods in core/include Document the non-trivial public methods of the core headers: the DCRTPoly and Poly interfaces and default implementations (RNS basis switching, scaling, modulus raising and reduction, sampling constructors), element parameters, Field2n, the BigInteger and BigVector interfaces with the naming conventions of their in-place, fast, and Barrett variants, the native-integer backend, NTT helpers, matrices, distribution generators, number theory helpers, trapdoor parameters, standard lattice parameter tables, thread controls, exceptions, and the PRNG. Plain getters and setters, serialization boilerplate, and member-copying constructors are left undocumented; the bundled BLAKE2 header is untouched. Formulas written with LaTeX-style commands in dcrtpoly-interface.h are rewritten as plain text, and a few existing comments that contradicted the code are corrected. Comments only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Update src/pke/examples documentation for v1.6.0 (#1346) Document Fourier-extension functional bootstrapping, the new bootstrapping examples and APIs, and fix stale names and broken links. * Address review comments on the functional bootstrapping documentation - Point to the fefbt-python repository for generating the Fourier extension coefficients - Describe the supported scaling techniques and packing modes outside the lists of current limitations, for both the lookup table and the Fourier-extension functional bootstrapping - Remove the duplicate secret key distribution bullet from the lookup table limitations; the distributions are described in the section above it - State that the 128-bit build is not supported, without "yet"
cpplint rejects comments like "//comment" (whitespace/comments), but clang-format left them alone, so the problem only showed up when the cpplint hook ran. The Google base style already sets SpacesInLineCommentPrefix to require one space. Our "ReflowComments: false" was switching that off. Set ReflowComments to IndentOnly, which applies the comment spacing rules without rewrapping long comments. The value needs clang-format 20 or newer, so the pinned version moves from 18.1.8 to 23.1.1, and the docs that give the pip install command are updated to match. The tree is reformatted with the new settings. Almost all of the diff is doxygen /** */ comments whose continuation "*" moves one column to line up under the first "*". The newer version also moves a few line breaks in ubintnat.h and packedencoding.cpp. No code tokens change. Editors that run their own clang-format need 23.1.1 (pip3 install clang-format==23.1.1). Version 18 cannot parse the new .clang-format. Fixes #160
…1350) * Re-tune the order-1 FBT benchmark rows and add FLEXIBLEAUTO variants (#1275) Key arblut_configs by (log p, scaling technique) and carry the secret key distribution and scaling technique in fbt_config, so the benchmarks read them from the row instead of hardcoding SPARSE_ENCAPSULATED / FIXEDMANUAL. Register FBTArbLUT once per technique; the rows appear as FBTArbLUT/FIXEDMANUAL/bits:N and FBTArbLUT/FLEXIBLEAUTO/bits:N. The 1-, 4- and 8-bit rows now use the smallest scaling factor whose output error stays below Q/(2p) with probability 2^-40 per ciphertext: for every input value m, |mean| + t*sigma of the residual against f(m)*Q/p over all N coefficients, t the two-sided Gaussian quantile of 2^-40/N, plus zero wrongly decoded coefficients. Measured on dev with fully packed ciphertexts and inputs covering every residue, then confirmed with fresh keys and random inputs. FIXEDMANUAL log Q 33 / 38 / 46 (was 33 / 38 / 47) FLEXIBLEAUTO log Q 24 / 28 / 35 (new rows) Ring dimensions are unchanged. The 1-bit FIXEDMANUAL row uses dnum 4 so that log(Q'_L P') stays under the 767-bit limit for N = 2^15 (dnum 3 gives 769); the cost is 5% on the FBT. The 1-bit FLEXIBLEAUTO row stops at log Q 24 because the FLEXIBLEAUTO prime selection fails at 23. UNIFORM_TERNARY rows are not added, following the discussion in #1275. * Search ring dimension, level budget and dnum for the FBT benchmark rows (#1275) Beyond the scaling factor, each order-1 row now uses the ring dimension, level budget and dnum that give the fastest FBT among the configurations passing the 2^-40 per-ciphertext criterion and the 128-bit limit on log(Q'_L P') (767 / 1553 bits at N = 2^15 / 2^16). 267 configurations were measured for the 1-, 4- and 8-bit rows and 127 for the 2-bit rows, on dev 7c25a23. FIXEDMANUAL 2-bit: log Q 32 at N = 2^15, budget {2,2}, dnum 4 (was 35 at 2^16, {4,4}, dnum 3); 4-bit: dnum 2 (was 3) FLEXIBLEAUTO 2-bit: new row, log Q 24 at 2^15, {3,3}, dnum 3; 4-bit: log Q 27 at 2^15, {3,3}, dnum 4 (was 28 at 2^16, {4,4}, dnum 3); 1- and 8-bit: dnum 2 (were 3 and 4) The 1-, 4- and 8-bit FIXEDMANUAL rows keep their budgets: at every size a {2,2} budget is 30-70% slower than {3,3} and {5,5} never wins, and the smallest dnum the security limit admits is the fastest except on the 8-bit FIXEDMANUAL chain. Moving the 2-bit FIXEDMANUAL and 4-bit FLEXIBLEAUTO rows to N = 2^15 makes them 1.6x and 2.1x faster; the 4-bit row also passes one bit lower there. scaleTHI (8 / 32 / 128) does not change the margins.
#1353) * Fix double rescale when EvalMultMutable gets the same ciphertext twice (#1339) With the automatic scaling techniques, AdjustLevelsAndDepthToOneInPlace rescales ciphertext1 and then ciphertext2. When both arguments are the same object, that object was rescaled twice. In CKKS this made decryption throw, or with FIXEDAUTO return a wrong value with no error, and it left the operand corrupted. In BGV the value was correct but an extra level was used. - CKKS and BGV AdjustLevelsAndDepthToOneInPlace: skip the second rescale when both arguments are the same object - LeveledSHERNS::EvalMultMutable: send the same-object case to EvalSquareMutable, which rescales once and does fewer polynomial products - UnitTestSHE EVAL_MUTABLE: test same-object calls on fresh and degree-2 inputs, checking value, level and the operand afterwards; add FIXEDAUTO and FLEXIBLEAUTO cases for BGV and CKKS, and use EPSILON_HIGH for CKKS in this test, since FIXEDAUTO's error is above EPSILON * Skip the FLEXIBLEAUTO CKKS EVAL_MUTABLE test case in 128-bit builds 128-bit CKKS does not support FLEXIBLEAUTO, so creating the context for case 06 threw an exception.
* Fix key and metadata state handling - preserve key tags through evaluation-key copy and move operations - add explicit move semantics to key base classes - initialize encryption and decryption result fields - preserve derived metadata when cloning by key - complete RingGSW parameter equality checks - correct the LWE switching-key serialized name - add regression coverage for the fixes * Preserve the plaintext modulus of LWE ciphertexts in multi-input gates LWE ciphertexts did not serialize their plaintext modulus, so inputs encrypted with p=6 or p=8 came back with the default of 4. Multi-input gates took their output encoding from that tag, and OR3, AND4 and OR4 then decrypted wrongly after a round trip. Serialize and compare the modulus, and take it from the gate rather than from the first input, which also covers copies. Ciphertext archives written before this change do not load. * take the Cholesky pivot from the updated trailing block * Keep DCRTPoly format and parameters consistent - operator=(uint64_t) and operator=(const PolyType&) now set the element's format to match its towers - CloneWithNoise samples the noise as coefficients and converts to the requested format, instead of ignoring it and labelling the samples with the element's current format - SwitchModulusAtIndex updates a copy of the parameters rather than the object shared with other elements, which it also left stale - AddILElementOne's error names EVALUATION, the format it requires - document that the PolyType and PolyLargeType conversions need COEFFICIENT input * Implement ternary GenerateInteger and fix sparse sampling for h ≤ 3 * make HashUtil::Hash compute SHA-256 and reject SHA-512 * make declared defaults and const getters usable from headers * Test BGV plaintext multiplication across levels --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
…1352) * Rewrite Best_Performance.md for v1.6 and fix the README links to it * Address the review of the Best_Performance.md rewrite Name the two measurement machines once, after the first results table, so every table inherits them: the Xeon 8360Y at a fixed 2.4 GHz with clang 18 and the i7-9700 at a fixed 3.0 GHz with clang 15, both with turbo off, and what turbo changes on the i7 at one and at eight threads. Recommend the composite scaling techniques over NATIVE_SIZE=128 for CKKS scaling factors above 60 bits. Add i7 columns, at the fixed clock and with turbo, to the binfhe gate table, and the GCC 11 gap on that machine to the compiler paragraph. Add SlotsToCoeffs-first bootstrapping (BTSlotsEncoding) with its latency against ModRaise-first at ring dimension 2^16, full packing, SPARSE_ENCAPSULATED, on both machines and with turbo, and the functional bootstrapping table for 1-, 2-, 4- and 8-bit lookup tables under FIXEDMANUAL and FLEXIBLEAUTO on both machines. * add more bootstrapping benchmark numbers * Address the second review of Best_Performance.md and favor fast choices Keep only GINX and LMKCDEY in the binfhe gate table, say what each method is, and recommend GINX for multithreaded gates. Replace the two bootstrapping tables with one, taken from the retuned configurations of the ckks-bootstrapping benchmark (#1355): both variants for full and sparse packing at ring dimension 2^16, ring dimension 2^17 at 5 and 15 levels, and Set II of the security guidelines for UNIFORM_TERNARY secrets, with an output-precision column and the levels left after bootstrapping in each row. Drop the SPARSE_TERNARY rows. Link each table to the benchmark it comes from: binfhe-paramsets, ckks-bootstrapping and ckks-functional-bootstrapping. Turn the trade-off descriptions into recommendations where the measurements support one: SlotsToCoeffs-first for fully packed ciphertexts, SPARSE_ENCAPSULATED secrets, level budgets {4, 3} and {3, 3}, the scaling modulus chosen for precision and the rest for speed, the functional bootstrapping scaling technique by table size, one functional bootstrapping stream per socket for throughput, minimal depth and key-switching digits, fast rotations, about 8 threads for operations that saturate there, and the default radix, allocator and NUMA settings. Note the modulus-size trade-off under HEXL.
* Throw on the EstimateLogP check only when the real P exceeds the estimate (#1358) * Bound the composite-scaling prime searches and fix the clang build of core_tests (#1358) CompositePrimeModuliGen could run forever: when the running scaling factor collapsed, the next target prime fell below the cyclotomic order and the candidate wrapped to just under 2^64, and searches that started above the register word size walked down one candidate at a time. Throw the prime sampling error before the wrap, and start each search at the register word size, which selects the same primes. PreviousPrime now throws when its input is at or below the step instead of returning a wrapped value. Move TernaryUniformGeneratorImpl::GenerateInteger next to the static distribution it uses, which clang rejected under -Werror=undefined-var-template. * Remove a slot's old prime from the record once before searching for its replacement, so the search cannot accept a prime already used in the same level, and throw as soon as raising a prime would cross the register word size, where the search previously walked down to the bottom of the range. * Start clamped composite prime searches at 2^registerWordSize + 1 (#1358) When cyclOrder divides 2^registerWordSize, as it must for any prime to fit the register word, the ceiling is 2^registerWordSize + 1, so the first candidate is the largest value that is 1 mod cyclOrder at or below the limit instead of one step above it. Otherwise the ceiling is 1 and PreviousPrime throws at once. The selected primes do not change.
…posite scaling (#1357) Replace the configuration table with rows chosen by a parameter-space exploration over the scaling modulus, level budget, key-switching digits and levels after bootstrapping, under the 128-bit bound at each ring dimension, a floor of 15 bits of mean output precision, and the most levels that fit with at most eight digits. The first five rows keep their identities and move from uniform to sparse-encapsulated secrets; the table is ordered by ring dimension and then by one changing parameter per row: levels, iterations, scaling technique, packing density, secret distribution. The uniform row is the security guidelines' Set II with four levels after bootstrapping instead of five, which is what the library's bound admits. The sparse-ternary rows are gone. Give each row the single-round precision its second Meta-BTS iteration needs and pass it to EvalBootstrap; without it the second iteration refined nothing. Enable the StC-first composite-scaling row and the corresponding example call.
* updated the documentation to point to 1.6.0 * Use a relative link to Best_Performance.md in the release notes --------- Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com> Co-authored-by: Carlo Pascoe <cpascoe@dualitytech.com>
dsuponitskiy
approved these changes
Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.