Skip to content

Commit

Permalink
Adaptions towards final FIPS 203
Browse files Browse the repository at this point in the history
Mostly updating document references and renaming a few local variables
to better align with the final specs.

Co-Authored-By: Fabian Albert <[email protected]>
  • Loading branch information
reneme and FAlbertDev committed Aug 14, 2024
1 parent 33db868 commit eb47076
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 51 deletions.
25 changes: 16 additions & 9 deletions src/lib/pubkey/kyber/kyber_common/kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,36 +229,43 @@ std::unique_ptr<Private_Key> Kyber_PublicKey::generate_another(RandomNumberGener
}

/**
* NIST FIPS 203 IPD, Algorithms 12 (K-PKE.KeyGen) and 15 (ML-KEM.KeyGen)
* NIST FIPS 203, Algorithms 13 (K-PKE.KeyGen),
* 16 (ML-KEM.KeyGen_internal), and
* 19 (ML-KEM.KeyGen)
*
* In contrast to the specification, random value generation is inlined
* with the actual PKE key generation. The sampling loops spelled out in
* FIPS 203 are hidden in the sample_* functions. The keys are kept in
* memory without serialization, which is deferred until requested.
*/
Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) {
KyberConstants mode(m);

// Algorithm 12 (K-PKE.KeyGen) ----------------
const auto d = CT::driveby_poison(rng.random_vec<KyberSeedRandomness>(KyberConstants::SEED_BYTES));

const auto d = rng.random_vec<KyberSeedRandomness>(KyberConstants::SEED_BYTES);
CT::poison(d);
// Algorithm 13 (K-PKE.KeyGen) ----------------

auto [rho, sigma] = mode.symmetric_primitives().G(d);
Kyber_Algos::PolynomialSampler ps(sigma, mode);
auto [rho, sigma] = mode.symmetric_primitives().G(d); // TODO: ML-KEM adds a single-byte k as domain separation

CT::unpoison(rho); // rho is public (seed for the public matrix A)

auto A = Kyber_Algos::sample_matrix(rho, false /* not transposed */, mode);

// The nonce N is handled internally by the PolynomialSampler
Kyber_Algos::PolynomialSampler ps(sigma, mode);
auto s = ntt(ps.sample_polynomial_vector_cbd_eta1());
const auto e = ntt(ps.sample_polynomial_vector_cbd_eta1());

auto t = montgomery(A * s);
t += e;
t.reduce();

// End Algorithm 12 ---------------------------
// End Algorithm 13 ---------------------------

CT::unpoison_all(t, s);

m_public = std::make_shared<Kyber_PublicKeyInternal>(mode, std::move(t), std::move(rho));
m_private = std::make_shared<Kyber_PrivateKeyInternal>(
std::move(mode), std::move(s), rng.random_vec<KyberImplicitRejectionValue>(KyberConstants::SEED_BYTES));
std::move(mode), std::move(s), /* z = */ rng.random_vec<KyberImplicitRejectionValue>(KyberConstants::SEED_BYTES));
}

