From ead22f615120a0cd9926cfc5d5787fe4bcda379c Mon Sep 17 00:00:00 2001 From: PETAce Date: Tue, 24 Oct 2023 01:46:44 -0700 Subject: [PATCH] feat: version 0.2.0 --- CHANGELOG.md | 7 + CMakeLists.txt | 26 +- README.md | 19 +- bench/CMakeLists.txt | 8 +- cmake/PETAce-SoloConfig.cmake.in | 3 + example/CMakeLists.txt | 4 +- licenses/LICENSE-HashingTables.txt | 21 ++ src/solo/CMakeLists.txt | 6 + src/solo/ahe_paillier.cpp | 296 ++++++++++++++++++++++ src/solo/ahe_paillier.h | 390 +++++++++++++++++++++++++++++ src/solo/cuckoo_hashing.cpp | 289 +++++++++++++++++++++ src/solo/cuckoo_hashing.h | 198 +++++++++++++++ src/solo/simple_hashing.cpp | 279 +++++++++++++++++++++ src/solo/simple_hashing.h | 198 +++++++++++++++ src/solo/solo.h | 3 + src/solo/util/CMakeLists.txt | 2 + src/solo/util/config.h.in | 2 +- src/solo/util/defines.h | 27 ++ src/solo/util/hash_table_entry.cpp | 89 +++++++ src/solo/util/hash_table_entry.h | 128 ++++++++++ test/CMakeLists.txt | 12 +- test/ahe_paillier_test.cpp | 366 +++++++++++++++++++++++++++ test/cuckoo_hashing_test.cpp | 153 +++++++++++ test/hash_table_entry_test.cpp | 125 +++++++++ test/simple_hashing_test.cpp | 114 +++++++++ 25 files changed, 2752 insertions(+), 13 deletions(-) create mode 100644 licenses/LICENSE-HashingTables.txt create mode 100644 src/solo/ahe_paillier.cpp create mode 100644 src/solo/ahe_paillier.h create mode 100644 src/solo/cuckoo_hashing.cpp create mode 100644 src/solo/cuckoo_hashing.h create mode 100644 src/solo/simple_hashing.cpp create mode 100644 src/solo/simple_hashing.h create mode 100644 src/solo/util/hash_table_entry.cpp create mode 100644 src/solo/util/hash_table_entry.h create mode 100644 test/ahe_paillier_test.cpp create mode 100644 test/cuckoo_hashing_test.cpp create mode 100644 test/hash_table_entry_test.cpp create mode 100644 test/simple_hashing_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 32fb996..368aaed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,10 @@ - Added psuedo-random number generators based on: SHAKE_128, BLAKE2Xb, and AES_ECB_CTR. - Added sampling of bytes, 32-bit unsigned integers, and 64-bit unsigned integers from the uniform distribution. - Added prime field elliptic curve group arithmetics including hash-to-curve. + +## Version 0.2.0 + +### Features + +- Added hashing tables: Cuckoo hashing and simple hashing. +- Added partially homomorphic encryption: the Paillier cryptosystem. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c29587..79522fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ if(NOT CMAKE_BUILD_TYPE) endif() message(STATUS "Build type (CMAKE_BUILD_TYPE): ${CMAKE_BUILD_TYPE}") -project(SOLO VERSION 0.1.0 LANGUAGES CXX C) +project(SOLO VERSION 0.2.0 LANGUAGES CXX C) ######################## # Global configuration # @@ -171,6 +171,11 @@ set(SOLO_BUILD_DEPS_OPTION_STR "Automatically download and build unmet dependenc option(SOLO_BUILD_DEPS ${SOLO_BUILD_DEPS_OPTION_STR} ON) message(STATUS "SOLO_BUILD_DEPS: ${SOLO_BUILD_DEPS}") +# [OPTION] SOLO_USE_IPCL (default: OFF) +set(SOLO_USE_IPCL_OPTION_STR "Use IPCL") +option(SOLO_USE_IPCL ${SOLO_USE_IPCL_OPTION_STR} OFF) +message(STATUS "SOLO_USE_IPCL: ${SOLO_USE_IPCL}") + if(SOLO_BUILD_DEPS) include(FetchContent) mark_as_advanced(FETCHCONTENT_BASE_DIR) @@ -195,6 +200,19 @@ else() endif() endif() +# Intel Paillier Cryptosystem Library +if(SOLO_USE_IPCL) + find_package(IPCL QUIET REQUIRED) + if(IPCL_FOUND) + message(STATUS "IPCL: found") + if (TARGET ipcl AND NOT TARGET IPCL::ipcl) + add_library(IPCL::ipcl ALIAS ipcl) + endif() + else() + message(FATAL_ERROR "IPCL: not found, please download and install manually") + endif() +endif() + # Require Threads::Threads if(NOT TARGET Threads::Threads) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) @@ -254,6 +272,9 @@ if(NOT SOLO_BUILD_SHARED_LIBS) set(SOLO_CARRY_OPENSSL FALSE) endif() + if(SOLO_USE_IPCL) + target_link_libraries(solo PUBLIC IPCL::ipcl) + endif() target_link_libraries(solo PUBLIC Threads::Threads) # Build only a shared library @@ -287,6 +308,9 @@ else() endif() set(SOLO_CARRY_OPENSSL FALSE) + if(SOLO_USE_IPCL) + target_link_libraries(solo_shared PUBLIC IPCL::ipcl) + endif() target_link_libraries(solo_shared PUBLIC Threads::Threads) endif() diff --git a/README.md b/README.md index 410963e..1fed30f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ PETAce-Solo implements or wraps the following primitives that involves only one - Psuedo-random number generators based on: SHAKE_128, BLAKE2Xb, and AES_ECB_CTR. - Sampling of bytes, 32-bit unsigned integers, and 64-bit unsigned integers from the uniform distribution. - Prime field elliptic curve group arithmetics including hash-to-curve. +- Hashing tables: Cuckoo hashing and simple hashing. +- Partially homomorphic encryption: the Paillier cryptosystem. ## Requirements @@ -23,6 +25,7 @@ PETAce-Solo implements or wraps the following primitives that involves only one |--------------------------------------------------------|----------------|------------------------| | [GoogleTest](https://github.com/google/googletest) | 1.12.1 | For running tests | | [GoogleBenchmark](https://github.com/google/benchmark) | 1.6.1 | For running benchmarks | +| [Intel Paillier Cryptosystem Library](https://github.com/intel/pailliercryptolib) | 495beaad1f6e70741f2b5cf1279cb919fd66d894 | For partially homomorphic encryption | ## Building PETAce-Solo @@ -45,6 +48,16 @@ Output binaries can be found in `build/lib/` and `build/bin/` directories. | `SOLO_BUILD_EXAMPLE` | ON/OFF | ON | Build C++ example if set to ON. | | `SOLO_BUILD_TEST` | ON/OFF | ON | Build C++ test if set to ON. | | `SOLO_BUILD_DEPS` | ON/OFF | ON | Download and build unmet dependencies if set to ON. | +| `SOLO_USE_IPCL` | ON/OFF | OFF | Add PHE and depends on IPCL if set to ON. | + +### Adding Partially Homomorphic Encryption + +Follow instructions of [Intel Paillier Cryptosystem Library](https://github.com/intel/pailliercryptolib) and install it to `${IPCL_INSTALL_DIR}`. +We recommend the commit `495beaad1f6e70741f2b5cf1279cb919fd66d894` instead of v2.0.0. +Build PETAce-Solo library with extra configuration: +```bash +cmake -S . -B build -DSOLO_USE_IPCL=ON -DIPCL_DIR=${IPCL_INSTALL_DIR}/lib/cmake/ipcl-2.0.0 +``` ## Contribution @@ -62,13 +75,13 @@ This project is licensed under the [Apache-2.0 License](LICENSE). To cite PETAce in academic papers, please use the following BibTeX entries. -### Version 0.1.0 +### Version 0.2.0 ```tex @misc{petace, - title = {PETAce (release 0.1.0)}, + title = {PETAce (release 0.2.0)}, howpublished = {\url{https://github.com/tiktok-privacy-innovation/PETAce}}, - month = July, + month = Oct, year = 2023, note = {TikTok Pte. Ltd.}, key = {PETAce} diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index 00f76cb..e5f3489 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -14,13 +14,13 @@ cmake_minimum_required(VERSION 3.14) -project(SOLOBench VERSION 0.1.0 LANGUAGES CXX) +project(SOLOBench VERSION 0.2.0 LANGUAGES CXX) # If not called from root CMakeLists.txt if(NOT DEFINED SOLO_BUILD_BENCH) set(SOLO_BUILD_BENCH ON) - find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + find_package(PETAce-Solo 0.2.0 EXACT REQUIRED) # Must define these variables and include macros set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) @@ -74,9 +74,9 @@ if(SOLO_BUILD_BENCH) add_executable(solo_bench ${SOLO_BENCH_FILES}) if(TARGET PETAce-Solo::solo) - target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo benchmark::benchmark) + target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo benchmark::benchmark m) elseif(TARGET PETAce-Solo::solo_shared) - target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo_shared benchmark::benchmark) + target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo_shared benchmark::benchmark m) else() message(FATAL_ERROR "Cannot find target PETAce-Solo::solo or PETAce-Solo::solo_shared") endif() diff --git a/cmake/PETAce-SoloConfig.cmake.in b/cmake/PETAce-SoloConfig.cmake.in index 5b49f00..590e429 100644 --- a/cmake/PETAce-SoloConfig.cmake.in +++ b/cmake/PETAce-SoloConfig.cmake.in @@ -43,6 +43,9 @@ solo_find_dependency(Threads) if (NOT SOLO_CARRY_OPENSSL) solo_find_dependency(OpenSSL) endif() +if (SOLO_USE_IPCL) + solo_find_dependency(IPCL) +endif() include(${CMAKE_CURRENT_LIST_DIR}/PETAce-SoloTargets.cmake) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index db5bdf1..68dac86 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -14,14 +14,14 @@ cmake_minimum_required(VERSION 3.14) -project(SOLOExample VERSION 0.1.0 LANGUAGES CXX) +project(SOLOExample VERSION 0.2.0 LANGUAGES CXX) # If not called from root CMakeLists.txt if(NOT DEFINED SOLO_BUILD_EXAMPLE) set(SOLO_BUILD_EXAMPLE ON) # Import PETAce-Solo - find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + find_package(PETAce-Solo 0.2.0 EXACT REQUIRED) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) endif() diff --git a/licenses/LICENSE-HashingTables.txt b/licenses/LICENSE-HashingTables.txt new file mode 100644 index 0000000..f8fe08c --- /dev/null +++ b/licenses/LICENSE-HashingTables.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Oleksandr Tkachenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/solo/CMakeLists.txt b/src/solo/CMakeLists.txt index 79a5d28..6d8e544 100644 --- a/src/solo/CMakeLists.txt +++ b/src/solo/CMakeLists.txt @@ -14,19 +14,25 @@ # Source files in this directory set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/ahe_paillier.cpp + ${CMAKE_CURRENT_LIST_DIR}/cuckoo_hashing.cpp ${CMAKE_CURRENT_LIST_DIR}/ec_openssl.cpp ${CMAKE_CURRENT_LIST_DIR}/hash.cpp ${CMAKE_CURRENT_LIST_DIR}/prng.cpp ${CMAKE_CURRENT_LIST_DIR}/sampling.cpp + ${CMAKE_CURRENT_LIST_DIR}/simple_hashing.cpp ) # Add header files for installation install( FILES + ${CMAKE_CURRENT_LIST_DIR}/ahe_paillier.h + ${CMAKE_CURRENT_LIST_DIR}/cuckoo_hashing.h ${CMAKE_CURRENT_LIST_DIR}/ec_openssl.h ${CMAKE_CURRENT_LIST_DIR}/hash.h ${CMAKE_CURRENT_LIST_DIR}/prng.h ${CMAKE_CURRENT_LIST_DIR}/sampling.h + ${CMAKE_CURRENT_LIST_DIR}/simple_hashing.h ${CMAKE_CURRENT_LIST_DIR}/solo.h DESTINATION ${SOLO_INCLUDES_INSTALL_DIR}/solo diff --git a/src/solo/ahe_paillier.cpp b/src/solo/ahe_paillier.cpp new file mode 100644 index 0000000..7341f02 --- /dev/null +++ b/src/solo/ahe_paillier.cpp @@ -0,0 +1,296 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/ahe_paillier.h" + +#ifdef SOLO_USE_IPCL + +#include +#include +#include +#include +#include +#include + +#include "ipcl/ipcl.hpp" + +namespace petace { +namespace solo { +namespace ahepaillier { + +static BigNum bn_from_u64(std::uint64_t in) { + std::vector vec_u32 = {static_cast(in), static_cast(in >> 32)}; + return BigNum(vec_u32.data(), 2); +} + +static std::uint64_t u64_from_bn(const BigNum& in) { + std::size_t length = in.DwordSize(); + if (length == 0) { + return 0; + } + std::vector data; + in.num2vec(data); + std::uint64_t value = data[0]; + if (length > 1) { + value += static_cast(data[1]) << 32; + } + return value; +} + +void Serialization::bn_from_bytes(const Byte* in, std::size_t in_byte_count, BigNum& out) { + if (in == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + std::size_t length = (in_byte_count + 3) / 4; + std::vector vec_u32(length, 0); + for (std::size_t i = 0; i < length; ++i) { + for (std::size_t j = 0; j < 4; ++j) { + std::size_t cur_idx = i * 4 + j; + if (cur_idx < in_byte_count) { + vec_u32[i] += reinterpret_cast(in)[cur_idx] << (8 * j); + } + } + } + out = BigNum(vec_u32.data(), static_cast(length)); +} + +void Serialization::bn_to_bytes(const BigNum& in, Byte* out, std::size_t out_byte_count) { + if (out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + std::size_t length = (in.BitSize() + 7) / 8; + std::vector temp(length); + in.num2char(temp); + std::size_t byte_count = std::min(length, out_byte_count); + for (std::size_t idx = 0; idx < byte_count; ++idx) { + out[idx] = static_cast(temp[idx]); + } + if (length < out_byte_count) { + std::fill_n(out + length, out_byte_count - length, Byte('\x00')); + } +} + +Serialization::Serialization(std::size_t key_length, bool enable_djn) { + if (key_length != 1024 && key_length != 2048) { + throw std::invalid_argument("AHE key length is invalid."); + } + key_length_ = key_length; + n_byte_count_ = (key_length_ + 7) / 8; + enable_djn_ = enable_djn; +} + +std::size_t Serialization::public_key_byte_count() const noexcept { + return n_byte_count_ * (1 + 2 * static_cast(enable_djn_)); +} + +std::size_t Serialization::secret_key_byte_count() const noexcept { + return n_byte_count_ * 2; +} + +void Serialization::serialize_public_key_to_bytes( + const std::shared_ptr& pk, Byte* out, std::size_t out_byte_count) const { + if (pk == nullptr) { + throw std::invalid_argument("pk is nullptr"); + } + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + if (out_byte_count != public_key_byte_count()) { + throw std::invalid_argument("out_byte_count is not equal to public_key_byte_count"); + } + bn_to_bytes(*(pk->getN()), out, n_byte_count_); + if (enable_djn_) { + bn_to_bytes(pk->getHS(), out + n_byte_count_, out_byte_count - n_byte_count_); + } +} + +void Serialization::serialize_secret_key_to_bytes( + const std::shared_ptr& sk, Byte* out, std::size_t out_byte_count) const { + if (sk == nullptr) { + throw std::invalid_argument("sk is nullptr"); + } + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + if (out_byte_count != secret_key_byte_count()) { + throw std::invalid_argument("out_byte_count is not equal to secret_key_byte_count"); + } + bn_to_bytes(*(sk->getN()), out, n_byte_count_); + std::size_t pq_byte_count = n_byte_count_ / 2; + bn_to_bytes(*(sk->getP()), out + n_byte_count_, pq_byte_count); + bn_to_bytes(*(sk->getQ()), out + n_byte_count_ + pq_byte_count, pq_byte_count); +} + +void Serialization::deserialize_public_key_from_bytes( + const Byte* in, std::size_t in_byte_count, std::shared_ptr& pk) const { + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (in_byte_count != public_key_byte_count()) { + throw std::invalid_argument("in_byte_count is not equal to public_key_byte_count"); + } + pk = std::make_shared(); + if (enable_djn_) { + BigNum n; + bn_from_bytes(in, n_byte_count_, n); + BigNum hs; + bn_from_bytes(in + n_byte_count_, in_byte_count - n_byte_count_, hs); + pk->create(n, static_cast(n_byte_count_ * 8), hs, static_cast(n_byte_count_ * 4)); + } else { + BigNum n; + bn_from_bytes(in, n_byte_count_, n); + pk->create(n, static_cast(n_byte_count_ * 8), false); + } +} + +void Serialization::deserialize_secret_key_from_bytes( + const Byte* in, std::size_t in_byte_count, std::shared_ptr& sk) const { + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (in_byte_count != secret_key_byte_count()) { + throw std::invalid_argument("in_byte_count is not equal to secret_key_byte_count"); + } + BigNum n; + bn_from_bytes(in, n_byte_count_, n); + BigNum p; + BigNum q; + std::size_t pq_byte_count = n_byte_count_ / 2; + bn_from_bytes(in + n_byte_count_, pq_byte_count, p); + bn_from_bytes(in + n_byte_count_ + pq_byte_count, pq_byte_count, q); + sk = std::make_shared(n, p, q); +} + +Plaintext& Plaintext::operator=(const ipcl::PlainText& other) noexcept { + ipcl::PlainText::operator=(other); + return *this; +} + +Plaintext::operator std::vector() const { + return ipcl::PlainText::operator std::vector(); +} + +std::size_t Plaintext::slot_count() const noexcept { + return ipcl::PlainText::getSize(); +} + +Ciphertext& Ciphertext::operator=(const ipcl::CipherText& other) noexcept { + ipcl::CipherText::operator=(other); + return *this; +} + +Ciphertext::operator std::vector() const { + return getTexts(); +} + +std::size_t Ciphertext::slot_count() const noexcept { + return ipcl::CipherText::getSize(); +} + +void Encoder::encode(std::uint64_t in, Plaintext& out) const noexcept { + out = ipcl::PlainText(bn_from_u64(in)); +} + +std::uint64_t Encoder::decode(const Plaintext& in) const noexcept { + return u64_from_bn(BigNum(in)); +} + +void Encoder::encode(const std::vector& in, Plaintext& out) const noexcept { + std::vector tmp(in.size()); + std::transform(in.begin(), in.end(), tmp.begin(), [&](std::uint64_t val) { return bn_from_u64(val); }); + out = ipcl::PlainText(tmp); +} + +void Encoder::decode(const Plaintext& in, std::vector& out) const noexcept { + std::vector tmp = std::vector(in); + out.resize(tmp.size()); + std::transform(tmp.begin(), tmp.end(), out.begin(), [&](BigNum bn) { return u64_from_bn(bn); }); +} + +void KeyGenerator::get_key_pair( + std::shared_ptr& sk, std::shared_ptr& pk, bool enable_djn) const noexcept { + ipcl::KeyPair key_pair = ipcl::generateKeypair(static_cast(key_length_), enable_djn); + sk = std::make_shared( + *(key_pair.priv_key.getN()), *(key_pair.priv_key.getP()), *(key_pair.priv_key.getQ())); + pk = std::make_shared(); + if (enable_djn) { + pk->create(*(key_pair.pub_key.getN()), key_pair.pub_key.getBits(), key_pair.pub_key.getHS(), + key_pair.pub_key.getRandBits()); + } else { + pk->create(*(key_pair.pub_key.getN()), key_pair.pub_key.getBits(), false); + } +} + +Encryptor::Encryptor(const std::shared_ptr& pk, bool enable_djn) { + if (pk == nullptr) { + throw std::invalid_argument("pk is nullptr"); + } + pk_ = std::make_shared(); + if (enable_djn) { + pk_->create(*(pk->getN()), pk->getBits(), pk->getHS(), pk->getRandBits()); + } else { + pk_->create(*(pk->getN()), pk->getBits(), false); + } +} + +void Encryptor::encrypt(const Plaintext& in, Ciphertext& out) const noexcept { + ipcl::setHybridMode(ipcl::HybridMode::IPP); + out = pk_->encrypt(in, true); + ipcl::setHybridOff(); +} + +Decryptor::Decryptor(const std::shared_ptr& sk) { + if (sk == nullptr) { + throw std::invalid_argument("sk is nullptr"); + } + sk_ = std::make_shared(*(sk->getN()), *(sk->getP()), *(sk->getQ())); +} + +void Decryptor::decrypt(const Ciphertext& in, Plaintext& out) const noexcept { + ipcl::setHybridMode(ipcl::HybridMode::IPP); + out = sk_->decrypt(in); + ipcl::setHybridOff(); +} + +void Evaluator::add(const Ciphertext& in_0, const Ciphertext& in_1, Ciphertext& out) const noexcept { + out = in_0 + in_1; +} + +void Evaluator::add(const Ciphertext& in_0, const Plaintext& in_1, Ciphertext& out) const noexcept { + out = in_0 + in_1; +} + +void Evaluator::mul(const Ciphertext& in_0, const Plaintext& in_1, Ciphertext& out) const noexcept { + out = in_0 * in_1; +} + +namespace utils { +void bn_lshift(BigNum& in, const std::size_t bits) { + std::size_t length = (bits + 1 + 31) / 32; + std::size_t remainder = bits % 32; + std::vector temp(length, 0); + temp[length - 1] = 1 << remainder; + BigNumber shift_bn(temp.data(), static_cast(length)); + in *= shift_bn; +} + +BigNum get_random_bn(std::size_t bits) { + return ipcl::getRandomBN(bits); +} +} // namespace utils + +} // namespace ahepaillier +} // namespace solo +} // namespace petace +#endif diff --git a/src/solo/ahe_paillier.h b/src/solo/ahe_paillier.h new file mode 100644 index 0000000..c2b6cd1 --- /dev/null +++ b/src/solo/ahe_paillier.h @@ -0,0 +1,390 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "solo/util/defines.h" + +#ifdef SOLO_USE_IPCL + +#include +#include +#include +#include + +#include "ipcl/ipcl.hpp" + +namespace petace { +namespace solo { +namespace ahepaillier { + +using SecretKey = ipcl::PrivateKey; +using PublicKey = ipcl::PublicKey; +using BigNum = BigNumber; + +/** + * @brief Provides methods to serialize Paillier cryptosystem objects. + */ +class Serialization { +public: + /** + * @brief Creates a serialization instance. + * + * @param[in] key_length The length of key in bits + * @param[in] enable_djn Enable the Damgard-Jurik-Nielsen scheme + * @throws std::invalid_argument if key_length is not 1024 or 2048 + */ + Serialization(std::size_t key_length, bool enable_djn = true); + + /** + * @brief Converts a big number into an array of bytes. + * + * @param[in] in The big number to be converted + * @param[out] out The pointer of byte array to write to + * @param[out] out_byte_count The number of bytes allocated in the byte array. + * @throws std::invalid_argument if out is nullptr + */ + static void bn_to_bytes(const BigNum& in, Byte* out, std::size_t out_byte_count); + + /** + * @brief Creates a big number from an array of bytes. + * + * @param[in] in The pointer of the byte array to read from. + * @param[in] in_byte_count The number of bytes allocated in the byte array. + * @param[out] out The big number to write to. + * @throws std::invalid_argument if in is nullptr + */ + static void bn_from_bytes(const Byte* in, std::size_t in_byte_count, BigNum& out); + + /** + * @brief Returns the number of bytes in a public key. + */ + std::size_t public_key_byte_count() const noexcept; + + /** + * @brief Returns the number of bytes in a secret key. + */ + std::size_t secret_key_byte_count() const noexcept; + + /** + * @brief Serializes a public key into an array of bytes. + * + * @param[in] pk The public key to be serialized + * @param[out] out The pointer of byte array to write to + * @param[out] out_byte_count The number of bytes allocated in the byte array + * @throws std::invalid_argument if pk or out is nullptr or if out_byte_count is not equal to public_key_byte_count + */ + void serialize_public_key_to_bytes( + const std::shared_ptr& pk, Byte* out, std::size_t out_byte_count) const; + + /** + * @brief Serializes a secret key into an array of bytes. + * + * @param[in] sk The secret key to be serialized + * @param[out] out The pointer of byte array to write to + * @param[out] out_byte_count The number of bytes allocated in the byte array + * @throws std::invalid_argument if sk or out is nullptr or if out_byte_count is not equal to secret_key_byte_count + */ + void serialize_secret_key_to_bytes( + const std::shared_ptr& sk, Byte* out, std::size_t out_byte_count) const; + + /** + * @brief Creates a public key from an array of bytes. + * + * @param[in] in The pointer of the byte array to read from + * @param[in] in_byte_count The number of bytes allocated in the byte array + * @param[out] pk The public key to write to + * @throws std::invalid_argument if in is nullptr or in_byte_count is not equal to public_key_byte_count + */ + void deserialize_public_key_from_bytes( + const Byte* in, std::size_t in_byte_count, std::shared_ptr& pk) const; + + /** + * @brief Creates a secret key from an array of bytes. + * + * @param[in] in The pointer of the byte array to read from + * @param[in] in_byte_count The number of bytes allocated in the byte array + * @param[out] sk The secret key to write to + * @throws std::invalid_argument if in is nullptr or in_byte_count is not equal to secret_key_byte_count + */ + void deserialize_secret_key_from_bytes( + const Byte* in, std::size_t in_byte_count, std::shared_ptr& sk) const; + +private: + std::size_t key_length_ = 0; + std::size_t n_byte_count_ = 0; + bool enable_djn_ = false; +}; + +/** + * @brief An array of plaintexts in Paillier cryptosystem. + */ +class Plaintext : public ipcl::PlainText { +public: + /** + * @brief Default constructor. + */ + Plaintext() = default; + + /** + * @brief Constructs a Plaintext from a vector of BigNumber elements. + * + * @param[in] bn_v The vector of BigNumber elements + */ + explicit Plaintext(const std::vector& bn_v) : ipcl::PlainText(bn_v) { + } + + /** + * @brief Copies a Plaintext from an ipcl::PlainText. + * + * @param[in] other The ipcl::PlainText to copy from + */ + Plaintext& operator=(const ipcl::PlainText& other) noexcept; + + /** + * @brief Converts a Plaintext into a vector of BigNumber elements. + */ + operator std::vector() const; + + /** + * @brief Returns the number of plaintexts stored in this Plaintext. + */ + std::size_t slot_count() const noexcept; +}; + +/** + * @brief An array of ciphertexts in Paillier cryptosystem. + */ +class Ciphertext : public ipcl::CipherText { +public: + /** + * @brief Default constructor. + */ + Ciphertext() = default; + + /** + * @brief Constructs a Ciphertext from a vector of BigNumber elements and a public key. + * + * @param[in] pk The public key + * @param[in] bn_vec The vector of BigNumber elements + */ + explicit Ciphertext(const PublicKey& pk, const std::vector& bn_vec) : ipcl::CipherText(pk, bn_vec) { + } + + /** + * @brief Copies a Ciphertext from an ipcl::CipherText. + * + * @param[in] other The ipcl::CipherText to copy from + */ + Ciphertext& operator=(const ipcl::CipherText& other) noexcept; + + /** + * @brief Converts a Ciphertext into a vector of BigNumber elements. + */ + operator std::vector() const; + + /** + * @brief Returns the number of ciphertexts stored in this Ciphertext. + */ + std::size_t slot_count() const noexcept; +}; + +/** + * @brief Provides methods to encode/decode one or multiple integers to/from a Plaintext. + */ +class Encoder { +public: + /** + * @brief Default constructor. + */ + Encoder() { + } + + /** + * @brief Encodes one integer to a Plaintext. + * + * @param[in] in The integer + * @param[out] out The Plaintext to write to + */ + void encode(std::uint64_t in, Plaintext& out) const noexcept; + + /** + * @brief Returns the integer decoded from a Plaintext. + * + * @param[in] in The Plaintext + */ + std::uint64_t decode(const Plaintext& in) const noexcept; + + /** + * @brief Encodes a vector of integers to a Plaintext. + * + * @param[in] in The integer vector + * @param[out] out The Plaintext to write to + */ + void encode(const std::vector& in, Plaintext& out) const noexcept; + + /** + * @brief Decodes a vector of integers from a Plaintext. + * + * @param[in] in The Plaintext + * @param[out] out The integer vector to write to + */ + void decode(const Plaintext& in, std::vector& out) const noexcept; +}; + +/** + * @brief Provides methods to generate keys. + */ +class KeyGenerator { +public: + /** + * @brief Constructs a key generator for a given key length. + * + * @param[in] key_length The key length in bits + * @throws std::invalid_argument if key_length is less than 1024 or greater than 2048 + */ + explicit KeyGenerator(std::size_t key_length) : key_length_(key_length) { + if (key_length < 1024) { + throw std::invalid_argument("key length is less than 1024"); + } + if (key_length > 2048) { + throw std::invalid_argument("key length is greater than 2048"); + } + } + + /** + * @brief Creates a pair of keys. + * + * @param[out] sk The secret key + * @param[out] pk The public key + * @param[in] enable_djn Enable the Damgard-Jurik-Nielsen scheme + */ + void get_key_pair( + std::shared_ptr& sk, std::shared_ptr& pk, bool enable_djn = true) const noexcept; + +private: + std::size_t key_length_ = 0; +}; + +/** + * @brief Provides methods that encrypt a plaintext into a ciphertext. + */ +class Encryptor { +public: + /** + * @brief Constructs an encryptor with a public key. + * + * @param[in] pk The public key + * @param[in] enable_djn + * @throws std::invalid_argument if pk is nullptr + */ + explicit Encryptor(const std::shared_ptr& pk, bool enable_djn = true); + + /** + * @brief Encrypts a plaintext into a ciphertext. + * + * @param[in] in The plaintext + * @param[out] out The resulting ciphertext + */ + void encrypt(const Plaintext& in, Ciphertext& out) const noexcept; + +private: + std::shared_ptr pk_ = nullptr; +}; + +/** + * @brief Provides methods that decrypt a ciphertext into a plaintext. + */ +class Decryptor { +public: + /** + * @brief Constructs an decryptor with a secret key. + * + * @param[in] sk The secret key + * @throws std::invalid_argument if sk is nullptr + */ + explicit Decryptor(const std::shared_ptr& sk); + + /** + * @brief Decrypts a ciphertext into a plaintext. + * + * @param[in] in The ciphertext + * @param[out] out The resulting plaintext + */ + void decrypt(const Ciphertext& in, Plaintext& out) const noexcept; + +private: + std::shared_ptr sk_ = nullptr; +}; + +/** + * @brief Provides methods that evaluates arithmetic operations on ciphertexts. + */ +class Evaluator { +public: + /** + * @brief Default constructor. + */ + Evaluator() { + } + + /** + * @brief Addition of two ciphertexts. + * + * @param[in] in_0 The first ciphertext to add + * @param[in] in_1 The second ciphertext to add + * @param[out] out The ciphertext to overwrite with the addition result + */ + void add(const Ciphertext& in_0, const Ciphertext& in_1, Ciphertext& out) const noexcept; + + /** + * @brief Addition of a ciphertext and a plaintext. + * + * @param[in] in_0 The ciphertext to add + * @param[in] in_1 The plaintext to add + * @param[out] out The ciphertext to overwrite with the addition result + */ + void add(const Ciphertext& in_0, const Plaintext& in_1, Ciphertext& out) const noexcept; + + /** + * @brief Multiplication of a ciphertext and a plaintext. + * + * @param[in] in_0 The ciphertext to multiply + * @param[in] in_1 The plaintext to multiply + * @param[out] out The ciphertext to overwrite with the multiplication result + */ + void mul(const Ciphertext& in_0, const Plaintext& in_1, Ciphertext& out) const noexcept; +}; + +namespace utils { +/** + * @brief Shifts a BigNum to the left. + * + * @param[in] in The BigNum + * @param[in] bits The offset + */ +void bn_lshift(BigNum& in, std::size_t bits); + +/** + * @brief Returns a random BigNum. + * + * @param[in] bits The bitsize of the random BigNum + */ +BigNum get_random_bn(std::size_t bits); +} // namespace utils + +} // namespace ahepaillier +} // namespace solo +} // namespace petace +#endif diff --git a/src/solo/cuckoo_hashing.cpp b/src/solo/cuckoo_hashing.cpp new file mode 100644 index 0000000..758c6c1 --- /dev/null +++ b/src/solo/cuckoo_hashing.cpp @@ -0,0 +1,289 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file cuckoo_hashing.cpp +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#include "solo/cuckoo_hashing.h" + +#include + +#include +#include +#include +#include + +namespace petace { +namespace solo { + +template +void CuckooHashing::insert(Item element) noexcept { + elements_.push_back(element); +} + +template +void CuckooHashing::insert(const std::vector& elements) noexcept { + elements_.insert(elements_.end(), elements.begin(), elements.end()); +} + +template +void CuckooHashing::set_num_of_hash_functions(std::size_t n) { + if (n <= 1) { + throw std::invalid_argument("Must have at least 2 hash functions"); + } + num_of_hash_functions_ = n; +} + +template +void CuckooHashing::set_recursive_insertion_limiter(std::size_t limiter) noexcept { + recursion_limiter_ = limiter; +} + +template +void CuckooHashing::map_elements() noexcept { + allocate_table(); + map_elements_to_table(); + mapped_ = true; +} + +template +bool CuckooHashing::map_elements_to_table() { + allocate_luts(); + generate_luts(); + + for (std::size_t element_id = 0; element_id < elements_.size(); ++element_id) { + util::HashTableEntry current_entry( + elements_.at(element_id), element_id, num_of_hash_functions_, num_bins_); + + // find the new element's mappings and put them to the corresponding std::vector + auto addresses = hash_to_position(elements_.at(element_id)); + current_entry.set_possible_addresses(std::move(addresses)); + current_entry.set_current_function_id(0); + + util::swap(current_entry, hash_table_.at(current_entry.get_current_address())); + + for (std::size_t recursion_step = 0; !current_entry.is_empty(); ++recursion_step) { + if (recursion_step > recursion_limiter_) { + stash_.push_back(current_entry); + break; + } else { + current_entry.iterate_function_number(); + current_entry.get_current_address(); + util::swap(current_entry, hash_table_.at(current_entry.get_current_address())); + } + } + } + + mapped_ = true; + + return true; +} + +template +std::vector CuckooHashing::get_element_addresses() noexcept { + std::vector hash_addresses; + hash_addresses.resize(elements_.size() * num_of_hash_functions_); + + allocate_luts(); + generate_luts(); + for (std::size_t i = 0; i < elements_.size(); ++i) { + util::HashTableEntry current_entry(elements_.at(i), i, num_of_hash_functions_, num_bins_); + auto addresses = hash_to_position(elements_.at(i)); + current_entry.set_possible_addresses(std::move(addresses)); + for (std::size_t j = 0; j < num_of_hash_functions_; ++j) { + hash_addresses[i * num_of_hash_functions_ + j] = current_entry.get_address_at(j); + } + } + return hash_addresses; +} + +template +std::vector> CuckooHashing::obtain_entry_values() const noexcept { + std::vector raw_table; + raw_table.reserve(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + std::uint8_t current_function_id = static_cast(hash_table_.at(i).get_current_function_id()); + Item element = hash_table_.at(i).get_element(); + element[0] ^= Byte(current_function_id); + raw_table.push_back(element); + } + + return raw_table; +} + +template +std::vector> CuckooHashing::obtain_entry_source_values() + const noexcept { + std::vector raw_table; + raw_table.reserve(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + Item element = hash_table_.at(i).get_element(); + raw_table.push_back(element); + } + + return raw_table; +} + +template +std::vector CuckooHashing::obtain_entry_function_ids() const noexcept { + std::vector funtion_id_table; + funtion_id_table.reserve(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + std::uint8_t current_function_id = static_cast(hash_table_.at(i).get_current_function_id()); + funtion_id_table.push_back(current_function_id); + } + + return funtion_id_table; +} + +template +std::vector CuckooHashing::obtain_entry_ids() const noexcept { + std::vector id_table; + id_table.reserve(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + id_table.push_back(hash_table_.at(i).get_global_id()); + } + + return id_table; +} + +template +std::vector CuckooHashing::obtain_bin_occupancy() const noexcept { + // Shows whether the entry is not empty + std::vector occ_table; + occ_table.reserve(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + occ_table.push_back(!hash_table_.at(i).is_empty()); + } + + return occ_table; +} + +template +std::vector CuckooHashing::get_num_of_elements_in_bins() const noexcept { + std::vector num_elements_in_bins(hash_table_.size(), 0); + for (std::size_t i = 0; i < hash_table_.size(); ++i) { + if (!hash_table_.at(i).is_empty()) { + ++num_elements_in_bins.at(i); + } + } + return num_elements_in_bins; +} + +template +CuckooHashing::CuckooHashing(double epsilon, std::size_t num_of_bins, const std::vector& seed) { + epsilon_ = epsilon; + num_bins_ = num_of_bins; + + if (seed.empty()) { + seed_.resize(16); + for (std::size_t i = 0; i < 16; i++) { + seed_[i] = (Byte)0; + } + } else { + if (seed.size() != 16) { + throw std::invalid_argument("The seed length must be 16."); + } + seed_ = seed; + } + + solo::PRNGFactory prng_factory(solo::PRNGScheme::AES_ECB_CTR); + generator_ = prng_factory.create(seed_); +} + +template +bool CuckooHashing::allocate_table() { + if (num_bins_ == 0 && epsilon_ == 0.0f) { + throw std::invalid_argument("Either the number of bins or epsilon must be non-zero."); + } else if (epsilon_ < 0.0f) { + throw std::invalid_argument("Epsilon cannot be negative."); + } + + if (epsilon_ > 0.0f) { + num_bins_ = static_cast(std::ceil(static_cast(elements_.size()) * epsilon_)); + } + hash_table_.resize(num_bins_); + return true; +} + +template +bool CuckooHashing::allocate_luts() { + luts_.resize(num_of_hash_functions_); + for (auto& luts : luts_) { + luts.resize(num_of_luts_); + for (auto& entry : luts) { + entry.resize(num_of_tables_in_lut_); + } + } + return true; +} + +template +bool CuckooHashing::generate_luts() { + for (std::size_t i = 0; i < num_of_hash_functions_; ++i) { + for (std::size_t j = 0; j < num_of_luts_; ++j) { + for (std::size_t k = 0; k < num_of_tables_in_lut_; k++) { + generator_->generate(sizeof(std::uint64_t), reinterpret_cast(&luts_.at(i).at(j).at(k))); + } + } + } + + return true; +} + +template +std::vector CuckooHashing::hash_to_position(const Item& element) const { + std::vector addresses; + for (std::size_t func_i = 0; func_i < num_of_hash_functions_; ++func_i) { + std::uint64_t address = 0; + for (std::size_t lut_i = 0; lut_i < num_of_luts_; ++lut_i) { + address ^= luts_.at(func_i).at(lut_i).at(static_cast(element[lut_i])); + } + addresses.push_back(address); + } + return addresses; +} + +template class CuckooHashing<16>; +template class CuckooHashing<8>; + +} // namespace solo +} // namespace petace diff --git a/src/solo/cuckoo_hashing.h b/src/solo/cuckoo_hashing.h new file mode 100644 index 0000000..2d2b8f3 --- /dev/null +++ b/src/solo/cuckoo_hashing.h @@ -0,0 +1,198 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file cuckoo_hashing.h +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#pragma once + +#include +#include +#include + +#include "solo/prng.h" +#include "solo/util/hash_table_entry.h" + +namespace petace { +namespace solo { + +/** + * @brief Provides a Cuckoo hashing table template that supports an arbitrary item size. + */ +template +class CuckooHashing { +public: + using Item = std::array; + + CuckooHashing() = delete; + + /** + * @brief Constructs an empty Cuckoo hashing table with a given #bins. + * + * @param[in] num_of_bins The #bins + */ + explicit CuckooHashing(std::size_t num_of_bins) noexcept : CuckooHashing(0.0f, num_of_bins, std::vector()) { + } + + /** + * @brief Constructs an empty Cuckoo hashing table with a given #bins and a seed. + * + * @param[in] num_of_bins The #bins + * @param[in] seed The seed to create hash functions + * @throws std::invalid_argument if the seed is not 16 bytes + */ + CuckooHashing(std::size_t num_of_bins, const std::vector& seed) : CuckooHashing(0.0f, num_of_bins, seed) { + } + + /** + * @brief Destructor. + */ + ~CuckooHashing() { + } + + /** + * @brief Registers an element to be added to the hashing table. + * + * @param[in] element The element + */ + void insert(Item element) noexcept; + + /** + * @brief Registers a vector of elements to be added to the hashing table. + * + * @param[in] elements The vector of elements + */ + void insert(const std::vector& elements) noexcept; + + /** + * @brief Sets the number of hash functions. + * + * @param[in] n The number of hash functions + * @throws std::invalid_argument if n is less than 2 + */ + void set_num_of_hash_functions(std::size_t n); + + /** + * @brief Maps registered elements to a Cuckook hashing table. + * Returns true, if success. + */ + void map_elements() noexcept; + + /** + * @brief Returns the elements' destinations. + */ + SOLO_NODISCARD std::vector get_element_addresses() noexcept; + + /** + * @brief Sets the maximum number of attempts + */ + void set_recursive_insertion_limiter(std::size_t limiter) noexcept; + + /** + * @brief Returns all hash table entries XORed with the function ID. + */ + SOLO_NODISCARD std::vector obtain_entry_values() const noexcept; + + /** + * @brief Returns all hash table entries. + */ + SOLO_NODISCARD std::vector obtain_entry_source_values() const noexcept; + + /** + * @brief Returns all hash table entries' function IDs. + */ + SOLO_NODISCARD std::vector obtain_entry_function_ids() const noexcept; + + /** + * @brief Returns all hash table entries' registration IDs. + */ + SOLO_NODISCARD std::vector obtain_entry_ids() const noexcept; + + /** + * @brief Returns whether each slot is empty. + */ + SOLO_NODISCARD std::vector obtain_bin_occupancy() const noexcept; + + /** + * @brief Returns the number of elements stored in bins (rather than stash). + */ + SOLO_NODISCARD std::vector get_num_of_elements_in_bins() const noexcept; + + /** + * @brief Returns the size of stash. + */ + SOLO_NODISCARD std::size_t get_stash_size() const noexcept { + return stash_.size(); + } + +private: + CuckooHashing(double epsilon, std::size_t num_of_bins, const std::vector& seed); + + bool allocate_table(); + + bool map_elements_to_table(); + + bool allocate_luts(); + + bool generate_luts(); + + std::vector hash_to_position(const Item& element) const; + + std::vector elements_; + std::vector> hash_table_; + std::vector> stash_; + + // binning + double epsilon_ = 1.2f; + std::size_t num_bins_ = 0; + std::size_t num_of_hash_functions_ = 2; + + // randomness + std::vector seed_{}; + std::shared_ptr generator_; + + // LUTs + static constexpr std::size_t num_of_luts_ = item_byte_count; + static constexpr std::size_t num_of_tables_in_lut_ = 256; + std::vector>> luts_{}; + + bool mapped_ = false; + + // Statistics + std::size_t recursion_limiter_ = 200; +}; +} // namespace solo +} // namespace petace diff --git a/src/solo/simple_hashing.cpp b/src/solo/simple_hashing.cpp new file mode 100644 index 0000000..b922e46 --- /dev/null +++ b/src/solo/simple_hashing.cpp @@ -0,0 +1,279 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file simple_hashing.cpp +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#include "solo/simple_hashing.h" + +#include + +#include +#include +#include +#include + +#include "solo/util/hash_table_entry.h" + +namespace petace { +namespace solo { + +template +void SimpleHashing::insert(const Item& element) noexcept { + elements_.push_back(element); +} + +template +void SimpleHashing::insert(const std::vector& elements) noexcept { + elements_.insert(this->elements_.end(), elements.begin(), elements.end()); +} + +template +void SimpleHashing::set_num_of_hash_functions(std::size_t n) { + if (n == 0) { + throw std::invalid_argument("Must have at least 1 hash function"); + } + num_of_hash_functions_ = n; +} + +template +void SimpleHashing::map_elements() noexcept { + allocate_table(); + map_elements_to_table(); + mapped_ = true; +} + +template +void SimpleHashing::set_maximum_bin_size(std::size_t size) noexcept { + maximum_bin_size_ = size; + pad_to_maximum_bin_size = true; +} + +template +std::vector> SimpleHashing::obtain_entry_values() const noexcept { + std::vector raw_table; + raw_table.reserve(elements_.size()); + + for (std::size_t i = 0; i < num_bins_; ++i) { + for (std::size_t j = 0; j < hash_table_.at(i).size(); ++j) { + std::uint8_t current_function_id = + static_cast(hash_table_.at(i).at(j).get_current_function_id()); + Item element = hash_table_.at(i).at(j).get_element(); + element[0] ^= Byte(current_function_id); + raw_table.push_back(element); + } + } + + return raw_table; +} + +template +std::vector>> SimpleHashing::obtain_bin_entry_values() + const noexcept { + std::vector> raw_table(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + for (std::size_t j = 0; j < hash_table_.at(i).size(); ++j) { + std::uint8_t current_function_id = + static_cast(hash_table_.at(i).at(j).get_current_function_id()); + Item element = hash_table_.at(i).at(j).get_element(); + element[0] ^= Byte(current_function_id); + raw_table.at(i).push_back(element); + } + } + + return raw_table; +} + +template +std::vector> SimpleHashing::obtain_bin_entry_function_ids() const noexcept { + std::vector> function_id_table(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + for (std::size_t j = 0; j < hash_table_.at(i).size(); ++j) { + function_id_table.at(i).push_back(hash_table_.at(i).at(j).get_current_function_id()); + } + } + + return function_id_table; +} + +template +std::vector> SimpleHashing::obtain_bin_entry_ids() const noexcept { + std::vector> id_table(num_bins_); + + for (std::size_t i = 0; i < num_bins_; ++i) { + for (std::size_t j = 0; j < hash_table_.at(i).size(); ++j) { + id_table.at(i).push_back(hash_table_.at(i).at(j).get_global_id()); + } + } + + return id_table; +} + +template +std::vector SimpleHashing::get_num_of_elements_in_bins() const noexcept { + std::vector num_elements_in_bins(hash_table_.size(), 0); + for (std::size_t i = 0; i < hash_table_.size(); ++i) { + num_elements_in_bins.at(i) = hash_table_.at(i).size(); + } + return num_elements_in_bins; +} + +template +std::vector> SimpleHashing::obtain_entry_values_padded() + const noexcept { + Item dummy_element_value; + std::fill_n(dummy_element_value.begin(), item_byte_count, Byte(0xFF)); + std::vector raw_table(maximum_bin_size_ * num_bins_, dummy_element_value); + + for (std::size_t i = 0; i < num_bins_; ++i) { + for (std::size_t j = 0; j < hash_table_.at(i).size(); ++j) { + raw_table.at(i * maximum_bin_size_ + j) = hash_table_.at(i).at(j).get_element(); + } + } + + return raw_table; +} + +template +SimpleHashing::SimpleHashing(double epsilon, std::size_t num_of_bins, const std::vector& seed) { + epsilon_ = epsilon; + num_bins_ = num_of_bins; + + if (seed.empty()) { + seed_.resize(16); + for (std::size_t i = 0; i < 16; i++) { + seed_[i] = (Byte)0; + } + } else { + if (seed.size() != 16) { + throw std::invalid_argument("The seed length must be 16."); + } + seed_ = seed; + } + + solo::PRNGFactory prng_factory(solo::PRNGScheme::AES_ECB_CTR); + generator_ = prng_factory.create(seed_); +} + +template +bool SimpleHashing::allocate_table() { + if (num_bins_ == 0 && epsilon_ == 0.0f) { + throw std::invalid_argument("Either the number of bins or epsilon must be non-zero."); + } else if (epsilon_ < 0) { + throw std::invalid_argument("Epsilon cannot be negative."); + } + + if (epsilon_ > 0) { + num_bins_ = static_cast(std::ceil(static_cast(elements_.size()) * epsilon_)); + } + hash_table_.resize(num_bins_); + return true; +} + +template +bool SimpleHashing::map_elements_to_table() { + allocate_luts(); + generate_luts(); + + for (std::size_t element_id = 0; element_id < elements_.size(); ++element_id) { + util::HashTableEntry current_entry( + elements_.at(element_id), element_id, num_of_hash_functions_, num_bins_); + + // find the new element's mappings and put them to the corresponding std::vector + auto addresses = hash_to_position(elements_.at(element_id)); + current_entry.set_possible_addresses(std::move(addresses)); + + for (std::size_t i = 0; i < num_of_hash_functions_; ++i) { + util::HashTableEntry entry_copy(current_entry); + entry_copy.set_current_function_id(i); + hash_table_.at(entry_copy.get_address_at(i)).push_back(entry_copy); + auto bin_size = hash_table_.at(entry_copy.get_address_at(i)).size(); + if (bin_size > max_observed_bin_size_) { + max_observed_bin_size_ = bin_size; + } + } + } + + mapped_ = true; + + return true; +} + +template +bool SimpleHashing::allocate_luts() { + luts_.resize(num_of_hash_functions_); + for (auto& luts : luts_) { + luts.resize(num_of_luts_); + for (auto& entry : luts) { + entry.resize(num_of_tables_in_lut_); + } + } + return true; +} + +template +bool SimpleHashing::generate_luts() { + for (std::size_t i = 0; i < num_of_hash_functions_; ++i) { + for (std::size_t j = 0; j < num_of_luts_; ++j) { + for (std::size_t k = 0; k < num_of_tables_in_lut_; k++) { + generator_->generate(sizeof(std::uint64_t), reinterpret_cast(&luts_.at(i).at(j).at(k))); + } + } + } + + return true; +} + +template +std::vector SimpleHashing::hash_to_position(const Item& element) const { + std::vector addresses; + for (std::size_t func_i = 0; func_i < num_of_hash_functions_; ++func_i) { + std::uint64_t address = 0; + for (std::size_t lut_i = 0; lut_i < num_of_luts_; ++lut_i) { + address ^= luts_.at(func_i).at(lut_i).at(static_cast(element[lut_i])); + } + addresses.push_back(address); + } + return addresses; +} + +template class SimpleHashing<16>; +template class SimpleHashing<8>; + +} // namespace solo +} // namespace petace diff --git a/src/solo/simple_hashing.h b/src/solo/simple_hashing.h new file mode 100644 index 0000000..0338259 --- /dev/null +++ b/src/solo/simple_hashing.h @@ -0,0 +1,198 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file simple_hashing.h +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#pragma once + +#include +#include +#include + +#include "solo/prng.h" +#include "solo/util/hash_table_entry.h" + +namespace petace { +namespace solo { + +/** + * @brief Provides a simple hashing table template that supports an arbitrary item size. + */ +template +class SimpleHashing { +public: + using Item = std::array; + + SimpleHashing() = delete; + + /** + * @brief Constructs an empty simple hashing table with a given #bins. + * + * @param[in] num_of_bins The #bins + */ + explicit SimpleHashing(std::size_t num_of_bins) noexcept : SimpleHashing(0.0f, num_of_bins, std::vector()) { + } + + /** + * @brief Constructs an empty simple hashing table with a given #bins and a seed. + * + * @param[in] num_of_bins The #bins + * @param[in] seed The seed to create hash functions + * @throws std::invalid_argument if the seed is not 16 bytes + */ + SimpleHashing(std::size_t num_of_bins, const std::vector& seed) : SimpleHashing(0.0f, num_of_bins, seed) { + } + + /** + * @brief Destructor. + */ + ~SimpleHashing() { + } + + /** + * @brief Registers an element to be added to the hashing table. + * + * @param[in] element The element + */ + void insert(const Item& element) noexcept; + + /** + * @brief Registers a vector of elements to be added to the hashing table. + * + * @param[in] elements The vector of elements + */ + void insert(const std::vector& elements) noexcept; + + /** + * @brief Sets the number of hash functions. + * + * @param[in] n The number of hash functions + * @throws std::invalid_argument if n is 0 + */ + void set_num_of_hash_functions(std::size_t n); + + /** + * @brief Maps registered elements to a simple hashing table. + * Returns true, if success. + */ + void map_elements() noexcept; + + /** + * @brief Set a maximum bin size to pad to. + * + * @param[in] size The maximum bin size + */ + void set_maximum_bin_size(std::size_t size) noexcept; + + /** + * @brief Returns all hash table entries XORed with the function ID. + */ + SOLO_NODISCARD std::vector obtain_entry_values() const noexcept; + + /** + * @brief Returns all hash table entries including padded entries. + */ + SOLO_NODISCARD std::vector obtain_entry_values_padded() const noexcept; + + /** + * @brief Returns all hash table bin entries. + */ + SOLO_NODISCARD std::vector> obtain_bin_entry_values() const noexcept; + + /** + * @brief Returns all hash table entries' function IDs. + */ + SOLO_NODISCARD std::vector> obtain_bin_entry_function_ids() const noexcept; + + /** + * @brief Returns all hash table entries' registration IDs. + */ + SOLO_NODISCARD std::vector> obtain_bin_entry_ids() const noexcept; + + /** + * @brief Returns the number of elements stored in bins. + */ + SOLO_NODISCARD std::vector get_num_of_elements_in_bins() const noexcept; + + /** + * @brief Returns the maximum number elements in a bin. + */ + SOLO_NODISCARD auto get_max_observed_bin_size() const noexcept { + return max_observed_bin_size_; + } + +private: + SimpleHashing(double epsilon, std::size_t num_of_bins, const std::vector& seed); + + bool allocate_table(); + + bool map_elements_to_table(); + + bool allocate_luts(); + + bool generate_luts(); + + std::vector hash_to_position(const Item& element) const; + + std::vector elements_; + std::vector>> hash_table_; + + // binning + double epsilon_ = 1.2f; + std::size_t num_bins_ = 0; + std::size_t num_of_hash_functions_ = 2; + + // randomness + std::vector seed_{}; + std::shared_ptr generator_; + + // LUTs + static constexpr std::size_t num_of_luts_ = item_byte_count; + static constexpr std::size_t num_of_tables_in_lut_ = 256; + std::vector>> luts_{}; + + bool mapped_ = false; + + std::size_t maximum_bin_size_ = 20; + bool pad_to_maximum_bin_size = false; + + // < the maximum number of elements in a single bin + std::size_t max_observed_bin_size_ = 0; +}; + +} // namespace solo +} // namespace petace diff --git a/src/solo/solo.h b/src/solo/solo.h index 8cadded..df2c380 100644 --- a/src/solo/solo.h +++ b/src/solo/solo.h @@ -14,7 +14,10 @@ #pragma once +#include "solo/ahe_paillier.h" +#include "solo/cuckoo_hashing.h" #include "solo/ec_openssl.h" #include "solo/hash.h" #include "solo/prng.h" #include "solo/sampling.h" +#include "solo/simple_hashing.h" diff --git a/src/solo/util/CMakeLists.txt b/src/solo/util/CMakeLists.txt index 8dd362d..79db3da 100644 --- a/src/solo/util/CMakeLists.txt +++ b/src/solo/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} ${CMAKE_CURRENT_LIST_DIR}/blake2b-ref.c ${CMAKE_CURRENT_LIST_DIR}/blake2xb-ref.c + ${CMAKE_CURRENT_LIST_DIR}/hash_table_entry.cpp ) # Add header files for installation @@ -24,6 +25,7 @@ install( ${CMAKE_CURRENT_LIST_DIR}/blake2.h ${CMAKE_CURRENT_LIST_DIR}/blake2-impl.h ${CMAKE_CURRENT_LIST_DIR}/defines.h + ${CMAKE_CURRENT_LIST_DIR}/hash_table_entry.h DESTINATION ${SOLO_INCLUDES_INSTALL_DIR}/solo/util ) diff --git a/src/solo/util/config.h.in b/src/solo/util/config.h.in index 03478b9..4199e9d 100644 --- a/src/solo/util/config.h.in +++ b/src/solo/util/config.h.in @@ -34,4 +34,4 @@ #cmakedefine SOLO_USE_AES_INTRIN // Third-party dependencies -#cmakedefine SOLO_USE_ZLIB +#cmakedefine SOLO_USE_IPCL diff --git a/src/solo/util/defines.h b/src/solo/util/defines.h index cb49753..e467247 100644 --- a/src/solo/util/defines.h +++ b/src/solo/util/defines.h @@ -28,6 +28,33 @@ using Byte = std::byte; namespace petace { namespace solo { enum class Byte : unsigned char {}; + +constexpr Byte operator|(Byte l, Byte r) noexcept { + return static_cast(static_cast(l) | static_cast(r)); +} + +constexpr Byte operator^(Byte l, Byte r) noexcept { + return static_cast(static_cast(l) ^ static_cast(r)); +} + +constexpr Byte operator&(Byte l, Byte r) noexcept { + return static_cast(static_cast(l) & static_cast(r)); +} + +constexpr Byte& operator|=(Byte& l, Byte r) noexcept { + l = l | r; + return l; +} + +constexpr Byte& operator^=(Byte& l, Byte r) noexcept { + l = l ^ r; + return l; +} + +constexpr Byte& operator&=(Byte& l, Byte r) noexcept { + l = l & r; + return l; +} } // namespace solo } // namespace petace #endif diff --git a/src/solo/util/hash_table_entry.cpp b/src/solo/util/hash_table_entry.cpp new file mode 100644 index 0000000..d35ef00 --- /dev/null +++ b/src/solo/util/hash_table_entry.cpp @@ -0,0 +1,89 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file hash_table_entry.h +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#include "solo/util/hash_table_entry.h" + +#include +#include + +namespace petace { +namespace solo { +namespace util { + +const std::size_t kHashTableDummyElement = std::numeric_limits::max(); + +template +HashTableEntry::HashTableEntry( + Item value, std::size_t global_id, std::size_t num_of_functions, std::size_t num_of_bins) { + value_ = value; + global_id_ = global_id; + num_of_hash_functions_ = num_of_functions; + num_of_bins_ = num_of_bins; +} + +template +HashTableEntry::HashTableEntry(const HashTableEntry& other) { + num_of_hash_functions_ = other.num_of_hash_functions_; + num_of_bins_ = other.num_of_bins_; + global_id_ = other.global_id_; + + value_ = other.value_; + current_function_id_ = other.current_function_id_; + possible_addresses_ = other.possible_addresses_; +} + +template +void swap(HashTableEntry& a, HashTableEntry& b) noexcept { + std::swap(a.value_, b.value_); + std::swap(a.global_id_, b.global_id_); + std::swap(a.possible_addresses_, b.possible_addresses_); + std::swap(a.current_function_id_, b.current_function_id_); + std::swap(a.num_of_bins_, b.num_of_bins_); + std::swap(a.num_of_hash_functions_, b.num_of_hash_functions_); +} + +template class HashTableEntry<16>; +template class HashTableEntry<8>; + +template void swap<8>(HashTableEntry<8>&, HashTableEntry<8>&); +template void swap<16>(HashTableEntry<16>&, HashTableEntry<16>&); + +} // namespace util +} // namespace solo +} // namespace petace diff --git a/src/solo/util/hash_table_entry.h b/src/solo/util/hash_table_entry.h new file mode 100644 index 0000000..ef2807e --- /dev/null +++ b/src/solo/util/hash_table_entry.h @@ -0,0 +1,128 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// \file hash_table_entry.h +// \author Oleksandr Tkachenko +// \email tkachenko@encrypto.cs.tu-darmstadt.de +// \organization Cryptography and Privacy Engineering Group (ENCRYPTO) +// \TU Darmstadt, Computer Science department +// \copyright The MIT License. Copyright 2019 + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR +// A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file may have been modified by PETAce-Solo Authors. (“PETAce-Solo Modifications”). +// All PETAce-Solo Modifications are Copyright 2023 PETAce-Solo Authors. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "solo/util/defines.h" + +namespace petace { +namespace solo { +namespace util { + +extern const std::size_t kHashTableDummyElement; + +template +class HashTableEntry { +public: + using Item = std::array; + + HashTableEntry() { + global_id_ = kHashTableDummyElement; + std::fill_n(value_.begin(), item_byte_count, Byte(0xFF)); + } + + HashTableEntry(Item value, std::size_t global_id, std::size_t num_of_functions, std::size_t num_of_bins); + + HashTableEntry(const HashTableEntry& other); + + void set_current_function_id(std::size_t function_id) { + current_function_id_ = function_id; + } + + void set_possible_addresses(std::vector&& addresses) { + possible_addresses_ = std::move(addresses); + } + + std::uint64_t get_address_at(std::size_t function_id) const { + return possible_addresses_.at(function_id) % num_of_bins_; + } + + std::size_t get_current_function_id() const { + return current_function_id_; + } + + std::uint64_t get_current_address() const { + return possible_addresses_.at(current_function_id_) % num_of_bins_; + } + + const std::vector get_possible_addresses() const { + return possible_addresses_; + } + + bool is_empty() const { + return std::all_of(value_.begin(), value_.end(), [](Byte i) { return i == Byte(0xFF); }); + } + + std::size_t get_global_id() const { + return global_id_; + } + + Item get_element() const { + return value_; + } + + void iterate_function_number() { + current_function_id_ = (current_function_id_ + 1) % num_of_hash_functions_; + } + + template + friend void swap(HashTableEntry& a, HashTableEntry& b) noexcept; + +private: + std::size_t num_of_hash_functions_ = 0; + std::size_t num_of_bins_ = 0; + std::size_t global_id_ = 0; + Item value_{}; + std::size_t current_function_id_ = 0; + std::vector possible_addresses_{}; +}; + +template +void swap(HashTableEntry& a, HashTableEntry& b) noexcept; + +} // namespace util +} // namespace solo +} // namespace petace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e8f083..5e3acf3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,13 +14,13 @@ cmake_minimum_required(VERSION 3.14) -project(SOLOTest VERSION 0.1.0 LANGUAGES CXX C) +project(SOLOTest VERSION 0.2.0 LANGUAGES CXX C) # If not called from root CMakeLists.txt if(NOT DEFINED SOLO_BUILD_TEST) set(SOLO_BUILD_TEST ON) - find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + find_package(PETAce-Solo 0.2.0 EXACT REQUIRED) # Must define these variables and include macros set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) @@ -66,8 +66,16 @@ if(SOLO_BUILD_TEST) ${CMAKE_CURRENT_LIST_DIR}/hash_test.cpp ${CMAKE_CURRENT_LIST_DIR}/prng_test.cpp ${CMAKE_CURRENT_LIST_DIR}/sampling_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/simple_hashing_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/cuckoo_hashing_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/hash_table_entry_test.cpp ${CMAKE_CURRENT_LIST_DIR}/test_runner.cpp ) + if(SOLO_USE_IPCL) + set(SOLO_TEST_FILES ${SOLO_TEST_FILES} + ${CMAKE_CURRENT_LIST_DIR}/ahe_paillier_test.cpp + ) + endif() set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") diff --git a/test/ahe_paillier_test.cpp b/test/ahe_paillier_test.cpp new file mode 100644 index 0000000..fd50ecc --- /dev/null +++ b/test/ahe_paillier_test.cpp @@ -0,0 +1,366 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/ahe_paillier.h" + +#ifdef SOLO_USE_IPCL + +#include + +#include "gtest/gtest.h" + +TEST(AHEPaillierTest, KeyGen) { + { + auto keygen = [](std::size_t key_length) { petace::solo::ahepaillier::KeyGenerator generator(key_length); }; + ASSERT_THROW(keygen(512), std::invalid_argument); + ASSERT_THROW(keygen(3072), std::invalid_argument); + } + { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk); + ASSERT_NE(sk, nullptr); + ASSERT_NE(pk, nullptr); + } + { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(2048); + generator.get_key_pair(sk, pk); + ASSERT_NE(sk, nullptr); + ASSERT_NE(pk, nullptr); + } +} + +TEST(AHEPaillierTest, Serialization) { + { + auto serialization_constructor = [](std::size_t key_length) { + petace::solo::ahepaillier::Serialization serialization(key_length); + }; + ASSERT_THROW(serialization_constructor(512), std::invalid_argument); + ASSERT_THROW(serialization_constructor(3072), std::invalid_argument); + } + { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk); + petace::solo::ahepaillier::Serialization serialization(1024, true); + ASSERT_EQ(serialization.public_key_byte_count(), 384); + ASSERT_EQ(serialization.secret_key_byte_count(), 256); + std::size_t public_key_byte_count = serialization.public_key_byte_count(); + std::vector pk_bytes(public_key_byte_count); + + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, nullptr, 0), std::invalid_argument); + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, nullptr, 383), std::invalid_argument); + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, pk_bytes.data(), 385), std::invalid_argument); + serialization.serialize_public_key_to_bytes(pk, pk_bytes.data(), public_key_byte_count); + + std::shared_ptr sk_deserialized = nullptr; + std::shared_ptr pk_deserialized = nullptr; + ASSERT_THROW( + serialization.deserialize_public_key_from_bytes(nullptr, 0, pk_deserialized), std::invalid_argument); + ASSERT_THROW( + serialization.deserialize_public_key_from_bytes(nullptr, 383, pk_deserialized), std::invalid_argument); + ASSERT_THROW(serialization.deserialize_public_key_from_bytes(pk_bytes.data(), 385, pk_deserialized), + std::invalid_argument); + serialization.deserialize_public_key_from_bytes(pk_bytes.data(), pk_bytes.size(), pk_deserialized); + ASSERT_EQ(*(pk->getN()), *(pk_deserialized->getN())); + ASSERT_EQ(pk->getHS(), pk_deserialized->getHS()); + + std::size_t secret_key_byte_count = serialization.secret_key_byte_count(); + std::vector sk_bytes(secret_key_byte_count); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, nullptr, 0), std::invalid_argument); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, nullptr, 255), std::invalid_argument); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, sk_bytes.data(), 257), std::invalid_argument); + serialization.serialize_secret_key_to_bytes(sk, sk_bytes.data(), secret_key_byte_count); + + ASSERT_THROW( + serialization.deserialize_secret_key_from_bytes(nullptr, 0, sk_deserialized), std::invalid_argument); + ASSERT_THROW( + serialization.deserialize_secret_key_from_bytes(nullptr, 255, sk_deserialized), std::invalid_argument); + ASSERT_THROW(serialization.deserialize_secret_key_from_bytes(sk_bytes.data(), 257, sk_deserialized), + std::invalid_argument); + serialization.deserialize_secret_key_from_bytes(sk_bytes.data(), sk_bytes.size(), sk_deserialized); + ASSERT_EQ(*(sk->getN()), *(sk_deserialized->getN())); + ASSERT_EQ(*(sk->getP()), *(sk_deserialized->getP())); + ASSERT_EQ(*(sk->getQ()), *(sk_deserialized->getQ())); + } + { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk, false); + petace::solo::ahepaillier::Serialization serialization(1024, false); + ASSERT_EQ(serialization.public_key_byte_count(), 128); + ASSERT_EQ(serialization.secret_key_byte_count(), 256); + std::size_t public_key_byte_count = serialization.public_key_byte_count(); + std::vector pk_bytes(public_key_byte_count); + + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, nullptr, 0), std::invalid_argument); + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, nullptr, 127), std::invalid_argument); + ASSERT_THROW(serialization.serialize_public_key_to_bytes(pk, pk_bytes.data(), 129), std::invalid_argument); + serialization.serialize_public_key_to_bytes(pk, pk_bytes.data(), public_key_byte_count); + + std::shared_ptr sk_deserialized = nullptr; + std::shared_ptr pk_deserialized = nullptr; + ASSERT_THROW( + serialization.deserialize_public_key_from_bytes(nullptr, 0, pk_deserialized), std::invalid_argument); + ASSERT_THROW( + serialization.deserialize_public_key_from_bytes(nullptr, 127, pk_deserialized), std::invalid_argument); + ASSERT_THROW(serialization.deserialize_public_key_from_bytes(pk_bytes.data(), 129, pk_deserialized), + std::invalid_argument); + serialization.deserialize_public_key_from_bytes(pk_bytes.data(), pk_bytes.size(), pk_deserialized); + ASSERT_EQ(*(pk->getN()), *(pk_deserialized->getN())); + + std::size_t secret_key_byte_count = serialization.secret_key_byte_count(); + std::vector sk_bytes(secret_key_byte_count); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, nullptr, 0), std::invalid_argument); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, nullptr, 255), std::invalid_argument); + ASSERT_THROW(serialization.serialize_secret_key_to_bytes(sk, sk_bytes.data(), 257), std::invalid_argument); + serialization.serialize_secret_key_to_bytes(sk, sk_bytes.data(), secret_key_byte_count); + + ASSERT_THROW( + serialization.deserialize_secret_key_from_bytes(nullptr, 0, sk_deserialized), std::invalid_argument); + ASSERT_THROW( + serialization.deserialize_secret_key_from_bytes(nullptr, 255, sk_deserialized), std::invalid_argument); + ASSERT_THROW(serialization.deserialize_secret_key_from_bytes(sk_bytes.data(), 257, sk_deserialized), + std::invalid_argument); + serialization.deserialize_secret_key_from_bytes(sk_bytes.data(), sk_bytes.size(), sk_deserialized); + ASSERT_EQ(*(sk->getN()), *(sk_deserialized->getN())); + ASSERT_EQ(*(sk->getP()), *(sk_deserialized->getP())); + ASSERT_EQ(*(sk->getQ()), *(sk_deserialized->getQ())); + } + { + const std::vector bits_vec = {2, 31, 32, 480, 511, 512, 994, 1023, 1024, 2018, 2047, 2048}; + for (std::size_t i = 0; i < bits_vec.size(); ++i) { + petace::solo::ahepaillier::BigNum bn = petace::solo::ahepaillier::utils::get_random_bn(bits_vec[i]); + std::vector bn_bytes((bits_vec[i] + 7) / 8); + petace::solo::ahepaillier::Serialization::bn_to_bytes(bn, bn_bytes.data(), bn_bytes.size()); + petace::solo::ahepaillier::BigNum bn_deserialized; + petace::solo::ahepaillier::Serialization::bn_from_bytes(bn_bytes.data(), bn_bytes.size(), bn_deserialized); + ASSERT_EQ(bn, bn_deserialized); + } + } + { + const std::vector bits_vec = {2, 31, 32, 482, 511, 512, 994, 1023, 1024, 2018, 2047, 2048}; + for (std::size_t i = 0; i < bits_vec.size(); ++i) { + petace::solo::ahepaillier::BigNum bn = petace::solo::ahepaillier::utils::get_random_bn(bits_vec[i]); + std::vector bn_bytes((bits_vec[i] + 7) / 8 + 1); + petace::solo::ahepaillier::Serialization::bn_to_bytes(bn, bn_bytes.data(), bn_bytes.size()); + petace::solo::ahepaillier::BigNum bn_deserialized; + petace::solo::ahepaillier::Serialization::bn_from_bytes(bn_bytes.data(), bn_bytes.size(), bn_deserialized); + ASSERT_EQ(bn, bn_deserialized); + } + } +} + +TEST(AHEPaillierTest, Encode) { + { + std::uint64_t in = 1234; + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Plaintext pt; + encoder.encode(in, pt); + std::uint64_t out = encoder.decode(pt); + ASSERT_EQ(in, out); + ASSERT_EQ(pt.slot_count(), 1); + ASSERT_EQ(pt[0], petace::solo::ahepaillier::BigNum(1234)); + } + { + std::vector in = {1, 2, 3, 4, 5, 6, 7, 8}; + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Plaintext pt; + encoder.encode(in, pt); + std::vector out; + encoder.decode(pt, out); + ASSERT_EQ(in, out); + ASSERT_EQ(pt.slot_count(), 8); + ASSERT_EQ(pt[0], petace::solo::ahepaillier::BigNum(1)); + } + { + std::vector in = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Plaintext pt(in); + std::vector out(pt); + ASSERT_EQ(in, out); + ASSERT_EQ(pt.slot_count(), 16); + ASSERT_EQ(pt[0], petace::solo::ahepaillier::BigNum(1)); + } + { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk, true); + petace::solo::ahepaillier::Encryptor encryptor(pk, true); + + std::vector in = {1, 2, 3, 4, 5, 6, 7, 8}; + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Plaintext pt; + petace::solo::ahepaillier::Ciphertext ct; + encoder.encode(in, pt); + encryptor.encrypt(pt, ct); + std::vector bn_ct(ct); + petace::solo::ahepaillier::Ciphertext ct_check(*(pk.get()), bn_ct); + for (std::size_t i = 0; i < ct.slot_count(); ++i) { + ASSERT_EQ(ct[i], ct_check[i]); + } + } + { + std::uint64_t in = 1ull << 33; + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Plaintext pt; + encoder.encode(in, pt); + encoder.decode(pt); + } +} + +TEST(AHEPaillierTest, Encrypt) { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk, true); + petace::solo::ahepaillier::Encryptor encryptor(pk, true); + petace::solo::ahepaillier::Decryptor decryptor(sk); + petace::solo::ahepaillier::Encoder encoder; + { + std::uint64_t in = 1234; + petace::solo::ahepaillier::Plaintext pt; + petace::solo::ahepaillier::Ciphertext ct; + encoder.encode(in, pt); + encryptor.encrypt(pt, ct); + ASSERT_EQ(ct.slot_count(), 1); + ct[0]; + + petace::solo::ahepaillier::Plaintext pt_check; + decryptor.decrypt(ct, pt_check); + std::uint64_t out = encoder.decode(pt_check); + ASSERT_EQ(in, out); + } + { + std::vector in = {1, 2, 3, 4, 5, 6, 7, 8}; + petace::solo::ahepaillier::Plaintext pt; + petace::solo::ahepaillier::Ciphertext ct; + encoder.encode(in, pt); + encryptor.encrypt(pt, ct); + ASSERT_EQ(ct.slot_count(), 8); + ct[0]; + + petace::solo::ahepaillier::Plaintext pt_check; + decryptor.decrypt(ct, pt_check); + std::vector out; + encoder.decode(pt_check, out); + ASSERT_EQ(in, out); + } + { + std::vector in = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + petace::solo::ahepaillier::Plaintext pt(in); + petace::solo::ahepaillier::Ciphertext ct; + encryptor.encrypt(pt, ct); + ASSERT_EQ(ct.slot_count(), 16); + ct[0]; + + petace::solo::ahepaillier::Plaintext pt_check; + decryptor.decrypt(ct, pt_check); + std::vector out(pt_check); + ASSERT_EQ(in, out); + } + { ASSERT_THROW(petace::solo::ahepaillier::Encryptor(nullptr, true), std::invalid_argument); } + { ASSERT_THROW(petace::solo::ahepaillier::Decryptor(nullptr), std::invalid_argument); } +} + +TEST(AHEPaillierTest, Evaluator) { + std::shared_ptr sk = nullptr; + std::shared_ptr pk = nullptr; + petace::solo::ahepaillier::KeyGenerator generator(1024); + generator.get_key_pair(sk, pk, true); + petace::solo::ahepaillier::Encryptor encryptor(pk, true); + petace::solo::ahepaillier::Decryptor decryptor(sk); + petace::solo::ahepaillier::Encoder encoder; + petace::solo::ahepaillier::Evaluator evaluator; + { + std::vector in_0 = {1, 2, 3, 4, 5, 6, 7, 8}; + std::vector in_1 = {8, 7, 6, 5, 4, 3, 2, 1}; + std::vector in_sum = {9, 9, 9, 9, 9, 9, 9, 9}; + std::vector in_check; + petace::solo::ahepaillier::Plaintext pt_0; + petace::solo::ahepaillier::Plaintext pt_1; + petace::solo::ahepaillier::Plaintext pt_check; + petace::solo::ahepaillier::Ciphertext ct_0; + petace::solo::ahepaillier::Ciphertext ct_1; + petace::solo::ahepaillier::Ciphertext ct_out; + + encoder.encode(in_0, pt_0); + encoder.encode(in_1, pt_1); + encryptor.encrypt(pt_0, ct_0); + encryptor.encrypt(pt_1, ct_1); + + evaluator.add(ct_0, ct_1, ct_out); + decryptor.decrypt(ct_out, pt_check); + encoder.decode(pt_check, in_check); + ASSERT_EQ(in_check, in_sum); + } + { + std::vector in_0 = {1, 2, 3, 4, 5, 6, 7, 8}; + std::vector in_1 = {8, 7, 6, 5, 4, 3, 2, 1}; + std::vector in_sum = {9, 9, 9, 9, 9, 9, 9, 9}; + std::vector in_check; + petace::solo::ahepaillier::Plaintext pt_0; + petace::solo::ahepaillier::Plaintext pt_1; + petace::solo::ahepaillier::Plaintext pt_check; + petace::solo::ahepaillier::Ciphertext ct_0; + petace::solo::ahepaillier::Ciphertext ct_1; + petace::solo::ahepaillier::Ciphertext ct_out; + + encoder.encode(in_0, pt_0); + encoder.encode(in_1, pt_1); + encryptor.encrypt(pt_0, ct_0); + + evaluator.add(ct_0, pt_1, ct_out); + decryptor.decrypt(ct_out, pt_check); + encoder.decode(pt_check, in_check); + ASSERT_EQ(in_check, in_sum); + } + { + std::vector in_0 = {1, 2, 3, 4, 5, 6, 7, 8}; + std::vector in_1 = {8, 7, 6, 5, 4, 3, 2, 1}; + std::vector in_mul = {8, 14, 18, 20, 20, 18, 14, 8}; + std::vector in_check; + petace::solo::ahepaillier::Plaintext pt_0; + petace::solo::ahepaillier::Plaintext pt_1; + petace::solo::ahepaillier::Plaintext pt_check; + petace::solo::ahepaillier::Ciphertext ct_0; + petace::solo::ahepaillier::Ciphertext ct_1; + petace::solo::ahepaillier::Ciphertext ct_out; + + encoder.encode(in_0, pt_0); + encoder.encode(in_1, pt_1); + encryptor.encrypt(pt_0, ct_0); + + evaluator.mul(ct_0, pt_1, ct_out); + decryptor.decrypt(ct_out, pt_check); + encoder.decode(pt_check, in_check); + ASSERT_EQ(in_check, in_mul); + } +} + +TEST(AHEPaillierTest, Utils) { + const std::vector bits_vec = {2, 31, 32, 482, 511, 512, 994, 1023, 1024, 2018, 2047, 2048}; + for (std::size_t i = 0; i < bits_vec.size(); ++i) { + auto bn = petace::solo::ahepaillier::BigNum::One(); + petace::solo::ahepaillier::utils::bn_lshift(bn, bits_vec[i]); + } +} + +#endif diff --git a/test/cuckoo_hashing_test.cpp b/test/cuckoo_hashing_test.cpp new file mode 100644 index 0000000..ec1816d --- /dev/null +++ b/test/cuckoo_hashing_test.cpp @@ -0,0 +1,153 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/cuckoo_hashing.h" + +#include + +#include + +#include "gtest/gtest.h" + +TEST(CuckooHashingTest, Constructor) { + std::vector seed(16, petace::solo::Byte(0)); + std::vector seed_invalid(8, petace::solo::Byte(0)); + { + petace::solo::CuckooHashing<8>(std::size_t(1 << 16)); + petace::solo::CuckooHashing<8>(std::size_t(1 << 16), seed); + ASSERT_THROW(petace::solo::CuckooHashing<8>(std::size_t(1 << 16), seed_invalid), std::invalid_argument); + } + { + petace::solo::CuckooHashing<16>(std::size_t(1 << 16)); + petace::solo::CuckooHashing<16>(std::size_t(1 << 16), seed); + ASSERT_THROW(petace::solo::CuckooHashing<16>(std::size_t(1 << 16), seed_invalid), std::invalid_argument); + } +} + +TEST(CuckooHashingTest, Default) { + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128); + auto prng = prng_factory.create(); + std::vector seed(16, petace::solo::Byte(0)); + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(8, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::CuckooHashing<8> cuckoo_hash(num_of_bins, seed); + cuckoo_hash.insert(elements); + cuckoo_hash.set_num_of_hash_functions(3); + cuckoo_hash.set_recursive_insertion_limiter(200); + cuckoo_hash.map_elements(); + ASSERT_EQ(cuckoo_hash.obtain_entry_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_source_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_ids().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_function_ids().size(), num_of_bins); + + auto bin_occupancy = cuckoo_hash.obtain_bin_occupancy(); + auto bin_occupancy_sum = std::count_if(bin_occupancy.begin(), bin_occupancy.end(), [](bool a) { return a; }); + ASSERT_EQ(bin_occupancy_sum, 1024); + + std::vector num_elements_in_bins = cuckoo_hash.get_num_of_elements_in_bins(); + std::size_t num_elements_sum = 0; + std::for_each(num_elements_in_bins.begin(), num_elements_in_bins.end(), + [&num_elements_sum](std::size_t i) { num_elements_sum += i; }); + ASSERT_EQ(num_elements_sum, 1024); + + ASSERT_EQ(cuckoo_hash.get_stash_size(), 0); + cuckoo_hash.insert(elements[0]); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(8, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::CuckooHashing<8> cuckoo_hash(num_of_bins, seed); + cuckoo_hash.insert(elements); + cuckoo_hash.set_num_of_hash_functions(3); + auto element_addresses = cuckoo_hash.get_element_addresses(); + ASSERT_EQ(element_addresses.size(), 1024 * 3); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(16, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::CuckooHashing<16> cuckoo_hash(num_of_bins, seed); + cuckoo_hash.insert(elements); + cuckoo_hash.set_num_of_hash_functions(3); + cuckoo_hash.set_recursive_insertion_limiter(200); + cuckoo_hash.map_elements(); + ASSERT_EQ(cuckoo_hash.obtain_entry_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_source_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_ids().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_function_ids().size(), num_of_bins); + + auto bin_occupancy = cuckoo_hash.obtain_bin_occupancy(); + auto bin_occupancy_sum = std::count_if(bin_occupancy.begin(), bin_occupancy.end(), [](bool a) { return a; }); + ASSERT_EQ(bin_occupancy_sum, 1024); + + std::vector num_elements_in_bins = cuckoo_hash.get_num_of_elements_in_bins(); + std::size_t num_elements_sum = 0; + std::for_each(num_elements_in_bins.begin(), num_elements_in_bins.end(), + [&num_elements_sum](std::size_t i) { num_elements_sum += i; }); + ASSERT_EQ(num_elements_sum, 1024); + + ASSERT_EQ(cuckoo_hash.get_stash_size(), 0); + cuckoo_hash.insert(elements[0]); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(16, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::CuckooHashing<16> cuckoo_hash(num_of_bins, seed); + cuckoo_hash.insert(elements); + cuckoo_hash.set_num_of_hash_functions(3); + auto element_addresses = cuckoo_hash.get_element_addresses(); + ASSERT_EQ(element_addresses.size(), 1024 * 3); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(8, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::CuckooHashing<8> cuckoo_hash(num_of_bins, seed); + cuckoo_hash.insert(elements); + cuckoo_hash.set_num_of_hash_functions(3); + cuckoo_hash.set_recursive_insertion_limiter(200); + cuckoo_hash.map_elements(); + ASSERT_EQ(cuckoo_hash.obtain_entry_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_source_values().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_ids().size(), num_of_bins); + ASSERT_EQ(cuckoo_hash.obtain_entry_function_ids().size(), num_of_bins); + + auto bin_occupancy = cuckoo_hash.obtain_bin_occupancy(); + auto bin_occupancy_sum = std::count_if(bin_occupancy.begin(), bin_occupancy.end(), [](bool a) { return a; }); + ASSERT_EQ(bin_occupancy_sum, 1024); + + std::vector num_elements_in_bins = cuckoo_hash.get_num_of_elements_in_bins(); + std::size_t num_elements_sum = 0; + std::for_each(num_elements_in_bins.begin(), num_elements_in_bins.end(), + [&num_elements_sum](std::size_t i) { num_elements_sum += i; }); + ASSERT_EQ(num_elements_sum, 1024); + + ASSERT_EQ(cuckoo_hash.get_stash_size(), 0); + cuckoo_hash.insert(elements[0]); + } +} diff --git a/test/hash_table_entry_test.cpp b/test/hash_table_entry_test.cpp new file mode 100644 index 0000000..d2ce11b --- /dev/null +++ b/test/hash_table_entry_test.cpp @@ -0,0 +1,125 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/util/hash_table_entry.h" + +#include + +#include "gtest/gtest.h" + +TEST(HashTableEntryTest, Constructor) { + { + petace::solo::util::HashTableEntry<8> hash_table_entry; + ASSERT_EQ(hash_table_entry.get_global_id(), petace::solo::util::kHashTableDummyElement); + std::array dummy_value; + std::fill_n(dummy_value.begin(), 8, petace::solo::Byte(0xFF)); + ASSERT_EQ(hash_table_entry.get_element(), dummy_value); + } + { + petace::solo::util::HashTableEntry<16> hash_table_entry; + ASSERT_EQ(hash_table_entry.get_global_id(), petace::solo::util::kHashTableDummyElement); + std::array dummy_value; + std::fill_n(dummy_value.begin(), 16, petace::solo::Byte(0xFF)); + ASSERT_EQ(hash_table_entry.get_element(), dummy_value); + } + { + petace::solo::util::HashTableEntry<8> hash_table_entry; + petace::solo::util::HashTableEntry<8> hash_table_entry_copy(hash_table_entry); + ASSERT_EQ(hash_table_entry_copy.get_global_id(), petace::solo::util::kHashTableDummyElement); + std::array dummy_value; + std::fill_n(dummy_value.begin(), 8, petace::solo::Byte(0xFF)); + ASSERT_EQ(hash_table_entry_copy.get_element(), dummy_value); + } + { + petace::solo::util::HashTableEntry<16> hash_table_entry; + petace::solo::util::HashTableEntry<16> hash_table_entry_copy(hash_table_entry); + ASSERT_EQ(hash_table_entry_copy.get_global_id(), petace::solo::util::kHashTableDummyElement); + std::array dummy_value; + std::fill_n(dummy_value.begin(), 16, petace::solo::Byte(0xFF)); + ASSERT_EQ(hash_table_entry_copy.get_element(), dummy_value); + } + { + std::array value = {petace::solo::Byte(0), petace::solo::Byte(1)}; + petace::solo::util::HashTableEntry<8> hash_table_entry(value, 1, 3, 10); + ASSERT_EQ(hash_table_entry.get_global_id(), 1); + ASSERT_EQ(hash_table_entry.get_element(), value); + } + { + std::array value = {petace::solo::Byte(0), petace::solo::Byte(1)}; + petace::solo::util::HashTableEntry<16> hash_table_entry(value, 1, 3, 10); + ASSERT_EQ(hash_table_entry.get_global_id(), 1); + ASSERT_EQ(hash_table_entry.get_element(), value); + } +} + +TEST(HashTableEntryTest, Default) { + { + std::array value = {petace::solo::Byte(0), petace::solo::Byte(1)}; + petace::solo::util::HashTableEntry<8> hash_table_entry(value, 1, 3, 10); + ASSERT_EQ(hash_table_entry.get_global_id(), 1); + ASSERT_EQ(hash_table_entry.get_element(), value); + hash_table_entry.set_current_function_id(0); + std::vector addresses = {22, 33, 44}; + auto addresses_copy = addresses; + hash_table_entry.set_possible_addresses(std::move(addresses)); + ASSERT_EQ(hash_table_entry.get_address_at(0), 2); + ASSERT_EQ(hash_table_entry.get_address_at(1), 3); + ASSERT_EQ(hash_table_entry.get_address_at(2), 4); + ASSERT_EQ(hash_table_entry.get_current_function_id(), 0); + ASSERT_EQ(hash_table_entry.get_current_address(), 2); + ASSERT_EQ(hash_table_entry.get_possible_addresses(), addresses_copy); + ASSERT_EQ(hash_table_entry.is_empty(), false); + hash_table_entry.iterate_function_number(); + ASSERT_EQ(hash_table_entry.get_current_address(), 3); + + std::array value_swap = {petace::solo::Byte(1), petace::solo::Byte(0)}; + std::vector addresses_swap = {44, 33, 22}; + auto addresses_swap_copy = addresses_swap; + petace::solo::util::HashTableEntry<8> hash_table_entry_swap(value_swap, 2, 3, 50); + hash_table_entry_swap.set_possible_addresses(std::move(addresses_swap)); + petace::solo::util::swap(hash_table_entry, hash_table_entry_swap); + ASSERT_EQ(hash_table_entry.get_element(), value_swap); + ASSERT_EQ(hash_table_entry.get_global_id(), 2); + ASSERT_EQ(hash_table_entry.get_possible_addresses(), addresses_swap_copy); + } + { + std::array value = {petace::solo::Byte(0), petace::solo::Byte(1)}; + petace::solo::util::HashTableEntry<16> hash_table_entry(value, 1, 3, 10); + ASSERT_EQ(hash_table_entry.get_global_id(), 1); + ASSERT_EQ(hash_table_entry.get_element(), value); + hash_table_entry.set_current_function_id(0); + std::vector addresses = {22, 33, 44}; + auto addresses_copy = addresses; + hash_table_entry.set_possible_addresses(std::move(addresses)); + ASSERT_EQ(hash_table_entry.get_address_at(0), 2); + ASSERT_EQ(hash_table_entry.get_address_at(1), 3); + ASSERT_EQ(hash_table_entry.get_address_at(2), 4); + ASSERT_EQ(hash_table_entry.get_current_function_id(), 0); + ASSERT_EQ(hash_table_entry.get_current_address(), 2); + ASSERT_EQ(hash_table_entry.get_possible_addresses(), addresses_copy); + ASSERT_EQ(hash_table_entry.is_empty(), false); + hash_table_entry.iterate_function_number(); + ASSERT_EQ(hash_table_entry.get_current_address(), 3); + + std::array value_swap = {petace::solo::Byte(1), petace::solo::Byte(0)}; + std::vector addresses_swap = {44, 33, 22}; + auto addresses_swap_copy = addresses_swap; + petace::solo::util::HashTableEntry<16> hash_table_entry_swap(value_swap, 2, 3, 50); + hash_table_entry_swap.set_possible_addresses(std::move(addresses_swap)); + petace::solo::util::swap(hash_table_entry, hash_table_entry_swap); + ASSERT_EQ(hash_table_entry.get_element(), value_swap); + ASSERT_EQ(hash_table_entry.get_global_id(), 2); + ASSERT_EQ(hash_table_entry.get_possible_addresses(), addresses_swap_copy); + } +} diff --git a/test/simple_hashing_test.cpp b/test/simple_hashing_test.cpp new file mode 100644 index 0000000..39f5ea3 --- /dev/null +++ b/test/simple_hashing_test.cpp @@ -0,0 +1,114 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/simple_hashing.h" + +#include + +#include + +#include "gtest/gtest.h" + +TEST(SimpleHashingTest, Constructor) { + std::vector seed(16, petace::solo::Byte(0)); + std::vector seed_invalid(8, petace::solo::Byte(0)); + { + petace::solo::SimpleHashing<8>(std::size_t(1 << 16)); + petace::solo::SimpleHashing<8>(std::size_t(1 << 16), seed); + ASSERT_THROW(petace::solo::SimpleHashing<8>(std::size_t(1 << 16), seed_invalid), std::invalid_argument); + } + { + petace::solo::SimpleHashing<16>(std::size_t(1 << 16)); + petace::solo::SimpleHashing<16>(std::size_t(1 << 16), seed); + ASSERT_THROW(petace::solo::SimpleHashing<16>(std::size_t(1 << 16), seed_invalid), std::invalid_argument); + } +} + +TEST(SimpleHashingTest, Default) { + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128); + auto prng = prng_factory.create(); + std::vector seed(16, petace::solo::Byte(0)); + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(8, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::SimpleHashing<8> simple_hash(num_of_bins, seed); + simple_hash.insert(elements); + simple_hash.set_num_of_hash_functions(3); + simple_hash.map_elements(); + simple_hash.set_maximum_bin_size(simple_hash.get_max_observed_bin_size()); + ASSERT_EQ(simple_hash.obtain_entry_values().size(), 3 * 1024); + ASSERT_EQ( + simple_hash.obtain_entry_values_padded().size(), simple_hash.get_max_observed_bin_size() * num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_values().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_ids().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_function_ids().size(), num_of_bins); + auto num_of_elements_in_bins = simple_hash.get_num_of_elements_in_bins(); + std::size_t sum_elements = 0; + std::for_each(num_of_elements_in_bins.begin(), num_of_elements_in_bins.end(), + [&sum_elements](int n) { sum_elements += n; }); + ASSERT_EQ(sum_elements, 3 * 1024); + simple_hash.insert(elements[0]); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(16, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::SimpleHashing<16> simple_hash(num_of_bins, seed); + simple_hash.insert(elements); + simple_hash.set_num_of_hash_functions(3); + simple_hash.map_elements(); + simple_hash.set_maximum_bin_size(simple_hash.get_max_observed_bin_size()); + ASSERT_EQ(simple_hash.obtain_entry_values().size(), 3 * 1024); + ASSERT_EQ( + simple_hash.obtain_entry_values_padded().size(), simple_hash.get_max_observed_bin_size() * num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_values().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_ids().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_function_ids().size(), num_of_bins); + auto num_of_elements_in_bins = simple_hash.get_num_of_elements_in_bins(); + std::size_t sum_elements = 0; + std::for_each(num_of_elements_in_bins.begin(), num_of_elements_in_bins.end(), + [&sum_elements](int n) { sum_elements += n; }); + ASSERT_EQ(sum_elements, 3 * 1024); + simple_hash.insert(elements[0]); + } + { + double epsilon = 1.27f; + std::vector> elements(1024); + std::for_each(elements.begin(), elements.end(), + [&prng](std::array& element) { prng->generate(8, element.data()); }); + std::size_t num_of_bins = static_cast(std::ceil(1024 * epsilon)); + petace::solo::SimpleHashing<8> simple_hash(num_of_bins, seed); + simple_hash.insert(elements); + simple_hash.set_num_of_hash_functions(3); + simple_hash.map_elements(); + simple_hash.set_maximum_bin_size(simple_hash.get_max_observed_bin_size()); + ASSERT_EQ(simple_hash.obtain_entry_values().size(), 3 * 1024); + ASSERT_EQ( + simple_hash.obtain_entry_values_padded().size(), simple_hash.get_max_observed_bin_size() * num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_values().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_ids().size(), num_of_bins); + ASSERT_EQ(simple_hash.obtain_bin_entry_function_ids().size(), num_of_bins); + auto num_of_elements_in_bins = simple_hash.get_num_of_elements_in_bins(); + std::size_t sum_elements = 0; + std::for_each(num_of_elements_in_bins.begin(), num_of_elements_in_bins.end(), + [&sum_elements](int n) { sum_elements += n; }); + ASSERT_EQ(sum_elements, 3 * 1024); + simple_hash.insert(elements[0]); + } +}