Skip to content

fix for composite scaling example - #1359

Merged
dsuponitskiy merged 4 commits into
devfrom
issue1358
Sep 27, 2026
Merged

dsuponitskiy merged 4 commits into
devfrom
issue1358

Conversation

@pascoec

@pascoec pascoec commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1358

  1. The symptom. The composite-scaling polynomial example aborted at its own defaults with "EstimateLogP() failure: expected sizeP [9], but got sizeP [7]". It failed on main as well, so it is not a regression.
  2. The cause. The early estimate of P splits the digits over whole scaling primes. The real setup splits them over composite limbs, and a digit boundary inside a prime makes the estimate larger than the real value. The check added in Fix BGV ring dimension underestimation with NOISE_FLOODING_MULTIPARTY + HYBRID on 128-bit builds #1158 required exact equality.
  3. How big. A sweep of 13,200 composite configurations gave 1,880 mismatches. In 1,872 the estimate was larger, which errs toward security, and in 8 it was smaller.
  4. First fix. Making the estimate model the limbs broke 36 working configurations and raised the too-small cases to 51, so it was rejected. Making the check one-sided, throwing only when the real P exceeds the estimate, left every working configuration unchanged and fixed 1,856 of the 1,880.
  5. A second bug. The same sweep found 2,505 configurations where context generation never returned. Debugging traced them to three causes in CompositePrimeModuliGen:
    (1) The target prime fell below the cyclotomic order and the unsigned candidate wrapped to just under 2^64.
    (2) Searches started far above the register word and walked down one candidate at a time.
    (3) The raise-the-product loop crossed the register word, turned around and walked to the bottom of the range.
  6. Second fix. Throw before the wrap. Start searches at the register word, which picks the same primes. Throw as soon as the raise loop crosses the word. Make PreviousPrime refuse to wrap.
  7. A bug the hangs had masked. 222 configurations that used to hang now crashed dividing by zero, because a level held the same prime twice. The adjustment loops removed each rejected candidate from the record of primes in use, so the search could later accept a prime another slot held. Removing only the slot's old prime, once, fixed it.
  8. Review cleanup. The search ceiling became 2^registerWordSize + 1, one step lower, which removes one wasted test per clamped search. It changes no selected prime.

Validation.

  • Sweep, no hangs: all 13,200 configurations finish, none takes over 60 seconds, and 4,159 now work.
  • Sweep, identical primes: every configuration that worked before produces identical moduli.
  • Examples and tests: all examples and extras pass, as do the full core, pke and binfhe suites, under both clang 18 and gcc 14.

… 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.
@pascoec pascoec added this to the Release 1.6.0 milestone Sep 27, 2026
@pascoec
pascoec requested a review from yspolyakov September 27, 2026 00:15
@pascoec pascoec self-assigned this Sep 27, 2026
@pascoec pascoec added the bug Something isn't working label Sep 27, 2026
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.
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.
@dsuponitskiy
dsuponitskiy merged commit b6a5495 into dev Sep 27, 2026
20 checks passed
@dsuponitskiy
dsuponitskiy deleted the issue1358 branch September 27, 2026 21:59
@pascoec pascoec linked an issue Sep 28, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

3 participants