Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Checks: '-*,
-bugprone-implicit-widening-of-multiplication-result,
performance-*,
-performance-unnecessary-value-param,
ccpcoreguidelines-*,
cppcoreguidelines-*,
misc-static-assert,
misc-throw-by-value-catch-by-reference,
misc-unconventional-assign-operator,
Expand Down
63 changes: 58 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
cmake_minimum_required(VERSION 3.21)
project(
deb
VERSION 0.4.1
VERSION 0.5.0
LANGUAGES CXX
DESCRIPTION "CryptoLab's official cryptosystem library for FHE.")

Expand Down Expand Up @@ -73,6 +73,12 @@ option(DEB_RUNTIME_RESOURCE_CHECK "Enable runtime resource check." ON)
option(DEB_SERIALIZE_API "Enable serialize API." ON)
option(DEB_SUPPORT_U64 "Compile u64 coefficient word type support." ON)
option(DEB_SUPPORT_U32 "Compile u32 coefficient word type support." OFF)
set(DEB_ARCH
""
CACHE
STRING
"Target CPU microarchitecture passed to -march (e.g. x86-64-v3, native). Empty keeps the portable default. x86-64-v3 (AVX2+FMA+BMI2) is recommended for modern x86 and speeds up the hot NTT/modular-arithmetic kernels without the AVX-512 downclocking that -march=native can cause."
)

if(NOT DEB_SUPPORT_U64 AND NOT DEB_SUPPORT_U32)
message(
Expand Down Expand Up @@ -101,6 +107,26 @@ if(BUILD_SHARED_LIBS)
FORCE)
endif()

# The serialize API puts flatbuffers into the PUBLIC include graph: the
# installed Serialize.hpp includes DebFBType.h, which includes
# <flatbuffers/flatbuffers.h>. A consumer cannot compile against an install that
# omits those headers, so this is not optional whenever we install at all.
if(DEB_INSTALL AND DEB_SERIALIZE_API)
set(DEB_INSTALL_FLATBUFFERS
ON
CACHE BOOL
"Enable installation of flatbuffers required by the serialize API."
FORCE)
endif()

# When tuning for a specific architecture, also tune the C dependencies (most
# importantly the alea RNG's Keccak permutation, which is a measurable part of
# encryption). Done before add_subdirectory(external) so those targets inherit
# it; the deb C++ library itself gets -march via target_compile_options below.
if(DEB_ARCH AND NOT MSVC)
string(APPEND CMAKE_C_FLAGS " -march=${DEB_ARCH}")
endif()

add_subdirectory(external)
add_subdirectory(prebuild)

Expand Down Expand Up @@ -150,6 +176,15 @@ if(NOT MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-pedantic)
endif()

# Optional target-architecture tuning. Applied PUBLIC so the hot kernels in the
# deb library itself and any inlined template code in dependents (benchmark,
# examples) are all compiled for the same ISA. Empty by default to preserve a
# portable baseline build.
if(DEB_ARCH AND NOT MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC -march=${DEB_ARCH})
message(STATUS "deb: tuning for -march=${DEB_ARCH}")
endif()

string(TOUPPER "${DEB_EXT_LIB_FOR_SECURE_ZERO}" _deb_secure_zero_backend)
if(_deb_secure_zero_backend STREQUAL "LIBSODIUM")
target_compile_definitions(${PROJECT_NAME} PUBLIC DEB_SECURE_ZERO_LIBSODIUM)
Expand All @@ -162,11 +197,18 @@ elseif(_deb_secure_zero_backend STREQUAL "NATIVE")
endif()
unset(_deb_secure_zero_backend)

# The install interface must mirror the build interface. The public headers
# include each other unqualified relative to include/${PROJECT_NAME} (e.g.
# utils/RandomGenerator.hpp does #include "Types.hpp", and Preset.hpp does
# #include "DebParam.hpp"), so that directory has to be an include root for
# consumers too. The plain include dir is kept as a root as well so consumers
# can write the qualified #include <${PROJECT_NAME}/Encryptor.hpp>.
target_include_directories(
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}>
$<BUILD_INTERFACE:${PRE_BUILD_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)

if(DEB_RUNTIME_RESOURCE_CHECK)
target_compile_definitions(${PROJECT_NAME} PUBLIC DEB_RESOURCE_CHECK)
Expand Down Expand Up @@ -266,9 +308,20 @@ if(DEB_INSTALL)
INCLUDES
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# Install header files
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# Install header files. The generated headers are excluded here and installed
# separately below, because they must not keep their extra "generated/" level
# in the installed tree.
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
PATTERN "generated" EXCLUDE)

