Skip to content

Bootstrap: reject inputs with fewer limbs than their NoiseLevel needs instead of returning garbage - #37

Open
seyfal wants to merge 1 commit into
CAPS-UMU:mainfrom
seyfal:fix/bootstrap-level-precondition
Open

seyfal wants to merge 1 commit into
CAPS-UMU:mainfrom
seyfal:fix/bootstrap-level-precondition

Conversation

@seyfal

@seyfal seyfal commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bootstrap() on a ciphertext at level 1 with NoiseLevel 2, or at level 0 with NoiseLevel 1, runs to completion without any error and returns a ciphertext at the normal post-bootstrap level, but the content is noise (OpenFHE's Decrypt reports "the approximation error is too high"). Level 2 / NoiseLevel 2 and level 1 / NoiseLevel 1 bootstrap correctly.

Cause: ModRaise (src/CKKS/Bootstrap.cu) checks getLevel() - NoiseLevel + 1 >= 1 only with assert(), which is compiled out in Release builds. The raise then consumes the pending rescale of a NoiseLevel-2 input and one more limb (multScalar + rescale before dropToLevel(0)), so an input with fewer limbs than that rescales a single-limb polynomial (LimbPartition::rescale's own assert(limbsize > 1) is compiled out too) and the raise works on a level -1 ciphertext.

This PR turns the check into a real one: std::invalid_argument naming the level, the NoiseLevel and the required minimum (level >= NoiseLevel; for prescaled == true, level == NoiseLevel - 1), and documents the precondition on Bootstrap() in Bootstrap.cuh. Valid inputs are unchanged.

Tested on v2.1.3 (786c760), CUDA 13.0, H200 with N=2^16 / depth 25 / scaleModSize 52 / firstModSize 56 / 3 digits / FLEXIBLEAUTO / UNIFORM_TERNARY / 16384 slots, with the examples/bootstrap parameters (N=2^12, full slots) and with the UNIFORM bootstrap configuration: the two bad cases (level 1 / NoiseLevel 2, level 0 / NoiseLevel 1) went from silent garbage to the exception; the three good cases are unchanged (max error 2.0e-3 to 2.2e-3 at our parameters, 3e-5 at the example parameters).

The commit also applies cleanly on OpenFHECompatTests.

Fable 5.1 on behalf of Seyfal

… instead of returning garbage

ModRaise checked `getLevel() - NoiseLevel + 1 >= 1` only with assert(), which
is compiled out in Release builds. A ciphertext at level 1 with NoiseLevel 2,
or at level 0 with NoiseLevel 1, then goes through the raise: the pending
rescale (and the raise's own multScalar + rescale) run on a single-limb
polynomial, the level drops to -1 and the bootstrap completes normally but
returns noise (OpenFHE's Decrypt reports "approximation error is too high").

Throw std::invalid_argument naming the level, the NoiseLevel and the required
minimum (level >= NoiseLevel; for prescaled inputs level == NoiseLevel - 1),
and document the precondition on Bootstrap() in Bootstrap.cuh. Valid inputs
are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant