From 3ece51be919798fcd6e042bd70c8a85d363ffd32 Mon Sep 17 00:00:00 2001 From: Seyfal Sultanov Date: Sat, 5 Sep 2026 06:04:41 +0000 Subject: [PATCH 1/2] LimbPartition::multPt: use the top limb of the current level, not the physical top of the storage LimbPartition::multPt takes limb.back() (and p.limb.back()) for the multiply + INTT of the top limb, while the fused NTT_MULTPT launch it feeds is given limbsize = getLimbSize(*level) and base-converts from limb[limbsize - 1]. The two differ whenever the storage vector holds more limbs than the current level, which is the normal state: dropToLevel and rescale never pop limbs and polys are recycled through the auxiliary pool. Under FIXEDMANUAL, multPt(pt, rescale = true) therefore decrypts correctly only for a ciphertext at the top level and returns garbage below it. Use limb.at(limbsize - 1), as LimbPartition::rescale already does. Co-Authored-By: Claude Fable 5.1 --- src/CKKS/LimbPartition.cu | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CKKS/LimbPartition.cu b/src/CKKS/LimbPartition.cu index 6ac9b92a..020d6936 100644 --- a/src/CKKS/LimbPartition.cu +++ b/src/CKKS/LimbPartition.cu @@ -662,13 +662,16 @@ void LimbPartition::multPt(const LimbPartition& p) { static std::map exec_map; { - LimbImpl& top = limb.back(); + // The storage vector may hold more limbs than the current level (limbs are never popped by rescale/dropToLevel and + // polys are recycled through the auxiliary-poly pool); the top limb of the CURRENT level is limb[limbsize - 1], as in + // LimbPartition::rescale and in the fused NTT_MULTPT kernel below (ApplyNTT receives limbsize from NTT()). + LimbImpl& top = limb.at(limbsize - 1); cudaGraphExec_t& exec = exec_map[limbsize]; run_in_graph(exec, s, [&]() { STREAM(top).wait(s); - SWITCH(top, mult(p.limb.back())); + SWITCH(top, mult(p.limb.at(limbsize - 1))); SWITCH(top, INTT()); for (int32_t i = 0; i < limbsize - 1; i += cc.batch) { From 7fec2819b03e1f025d785dc6247f413de98319e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Agull=C3=B3=20Domingo?= <72665336+carlostriste@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:52:03 +0200 Subject: [PATCH 2/2] Remove comments regarding limb storage in LimbPartition Removed comments about limb storage and recycling in LimbPartition. --- src/CKKS/LimbPartition.cu | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/CKKS/LimbPartition.cu b/src/CKKS/LimbPartition.cu index 020d6936..a92887c1 100644 --- a/src/CKKS/LimbPartition.cu +++ b/src/CKKS/LimbPartition.cu @@ -662,9 +662,6 @@ void LimbPartition::multPt(const LimbPartition& p) { static std::map exec_map; { - // The storage vector may hold more limbs than the current level (limbs are never popped by rescale/dropToLevel and - // polys are recycled through the auxiliary-poly pool); the top limb of the CURRENT level is limb[limbsize - 1], as in - // LimbPartition::rescale and in the fused NTT_MULTPT kernel below (ApplyNTT receives limbsize from NTT()). LimbImpl& top = limb.at(limbsize - 1); cudaGraphExec_t& exec = exec_map[limbsize];