# ${PRE_BUILD_DIR} is a second include root at build time, so headers such as
# Preset.hpp and Serialize.hpp include the generated ones unqualified
# (#include "DebParam.hpp"). The install tree exposes a single include root,
# so put the generated headers flat next to the headers that include them.
install(DIRECTORY ${PRE_BUILD_DIR}/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})

# Export target for use with find_package
install(
Expand Down
18 changes: 18 additions & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ add_executable(deb-bench-ct-vs-rt bench_ct_vs_rt.cpp)
target_link_libraries(deb-bench-ct-vs-rt PRIVATE deb)

enable_language(C)
# BLAKE3 is used only by the benchmark below, never by the library, so none of
# it belongs in the install tree. Its install rules are also broken for a
# subproject build: upstream writes libblake3.pc into its own binary dir but
# installs it from ${CMAKE_BINARY_DIR} (the top-level one), so `cmake --install`
# fails on a file that was never there. There is no BLAKE3_INSTALL option to
# turn this off, and EXCLUDE_FROM_ALL does not suppress install() rules, so the
# rules are skipped for the duration of the subdirectory instead.
set(_deb_skip_install_backup ${CMAKE_SKIP_INSTALL_RULES})
set(CMAKE_SKIP_INSTALL_RULES TRUE)
cpmaddpackage(
NAME
blake3
Expand All @@ -35,6 +44,15 @@ cpmaddpackage(
1.8.1
SOURCE_SUBDIR
c)
set(CMAKE_SKIP_INSTALL_RULES ${_deb_skip_install_backup})
unset(_deb_skip_install_backup)
# CMAKE_SKIP_INSTALL_RULES suppresses the generated cmake_install.cmake for that
# directory, but this directory's own script still include()s it, so leave an
# empty one in its place.
file(
WRITE ${blake3_BINARY_DIR}/cmake_install.cmake
"# BLAKE3 is a benchmark-only dependency and is intentionally not installed.\n"
)

add_executable(deb-benchmark-blake3 benchmark_blake3.cpp)
target_link_libraries(deb-benchmark-blake3
Expand Down
18 changes: 16 additions & 2 deletions cmake/debConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,22 @@

include(CMakeFindDependencyMacro)

find_dependency(alea REQUIRED)
find_dependency(flatbuffers REQUIRED)
# alea and flatbuffers are only dependencies a consumer has to find when they
# were installed alongside deb, which is the shared-library build. A static deb
# links them through BUILD_INTERFACE and bakes their objects into libdeb.a, so
# requiring them here would make find_package(deb) fail on every default
# (static) install, where neither package is present.
set(DEB_INSTALL_ALEA @DEB_INSTALL_ALEA@)
if(DEB_INSTALL_ALEA)
find_dependency(alea REQUIRED)
endif()
unset(DEB_INSTALL_ALEA)

set(DEB_INSTALL_FLATBUFFERS @DEB_INSTALL_FLATBUFFERS@)
if(DEB_INSTALL_FLATBUFFERS)
find_dependency(flatbuffers REQUIRED)
endif()
unset(DEB_INSTALL_FLATBUFFERS)

set(DEB_BUILD_WITH_OMP @DEB_BUILD_WITH_OMP@)
if(DEB_BUILD_WITH_OMP)
Expand Down
17 changes: 17 additions & 0 deletions cmake/warnings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,20 @@ function(set_deb_warnings target)
$<$<CXX_COMPILER_ID:MSVC>:
/W4>)
endfunction()

# Silence all warnings for a third-party target we pull in via CPM. A consumer
# project that includes deb with global warning flags (e.g.
# add_compile_options(-Wall ...) or CMAKE_CXX_FLAGS) leaks those flags into
# every add_subdirectory(), including our dependencies. A target-level -w / /w
# is appended after those global flags and disables the warnings we neither own
# nor can fix, so the dependency stays quiet regardless of who builds deb.
function(set_deb_no_warnings target)
if(NOT TARGET ${target})
return()
endif()
target_compile_options(
${target}
PRIVATE
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:-w>
$<$<CXX_COMPILER_ID:MSVC>:/w>)
endfunction()
28 changes: 15 additions & 13 deletions examples/SeedOnlyCiphertext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,33 +114,35 @@ int main() {
}

// ---------------------------------------------------------------------
// 3. Public-key seed-only encryption (needs the enc key to expand 'a')
// 3. Seed compression is rejected for public-key encryption
// ---------------------------------------------------------------------
// Under a public key the 'a' part is a = v*ax + e_a, derived from the same
// stream as the ephemeral encryption randomness v. Storing that seed in the
// ciphertext would let anyone holding the ciphertext and the (public)
// encryption key replay v and recover the plaintext without the secret key,
// so the library refuses the combination. Seed compression is only sound
// for secret-key encryption, where 'a' is public uniform randomness.
{
KeyGenerator keygen(preset);
SwitchKey ek = keygen.genEncKey(sk);

Ciphertext ctxt(preset);
enc.encrypt(msg, ek, ctxt, EncryptOptions().SeedOnlyA(true));
std::cout << "\n[Public-key seed-only]" << std::endl;
std::cout << " isAxFlushed=" << ctxt.isAxFlushed() << std::endl;

// a = v*ax + e cannot be regenerated without the encryption key, so the
// decryptor refuses a still-compressed public-key ciphertext.
try {
Message tmp(preset);
dec.decrypt(ctxt, sk, tmp);
std::cout << " unexpected: decrypt succeeded while compressed"
Ciphertext rejected(preset);
enc.encrypt(msg, ek, rejected, EncryptOptions().SeedOnlyA(true));
std::cout << " unexpected: encrypt accepted seed-only 'a'"
<< std::endl;
} catch (const std::exception &e) {
std::cout << " decrypt refused (expected): " << e.what()
std::cout << " encrypt refused (expected): " << e.what()
<< std::endl;
}

enc.completeCiphertext(ctxt, ek); // supply the encryption key
// Public-key encryption without seed compression works as usual.
Ciphertext ctxt(preset);
enc.encrypt(msg, ek, ctxt);
Message dec_msg(preset);
dec.decrypt(ctxt, sk, dec_msg);
std::cout << " log2 error (after completeCiphertext) = "
std::cout << " plain public-key encrypt log2 error = "
<< compareMessage(msg, dec_msg) << std::endl;
}

Expand Down
20 changes: 20 additions & 0 deletions external/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# ~~~

include(CPM)
include(warnings)

cpmaddpackage(
NAME
Expand Down Expand Up @@ -45,6 +46,25 @@ if(DEB_SERIALIZE_API)
set(flatbuffers_SOURCE_DIR
${flatbuffers_SOURCE_DIR}
CACHE PATH "" FORCE)

# flatbuffers is built from source via CPM. Keep its (and flatc's) compilation
# quiet so a consumer project's global warning flags don't surface warnings we
# neither own nor can fix. Also mark its headers as SYSTEM so the warnings
# don't leak through the generated headers we include either.
foreach(_fb_target flatbuffers flatbuffers_shared flatc flatlib)
set_deb_no_warnings(${_fb_target})
endforeach()
if(TARGET flatbuffers)
get_target_property(_fb_inc flatbuffers INTERFACE_INCLUDE_DIRECTORIES)
if(_fb_inc)
# Keep INTERFACE_INCLUDE_DIRECTORIES intact (it is what actually adds the
# headers to the compile line); INTERFACE_SYSTEM_INCLUDE_DIRECTORIES only
# flags which of those already-listed dirs are treated as -isystem.
set_target_properties(
flatbuffers PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
"${_fb_inc}")
endif()
endif()
endif()

string(TOUPPER "${DEB_EXT_LIB_FOR_SECURE_ZERO}" _deb_secure_zero_backend)
Expand Down
19 changes: 17 additions & 2 deletions include/deb/CKKSTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,12 @@ enum class CipherSeedMode : u8 {
NONE = 0, /**< Not seed-compressed; @c a is stored in full. */
UNIFORM =
1, /**< Secret-key origin: @c a is a uniform sample of the seed. */
PUBLICKEY = 2, /**< Public-key origin: @c a = v*ax + e; expanding requires
the encryption key. */
// Value 2 was PUBLICKEY (public-key origin, a = v*ax + e_a). It was removed
// because the stored seed also reproduces the ephemeral encryption
// randomness v, so publishing it reveals the plaintext to anyone holding
// the ciphertext and the public encryption key. Seed compression is only
// sound when 'a' is uniform. Buffers carrying it are rejected on
// deserialization; do not reuse the value.
};

/**
Expand Down Expand Up @@ -568,6 +572,17 @@ template <typename U = u64> class SwitchKeyT {
void setRotIdx(Size rot_idx) noexcept;
Size rotIdx() const noexcept;
Size dnum() const noexcept;
/**
* @brief Sets the decomposition count, which is also the number of @c ax
* polynomials the key holds.
*
* Every key kind keeps the invariant @c axSize()==dnum() and
* @c bxSize()==dnum()*num_secret. A self mod-pack key is sized by its
* @c pad_rank rather than the preset's gadget rank, so its generator uses
* this to record that rank; serialization relies on it to validate the
* key's shape.
*/
void setDnum(Size dnum) noexcept;

void
addAx(const Size num_polyunit, std::optional<Size> size = std::nullopt,
Expand Down
32 changes: 18 additions & 14 deletions include/deb/Encryptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ struct EncryptOptions {
release its storage after encryption. The
regenerated @c a follows the ciphertext's
domain (NTT, or coefficient when
ntt_out==false). Requires rank==1. */
ntt_out==false). Requires rank==1.
SECRET-KEY ENCRYPTION ONLY: encrypting under
a public key with this option throws, because
there @c a depends on the ephemeral
encryption randomness @c v and the stored
seed would reveal the plaintext to any holder
of the ciphertext and the public key. */
std::optional<RNGSeed> a_seed =
std::nullopt; /**< Optional fixed seed for the @c a part. When unset and
@ref seed_only_a is enabled, a fresh seed is drawn.
Expand Down Expand Up @@ -129,6 +135,15 @@ struct EncryptOptions {

/**
* @brief Provides CKKS encoding and encryption routines.
*
* @note Not thread-safe. The encode/encrypt methods are @c const, but they
* mutate the RNG streams and the reusable scratch buffers this object owns,
* so concurrent calls on the SAME instance are a data race. Use one instance
* per thread. Separate instances share no mutable state of their own, but
* constructing without an explicit seed, and encrypting with @ref
* EncryptOptions::seed_only_a but no @ref EncryptOptions::a_seed, both draw
* from the process-wide SeedGenerator singleton, which is itself
* unsynchronized; pass explicit seeds when such calls can overlap.
*/
template <Preset P = PRESET_EMPTY, typename U = u64>
class EncryptorT : public PresetTraits<P, U> {
Expand Down Expand Up @@ -246,16 +261,6 @@ class EncryptorT : public PresetTraits<P, U> {
*/
void completeCiphertext(CiphertextT<U> &ctxt) const;

/**
* @brief Regenerates the @c a part of a PUBLICKEY (public-key) seed-only
* ciphertext, where @c a = v*ax + e. The same encryption key used at
* encryption time must be supplied.
* @param ctxt Seed-only ciphertext to complete in place.
* @param enckey Encryption (switching) key used to produce @p ctxt.
*/
void completeCiphertext(CiphertextT<U> &ctxt,
const SwitchKeyT<U> &enckey) const;

private:
/**
* @brief Samples a zero-one polynomial.
Expand Down Expand Up @@ -302,9 +307,8 @@ class EncryptorT : public PresetTraits<P, U> {
*
* Mirrors @ref completeSecretKey: reseeds from the ciphertext's stored seed and
* refills the released @c a polynomial (no key required). Throws if the
* ciphertext has no seed, or if it is a PUBLICKEY seed-only ciphertext (use
* @ref EncryptorT::completeCiphertext with the encryption key for that case). A
* no-op when @c a is already present.
* ciphertext has no seed or is not @ref CipherSeedMode::UNIFORM. A no-op when
* @c a is already present.
*
* @param ctxt Seed-only ciphertext to complete in place.
*/
Expand Down
9 changes: 9 additions & 0 deletions include/deb/KeyGenerator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ namespace deb {

/**
* @brief Generates an encryption key and switching keys for CKKS presets.
*
* @note Not thread-safe. The genXxxKey methods are @c const, but they all
* draw from the RandomGenerator this object owns and that draw is not
* synchronized (the default ALEA backend states that its API "is not
* guaranteed to be thread-safe"), so concurrent calls on the SAME instance
* are a data race. Use one instance per thread. Separate instances share no
* mutable state once constructed; construction without an explicit seed or
* RNG draws from the process-wide SeedGenerator singleton, which is itself
* unsynchronized, so build the per-thread instances before the threads start.
*/
template <Preset P = PRESET_EMPTY, typename U = u64>
class KeyGeneratorT : public PresetTraits<P, U> {
Expand Down
Loading
Loading