diff --git a/src/core/include/math/hal/intnat/transformnat-impl.h b/src/core/include/math/hal/intnat/transformnat-impl.h index 543d6b152..71655b65a 100644 --- a/src/core/include/math/hal/intnat/transformnat-impl.h +++ b/src/core/include/math/hal/intnat/transformnat-impl.h @@ -220,18 +220,8 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverseInPlace(c omegaFactor = (*element)[indexHi]; omegaFactor.ModMulFastEq(omega, modulus, mu); - hiVal = loVal + omegaFactor; - if (hiVal >= modulus) { - hiVal -= modulus; - } - - if (loVal < omegaFactor) { - loVal += modulus; - } - loVal -= omegaFactor; - - (*element)[indexLo] = hiVal; - (*element)[indexHi] = loVal; + (*element)[indexLo] = loVal.ModAddFast(omegaFactor, modulus); + (*element)[indexHi] = loVal.ModSubFast(omegaFactor, modulus); } } t >>= 1; @@ -254,7 +244,7 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverse(const Ve result->SetModulus(modulus); uint32_t i, m, j1, j2, indexOmega, indexLo, indexHi; - IntType omega, omegaFactor, loVal, hiVal, zero(0); + IntType omega, omegaFactor, loVal; for (i = 0; i < n; ++i) { (*result)[i] = element[i]; @@ -272,25 +262,9 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverse(const Ve indexHi = indexLo + t; loVal = (*result)[indexLo]; omegaFactor = (*result)[indexHi]; - if (omegaFactor != zero) { - omegaFactor.ModMulFastEq(omega, modulus, mu); - - hiVal = loVal + omegaFactor; - if (hiVal >= modulus) { - hiVal -= modulus; - } - - if (loVal < omegaFactor) { - loVal += modulus; - } - loVal -= omegaFactor; - - (*result)[indexLo] = hiVal; - (*result)[indexHi] = loVal; - } - else { - (*result)[indexHi] = loVal; - } + omegaFactor.ModMulFastEq(omega, modulus, mu); + (*result)[indexLo] = loVal.ModAddFast(omegaFactor, modulus); + (*result)[indexHi] = loVal.ModSubFast(omegaFactor, modulus); } } t >>= 1; @@ -329,22 +303,8 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverseInPlace(c auto omegaFactor{(*element)[j1 + t]}; omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); auto loVal{(*element)[j1 + 0]}; -#if defined(__GNUC__) && !defined(__clang__) - auto hiVal{loVal + omegaFactor}; - if (hiVal >= modulus) - hiVal -= modulus; - if (loVal < omegaFactor) - loVal += modulus; - loVal -= omegaFactor; - (*element)[j1 + 0] = hiVal; - (*element)[j1 + t] = loVal; -#else - // fixes Clang slowdown issue, but requires lowVal be less than modulus - (*element)[j1 + 0] += omegaFactor - (omegaFactor >= (modulus - loVal) ? modulus : 0); - if (omegaFactor > loVal) - loVal += modulus; - (*element)[j1 + t] = loVal - omegaFactor; -#endif + (*element)[j1 + 0] = loVal.ModAddFast(omegaFactor, modulus); + (*element)[j1 + t] = loVal.ModSubFast(omegaFactor, modulus); } } } @@ -355,21 +315,8 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverseInPlace(c auto preconOmega{preconRootOfUnityTable[(i >> 1) + n]}; omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); auto loVal{(*element)[i + 0]}; -#if defined(__GNUC__) && !defined(__clang__) - auto hiVal{loVal + omegaFactor}; - if (hiVal >= modulus) - hiVal -= modulus; - if (loVal < omegaFactor) - loVal += modulus; - loVal -= omegaFactor; - (*element)[i + 0] = hiVal; - (*element)[i + 1] = loVal; -#else - (*element)[i + 0] += omegaFactor - (omegaFactor >= (modulus - loVal) ? modulus : 0); - if (omegaFactor > loVal) - loVal += modulus; - (*element)[i + 1] = loVal - omegaFactor; -#endif + (*element)[i + 0] = loVal.ModAddFast(omegaFactor, modulus); + (*element)[i + 1] = loVal.ModSubFast(omegaFactor, modulus); } } @@ -394,7 +341,7 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverse(const Ve uint32_t indexOmega, indexHi; NativeInteger preconOmega; - IntType omega, omegaFactor, loVal, hiVal, zero(0); + IntType omega, omegaFactor, loVal; uint32_t t = (n >> 1); uint32_t logt1 = GetMSB(t); @@ -410,25 +357,9 @@ void NumberTheoreticTransformNat::ForwardTransformToBitReverse(const Ve indexHi = indexLo + t; loVal = (*result)[indexLo]; omegaFactor = (*result)[indexHi]; - if (omegaFactor != zero) { - omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); - - hiVal = loVal + omegaFactor; - if (hiVal >= modulus) { - hiVal -= modulus; - } - - if (loVal < omegaFactor) { - loVal += modulus; - } - loVal -= omegaFactor; - - (*result)[indexLo] = hiVal; - (*result)[indexHi] = loVal; - } - else { - (*result)[indexHi] = loVal; - } + omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); + (*result)[indexLo] = loVal.ModAddFast(omegaFactor, modulus); + (*result)[indexHi] = loVal.ModSubFast(omegaFactor, modulus); } } } @@ -461,17 +392,8 @@ void NumberTheoreticTransformNat::InverseTransformFromBitReverseInPlace hiVal = (*element)[indexHi]; loVal = (*element)[indexLo]; - omegaFactor = loVal; - if (omegaFactor < hiVal) { - omegaFactor += modulus; - } - - omegaFactor -= hiVal; - - loVal += hiVal; - if (loVal >= modulus) { - loVal -= modulus; - } + omegaFactor = loVal.ModSubFast(hiVal, modulus); + loVal.ModAddFastEq(hiVal, modulus); omegaFactor.ModMulFastEq(omega, modulus, mu); @@ -545,24 +467,11 @@ void NumberTheoreticTransformNat::InverseTransformFromBitReverseInPlace auto preconOmega{preconRootOfUnityInverseTable[(i + n) >> 1]}; auto loVal{(*element)[i + 0]}; auto hiVal{(*element)[i + 1]}; -#if defined(__GNUC__) && !defined(__clang__) - auto omegaFactor{loVal}; - if (omegaFactor < hiVal) - omegaFactor += modulus; - omegaFactor -= hiVal; - loVal += hiVal; - if (loVal >= modulus) - loVal -= modulus; + auto omegaFactor = loVal.ModSubFast(hiVal, modulus); + loVal.ModAddFastEq(hiVal, modulus); omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); (*element)[i + 0] = loVal; (*element)[i + 1] = omegaFactor; -#else - auto omegaFactor{loVal + (hiVal > loVal ? modulus : 0) - hiVal}; - loVal += hiVal - (hiVal >= (modulus - loVal) ? modulus : 0); - (*element)[i + 0] = loVal; - omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); - (*element)[i + 1] = omegaFactor; -#endif } } // inner stages @@ -573,23 +482,11 @@ void NumberTheoreticTransformNat::InverseTransformFromBitReverseInPlace for (uint32_t j1{i << logt}, j2{j1 + t}; j1 < j2; ++j1) { auto loVal{(*element)[j1 + 0]}; auto hiVal{(*element)[j1 + t]}; -#if defined(__GNUC__) && !defined(__clang__) - auto omegaFactor{loVal}; - if (omegaFactor < hiVal) - omegaFactor += modulus; - omegaFactor -= hiVal; - loVal += hiVal; - if (loVal >= modulus) - loVal -= modulus; + auto omegaFactor = loVal.ModSubFast(hiVal, modulus); + loVal.ModAddFastEq(hiVal, modulus); omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); (*element)[j1 + 0] = loVal; (*element)[j1 + t] = omegaFactor; -#else - (*element)[j1 + 0] += hiVal - (hiVal >= (modulus - loVal) ? modulus : 0); - auto omegaFactor = loVal + (hiVal > loVal ? modulus : 0) - hiVal; - omegaFactor.ModMulFastConstEq(omega, modulus, preconOmega); - (*element)[j1 + t] = omegaFactor; -#endif } } } @@ -601,23 +498,11 @@ void NumberTheoreticTransformNat::InverseTransformFromBitReverseInPlace for (uint32_t j1{0}; j1 < j2; ++j1) { auto loVal{(*element)[j1]}; auto hiVal{(*element)[j1 + j2]}; -#if defined(__GNUC__) && !defined(__clang__) - auto omegaFactor{loVal}; - if (omegaFactor < hiVal) - omegaFactor += modulus; - omegaFactor -= hiVal; - loVal += hiVal; - if (loVal >= modulus) - loVal -= modulus; + auto omegaFactor = loVal.ModSubFast(hiVal, modulus); + loVal.ModAddFastEq(hiVal, modulus); omegaFactor.ModMulFastConstEq(omega1Inv, modulus, preconOmega1Inv); (*element)[j1 + 0] = loVal; (*element)[j1 + j2] = omegaFactor; -#else - (*element)[j1] += hiVal - (hiVal >= (modulus - loVal) ? modulus : 0); - auto omegaFactor = loVal + (hiVal > loVal ? modulus : 0) - hiVal; - omegaFactor.ModMulFastConstEq(omega1Inv, modulus, preconOmega1Inv); - (*element)[j1 + j2] = omegaFactor; -#endif } // perform remaining n/2 scalar multiplies by (n inverse) for (uint32_t i = 0; i < j2; ++i) diff --git a/src/core/include/math/hal/intnat/ubintnat.h b/src/core/include/math/hal/intnat/ubintnat.h index 2454b5ac1..7ecb8e7cc 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" @@ -735,11 +736,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface= mv) - r -= mv; - return {r}; + return {::lbcrypto::ct::SubIfGE(m_value + b.m_value, modulus.m_value)}; } /** * Modulus addition where operands are < modulus. In-place variant. @@ -749,10 +746,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface= mv) - m_value -= mv; + m_value = ::lbcrypto::ct::SubIfGE(m_value + b.m_value, modulus.m_value); return *this; } @@ -909,9 +903,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface> (n + 7)); - NativeIntegerT r(rv); - if (r.m_value >= mv) - r.m_value -= mv; - return r; + return {::lbcrypto::ct::SubIfGE(NativeInt(rv), mv)}; } template @@ -1371,9 +1359,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface= mv) - r.lo -= mv; - return {r.lo}; + return {::lbcrypto::ct::SubIfGE(r.lo, mv)}; } /** @@ -1395,9 +1381,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface> (n + 7)); - m_value = NativeInt(rv); - if (m_value >= mv) - m_value -= mv; + m_value = ::lbcrypto::ct::SubIfGE(NativeInt(rv), mv); return *this; } @@ -1412,9 +1396,7 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface= mv) - m_value -= mv; + m_value = ::lbcrypto::ct::SubIfGE(r.lo, mv); return *this; } @@ -1463,9 +1445,9 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface(m_value * b.m_value - q * modulus.m_value); - return {yprime >= 0 ? yprime : yprime + modulus.m_value}; + NativeInt q = MultDHi(m_value, bInv.m_value) + 1; + SignedNativeInt y = static_cast(m_value * b.m_value - q * modulus.m_value); + return {static_cast(::lbcrypto::ct::AddIfNeg(y, static_cast(modulus.m_value)))}; } /** @@ -1479,9 +1461,9 @@ class NativeIntegerT final : public lbcrypto::BigIntegerInterface(m_value * b.m_value - q * modulus.m_value); - m_value = static_cast(yprime >= 0 ? yprime : yprime + modulus.m_value); + NativeInt q = MultDHi(m_value, bInv.m_value) + 1; + SignedNativeInt y = static_cast(m_value * b.m_value - q * modulus.m_value); + m_value = static_cast(::lbcrypto::ct::AddIfNeg(y, static_cast(modulus.m_value))); return *this; } 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/core/unittest/UnitTestNTT.cpp b/src/core/unittest/UnitTestNTT.cpp index d2b64ee6a..75e7078ed 100644 --- a/src/core/unittest/UnitTestNTT.cpp +++ b/src/core/unittest/UnitTestNTT.cpp @@ -90,6 +90,27 @@ TEST(UTNTT, switch_format_simple_single_crt) { RUN_ALL_POLYS(switch_format_simple_single_crt, "switch_format_simple_single_crt") } +template +void switch_format_sparse_zeros(const std::string& msg) { + using ParmType = typename Element::Params; + + uint32_t m = 16; + uint32_t bits = 16; + auto params = std::make_shared(m, bits); + + Element x(params, Format::COEFFICIENT); + x = {1234, 5678, 0, 0, 0, 0, 0, 0}; + + Element xClone(x); + x.SwitchFormat(); + x.SwitchFormat(); + EXPECT_EQ(x, xClone) << msg; +} + +TEST(UTNTT, switch_format_sparse_zeros) { + RUN_ALL_POLYS(switch_format_sparse_zeros, "switch_format_sparse_zeros") +} + template void switch_format_simple_double_crt(const std::string& msg) { uint32_t init_m = 16;