Kyber_PrivateKey::Kyber_PrivateKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) :
Expand Down
52 changes: 36 additions & 16 deletions src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ namespace Botan::Kyber_Algos {
namespace {

/**
* NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d < 12 in combination with
* Formula 4.5 (Compress)
* NIST FIPS 203, Algorithm 5 (ByteEncode) for d < 12 in combination with
* Formula 4.7 (Compress)
*/
template <size_t d>
requires(d < 12)
Expand All @@ -33,15 +33,15 @@ void poly_compress_and_encode(BufferStuffer& bs, const KyberPoly& p) {
}

/**
* NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d == 12
* NIST FIPS 203, Algorithm 5 (ByteEncode) for d == 12
*/
void byte_encode(BufferStuffer& bs, const KyberPolyNTT& p) {
CRYSTALS::pack<KyberConstants::Q - 1>(p, bs);
}

/**
* NIST FIPS 203 IPD, Algorithm 5 (ByteDecode) for d < 12 in combination with
* Formula 4.6 (Decompress)
* NIST FIPS 203, Algorithm 6 (ByteDecode) for d < 12 in combination with
* Formula 4.8 (Decompress)
*/
template <size_t d>
requires(d < 12)
Expand All @@ -50,7 +50,7 @@ void poly_decode_and_decompress(KyberPoly& p, BufferSlicer& bs) {
}

/**
* NIST FIPS 203 IPD, Algorithm 5 (ByteDecode) for d == 12
* NIST FIPS 203, Algorithm 6 (ByteDecode) for d == 12
*/
void byte_decode(KyberPolyNTT& p, BufferSlicer& bs) {
CRYSTALS::unpack<KyberConstants::Q - 1>(p, bs);
Expand All @@ -61,7 +61,11 @@ void byte_decode(KyberPolyNTT& p, BufferSlicer& bs) {
}

/**
* NIST FIPS 203 IPD, Algorithm 6 (SampleNTT)
* NIST FIPS 203, Algorithm 7 (SampleNTT)
*
* Note that this assumes that the XOF has been initialized with the correct
* seed + two bytes of indices prior to invoking this function.
* See sample_matrix() below.
*/
void sample_ntt_uniform(KyberPolyNTT& p, XOF& xof) {
auto sample = [&xof]() -> std::pair<uint16_t, uint16_t> {
Expand All @@ -82,15 +86,26 @@ void sample_ntt_uniform(KyberPolyNTT& p, XOF& xof) {
}

/**
* NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) for eta = 2
* NIST FIPS 203, Algorithm 8 (SamplePolyCBD)
*
* Implementations for eta = 2 and eta = 3 are provided separately as template
* specializations below.
*/
template <KyberConstants::KyberEta eta>
void sample_poly_cbd(KyberPoly& poly, StrongSpan<const KyberSamplingRandomness> randomness);

/**
* NIST FIPS 203, Algorithm 8 (SamplePolyCBD) for eta = 2
*/
void sample_poly_cbd2(KyberPoly& poly, StrongSpan<const KyberSamplingRandomness> randomness) {
template <>
void sample_poly_cbd<KyberConstants::KyberEta::_2>(KyberPoly& poly,
StrongSpan<const KyberSamplingRandomness> randomness) {
BufferSlicer bs(randomness);

for(size_t i = 0; i < poly.size() / 8; ++i) {
const uint32_t t = Botan::load_le(bs.take<4>());

// SIMD trick: calculate 16 2-bit-sums in parallel
// SWAR (SIMD within a Register) trick: calculate 16 2-bit-sums in parallel
constexpr uint32_t operand_bitmask = 0b01010101010101010101010101010101;

// clang-format off
Expand All @@ -109,15 +124,17 @@ void sample_poly_cbd2(KyberPoly& poly, StrongSpan<const KyberSamplingRandomness>
}

/**
* NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) for eta = 2
* NIST FIPS 203, Algorithm 8 (SamplePolyCBD) for eta = 3
*/
void sample_poly_cbd3(KyberPoly& poly, StrongSpan<const KyberSamplingRandomness> randomness) {
template <>
void sample_poly_cbd<KyberConstants::KyberEta::_3>(KyberPoly& poly,
StrongSpan<const KyberSamplingRandomness> randomness) {
BufferSlicer bs(randomness);

for(size_t i = 0; i < poly.size() / 4; ++i) {
const uint32_t t = load_le3(bs.take<3>());

// SIMD trick: calculate 8 3-bit-sums in parallel
// SWAR (SIMD within a Register) trick: calculate 8 3-bit-sums in parallel
constexpr uint32_t operand_bitmask = 0b00000000001001001001001001001001;

// clang-format off
Expand Down Expand Up @@ -309,16 +326,19 @@ KyberPolyMat sample_matrix(StrongSpan<const KyberSeedRho> seed, bool transposed,
}

/**
* NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD)
* NIST FIPS 203, Algorithm 8 (SamplePolyCBD)
*
* The actual implementation is above. This just dispatches to the correct
* specialization based on the eta of the chosen mode.
*/
void sample_polynomial_from_cbd(KyberPoly& poly,
KyberConstants::KyberEta eta,
const KyberSamplingRandomness& randomness) {
switch(eta) {
case KyberConstants::KyberEta::_2:
return sample_poly_cbd2(poly, randomness);
return sample_poly_cbd<KyberConstants::KyberEta::_2>(poly, randomness);
case KyberConstants::KyberEta::_3:
return sample_poly_cbd3(poly, randomness);
return sample_poly_cbd<KyberConstants::KyberEta::_3>(poly, randomness);
}

BOTAN_ASSERT_UNREACHABLE();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/pubkey/kyber/kyber_common/kyber_algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ T encode_polynomial_vector(const KyberPolyVecNTT& vec, const KyberConstants& mod
/**
* Allows sampling multiple polynomials from a single seed via a XOF.
*
* Used in Algorithms 12 (K-PKE.KeyGen) and 13 (K-PKE.Encrypt), and takes care
* Used in Algorithms 13 (K-PKE.KeyGen) and 14 (K-PKE.Encrypt), and takes care
* of the continuous nonce value internally.
*/
template <typename SeedT>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/pubkey/kyber/kyber_common/kyber_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class KyberConstants final {
/// modulus
static constexpr T Q = 3329;

/// as specified in FIPS 203 (see Algorithm 9 (NTT^-1), f = 128^-1 mod Q)
/// as specified in FIPS 203 (see Algorithm 10 (NTT^-1), f = 128^-1 mod Q)
static constexpr T F = 3303;

/// the primitive 256-th root of unity modulo Q (see FIPS 203 Section 4.3)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/pubkey/kyber/kyber_common/kyber_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ inline uint32_t load_le3(std::span<const uint8_t, 3> in) {
}

/**
* NIST FIPS 203 IPD, Formula 4.5 (Compress)
* NIST FIPS 203, Formula 4.7 (Compress)
*/
template <size_t d>
requires(d > 0 && d < 12)
Expand Down Expand Up @@ -57,7 +57,7 @@ constexpr std::make_unsigned_t<KyberConstants::T> compress(KyberConstants::T x)
};

/**
* NIST FIPS 203 IPD, Formula 4.6 (Decompress)
* NIST FIPS 203, Formula 4.8 (Decompress)
*/
template <size_t d>
requires(d > 0 && d < 12)
Expand Down
22 changes: 16 additions & 6 deletions src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,31 @@ Kyber_PublicKeyInternal::Kyber_PublicKeyInternal(KyberConstants mode, KyberPolyV
m_H_public_key_bits_raw(m_mode.symmetric_primitives().H(m_public_key_bits_raw)) {}

/**
* NIST FIPS 203 IPD, Algorithm 13 (K-PKE.Encrypt)
* NIST FIPS 203, Algorithm 14 (K-PKE.Encrypt)
*
* In contrast to FIPS 203, the matrix @p At is not sampled for every invocation,
* instead it is precomputed and passed in as a parameter. Similarly, the t^T is
* already decoded and available as a member variable. This allows to reuse these
* structures for multiple encryptions.
*
* The sampling loops spelled out in FIPS 203 are hidden in the sample_* functions.
*/
void Kyber_PublicKeyInternal::indcpa_encrypt(StrongSpan<KyberCompressedCiphertext> out_ct,
StrongSpan<const KyberMessage> m,
StrongSpan<const KyberEncryptionRandomness> r,
const KyberPolyMat& At) const {
// The nonce N is handled internally by the PolynomialSampler
Kyber_Algos::PolynomialSampler ps(r, m_mode);

const auto rv = ntt(ps.sample_polynomial_vector_cbd_eta1());
const auto y = ntt(ps.sample_polynomial_vector_cbd_eta1());
const auto e1 = ps.sample_polynomial_vector_cbd_eta2();
const auto e2 = ps.sample_polynomial_cbd_eta2();

auto u = inverse_ntt(At * rv);
auto u = inverse_ntt(At * y);
u += e1;
u.reduce();

const auto mu = Kyber_Algos::polynomial_from_message(m);
auto v = inverse_ntt(m_t * rv);
auto v = inverse_ntt(m_t * y);
v += e2;
v += mu;
v.reduce();
Expand All @@ -50,7 +57,10 @@ void Kyber_PublicKeyInternal::indcpa_encrypt(StrongSpan<KyberCompressedCiphertex
}

/**
* NIST FIPS 203 IPD, Algorithm 14 (K-PKE.Decrypt)
* NIST FIPS 203, Algorithm 15 (K-PKE.Decrypt)
*
* s^T is already decoded and available as a member variable. This allows to reuse
* the structure for multiple decryptions.
*/
KyberMessage Kyber_PrivateKeyInternal::indcpa_decrypt(StrongSpan<const KyberCompressedCiphertext> ct) const {
auto [u, v] = Kyber_Algos::decompress_ciphertext(ct, m_mode);
Expand Down
28 changes: 16 additions & 12 deletions src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base<KyberConstants, KyberP

public:
/**
* NIST FIPS 203 IPD, Algorithm 8 (NTT)
* NIST FIPS 203, Algorithm 9 (NTT)
*
* Produces the result of the NTT transformation without any montgomery
* factors in the coefficients.
* factors in the coefficients. Zetas are pre-computed and stored in the
* zetas array. The zeta values contain the montgomery factor 2^16 mod q.
*/
constexpr static void ntt(std::span<T, N> p) {
for(size_t len = N / 2, k = 0; len >= 2; len /= 2) {
for(size_t len = N / 2, i = 0; len >= 2; len /= 2) {
for(size_t start = 0, j = 0; start < N; start = j + len) {
const auto zeta = zetas[++k];
const auto zeta = zetas[++i];
for(j = start; j < start + len; ++j) {
const auto t = fqmul(zeta, p[j + len]);
p[j + len] = p[j] - t;
Expand All @@ -63,7 +64,7 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base<KyberConstants, KyberP
}

/**
* NIST FIPS 203 IPD, Algorithm 9 (NTT^-1)
* NIST FIPS 203, Algorithm 10 (NTT^-1)
*
* The output is effectively multiplied by the montgomery parameter 2^16
* mod q so that the input factors 2^(-16) mod q are eliminated. Note
Expand All @@ -74,9 +75,9 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base<KyberConstants, KyberP
* factor of (2^16 mod q) added (!). See above.
*/
static constexpr void inverse_ntt(std::span<T, N> p) {
for(size_t len = 2, k = 127; len <= N / 2; len *= 2) {
for(size_t len = 2, i = 127; len <= N / 2; len *= 2) {
for(size_t start = 0, j = 0; start < N; start = j + len) {
const auto zeta = zetas[k--];
const auto zeta = zetas[i--];
for(j = start; j < start + len; ++j) {
const auto t = p[j];
p[j] = barrett_reduce_coefficient(t + p[j + len]);
Expand All @@ -91,17 +92,20 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base<KyberConstants, KyberP
}

/**
* NIST FIPS 203 IPD, Algorithms 10 (MultiplyNTTs) and 11 (BaseCaseMultiply)
* NIST FIPS 203, Algorithms 11 (MultiplyNTTs) and 12 (BaseCaseMultiply)
*
* The result contains factors of 2^(-16) mod q (i.e. the inverse montgomery factor).
* This factor is eliminated by the inverse NTT transformation, see above.
*/
static constexpr void poly_pointwise_montgomery(std::span<T, N> result,
std::span<const T, N> lhs,
std::span<const T, N> rhs) {
/**
* NIST FIPS 203 IPD, Algorithm 11 (BaseCaseMultiply)
* NIST FIPS 203, Algorithm 12 (BaseCaseMultiply)
*/
auto basemul = [](const auto s, const auto t, const T zeta) -> std::tuple<T, T> {
return {static_cast<T>(fqmul(fqmul(s[1], t[1]), zeta) + fqmul(s[0], t[0])),
static_cast<T>(fqmul(s[0], t[1]) + fqmul(s[1], t[0]))};
auto basemul = [](const auto a, const auto b, const T zeta) -> std::tuple<T, T> {
return {static_cast<T>(fqmul(a[0], b[0]) + fqmul(fqmul(a[1], b[1]), zeta)),
static_cast<T>(fqmul(a[0], b[1]) + fqmul(a[1], b[0]))};
};

auto Tq_elem_count = [](auto p) { return p.size() / 2; };
Expand Down
10 changes: 8 additions & 2 deletions src/lib/pubkey/kyber/ml_kem_ipd/ml_kem_ipd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
namespace Botan {

/**
* NIST FIPS 203 IPD, Algorithm 16 (ML-KEM.Encaps)
* NIST FIPS 203, Algorithm 17 (ML-KEM.Encaps_internal), and 20 (ML-KEM.Encaps)
*
* Generation of the random value is inlined with its usage. The public matrix
* A^T as well as H(pk) are precomputed and readily available.
*/
void ML_KEM_IPD_Encryptor::encapsulate(StrongSpan<KyberCompressedCiphertext> out_encapsulated_key,
StrongSpan<KyberSharedSecret> out_shared_key,
Expand All @@ -32,7 +35,10 @@ void ML_KEM_IPD_Encryptor::encapsulate(StrongSpan<KyberCompressedCiphertext> out
}

/**
* NIST FIPS 203 IPD, Algorithm 17 (ML-KEM.Decaps)
* NIST FIPS 203, Algorithm 18 (ML-KEM.Decaps_internal) and 21 (ML-KEM.Decaps)
*
* The public and private keys are readily available as member variables and
* don't need to be decoded.
*/
void ML_KEM_IPD_Decryptor::decapsulate(StrongSpan<KyberSharedSecret> out_shared_key,
StrongSpan<const KyberCompressedCiphertext> c) {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/pubkey/pqcrystals/pqcrystals_encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct BitPackingTrait final {
};

/**
* Base implementation of NIST FIPS 203 IPD Algorithm 4 (ByteEncode) and NIST
* Base implementation of NIST FIPS 203 Algorithm 5 (ByteEncode) and NIST
* FIPS 204 Algorithms 10 (SimpleBitPack) and 11 (BitPack).
*
* This takes a polynomial @p p and packs its coefficients into the buffer
Expand Down Expand Up @@ -165,7 +165,7 @@ constexpr void pack(const Polynomial<PolyTrait, D>& p, BufferStuffer& stuffer, M
}

/**
* Base implementation of NIST FIPS 203 IPD Algorithm 5 (ByteDecode) and NIST
* Base implementation of NIST FIPS 203 Algorithm 6 (ByteDecode) and NIST
* FIPS 204 Algorithms 12 (SimpleBitUnpack) and 13 (BitUnpack).
*
* This takes a byte sequence represented by @p byte_source and unpacks its
Expand Down

0 comments on commit eb47076

Please sign in to comment.