Skip to content

Commit

Permalink
Generic ct division for side-channel prevention
Browse files Browse the repository at this point in the history
Co-authored-by: René Meusel <[email protected]>
  • Loading branch information
FAlbertDev and reneme committed Jan 24, 2024
1 parent c5a5396 commit 58a9962
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 17 deletions.
71 changes: 54 additions & 17 deletions src/lib/pubkey/kyber/kyber_common/kyber_structures.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* (C) 2021-2024 Jack Lloyd
* (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity
* (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH
* (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity
* (C) 2024 René Meusel and Fabian Albert, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
Expand All @@ -28,6 +28,50 @@

namespace Botan {

namespace detail {

/**
* Helper to implement integer division by a divisor @p d > 1 known at compile
* time, that avoids the emission of potential variable time operations by the
* compiler. Note that this will work for numerators that must adhere to
* n < 2^W. @p W is given as a template parameter.
*
* Based on "Hacker's Delight" (Second Edition) by Henry S. Warren, Jr. Chapter
* 10-9 "Unsigned Division by Divisors >= 1"
*/
template <uint8_t W, uint16_t d>
requires(d >= 2) && (d < (1 << W)) && (W <= 32)
consteval std::pair<uint8_t, uint32_t> calculate_p_and_m() {
constexpr uint8_t p = [] {
constexpr auto nc = static_cast<uint32_t>((uint64_t(1) << W) - ((uint64_t(1) << W) % d) - 1);
for(uint8_t p_candidate = W; p_candidate <= 2 * W; ++p_candidate) {
const uint64_t pp2 = (uint64_t(1) << p_candidate);
if(pp2 > nc * (d - 1 - ((pp2 - 1) % d))) {
return p_candidate;
}
}

BOTAN_ASSERT_UNREACHABLE();
}();

static_assert(p < 64);
constexpr uint64_t pp2 = (uint64_t(1) << p);
constexpr uint64_t m = (pp2 + d - 1 - ((pp2 - 1) % d)) / d;
static_assert(m <= std::numeric_limits<uint32_t>::max());
return {p, static_cast<uint32_t>(m)};
}

} // namespace detail

template <uint16_t d>
uint32_t ct_divide_by(uint32_t n) {
// Get constants p and m for a division by d for numerators < 2^18.
constexpr uint8_t W = 18;
BOTAN_DEBUG_ASSERT(n < (1 << W));
constexpr auto pm = detail::calculate_p_and_m<W, d>();
return static_cast<uint32_t>((static_cast<uint64_t>(n) * pm.second) >> pm.first);
}

class Polynomial {
public:
Polynomial() : m_coeffs({0}) {}
Expand Down Expand Up @@ -180,21 +224,12 @@ class Polynomial {

this->csubq();

auto compress = [](uint32_t t) {
// (t << 1) + ((KyberConstants::Q / 2) / KyberConstants::Q) & 1
// Note that magic numbers assume that ::Q = 3329
t <<= 1;
t += 1665;
t *= 80635;
t >>= 28;
t &= 1;
return static_cast<uint8_t>(t);
};

for(size_t i = 0; i < size() / 8; ++i) {
result[i] = 0;
for(size_t j = 0; j < 8; ++j) {
result[i] |= compress(this->m_coeffs[8 * i + j]) << j;
const auto t = ct_divide_by<KyberConstants::Q>((static_cast<uint16_t>(this->m_coeffs[8 * i + j]) << 1) +
KyberConstants::Q / 2);
result[i] |= static_cast<uint8_t>(t & 1) << j;
}
}

Expand Down Expand Up @@ -632,8 +667,9 @@ class Ciphertext {
if(mode.k() == 2 || mode.k() == 3) {
for(size_t i = 0; i < p.size() / 8; ++i) {
for(size_t j = 0; j < 8; ++j) {
t[j] =
(((static_cast<uint16_t>(p[8 * i + j]) << 4) + KyberConstants::Q / 2) / KyberConstants::Q) & 15;
t[j] = static_cast<uint8_t>(ct_divide_by<KyberConstants::Q>(
(static_cast<uint16_t>(p[8 * i + j]) << 4) + KyberConstants::Q / 2) &
0b1111);
}

auto r = bs.next<4>();
Expand All @@ -645,8 +681,9 @@ class Ciphertext {
} else if(mode.k() == 4) {
for(size_t i = 0; i < p.size() / 8; ++i) {
for(size_t j = 0; j < 8; ++j) {
t[j] =
(((static_cast<uint32_t>(p[8 * i + j]) << 5) + KyberConstants::Q / 2) / KyberConstants::Q) & 31;
t[j] = static_cast<uint8_t>(ct_divide_by<KyberConstants::Q>(
(static_cast<uint32_t>(p[8 * i + j]) << 5) + KyberConstants::Q / 2) &
0b11111);
}

auto r = bs.next<5>();
Expand Down
22 changes: 22 additions & 0 deletions src/tests/test_kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <botan/pubkey.h>
#include <botan/rng.h>
#include <botan/internal/fmt.h>
#include <botan/internal/kyber_constants.h>
#include <botan/internal/kyber_structures.h>
#include <botan/internal/stl_util.h>
#endif

Expand Down Expand Up @@ -249,6 +251,26 @@ class Kyber_Keygen_Tests final : public PK_Key_Generation_Test {
};

BOTAN_REGISTER_TEST("kyber", "kyber_keygen", Kyber_Keygen_Tests);

namespace {

std::vector<Test::Result> test_kyber_utilities() {
return {
Botan_Tests::CHECK("constant-time division helper",
[](Test::Result& result) {
constexpr auto Q = Botan::KyberConstants::Q;
// Check ct_divide_by with all possible inputs that may appear in Kyber.
for(uint32_t i = 0; i < (1 << 18); ++i) {
result.test_is_eq<uint32_t>(Botan::fmt("{}/Q", i), Botan::ct_divide_by<Q>(i), i / Q);
}
}),
};
}

} // namespace

BOTAN_REGISTER_TEST_FN("kyber", "kyber_utils", test_kyber_utilities);

#endif

} // namespace Botan_Tests

0 comments on commit 58a9962

Please sign in to comment.