From 9ef786774f26cec873e4b98c4bbc5584f340f270 Mon Sep 17 00:00:00 2001 From: Shaun Zhang Date: Mon, 22 Jan 2024 21:24:38 +0800 Subject: [PATCH 1/5] [feat] add multiplicative inverse function `Inv64` for GF(2^64) --- yacl/math/f2k/f2k.h | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/yacl/math/f2k/f2k.h b/yacl/math/f2k/f2k.h index f7da1d1e..9896aec5 100644 --- a/yacl/math/f2k/f2k.h +++ b/yacl/math/f2k/f2k.h @@ -139,6 +139,56 @@ inline uint64_t GfMul64(uint64_t x, uint64_t y) { return Reduce64(ClMul64(x, y)); } +// inverse over Galois Field F_{2^64} +inline uint64_t Inv64(uint64_t x) { + uint64_t t0 = x; + uint64_t t1 = GfMul64(t0, t0); + uint64_t t2 = GfMul64(t1, t0); + t0 = GfMul64(t2, t2); + t0 = GfMul64(t0, t0); + t1 = GfMul64(t1, t0); + t2 = GfMul64(t2, t0); + t0 = GfMul64(t2, t2); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t1 = GfMul64(t1, t0); + t2 = GfMul64(t2, t0); + t0 = GfMul64(t2, t2); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t1 = GfMul64(t1, t0); + t2 = GfMul64(t2, t0); + t0 = GfMul64(t2, t2); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t0 = GfMul64(t0, t0); + t1 = GfMul64(t1, t0); + t0 = GfMul64(t0, t2); + for (int i = 0; i < 32; i++) { + t0 = GfMul64(t0, t0); + } + t0 = GfMul64(t0, t1); + return t0; +} + // Inner product inline std::pair ClMul128(absl::Span x, absl::Span y) { From 43c44e0ee474edc15ab4780daa932d8a22c88c8b Mon Sep 17 00:00:00 2001 From: Shaun Zhang Date: Mon, 22 Jan 2024 21:50:34 +0800 Subject: [PATCH 2/5] [feat] add KRTW19 PSU scheme --- yacl/crypto/primitives/psu/BUILD.bazel | 46 ++++ yacl/crypto/primitives/psu/krtw19_psu.cc | 241 ++++++++++++++++++ yacl/crypto/primitives/psu/krtw19_psu.h | 34 +++ yacl/crypto/primitives/psu/krtw19_psu_test.cc | 82 ++++++ 4 files changed, 403 insertions(+) create mode 100644 yacl/crypto/primitives/psu/BUILD.bazel create mode 100644 yacl/crypto/primitives/psu/krtw19_psu.cc create mode 100644 yacl/crypto/primitives/psu/krtw19_psu.h create mode 100644 yacl/crypto/primitives/psu/krtw19_psu_test.cc diff --git a/yacl/crypto/primitives/psu/BUILD.bazel b/yacl/crypto/primitives/psu/BUILD.bazel new file mode 100644 index 00000000..7406f9db --- /dev/null +++ b/yacl/crypto/primitives/psu/BUILD.bazel @@ -0,0 +1,46 @@ +# Copyright 2024 zhangwfjh +# +# 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. + +load("//bazel:yacl.bzl", "AES_COPT_FLAGS", "yacl_cc_library", "yacl_cc_test") + +package(default_visibility = ["//visibility:public"]) + +yacl_cc_library( + name = "krtw19_psu", + srcs = [ + "krtw19_psu.cc", + ], + hdrs = [ + "krtw19_psu.h", + ], + copts = AES_COPT_FLAGS, + deps = [ + "//yacl/base:exception", + "//yacl/base:int128", + "//yacl/crypto/base/hash:hash_utils", + "//yacl/crypto/primitives/ot:base_ot", + "//yacl/crypto/primitives/ot:iknp_ote", + "//yacl/crypto/primitives/ot:kkrt_ote", + "//yacl/crypto/utils:rand", + "//yacl/link", + "//yacl/math:gadget", + "@com_google_absl//absl/types:span", + ], +) + +yacl_cc_test( + name = "krtw19_psu_test", + srcs = ["krtw19_psu_test.cc"], + deps = [":krtw19_psu"], +) diff --git a/yacl/crypto/primitives/psu/krtw19_psu.cc b/yacl/crypto/primitives/psu/krtw19_psu.cc new file mode 100644 index 00000000..75c2db44 --- /dev/null +++ b/yacl/crypto/primitives/psu/krtw19_psu.cc @@ -0,0 +1,241 @@ +// Copyright 2024 zhangwfjh +// +// 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 "yacl/crypto/primitives/psu/krtw19_psu.h" + +#include +#include +#include +#include + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/primitives/ot/base_ot.h" +#include "yacl/crypto/primitives/ot/iknp_ote.h" +#include "yacl/crypto/primitives/ot/kkrt_ote.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/math/gadget.h" +#include "yacl/utils/serialize.h" + +namespace yacl::crypto::psu { + +namespace yc = yacl::crypto; + +namespace { + +// reference: https://eprint.iacr.org/2019/1234.pdf (Figure 2) +constexpr float ZETA{0.06f}; +constexpr size_t BIN_SIZE{64ul}; // m+1 +constexpr uint128_t BOT{}; +constexpr size_t NUM_BIN_PER_BATCH{16ul}; +constexpr size_t BATCH_SIZE{NUM_BIN_PER_BATCH * BIN_SIZE}; + +constexpr size_t NUM_BASE_OT{128ul}; +constexpr size_t NUM_INKP_OT{512ul}; + +static std::random_device rd; +static std::mt19937 gen(rd()); + +struct HashU128 { + size_t operator()(const uint128_t& x) const { + return yacl::math::UniversalHash(1, {x}); + } +}; + +yacl::Buffer Serialize(uint64_t num) { + yacl::Buffer buf(sizeof(uint64_t)); + std::memcpy(buf.data(), &num, sizeof(uint64_t)); + return buf; +} + +uint64_t Deserialize(const yacl::Buffer& buf) { + uint64_t num; + std::memcpy(&num, buf.data(), sizeof(uint64_t)); + return num; +} + +auto HashInputs(const std::vector& elem_hashes, size_t count) { + size_t num_bins = std::ceil(count * ZETA); + std::vector> hashing(num_bins); + for (auto elem : elem_hashes) { + auto hash = HashU128{}(elem); + hashing[hash % num_bins].push_back(elem); + } + return hashing; +} + +auto Evaluate(const std::vector& coeffs, uint64_t x) { + uint64_t y{coeffs.back()}; + for (auto it = std::next(coeffs.rbegin()); it != coeffs.rend(); ++it) { + y = GfMul64(y, x) ^ *it; + } + return y; +} + +auto Interpolate(const std::vector& xs, + const std::vector& ys) { + YACL_ENFORCE_EQ(xs.size(), ys.size(), "Sizes mismatch."); + size_t size{xs.size()}; + std::vector L_coeffs(size); + for (size_t i{}; i != size; ++i) { + std::vector Li_coeffs(size); + Li_coeffs[0] = ys[i]; + uint64_t prod{1}; + for (size_t j{}; j != size; ++j) { + if (xs[i] != xs[j]) { + prod = GfMul64(prod, xs[i] ^ xs[j]); + uint64_t sum{}; + for (size_t k{}; k != size; ++k) { + sum = std::exchange(Li_coeffs[k], GfMul64(Li_coeffs[k], xs[j]) ^ sum); + } + } + } + for (size_t k{}; k != size; ++k) { + L_coeffs[k] ^= GfMul64(Li_coeffs[k], Inv64(prod)); + } + } + return L_coeffs; +} + +} // namespace + +void KrtwPsuSend(std::shared_ptr ctx, + const std::vector& elem_hashes) { + ctx->SendAsync(ctx->NextRank(), Serialize(elem_hashes.size()), + "Send set size"); + size_t peer_count = + Deserialize(ctx->Recv(ctx->PrevRank(), "Receive set size")); + auto count = std::max(elem_hashes.size(), peer_count); + if (count == 0) { + return; + } + // Step 1. Hashes inputs + auto hashing = HashInputs(elem_hashes, count); + + // Step 2. Prepares OPRF + yc::KkrtOtExtReceiver receiver; + size_t num_ot{hashing.size() * BIN_SIZE}; + auto choice = yc::RandBits(NUM_BASE_OT); + auto base_ot = yc::BaseOtRecv(ctx, choice, NUM_BASE_OT); + auto store = yc::IknpOtExtSend(ctx, base_ot, NUM_INKP_OT); + receiver.Init(ctx, store, num_ot); + receiver.SetBatchSize(BATCH_SIZE); + + std::vector elems; + elems.reserve(num_ot); + size_t oprf_idx{}; + for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { + if (bin_idx % NUM_BIN_PER_BATCH == 0) { + receiver.SendCorrection( + ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); + } + hashing[bin_idx].resize(BIN_SIZE); + std::shuffle(hashing[bin_idx].begin(), hashing[bin_idx].end(), gen); + // Step 3. For each bin element, invokes PSU(1, m+1) + for (auto elem : hashing[bin_idx]) { + elems.emplace_back(elem); + uint64_t eval; + receiver.Encode(oprf_idx++, elem, + {reinterpret_cast(&eval), sizeof eval}); + std::vector coeffs(BIN_SIZE); + auto buf = ctx->Recv(ctx->PrevRank(), "Receive coefficients"); + std::memcpy(coeffs.data(), buf.data(), buf.size()); + auto y = Evaluate(coeffs, HashU128{}(elem)) ^ eval; + ctx->SendAsync(ctx->NextRank(), Serialize(y), "Send evaluation"); + } + } + + // Step 4. Send new elements through OT + std::vector> keys(num_ot); + choice = yc::RandBits(NUM_BASE_OT); + base_ot = yc::BaseOtRecv(ctx, choice, NUM_BASE_OT); + yc::IknpOtExtSend(ctx, base_ot, absl::MakeSpan(keys)); + std::vector ciphers(num_ot); + for (size_t i{}; i != num_ot; ++i) { + ciphers[i] = elems[i] ^ keys[i][0]; + } + ctx->SendAsync(ctx->NextRank(), + yacl::Buffer{reinterpret_cast(ciphers.data()), + ciphers.size() * sizeof(uint128_t)}, + "Send ciphertexts"); +} + +std::vector KrtwPsuRecv(std::shared_ptr ctx, + const std::vector& elem_hashes) { + size_t peer_count = + Deserialize(ctx->Recv(ctx->PrevRank(), "Receive set size")); + ctx->SendAsync(ctx->NextRank(), Serialize(elem_hashes.size()), + "Send set size"); + auto count = std::max(elem_hashes.size(), peer_count); + if (count == 0) { + return {}; + } + // Step 1. Hashes inputs + auto hashing = HashInputs(elem_hashes, count); + + // Step 2. Prepares OPRF + yc::KkrtOtExtSender sender; + size_t num_ot{hashing.size() * BIN_SIZE}; + auto base_ot = yc::BaseOtSend(ctx, NUM_BASE_OT); + auto choice = yc::RandBits(NUM_INKP_OT); + auto store = yc::IknpOtExtRecv(ctx, base_ot, choice, NUM_INKP_OT); + sender.Init(ctx, store, num_ot); + sender.SetBatchSize(BATCH_SIZE); + auto oprf = sender.GetOprf(); + + yacl::dynamic_bitset<> ot_choice(num_ot); + size_t oprf_idx{}; + // Step 3. For each bin, invokes PSU(1, m+1) + for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { + if (bin_idx % NUM_BIN_PER_BATCH == 0) { + sender.RecvCorrection( + ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); + } + auto bin_size = hashing[bin_idx].size(); + for (size_t elem_idx{}; elem_idx != BIN_SIZE; ++elem_idx, ++oprf_idx) { + auto seed = yc::FastRandU64(); + std::vector xs(BIN_SIZE), ys(BIN_SIZE); + for (size_t i{}; i != BIN_SIZE; ++i) { + xs[i] = (i < bin_size ? HashU128{}(hashing[bin_idx][i]) + : i > bin_size ? yc::FastRandU64() + : BOT); + ys[i] = oprf->Eval(oprf_idx, xs[i]) ^ seed; + } + auto coeffs = Interpolate(xs, ys); + yacl::Buffer buf(coeffs.data(), coeffs.size() * sizeof(uint64_t)); + ctx->SendAsync(ctx->NextRank(), buf, "Send coefficients"); + auto eval = Deserialize(ctx->Recv(ctx->PrevRank(), "Receive evaluation")); + ot_choice[oprf_idx] = eval == seed; + } + } + + // Step 4. Receive new elements through OT + std::vector keys(num_ot); + base_ot = yc::BaseOtSend(ctx, NUM_BASE_OT); + yc::IknpOtExtRecv(ctx, base_ot, ot_choice, absl::MakeSpan(keys)); + std::vector ciphers(num_ot); + auto buf = ctx->Recv(ctx->PrevRank(), "Receive ciphertexts"); + std::memcpy(ciphers.data(), buf.data(), buf.size()); + std::unordered_set set_union(elem_hashes.begin(), + elem_hashes.end()); + for (size_t i{}; i != num_ot; ++i) { + if (!ot_choice[i]) { + if (auto new_elem = ciphers[i] ^ keys[i]; new_elem != BOT) { + set_union.emplace(ciphers[i] ^ keys[i]); + } + } + } + return std::vector(set_union.begin(), set_union.end()); +} + +} // namespace yacl::crypto::psu diff --git a/yacl/crypto/primitives/psu/krtw19_psu.h b/yacl/crypto/primitives/psu/krtw19_psu.h new file mode 100644 index 00000000..472667d4 --- /dev/null +++ b/yacl/crypto/primitives/psu/krtw19_psu.h @@ -0,0 +1,34 @@ +// Copyright 2024 zhangwfjh +// +// 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 +#include + +#include "yacl/base/int128.h" +#include "yacl/link/link.h" + +namespace yacl::crypto::psu { + +// Scalable Private Set Union from Symmetric-Key Techniques +// https://eprint.iacr.org/2019/776.pdf + +void KrtwPsuSend(std::shared_ptr, + const std::vector&); + +std::vector KrtwPsuRecv(std::shared_ptr, + const std::vector&); + +} // namespace yacl::crypto::psu diff --git a/yacl/crypto/primitives/psu/krtw19_psu_test.cc b/yacl/crypto/primitives/psu/krtw19_psu_test.cc new file mode 100644 index 00000000..57d254fa --- /dev/null +++ b/yacl/crypto/primitives/psu/krtw19_psu_test.cc @@ -0,0 +1,82 @@ +// Copyright 2024 zhangwfjh +// +// 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 "yacl/crypto/primitives/psu/krtw19_psu.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" + +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/utils/secparam.h" +#include "yacl/link/test_util.h" + +struct TestParams { + std::vector items_a; + std::vector items_b; +}; + +namespace yacl::crypto::psu { + +class KrtwPsuTest : public testing::TestWithParam {}; + +TEST_P(KrtwPsuTest, Works) { + auto params = GetParam(); + const int kWorldSize = 2; + auto contexts = yacl::link::test::SetupWorld(kWorldSize); + + std::future krtwpsu_sender = + std::async([&] { return KrtwPsuSend(contexts[0], params.items_a); }); + std::future> krtwpsu_receiver = + std::async([&] { return KrtwPsuRecv(contexts[1], params.items_b); }); + + krtwpsu_sender.get(); + auto psu_result = krtwpsu_receiver.get(); + std::sort(psu_result.begin(), psu_result.end()); + + std::set union_set; + union_set.insert(params.items_a.begin(), params.items_a.end()); + union_set.insert(params.items_b.begin(), params.items_b.end()); + std::vector union_vec(union_set.begin(), union_set.end()); + + EXPECT_EQ(psu_result, union_vec); +} + +std::vector CreateRangeItems(size_t begin, size_t size) { + std::vector ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(yacl::crypto::Blake3_128(std::to_string(begin + i))); + } + return ret; +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, KrtwPsuTest, + testing::Values( + TestParams{{}, {}}, // + TestParams{{}, {yacl::crypto::Blake3_128("a")}}, // + TestParams{{yacl::crypto::Blake3_128("a")}, {}}, // + // No overlap + TestParams{CreateRangeItems(0, 1024), CreateRangeItems(1024, 1024)}, // + // Partial overlap + TestParams{CreateRangeItems(0, 1024), CreateRangeItems(512, 1024)}, // + // Complete overlap + TestParams{CreateRangeItems(0, 1024), CreateRangeItems(0, 1024)} // + )); + +} // namespace yacl::crypto::psu From a9b2ac242e3e847ae6af834a0e9497264d814b90 Mon Sep 17 00:00:00 2001 From: Shaun Zhang Date: Tue, 23 Jan 2024 13:26:11 +0800 Subject: [PATCH 3/5] [fix] several improvements 1. remove psu namespace 2. use const reference shared_ptr in parameters 3. separate CRHash from UHF 4. remove `Serialize` --- yacl/crypto/primitives/psu/krtw19_psu.cc | 104 +++++++++--------- yacl/crypto/primitives/psu/krtw19_psu.h | 8 +- yacl/crypto/primitives/psu/krtw19_psu_test.cc | 12 +- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/yacl/crypto/primitives/psu/krtw19_psu.cc b/yacl/crypto/primitives/psu/krtw19_psu.cc index 75c2db44..57c4aac3 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu.cc +++ b/yacl/crypto/primitives/psu/krtw19_psu.cc @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "yacl/crypto/base/hash/hash_utils.h" #include "yacl/crypto/primitives/ot/base_ot.h" @@ -27,9 +29,7 @@ #include "yacl/math/gadget.h" #include "yacl/utils/serialize.h" -namespace yacl::crypto::psu { - -namespace yc = yacl::crypto; +namespace yacl::crypto { namespace { @@ -37,8 +37,8 @@ namespace { constexpr float ZETA{0.06f}; constexpr size_t BIN_SIZE{64ul}; // m+1 constexpr uint128_t BOT{}; -constexpr size_t NUM_BIN_PER_BATCH{16ul}; -constexpr size_t BATCH_SIZE{NUM_BIN_PER_BATCH * BIN_SIZE}; +constexpr size_t NUM_BINS_PER_BATCH{16ul}; +constexpr size_t BATCH_SIZE{NUM_BINS_PER_BATCH * BIN_SIZE}; constexpr size_t NUM_BASE_OT{128ul}; constexpr size_t NUM_INKP_OT{512ul}; @@ -46,35 +46,33 @@ constexpr size_t NUM_INKP_OT{512ul}; static std::random_device rd; static std::mt19937 gen(rd()); -struct HashU128 { +struct U128Hasher { size_t operator()(const uint128_t& x) const { - return yacl::math::UniversalHash(1, {x}); + return yacl::math::UniversalHash( + 1, absl::MakeSpan(reinterpret_cast(&x), + sizeof x / sizeof(uint64_t))); } -}; - -yacl::Buffer Serialize(uint64_t num) { - yacl::Buffer buf(sizeof(uint64_t)); - std::memcpy(buf.data(), &num, sizeof(uint64_t)); - return buf; -} -uint64_t Deserialize(const yacl::Buffer& buf) { - uint64_t num; - std::memcpy(&num, buf.data(), sizeof(uint64_t)); - return num; -} + static uint64_t CRHash(const uint128_t& x) { + auto hash = + Blake3(absl::MakeSpan(reinterpret_cast(&x), sizeof x)); + uint64_t ret; + std::memcpy(&ret, hash.data(), sizeof ret); + return ret; + } +}; auto HashInputs(const std::vector& elem_hashes, size_t count) { size_t num_bins = std::ceil(count * ZETA); std::vector> hashing(num_bins); for (auto elem : elem_hashes) { - auto hash = HashU128{}(elem); + auto hash = U128Hasher{}(elem); hashing[hash % num_bins].push_back(elem); } return hashing; } -auto Evaluate(const std::vector& coeffs, uint64_t x) { +uint64_t Evaluate(const std::vector& coeffs, uint64_t x) { uint64_t y{coeffs.back()}; for (auto it = std::next(coeffs.rbegin()); it != coeffs.rend(); ++it) { y = GfMul64(y, x) ^ *it; @@ -109,12 +107,12 @@ auto Interpolate(const std::vector& xs, } // namespace -void KrtwPsuSend(std::shared_ptr ctx, +void KrtwPsuSend(const std::shared_ptr& ctx, const std::vector& elem_hashes) { - ctx->SendAsync(ctx->NextRank(), Serialize(elem_hashes.size()), + ctx->SendAsync(ctx->NextRank(), SerializeUint128(elem_hashes.size()), "Send set size"); size_t peer_count = - Deserialize(ctx->Recv(ctx->PrevRank(), "Receive set size")); + DeserializeUint128(ctx->Recv(ctx->PrevRank(), "Receive set size")); auto count = std::max(elem_hashes.size(), peer_count); if (count == 0) { return; @@ -123,11 +121,11 @@ void KrtwPsuSend(std::shared_ptr ctx, auto hashing = HashInputs(elem_hashes, count); // Step 2. Prepares OPRF - yc::KkrtOtExtReceiver receiver; + KkrtOtExtReceiver receiver; size_t num_ot{hashing.size() * BIN_SIZE}; - auto choice = yc::RandBits(NUM_BASE_OT); - auto base_ot = yc::BaseOtRecv(ctx, choice, NUM_BASE_OT); - auto store = yc::IknpOtExtSend(ctx, base_ot, NUM_INKP_OT); + auto choice = RandBits(NUM_BASE_OT); + auto base_ot = BaseOtRecv(ctx, choice, NUM_BASE_OT); + auto store = IknpOtExtSend(ctx, base_ot, NUM_INKP_OT); receiver.Init(ctx, store, num_ot); receiver.SetBatchSize(BATCH_SIZE); @@ -135,7 +133,7 @@ void KrtwPsuSend(std::shared_ptr ctx, elems.reserve(num_ot); size_t oprf_idx{}; for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { - if (bin_idx % NUM_BIN_PER_BATCH == 0) { + if (bin_idx % NUM_BINS_PER_BATCH == 0) { receiver.SendCorrection( ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); } @@ -150,16 +148,16 @@ void KrtwPsuSend(std::shared_ptr ctx, std::vector coeffs(BIN_SIZE); auto buf = ctx->Recv(ctx->PrevRank(), "Receive coefficients"); std::memcpy(coeffs.data(), buf.data(), buf.size()); - auto y = Evaluate(coeffs, HashU128{}(elem)) ^ eval; - ctx->SendAsync(ctx->NextRank(), Serialize(y), "Send evaluation"); + auto y = Evaluate(coeffs, U128Hasher::CRHash(elem)) ^ eval; + ctx->SendAsync(ctx->NextRank(), SerializeUint128(y), "Send evaluation"); } } // Step 4. Send new elements through OT std::vector> keys(num_ot); - choice = yc::RandBits(NUM_BASE_OT); - base_ot = yc::BaseOtRecv(ctx, choice, NUM_BASE_OT); - yc::IknpOtExtSend(ctx, base_ot, absl::MakeSpan(keys)); + choice = RandBits(NUM_BASE_OT); + base_ot = BaseOtRecv(ctx, choice, NUM_BASE_OT); + IknpOtExtSend(ctx, base_ot, absl::MakeSpan(keys)); std::vector ciphers(num_ot); for (size_t i{}; i != num_ot; ++i) { ciphers[i] = elems[i] ^ keys[i][0]; @@ -170,11 +168,12 @@ void KrtwPsuSend(std::shared_ptr ctx, "Send ciphertexts"); } -std::vector KrtwPsuRecv(std::shared_ptr ctx, - const std::vector& elem_hashes) { +std::vector KrtwPsuRecv( + const std::shared_ptr& ctx, + const std::vector& elem_hashes) { size_t peer_count = - Deserialize(ctx->Recv(ctx->PrevRank(), "Receive set size")); - ctx->SendAsync(ctx->NextRank(), Serialize(elem_hashes.size()), + DeserializeUint128(ctx->Recv(ctx->PrevRank(), "Receive set size")); + ctx->SendAsync(ctx->NextRank(), SerializeUint128(elem_hashes.size()), "Send set size"); auto count = std::max(elem_hashes.size(), peer_count); if (count == 0) { @@ -184,11 +183,11 @@ std::vector KrtwPsuRecv(std::shared_ptr ctx, auto hashing = HashInputs(elem_hashes, count); // Step 2. Prepares OPRF - yc::KkrtOtExtSender sender; + KkrtOtExtSender sender; size_t num_ot{hashing.size() * BIN_SIZE}; - auto base_ot = yc::BaseOtSend(ctx, NUM_BASE_OT); - auto choice = yc::RandBits(NUM_INKP_OT); - auto store = yc::IknpOtExtRecv(ctx, base_ot, choice, NUM_INKP_OT); + auto base_ot = BaseOtSend(ctx, NUM_BASE_OT); + auto choice = RandBits(NUM_INKP_OT); + auto store = IknpOtExtRecv(ctx, base_ot, choice, NUM_INKP_OT); sender.Init(ctx, store, num_ot); sender.SetBatchSize(BATCH_SIZE); auto oprf = sender.GetOprf(); @@ -197,37 +196,38 @@ std::vector KrtwPsuRecv(std::shared_ptr ctx, size_t oprf_idx{}; // Step 3. For each bin, invokes PSU(1, m+1) for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { - if (bin_idx % NUM_BIN_PER_BATCH == 0) { + if (bin_idx % NUM_BINS_PER_BATCH == 0) { sender.RecvCorrection( ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); } auto bin_size = hashing[bin_idx].size(); for (size_t elem_idx{}; elem_idx != BIN_SIZE; ++elem_idx, ++oprf_idx) { - auto seed = yc::FastRandU64(); + auto seed = FastRandU64(); std::vector xs(BIN_SIZE), ys(BIN_SIZE); for (size_t i{}; i != BIN_SIZE; ++i) { - xs[i] = (i < bin_size ? HashU128{}(hashing[bin_idx][i]) - : i > bin_size ? yc::FastRandU64() + xs[i] = (i < bin_size ? U128Hasher::CRHash(hashing[bin_idx][i]) + : i > bin_size ? FastRandU64() : BOT); ys[i] = oprf->Eval(oprf_idx, xs[i]) ^ seed; } auto coeffs = Interpolate(xs, ys); yacl::Buffer buf(coeffs.data(), coeffs.size() * sizeof(uint64_t)); ctx->SendAsync(ctx->NextRank(), buf, "Send coefficients"); - auto eval = Deserialize(ctx->Recv(ctx->PrevRank(), "Receive evaluation")); + auto eval = + DeserializeUint128(ctx->Recv(ctx->PrevRank(), "Receive evaluation")); ot_choice[oprf_idx] = eval == seed; } } // Step 4. Receive new elements through OT std::vector keys(num_ot); - base_ot = yc::BaseOtSend(ctx, NUM_BASE_OT); - yc::IknpOtExtRecv(ctx, base_ot, ot_choice, absl::MakeSpan(keys)); + base_ot = BaseOtSend(ctx, NUM_BASE_OT); + IknpOtExtRecv(ctx, base_ot, ot_choice, absl::MakeSpan(keys)); std::vector ciphers(num_ot); auto buf = ctx->Recv(ctx->PrevRank(), "Receive ciphertexts"); std::memcpy(ciphers.data(), buf.data(), buf.size()); - std::unordered_set set_union(elem_hashes.begin(), - elem_hashes.end()); + std::unordered_set set_union(elem_hashes.begin(), + elem_hashes.end()); for (size_t i{}; i != num_ot; ++i) { if (!ot_choice[i]) { if (auto new_elem = ciphers[i] ^ keys[i]; new_elem != BOT) { @@ -238,4 +238,4 @@ std::vector KrtwPsuRecv(std::shared_ptr ctx, return std::vector(set_union.begin(), set_union.end()); } -} // namespace yacl::crypto::psu +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/psu/krtw19_psu.h b/yacl/crypto/primitives/psu/krtw19_psu.h index 472667d4..8e738604 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu.h +++ b/yacl/crypto/primitives/psu/krtw19_psu.h @@ -20,15 +20,15 @@ #include "yacl/base/int128.h" #include "yacl/link/link.h" -namespace yacl::crypto::psu { +namespace yacl::crypto { // Scalable Private Set Union from Symmetric-Key Techniques // https://eprint.iacr.org/2019/776.pdf -void KrtwPsuSend(std::shared_ptr, +void KrtwPsuSend(const std::shared_ptr&, const std::vector&); -std::vector KrtwPsuRecv(std::shared_ptr, +std::vector KrtwPsuRecv(const std::shared_ptr&, const std::vector&); -} // namespace yacl::crypto::psu +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/psu/krtw19_psu_test.cc b/yacl/crypto/primitives/psu/krtw19_psu_test.cc index 57d254fa..c498b930 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu_test.cc +++ b/yacl/crypto/primitives/psu/krtw19_psu_test.cc @@ -31,7 +31,7 @@ struct TestParams { std::vector items_b; }; -namespace yacl::crypto::psu { +namespace yacl::crypto { class KrtwPsuTest : public testing::TestWithParam {}; @@ -60,7 +60,7 @@ TEST_P(KrtwPsuTest, Works) { std::vector CreateRangeItems(size_t begin, size_t size) { std::vector ret; for (size_t i = 0; i < size; i++) { - ret.push_back(yacl::crypto::Blake3_128(std::to_string(begin + i))); + ret.push_back(Blake3_128(std::to_string(begin + i))); } return ret; } @@ -68,9 +68,9 @@ std::vector CreateRangeItems(size_t begin, size_t size) { INSTANTIATE_TEST_SUITE_P( Works_Instances, KrtwPsuTest, testing::Values( - TestParams{{}, {}}, // - TestParams{{}, {yacl::crypto::Blake3_128("a")}}, // - TestParams{{yacl::crypto::Blake3_128("a")}, {}}, // + TestParams{{}, {}}, // + TestParams{{}, {Blake3_128("a")}}, // + TestParams{{Blake3_128("a")}, {}}, // // No overlap TestParams{CreateRangeItems(0, 1024), CreateRangeItems(1024, 1024)}, // // Partial overlap @@ -79,4 +79,4 @@ INSTANTIATE_TEST_SUITE_P( TestParams{CreateRangeItems(0, 1024), CreateRangeItems(0, 1024)} // )); -} // namespace yacl::crypto::psu +} // namespace yacl::crypto From 33965b5da5b25609c50e19411807edaadccf17a9 Mon Sep 17 00:00:00 2001 From: Shaun Zhang Date: Wed, 24 Jan 2024 00:03:06 +0800 Subject: [PATCH 4/5] [fix] minor modifications 1. always use `Blake` hash function 2. add submodules and security declarations 3. change to `SecureRandBits` for OTe --- yacl/crypto/primitives/psu/BUILD.bazel | 2 +- yacl/crypto/primitives/psu/krtw19_psu.cc | 31 +++++++----------------- yacl/crypto/primitives/psu/krtw19_psu.h | 14 ++++++++++- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/yacl/crypto/primitives/psu/BUILD.bazel b/yacl/crypto/primitives/psu/BUILD.bazel index 7406f9db..3c46ea68 100644 --- a/yacl/crypto/primitives/psu/BUILD.bazel +++ b/yacl/crypto/primitives/psu/BUILD.bazel @@ -34,7 +34,7 @@ yacl_cc_library( "//yacl/crypto/primitives/ot:kkrt_ote", "//yacl/crypto/utils:rand", "//yacl/link", - "//yacl/math:gadget", + "//yacl/math/f2k", "@com_google_absl//absl/types:span", ], ) diff --git a/yacl/crypto/primitives/psu/krtw19_psu.cc b/yacl/crypto/primitives/psu/krtw19_psu.cc index 57c4aac3..3f4961c2 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu.cc +++ b/yacl/crypto/primitives/psu/krtw19_psu.cc @@ -21,12 +21,6 @@ #include #include -#include "yacl/crypto/base/hash/hash_utils.h" -#include "yacl/crypto/primitives/ot/base_ot.h" -#include "yacl/crypto/primitives/ot/iknp_ote.h" -#include "yacl/crypto/primitives/ot/kkrt_ote.h" -#include "yacl/crypto/utils/rand.h" -#include "yacl/math/gadget.h" #include "yacl/utils/serialize.h" namespace yacl::crypto { @@ -48,16 +42,9 @@ static std::mt19937 gen(rd()); struct U128Hasher { size_t operator()(const uint128_t& x) const { - return yacl::math::UniversalHash( - 1, absl::MakeSpan(reinterpret_cast(&x), - sizeof x / sizeof(uint64_t))); - } - - static uint64_t CRHash(const uint128_t& x) { - auto hash = - Blake3(absl::MakeSpan(reinterpret_cast(&x), sizeof x)); - uint64_t ret; - std::memcpy(&ret, hash.data(), sizeof ret); + auto hash = Blake3_128({&x, sizeof x}); + size_t ret; + std::memcpy(&ret, &hash, 1); return ret; } }; @@ -67,7 +54,7 @@ auto HashInputs(const std::vector& elem_hashes, size_t count) { std::vector> hashing(num_bins); for (auto elem : elem_hashes) { auto hash = U128Hasher{}(elem); - hashing[hash % num_bins].push_back(elem); + hashing[hash % num_bins].emplace_back(elem); } return hashing; } @@ -148,14 +135,14 @@ void KrtwPsuSend(const std::shared_ptr& ctx, std::vector coeffs(BIN_SIZE); auto buf = ctx->Recv(ctx->PrevRank(), "Receive coefficients"); std::memcpy(coeffs.data(), buf.data(), buf.size()); - auto y = Evaluate(coeffs, U128Hasher::CRHash(elem)) ^ eval; + auto y = Evaluate(coeffs, U128Hasher{}(elem)) ^ eval; ctx->SendAsync(ctx->NextRank(), SerializeUint128(y), "Send evaluation"); } } - // Step 4. Send new elements through OT + // Step 4. Sends new elements through OT std::vector> keys(num_ot); - choice = RandBits(NUM_BASE_OT); + choice = SecureRandBits(NUM_BASE_OT); base_ot = BaseOtRecv(ctx, choice, NUM_BASE_OT); IknpOtExtSend(ctx, base_ot, absl::MakeSpan(keys)); std::vector ciphers(num_ot); @@ -205,7 +192,7 @@ std::vector KrtwPsuRecv( auto seed = FastRandU64(); std::vector xs(BIN_SIZE), ys(BIN_SIZE); for (size_t i{}; i != BIN_SIZE; ++i) { - xs[i] = (i < bin_size ? U128Hasher::CRHash(hashing[bin_idx][i]) + xs[i] = (i < bin_size ? U128Hasher{}(hashing[bin_idx][i]) : i > bin_size ? FastRandU64() : BOT); ys[i] = oprf->Eval(oprf_idx, xs[i]) ^ seed; @@ -219,7 +206,7 @@ std::vector KrtwPsuRecv( } } - // Step 4. Receive new elements through OT + // Step 4. Receives new elements through OT std::vector keys(num_ot); base_ot = BaseOtSend(ctx, NUM_BASE_OT); IknpOtExtRecv(ctx, base_ot, ot_choice, absl::MakeSpan(keys)); diff --git a/yacl/crypto/primitives/psu/krtw19_psu.h b/yacl/crypto/primitives/psu/krtw19_psu.h index 8e738604..c06535b1 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu.h +++ b/yacl/crypto/primitives/psu/krtw19_psu.h @@ -18,12 +18,24 @@ #include #include "yacl/base/int128.h" +#include "yacl/crypto/utils/secparam.h" #include "yacl/link/link.h" +#include "yacl/math/f2k/f2k.h" + +/* submodules */ +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/primitives/ot/base_ot.h" +#include "yacl/crypto/primitives/ot/iknp_ote.h" +#include "yacl/crypto/primitives/ot/kkrt_ote.h" +#include "yacl/crypto/utils/rand.h" + +/* security parameter declaration */ +YACL_MODULE_DECLARE("krtw_psu", SecParam::C::k128, SecParam::S::k40); namespace yacl::crypto { // Scalable Private Set Union from Symmetric-Key Techniques -// https://eprint.iacr.org/2019/776.pdf +// https://eprint.iacr.org/2019/776.pdf (Figure 10) void KrtwPsuSend(const std::shared_ptr&, const std::vector&); From 513a8fd23f48c981c57bdf439cd720509bca5200 Mon Sep 17 00:00:00 2001 From: Shaun Zhang Date: Thu, 25 Jan 2024 19:29:56 +0800 Subject: [PATCH 5/5] [fix] several improvements 1. fix `memcpy` bug 2. replace hasher by lambda 3. use hash sort as shuffle 4. rename constants --- yacl/crypto/primitives/psu/krtw19_psu.cc | 96 +++++++++++------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/yacl/crypto/primitives/psu/krtw19_psu.cc b/yacl/crypto/primitives/psu/krtw19_psu.cc index 3f4961c2..7a3f69ec 100644 --- a/yacl/crypto/primitives/psu/krtw19_psu.cc +++ b/yacl/crypto/primitives/psu/krtw19_psu.cc @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -28,32 +27,27 @@ namespace yacl::crypto { namespace { // reference: https://eprint.iacr.org/2019/1234.pdf (Figure 2) -constexpr float ZETA{0.06f}; -constexpr size_t BIN_SIZE{64ul}; // m+1 -constexpr uint128_t BOT{}; -constexpr size_t NUM_BINS_PER_BATCH{16ul}; -constexpr size_t BATCH_SIZE{NUM_BINS_PER_BATCH * BIN_SIZE}; - -constexpr size_t NUM_BASE_OT{128ul}; -constexpr size_t NUM_INKP_OT{512ul}; - -static std::random_device rd; -static std::mt19937 gen(rd()); - -struct U128Hasher { - size_t operator()(const uint128_t& x) const { - auto hash = Blake3_128({&x, sizeof x}); - size_t ret; - std::memcpy(&ret, &hash, 1); - return ret; - } +constexpr float kZeta{0.06f}; +constexpr size_t kBinSize{64ul}; // m+1 +constexpr uint128_t kBot{}; +constexpr size_t kNumBinsPerBatch{16ul}; +constexpr size_t kBatchSize{kNumBinsPerBatch * kBinSize}; + +constexpr size_t kNumBaseOT{128ul}; +constexpr size_t kNumInkpOT{512ul}; + +static auto HashToSizeT = [](const uint128_t& x) { + auto hash = Blake3_128({&x, sizeof x}); + size_t ret; + std::memcpy(&ret, &hash, sizeof ret); + return ret; }; auto HashInputs(const std::vector& elem_hashes, size_t count) { - size_t num_bins = std::ceil(count * ZETA); + size_t num_bins = std::ceil(count * kZeta); std::vector> hashing(num_bins); for (auto elem : elem_hashes) { - auto hash = U128Hasher{}(elem); + auto hash = HashToSizeT(elem); hashing[hash % num_bins].emplace_back(elem); } return hashing; @@ -109,41 +103,41 @@ void KrtwPsuSend(const std::shared_ptr& ctx, // Step 2. Prepares OPRF KkrtOtExtReceiver receiver; - size_t num_ot{hashing.size() * BIN_SIZE}; - auto choice = RandBits(NUM_BASE_OT); - auto base_ot = BaseOtRecv(ctx, choice, NUM_BASE_OT); - auto store = IknpOtExtSend(ctx, base_ot, NUM_INKP_OT); + size_t num_ot{hashing.size() * kBinSize}; + auto choice = RandBits(kNumBaseOT); + auto base_ot = BaseOtRecv(ctx, choice, kNumBaseOT); + auto store = IknpOtExtSend(ctx, base_ot, kNumInkpOT); receiver.Init(ctx, store, num_ot); - receiver.SetBatchSize(BATCH_SIZE); + receiver.SetBatchSize(kBatchSize); std::vector elems; elems.reserve(num_ot); size_t oprf_idx{}; for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { - if (bin_idx % NUM_BINS_PER_BATCH == 0) { + if (bin_idx % kNumBinsPerBatch == 0) { receiver.SendCorrection( - ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); + ctx, std::min(kBatchSize, (hashing.size() - bin_idx) * kBinSize)); } - hashing[bin_idx].resize(BIN_SIZE); - std::shuffle(hashing[bin_idx].begin(), hashing[bin_idx].end(), gen); + hashing[bin_idx].resize(kBinSize); + std::sort(hashing[bin_idx].begin(), hashing[bin_idx].end()); // Step 3. For each bin element, invokes PSU(1, m+1) for (auto elem : hashing[bin_idx]) { elems.emplace_back(elem); uint64_t eval; receiver.Encode(oprf_idx++, elem, {reinterpret_cast(&eval), sizeof eval}); - std::vector coeffs(BIN_SIZE); + std::vector coeffs(kBinSize); auto buf = ctx->Recv(ctx->PrevRank(), "Receive coefficients"); std::memcpy(coeffs.data(), buf.data(), buf.size()); - auto y = Evaluate(coeffs, U128Hasher{}(elem)) ^ eval; + auto y = Evaluate(coeffs, HashToSizeT(elem)) ^ eval; ctx->SendAsync(ctx->NextRank(), SerializeUint128(y), "Send evaluation"); } } // Step 4. Sends new elements through OT std::vector> keys(num_ot); - choice = SecureRandBits(NUM_BASE_OT); - base_ot = BaseOtRecv(ctx, choice, NUM_BASE_OT); + choice = SecureRandBits(kNumBaseOT); + base_ot = BaseOtRecv(ctx, choice, kNumBaseOT); IknpOtExtSend(ctx, base_ot, absl::MakeSpan(keys)); std::vector ciphers(num_ot); for (size_t i{}; i != num_ot; ++i) { @@ -171,30 +165,30 @@ std::vector KrtwPsuRecv( // Step 2. Prepares OPRF KkrtOtExtSender sender; - size_t num_ot{hashing.size() * BIN_SIZE}; - auto base_ot = BaseOtSend(ctx, NUM_BASE_OT); - auto choice = RandBits(NUM_INKP_OT); - auto store = IknpOtExtRecv(ctx, base_ot, choice, NUM_INKP_OT); + size_t num_ot{hashing.size() * kBinSize}; + auto base_ot = BaseOtSend(ctx, kNumBaseOT); + auto choice = RandBits(kNumInkpOT); + auto store = IknpOtExtRecv(ctx, base_ot, choice, kNumInkpOT); sender.Init(ctx, store, num_ot); - sender.SetBatchSize(BATCH_SIZE); + sender.SetBatchSize(kBatchSize); auto oprf = sender.GetOprf(); yacl::dynamic_bitset<> ot_choice(num_ot); size_t oprf_idx{}; // Step 3. For each bin, invokes PSU(1, m+1) for (size_t bin_idx{}; bin_idx != hashing.size(); ++bin_idx) { - if (bin_idx % NUM_BINS_PER_BATCH == 0) { + if (bin_idx % kNumBinsPerBatch == 0) { sender.RecvCorrection( - ctx, std::min(BATCH_SIZE, (hashing.size() - bin_idx) * BIN_SIZE)); + ctx, std::min(kBatchSize, (hashing.size() - bin_idx) * kBinSize)); } auto bin_size = hashing[bin_idx].size(); - for (size_t elem_idx{}; elem_idx != BIN_SIZE; ++elem_idx, ++oprf_idx) { + for (size_t elem_idx{}; elem_idx != kBinSize; ++elem_idx, ++oprf_idx) { auto seed = FastRandU64(); - std::vector xs(BIN_SIZE), ys(BIN_SIZE); - for (size_t i{}; i != BIN_SIZE; ++i) { - xs[i] = (i < bin_size ? U128Hasher{}(hashing[bin_idx][i]) + std::vector xs(kBinSize), ys(kBinSize); + for (size_t i{}; i != kBinSize; ++i) { + xs[i] = (i < bin_size ? HashToSizeT(hashing[bin_idx][i]) : i > bin_size ? FastRandU64() - : BOT); + : kBot); ys[i] = oprf->Eval(oprf_idx, xs[i]) ^ seed; } auto coeffs = Interpolate(xs, ys); @@ -208,16 +202,16 @@ std::vector KrtwPsuRecv( // Step 4. Receives new elements through OT std::vector keys(num_ot); - base_ot = BaseOtSend(ctx, NUM_BASE_OT); + base_ot = BaseOtSend(ctx, kNumBaseOT); IknpOtExtRecv(ctx, base_ot, ot_choice, absl::MakeSpan(keys)); std::vector ciphers(num_ot); auto buf = ctx->Recv(ctx->PrevRank(), "Receive ciphertexts"); std::memcpy(ciphers.data(), buf.data(), buf.size()); - std::unordered_set set_union(elem_hashes.begin(), - elem_hashes.end()); + std::unordered_set set_union( + elem_hashes.begin(), elem_hashes.end(), count, HashToSizeT); for (size_t i{}; i != num_ot; ++i) { if (!ot_choice[i]) { - if (auto new_elem = ciphers[i] ^ keys[i]; new_elem != BOT) { + if (auto new_elem = ciphers[i] ^ keys[i]; new_elem != kBot) { set_union.emplace(ciphers[i] ^ keys[i]); } }