From 2cb5d519d9eb852013103b52f387c8cf57bc7b52 Mon Sep 17 00:00:00 2001 From: BAder82t <41265463+BAder82t@users.noreply.github.com> Date: Tue, 30 Jun 2026 01:45:45 +0100 Subject: [PATCH] fix: constant-time modular arithmetic in interactive bootstrapping decrypt path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1198. Adds CT variants of ModAddFastEq and ModMulFastConst used in PolynomialRound, which operates on cs = c[1]*s + c[0] — a polynomial that is directly secret-key-correlated. The original branching variants remain untouched for server-side hot paths (NTT, EvalMult, etc.). New utils/constanttime.h provides SubIfGE and AddIfNeg using the branchless mask idiom (no asm barriers, vectorisation preserved). NativeIntegerT gains ModAddFastEqCT and ModMulFastConstCT alongside the existing fast variants. --- src/core/include/math/hal/intnat/ubintnat.h | 13 ++++ src/core/include/utils/constanttime.h | 82 +++++++++++++++++++++ src/pke/lib/schemerns/rns-multiparty.cpp | 8 +- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/core/include/utils/constanttime.h diff --git a/src/core/include/math/hal/intnat/ubintnat.h b/src/core/include/math/hal/intnat/ubintnat.h index 2454b5ac1..3af86e3bd 100644 --- a/src/core/include/math/hal/intnat/ubintnat.h +++ b/src/core/include/math/hal/intnat/ubintnat.h @@ -41,6 +41,7 @@ #include "math/hal/integer.h" #include "math/nbtheory.h" +#include "utils/constanttime.h" #include "utils/debug.h" #include "utils/exception.h" #include "utils/inttypes.h" @@ -756,6 +757,11 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface= 0 ? yprime : yprime + modulus.m_value}; } + NativeIntegerT ModMulFastConstCT(const NativeIntegerT& b, const NativeIntegerT& modulus, + const NativeIntegerT& bInv) const { + NativeInt q = MultDHi(m_value, bInv.m_value) + 1; + SignedNativeInt yprime = static_cast(m_value * b.m_value - q * modulus.m_value); + return {static_cast(::lbcrypto::ct::AddIfNeg(yprime, static_cast(modulus.m_value)))}; + } + /** * Modular multiplication using a precomputation for the multiplicand. * In-place variant. diff --git a/src/core/include/utils/constanttime.h b/src/core/include/utils/constanttime.h new file mode 100644 index 000000000..75a57ee6d --- /dev/null +++ b/src/core/include/utils/constanttime.h @@ -0,0 +1,82 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2014-2023, NJIT, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== + +// Branchless modular arithmetic primitives — eliminates secret-dependent branches +// in ModAddFast/ModSubFast/ModMulFastConst to prevent timing side-channels during +// multiparty decryption. No asm barriers; vectorisation is preserved. +// +// NOTE: CT guarantees do NOT hold on WASM (__EMSCRIPTEN__) — MultD uses +// software carry-detect branches that are data-dependent on every multiply. + +#ifndef LBCRYPTO_UTILS_CONSTANTTIME_H +#define LBCRYPTO_UTILS_CONSTANTTIME_H + +#include +#include + +namespace lbcrypto { +namespace ct { + +template +inline constexpr int kTopBit = static_cast(sizeof(U) * 8 - 1); + +// return x - m if x >= m, else x (branchless; both operands must be < 2^(bits-1)) +template +inline U SubIfGE(U x, U m) noexcept { + static_assert(std::is_unsigned_v, "SubIfGE requires unsigned type"); + const U diff = x - m; + const U mask = U(0) - (diff >> kTopBit); + return diff + (mask & m); +} + +// return (a - b) mod m (branchless; a,b must be in [0, m)) +template +inline U ModSubFast(U a, U b, U m) noexcept { + static_assert(std::is_unsigned_v, "ModSubFast requires unsigned type"); + const U diff = a - b; + const U mask = U(0) - (diff >> kTopBit); + return diff + (mask & m); +} + +// return x + m if x < 0, else x (branchless; Barrett correction step) +// Unsigned right-shift used for well-defined behaviour in C++17. +template +inline S AddIfNeg(S x, S m) noexcept { + static_assert(std::is_signed_v, "AddIfNeg requires signed type"); + using U = std::make_unsigned_t; + const S sign = -static_cast(static_cast(x) >> kTopBit); + return x + (sign & m); +} + +} // namespace ct +} // namespace lbcrypto + +#endif // LBCRYPTO_UTILS_CONSTANTTIME_H diff --git a/src/pke/lib/schemerns/rns-multiparty.cpp b/src/pke/lib/schemerns/rns-multiparty.cpp index 8e82aa5ad..07a26802f 100644 --- a/src/pke/lib/schemerns/rns-multiparty.cpp +++ b/src/pke/lib/schemerns/rns-multiparty.cpp @@ -276,13 +276,13 @@ void PolynomialRound(DCRTPoly& dcrtpoly) { // 128-bit integers for (size_t k = 0; k < dcrtpoly.GetRingDimension(); k++) { NativeInteger::DNativeInt x128 = - (poly[0][k].ModMulFastConst(qInv[0], q[0], precon[0])).ConvertToInt() * q[1].ConvertToInt(); - x128 += (poly[1][k].ModMulFastConst(qInv[1], q[1], precon[1])).ConvertToInt() * q[0].ConvertToInt(); + (poly[0][k].ModMulFastConstCT(qInv[0], q[0], precon[0])).ConvertToInt() * q[1].ConvertToInt(); + x128 += (poly[1][k].ModMulFastConstCT(qInv[1], q[1], precon[1])).ConvertToInt() * q[0].ConvertToInt(); if (x128 > Q) x128 %= Q; if ((x128 > Q1quart) && (x128 <= Q3quart)) { - poly[0][k].ModAddFastEq(qHalf[0], q[0]); - poly[1][k].ModAddFastEq(qHalf[1], q[1]); + poly[0][k].ModAddFastEqCT(qHalf[0], q[0]); + poly[1][k].ModAddFastEqCT(qHalf[1], q[1]); } }