From 56fa049e4e90b298c984db6b9ce5fc380f03e551 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Sat, 27 Aug 2022 12:23:21 +0200 Subject: [PATCH] add signed integers and tests --- src/motioncore/CMakeLists.txt | 1 + .../algorithm/boolean_algorithms.cpp | 6 + src/motioncore/base/party.h | 37 +++-- .../primitives/random/aes128_ctr_rng.cpp | 2 +- src/motioncore/protocols/share_wrapper.cpp | 21 +++ src/motioncore/protocols/share_wrapper.h | 3 - .../secure_type/secure_signed_integer.cpp | 104 ++++++++++++ .../secure_type/secure_signed_integer.h | 143 ++++++++++++++++ .../secure_type/secure_unsigned_integer.cpp | 3 + src/motioncore/utility/bit_vector.cpp | 56 +++---- src/motioncore/utility/bit_vector.h | 96 +++++------ src/motioncore/utility/helpers.h | 40 +++++ src/test/test_agmw.cpp | 152 +++++++++++++++--- src/test/test_bgmw.cpp | 123 ++++++++++++++ src/test/test_integer_operations.cpp | 7 +- src/test/test_misc.cpp | 30 ++++ 16 files changed, 705 insertions(+), 119 deletions(-) create mode 100644 src/motioncore/secure_type/secure_signed_integer.cpp create mode 100644 src/motioncore/secure_type/secure_signed_integer.h diff --git a/src/motioncore/CMakeLists.txt b/src/motioncore/CMakeLists.txt index 268916964..a7609f4ce 100644 --- a/src/motioncore/CMakeLists.txt +++ b/src/motioncore/CMakeLists.txt @@ -60,6 +60,7 @@ add_library(motion protocols/share.cpp protocols/share_wrapper.cpp protocols/wire.cpp + secure_type/secure_signed_integer.cpp secure_type/secure_unsigned_integer.cpp statistics/analysis.cpp statistics/run_time_statistics.cpp diff --git a/src/motioncore/algorithm/boolean_algorithms.cpp b/src/motioncore/algorithm/boolean_algorithms.cpp index 0285bc2e5..5ee269242 100644 --- a/src/motioncore/algorithm/boolean_algorithms.cpp +++ b/src/motioncore/algorithm/boolean_algorithms.cpp @@ -24,6 +24,12 @@ #include "boolean_algorithms.h" +#include +#include +#include + +#include "protocols/share.h" + namespace encrypto::motion::algorithm { std::pair FullAdder(const ShareWrapper& a, const ShareWrapper& b, diff --git a/src/motioncore/base/party.h b/src/motioncore/base/party.h index 540b9a17d..36f48d071 100644 --- a/src/motioncore/base/party.h +++ b/src/motioncore/base/party.h @@ -196,16 +196,25 @@ class Party { } } - template >> + template >> SharePointer In(const std::vector& input, std::size_t party_id = std::numeric_limits::max()) { switch (P) { case MpcProtocol::kArithmeticConstant: { - return backend_->ConstantArithmeticGmwInput(input); + if constexpr (std::is_unsigned_v) { + return backend_->ConstantArithmeticGmwInput(input); + } else { + return backend_->ConstantArithmeticGmwInput>( + ToTwosComplement(input)); + } } case MpcProtocol::kArithmeticGmw: { - return backend_->ArithmeticGmwInput(party_id, input); + if constexpr (std::is_unsigned_v) { + return backend_->ArithmeticGmwInput(party_id, input); + } else { + return backend_->ArithmeticGmwInput>(party_id, + ToTwosComplement(input)); + } } case MpcProtocol::kAstra: { return backend_->AstraInput(party_id, input); @@ -227,16 +236,25 @@ class Party { } } - template >> + template >> SharePointer In(std::vector&& input, std::size_t party_id = std::numeric_limits::max()) { switch (P) { case MpcProtocol::kArithmeticConstant: { - return backend_->ConstantArithmeticGmwInput(std::move(input)); + if constexpr (std::is_unsigned_v) { + return backend_->ConstantArithmeticGmwInput(input); + } else { + return backend_->ConstantArithmeticGmwInput>( + ToTwosComplement(input)); + } } case MpcProtocol::kArithmeticGmw: { - return backend_->ArithmeticGmwInput(party_id, std::move(input)); + if constexpr (std::is_unsigned_v) { + return backend_->ArithmeticGmwInput(party_id, input); + } else { + return backend_->ArithmeticGmwInput>(party_id, + ToTwosComplement(input)); + } } case MpcProtocol::kAstra: { return backend_->AstraInput(party_id, std::move(input)); @@ -258,8 +276,7 @@ class Party { } } - template >> + template >> SharePointer In(T input, std::size_t party_id = std::numeric_limits::max()) { if constexpr (std::is_same_v) { if constexpr (P == MpcProtocol::kBooleanGmw) diff --git a/src/motioncore/primitives/random/aes128_ctr_rng.cpp b/src/motioncore/primitives/random/aes128_ctr_rng.cpp index 18db4b5e9..a67b1198f 100644 --- a/src/motioncore/primitives/random/aes128_ctr_rng.cpp +++ b/src/motioncore/primitives/random/aes128_ctr_rng.cpp @@ -23,8 +23,8 @@ #include "aes128_ctr_rng.h" #include -#include #include +#include #include "primitives/aes/aesni_primitives.h" diff --git a/src/motioncore/protocols/share_wrapper.cpp b/src/motioncore/protocols/share_wrapper.cpp index 3785a8dba..fe39e3242 100644 --- a/src/motioncore/protocols/share_wrapper.cpp +++ b/src/motioncore/protocols/share_wrapper.cpp @@ -24,6 +24,7 @@ #include "share_wrapper.h" +#include #include #include @@ -52,6 +53,8 @@ #include "protocols/data_management/unsimdify_gate.h" #include "protocols/garbled_circuit/garbled_circuit_provider.h" #include "secure_type/secure_unsigned_integer.h" +#include "share.h" +#include "utility/bit_vector.h" namespace encrypto::motion { @@ -240,10 +243,12 @@ ShareWrapper ShareWrapper::operator*(const ShareWrapper& other) const { assert(*other); assert(share_); assert(share_->GetNumberOfSimdValues() == other->GetNumberOfSimdValues()); + bool lhs_is_arith = share_->GetCircuitType() == CircuitType::kArithmetic; bool rhs_is_arith = other->GetCircuitType() == CircuitType::kArithmetic; bool lhs_is_bool = share_->GetCircuitType() == CircuitType::kBoolean; bool rhs_is_bool = other->GetCircuitType() == CircuitType::kBoolean; + if (!lhs_is_arith || !rhs_is_arith) { if (lhs_is_bool && rhs_is_arith) { if (other->GetBitLength() == 8u) { @@ -815,6 +820,11 @@ T ShareWrapper::As() const { } else { throw std::invalid_argument("Unsupported arithmetic protocol in ShareWrapper::As()"); } + } else if constexpr (std::is_signed()) { + std::make_unsigned_t unsigned_value{As>()}; + bool msb{(unsigned_value >> sizeof(T) * 8 - 1) == 1}; + T signed_value{msb ? -static_cast(-unsigned_value) : static_cast(unsigned_value)}; + return signed_value; } else if constexpr (is_specialization::value && std::is_unsigned()) { // std::vector of unsigned integers @@ -847,6 +857,17 @@ T ShareWrapper::As() const { } else { throw std::invalid_argument("Unsupported arithmetic protocol in ShareWrapper::As()"); } + } else if constexpr (is_specialization::value && + std::is_signed()) { + auto unsigned_values{As>>()}; + T signed_values; + signed_values.reserve(unsigned_values.size()); + for (auto& v : unsigned_values) { + bool msb{(v >> sizeof(T) * 8 - 1) == 1}; + T signed_value{msb ? -static_cast(-v) : static_cast(v)}; + signed_values.emplace_back(signed_value); + } + return unsigned_values; } else { throw std::invalid_argument( fmt::format("Unsupported output type in ShareWrapper::As<{}>()", typeid(T).name())); diff --git a/src/motioncore/protocols/share_wrapper.h b/src/motioncore/protocols/share_wrapper.h index 090b3315f..560d1d68b 100644 --- a/src/motioncore/protocols/share_wrapper.h +++ b/src/motioncore/protocols/share_wrapper.h @@ -24,14 +24,11 @@ #pragma once -#include #include #include #include #include -#include "share.h" -#include "utility/bit_vector.h" #include "utility/typedefs.h" namespace encrypto::motion { diff --git a/src/motioncore/secure_type/secure_signed_integer.cpp b/src/motioncore/secure_type/secure_signed_integer.cpp new file mode 100644 index 000000000..68dbfc13a --- /dev/null +++ b/src/motioncore/secure_type/secure_signed_integer.cpp @@ -0,0 +1,104 @@ +// MIT License +// +// Copyright (c) 2022 Oleksandr Tkachenko +// Cryptography and Privacy Engineering Group (ENCRYPTO) +// TU Darmstadt, Germany +// +// 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. + +#include "secure_signed_integer.h" + +#include "protocols/share.h" +#include "utility/bit_vector.h" + +namespace encrypto::motion { + +SecureSignedInteger SecureSignedInteger::Simdify(std::span input) { + std::vector input_as_shares; + input_as_shares.reserve(input.size()); + std::transform(input.begin(), input.end(), std::back_inserter(input_as_shares), + [&](SecureSignedInteger& i) -> SharePointer { return i.Get().Get(); }); + return SecureSignedInteger(ShareWrapper::Simdify(input_as_shares)); +} + +SecureSignedInteger SecureSignedInteger::Simdify(std::vector&& input) { + return Simdify(input); +} + +SecureSignedInteger SecureSignedInteger::Subset(std::span positions) { + ShareWrapper unwrap{this->Get()}; + return SecureUnsignedInteger(unwrap.Subset(positions)); +} + +SecureSignedInteger SecureSignedInteger::Subset(std::vector&& positions) { + return Subset(std::span(positions)); +} + +std::vector SecureSignedInteger::Unsimdify() const { + auto unsigned_ints{share_.Unsimdify()}; + std::vector result(unsigned_ints.begin(), unsigned_ints.end()); + return result; +} + +SecureSignedInteger SecureSignedInteger::Out(std::size_t output_owner) const { + return SecureSignedInteger(share_.Out(output_owner)); +} + +template +T SecureSignedInteger::As() const { + if (share_.Get()->GetProtocol() == MpcProtocol::kArithmeticGmw) { + if constexpr (std::is_signed_v) { + using U = typename std::make_unsigned::type; + return FromTwosComplement(share_.Get().As()); + } else { + using value_type = typename T::value_type; + using unsigned_value_type = typename std::make_unsigned::type; + using U = typename std::vector; + return FromTwosComplement(share_.Get().As()); + } + } else if (share_.Get()->GetProtocol() == MpcProtocol::kBooleanGmw || + share_.Get()->GetProtocol() == MpcProtocol::kBmr) { + auto share_out = share_.Get().As>>(); + if constexpr (std::is_signed()) { + using U = typename std::make_unsigned::type; + U unsigned_output{encrypto::motion::ToOutput(share_out)}; + return FromTwosComplement(unsigned_output); + } else { + using value_type = typename T::value_type; + using unsigned_value_type = typename std::make_unsigned::type; + using U = typename std::vector; + U unsigned_output{encrypto::motion::ToVectorOutput(share_out)}; + return FromTwosComplement(unsigned_output); + } + } else { + throw std::invalid_argument("Unsupported protocol for SecureSignedInteger::As()"); + } +} + +template std::int8_t SecureSignedInteger::As() const; +template std::int16_t SecureSignedInteger::As() const; +template std::int32_t SecureSignedInteger::As() const; +template std::int64_t SecureSignedInteger::As() const; + +template std::vector SecureSignedInteger::As() const; +template std::vector SecureSignedInteger::As() const; +template std::vector SecureSignedInteger::As() const; +template std::vector SecureSignedInteger::As() const; + +} // namespace encrypto::motion \ No newline at end of file diff --git a/src/motioncore/secure_type/secure_signed_integer.h b/src/motioncore/secure_type/secure_signed_integer.h new file mode 100644 index 000000000..d0b895525 --- /dev/null +++ b/src/motioncore/secure_type/secure_signed_integer.h @@ -0,0 +1,143 @@ +// MIT License +// +// Copyright (c) 2022 Oleksandr Tkachenko +// Cryptography and Privacy Engineering Group (ENCRYPTO) +// TU Darmstadt, Germany +// +// 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. + +#pragma once + +#include "secure_unsigned_integer.h" + +namespace encrypto::motion { + +/// \b implements an interface for signed arithmetic on standard C++ data types and 128-bit integers +/// using the two's complement representation. +class SecureSignedInteger { + public: + SecureSignedInteger() = default; + SecureSignedInteger(SecureUnsignedInteger share) : share_(share) {} + SecureSignedInteger(ShareWrapper share) : share_(share) {} + SecureSignedInteger(SharePointer share) : share_(share) {} + + virtual ~SecureSignedInteger() = default; + + SecureSignedInteger& operator=(const SecureSignedInteger& other) { + share_ = other.share_; + return *this; + } + + SecureSignedInteger& operator=(SecureSignedInteger&& other) { + share_ = std::move(other.share_); + return *this; + } + + ShareWrapper& Get() { return share_.Get(); } + + const ShareWrapper& Get() const { return share_.Get(); } + + ShareWrapper& operator->() { return share_.Get(); } + + const ShareWrapper& operator->() const { return share_.Get(); } + + SecureSignedInteger operator+(const SecureSignedInteger& other) const { + return this->share_ + other.share_; + } + + SecureSignedInteger& operator+=(const SecureSignedInteger& other) { + *this = *this + other; + return *this; + } + + SecureSignedInteger operator-(const SecureSignedInteger& other) const { + return this->share_ - other.share_; + } + + SecureSignedInteger& operator-=(const SecureSignedInteger& other) { + *this = *this - other; + return *this; + } + + SecureSignedInteger operator*(const SecureSignedInteger& other) const { + return this->share_ * other.share_; + } + + SecureSignedInteger& operator*=(const SecureSignedInteger& other) { + *this = *this * other; + return *this; + } + + SecureSignedInteger operator/(const SecureSignedInteger& other) const { + // TODO implement + throw std::runtime_error("Not implemented yet"); + return this->share_ + other.share_; + } + + SecureSignedInteger& operator/=(const SecureSignedInteger& other) { + *this = *this / other; + return *this; + } + + ShareWrapper operator>(const SecureSignedInteger& other) const { + // TODO implement + throw std::runtime_error("Not implemented yet"); + return this->share_ > other.share_; + } + + ShareWrapper operator==(const SecureSignedInteger& other) const { + return this->share_ == other.share_; + } + + /// \brief internally extracts the ShareWrapper/SharePointer from input and + /// calls ShareWrapper::Simdify(std::span input) + static SecureSignedInteger Simdify(std::span input); + // + /// \brief internally extracts shares from each entry in input and calls + /// Simdify(std::span input) from the result + static SecureSignedInteger Simdify(std::vector&& input); + + /// \brief constructs a SubsetGate that returns values stored at positions in this->share_. + /// Internally calls ShareWrapper Subset(std::span positions). + SecureSignedInteger Subset(std::span positions); + + /// \brief constructs a SubsetGate that returns values stored at positions in this->share_. + /// Internally calls SecureUnsignedInteger Subset(std::span positions). + SecureSignedInteger Subset(std::vector&& positions); + + /// \brief decomposes this->share_->Get() into shares with exactly 1 SIMD value. + /// See the description in ShareWrapper::Unsimdify for reference. + std::vector Unsimdify() const; + + /// \brief constructs an output gate, which reconstructs the cleartext result. The default + /// parameter for the output owner corresponds to all parties being the output owners. + /// Uses ShareWrapper::Out. + SecureSignedInteger Out( + std::size_t output_owner = std::numeric_limits::max()) const; + + /// \brief converts the information on the wires to T in type Signed Integer. + /// See the description in ShareWrapper::As for reference. + template + T As() const; + + private: + SecureUnsignedInteger share_; +}; + +} // namespace encrypto::motion \ No newline at end of file diff --git a/src/motioncore/secure_type/secure_unsigned_integer.cpp b/src/motioncore/secure_type/secure_unsigned_integer.cpp index 597b65e72..b328bb499 100644 --- a/src/motioncore/secure_type/secure_unsigned_integer.cpp +++ b/src/motioncore/secure_type/secure_unsigned_integer.cpp @@ -321,6 +321,9 @@ T SecureUnsignedInteger::As() const { auto share_out = share_->As>>(); if constexpr (std::is_unsigned()) { return encrypto::motion::ToOutput(share_out); + } else if constexpr (is_specialization::value && + std::is_unsigned()) { + return encrypto::motion::ToVectorOutput(share_out); } else { throw std::invalid_argument( fmt::format("Unsupported output type in SecureUnsignedInteger::As<{}>() for {} Protocol", diff --git a/src/motioncore/utility/bit_vector.cpp b/src/motioncore/utility/bit_vector.cpp index d35354440..404962de7 100644 --- a/src/motioncore/utility/bit_vector.cpp +++ b/src/motioncore/utility/bit_vector.cpp @@ -1085,20 +1085,15 @@ std::vector> ToInput std::vector> ToInput(IntegralType integral_value) { constexpr auto kBitLength{sizeof(IntegralType) * 8}; - - static_assert(std::is_integral::value); - static_assert(sizeof(IntegralType) <= 8); - if constexpr (sizeof(IntegralType) == 1) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 2) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 4) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 8) { - static_assert(std::is_same_v); - } std::vector> result; - for (auto i = 0ull; i < kBitLength; ++i) result.emplace_back(1, ((integral_value >> i) & 1) == 1); + if constexpr (std::is_unsigned_v) { + for (auto i = 0ull; i < kBitLength; ++i) + result.emplace_back(1, ((integral_value >> i) & 1) == 1); + } else { + auto unsigned_value{ToTwosComplement(integral_value)}; + for (auto i = 0ull; i < kBitLength; ++i) + result.emplace_back(1, ((unsigned_value >> i) & 1) == 1); + } return result; } @@ -1106,26 +1101,27 @@ template std::vector> ToInput(std::uint8_t); template std::vector> ToInput(std::uint16_t); template std::vector> ToInput(std::uint32_t); template std::vector> ToInput(std::uint64_t); +template std::vector> ToInput(std::int8_t); +template std::vector> ToInput(std::int16_t); +template std::vector> ToInput(std::int32_t); +template std::vector> ToInput(std::int64_t); template std::vector> ToInput(const std::vector& input_vector) { - static_assert(std::is_integral::value); - static_assert(sizeof(IntegralType) <= 8); - if constexpr (sizeof(IntegralType) == 1) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 2) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 4) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(IntegralType) == 8) { - static_assert(std::is_same_v); - } - constexpr auto kBitLength{sizeof(IntegralType) * 8}; std::vector> result(kBitLength); - for (auto i = 0ull; i < input_vector.size(); ++i) { - for (auto j = 0ull; j < kBitLength; ++j) { - result.at(j).Append(((input_vector.at(i) >> j) & 1) == 1); + if constexpr (std::is_unsigned_v) { + for (auto i = 0ull; i < input_vector.size(); ++i) { + for (auto j = 0ull; j < kBitLength; ++j) { + result[j].Append(((input_vector[i] >> j) & 1) == 1); + } + } + } else { + for (auto i = 0ull; i < input_vector.size(); ++i) { + auto unsigned_value{ToTwosComplement(input_vector[i])}; + for (auto j = 0ull; j < kBitLength; ++j) { + result[j].Append(((unsigned_value >> j) & 1) == 1); + } } } return result; @@ -1135,6 +1131,10 @@ template std::vector> ToInput(const std::vector> ToInput(const std::vector&); template std::vector> ToInput(const std::vector&); template std::vector> ToInput(const std::vector&); +template std::vector> ToInput(const std::vector&); +template std::vector> ToInput(const std::vector&); +template std::vector> ToInput(const std::vector&); +template std::vector> ToInput(const std::vector&); BitSpan::BitSpan(std::byte* buffer, std::size_t bit_size, bool aligned) : pointer_(buffer), bit_size_(bit_size), aligned_(aligned) {} diff --git a/src/motioncore/utility/bit_vector.h b/src/motioncore/utility/bit_vector.h index 8302ba146..efa4f58c2 100644 --- a/src/motioncore/utility/bit_vector.h +++ b/src/motioncore/utility/bit_vector.h @@ -452,7 +452,7 @@ std::ostream& operator<<(std::ostream& os, const BitVector& bit_vecto /// \param value /// \relates BitVector template || std::is_unsigned_v>, + typename = std::enable_if_t || std::is_integral_v>, typename Allocator = std::allocator> std::vector> ToInput(T value); @@ -471,7 +471,7 @@ std::vector> ToInput(T value); /// \tparam T /// \param vector template || std::is_unsigned_v>, + typename = std::enable_if_t || std::is_integral_v>, typename Allocator = std::allocator> std::vector> ToInput(const std::vector& vector); @@ -490,24 +490,11 @@ std::vector> ToInput(const std::vector& vector); /// - Each BitVector in \p bit_vectors has size equal 1. /// \tparam UnsignedIntegralType /// \param bit_vectors -template >, +template >, typename Allocator = std::allocator> -UnsignedIntegralType ToOutput(std::vector> bit_vectors) { - static_assert(std::is_integral::value); - static_assert(sizeof(UnsignedIntegralType) <= 8); - if constexpr (sizeof(UnsignedIntegralType) == 1) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 2) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 4) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 8) { - static_assert(std::is_same_v); - } - +IntegralType ToOutput(std::vector> bit_vectors) { // kBitLength is always equal to bit_vectors - constexpr auto kBitLength{sizeof(UnsignedIntegralType) * 8}; + constexpr auto kBitLength{sizeof(IntegralType) * 8}; assert(!bit_vectors.empty()); if (kBitLength != bit_vectors.size()) { @@ -520,11 +507,19 @@ UnsignedIntegralType ToOutput(std::vector> bit_vectors) { for ([[maybe_unused]] auto i = 0ull; i < bit_vectors.size(); ++i) assert(bit_vectors.at(i).GetSize() == bit_vectors.at(0).GetSize()); - UnsignedIntegralType output_value{0}; - // Converting values in a BitVector to UnsignedIntegralType - for (auto i = 0ull; i < kBitLength; ++i) { - assert(bit_vectors.at(i).GetSize() == 1); - output_value += static_cast(bit_vectors.at(i)[0]) << i; + IntegralType output_value{0}; + if constexpr (std::is_unsigned_v) { + // Converting values in a BitVector to UnsignedIntegralType + for (auto i = 0ull; i < kBitLength; ++i) { + assert(bit_vectors.at(i).GetSize() == 1); + output_value += static_cast(bit_vectors[i][0]) << i; + } + } else { + std::make_unsigned_t unsigned_value{0}; + for (auto j = 0ull; j < kBitLength; ++j) { + unsigned_value += static_cast>(bit_vectors[j][0]) << j; + } + output_value = FromTwosComplement(unsigned_value); } return output_value; @@ -543,23 +538,10 @@ UnsignedIntegralType ToOutput(std::vector> bit_vectors) { /// \tparam UnsignedIntegralType /// \param bit_vectors /// \relates BitVector -template >, +template >, typename Allocator = std::allocator> -std::vector ToVectorOutput(std::vector> bit_vectors) { - static_assert(std::is_integral::value); - static_assert(sizeof(UnsignedIntegralType) <= 8); - if constexpr (sizeof(UnsignedIntegralType) == 1) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 2) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 4) { - static_assert(std::is_same_v); - } else if constexpr (sizeof(UnsignedIntegralType) == 8) { - static_assert(std::is_same_v); - } - - constexpr auto kBitLength{sizeof(UnsignedIntegralType) * 8}; +std::vector ToVectorOutput(std::vector> bit_vectors) { + constexpr auto kBitLength{sizeof(IntegralType) * 8}; assert(!bit_vectors.empty()); if (kBitLength != bit_vectors.size()) { @@ -578,13 +560,22 @@ std::vector ToVectorOutput(std::vector output_vector; + std::vector output_vector; + output_vector.reserve(number_of_simd); for (auto i = 0ull; i < number_of_simd; ++i) { - UnsignedIntegralType value{0}; - for (auto j = 0ull; j < kBitLength; ++j) { - value += static_cast(bit_vectors.at(j)[i]) << j; + if constexpr (std::is_unsigned_v) { + IntegralType value{0}; + for (auto j = 0ull; j < kBitLength; ++j) { + value += static_cast(bit_vectors[j][i]) << j; + } + output_vector.emplace_back(value); + } else { + std::make_unsigned_t unsigned_value{0}; + for (auto j = 0ull; j < kBitLength; ++j) { + unsigned_value += static_cast>(bit_vectors[j][i]) << j; + } + output_vector.emplace_back(FromTwosComplement(unsigned_value)); } - output_vector.emplace_back(value); } return output_vector; } @@ -769,9 +760,8 @@ class BitSpan { /// \brief Returns true if Allocator is aligned allocator. bool IsAligned() const noexcept { return aligned_; } - /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, dest_to) - /// in this. - /// \throws std::out_of_range if accessing invalid positions in this or other. + /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, + /// dest_to) in this. \throws std::out_of_range if accessing invalid positions in this or other. template void Copy(const std::size_t dest_from, const std::size_t dest_to, BitVectorType& other); @@ -780,14 +770,14 @@ class BitSpan { template void Copy(const std::size_t dest_from, BitVectorType& other); - /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, dest_to) - /// in this. - /// \throws an std::out_of_range exception if accessing invalid positions in this or other. + /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, + /// dest_to) in this. \throws an std::out_of_range exception if accessing invalid positions in + /// this or other. void Copy(const std::size_t dest_from, const std::size_t dest_to, BitSpan& other); - /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, dest_to) - /// in this. - /// \throws an std::out_of_range exception if accessing invalid positions in this or other. + /// \brief copies the first (dest_to - dest_from) bits from other to the bits [dest_from, + /// dest_to) in this. \throws an std::out_of_range exception if accessing invalid positions in + /// this or other. void Copy(const std::size_t dest_from, const std::size_t dest_to, BitSpan&& other); /// \brief copies other to this[dest_from...dest_from+GetSize()]. diff --git a/src/motioncore/utility/helpers.h b/src/motioncore/utility/helpers.h index 7ca08217b..dcd754c61 100644 --- a/src/motioncore/utility/helpers.h +++ b/src/motioncore/utility/helpers.h @@ -24,13 +24,23 @@ #pragma once +#include +#include #include +#include #include +#include #include +#include #include #include +#include #include +#include "condition.h" +#include "primitives/random/default_rng.h" +#include "typedefs.h" + namespace encrypto::motion { /// \brief Returns a vector of \p length random unsigned integral values. @@ -500,4 +510,34 @@ inline std::vector RowDotProduct(std::span> a, return result; } +template +auto ToTwosComplement(T input) { + using U = typename std::make_unsigned_t; + return std::bit_cast(input); +} + +template +auto ToTwosComplement(const std::vector& input) { + using U = typename std::make_unsigned_t; + std::vector twos_complement; + twos_complement.reserve(input.size()); + for (const auto& x : input) twos_complement.emplace_back(ToTwosComplement(x)); + return twos_complement; +} + +template +auto FromTwosComplement(T input) { + using S = typename std::make_signed_t; + return std::bit_cast(input); +} + +template +auto FromTwosComplement(const std::vector& input) { + using S = typename std::make_signed_t; + std::vector result; + result.reserve(input.size()); + for (const auto& x : input) result.emplace_back(FromTwosComplement(x)); + return result; +} + } // namespace encrypto::motion diff --git a/src/test/test_agmw.cpp b/src/test/test_agmw.cpp index 3284cbc1a..7d5e57668 100644 --- a/src/test/test_agmw.cpp +++ b/src/test/test_agmw.cpp @@ -29,9 +29,12 @@ #include "protocols/arithmetic_gmw/arithmetic_gmw_gate.h" #include "protocols/arithmetic_gmw/arithmetic_gmw_wire.h" #include "protocols/share_wrapper.h" +#include "secure_type/secure_signed_integer.h" #include "test_constants.h" #include "test_helpers.h" + +namespace { using namespace encrypto::motion; auto random_value = std::mt19937{}; @@ -689,14 +692,7 @@ TYPED_TEST(ArithmeticGmwTest, GreaterThan_1_1000_Simd_2_parties) { } } -template -class TypedAgmwTest : public testing::Test { - public: - void SetUp() override { - GenerateParties(false); - GenerateRandomValues(); - } - +class PartyGenerator { protected: void GenerateParties(bool online_after_setup) { parties_ = std::move(MakeLocallyConnectedParties(2, kPortOffset)); @@ -706,35 +702,81 @@ class TypedAgmwTest : public testing::Test { } } - void GenerateRandomValues() { - std::mt19937_64 random(0); - std::uniform_int_distribution value_dist; + std::vector parties_; +}; + +class SeededRandomnessGenerator { + public: + SeededRandomnessGenerator() = default; + SeededRandomnessGenerator(std::size_t seed) { random_.seed(seed); } + + protected: + BitVector<> RandomBits(std::size_t size) { std::bernoulli_distribution bool_dist; + BitVector<> result; + result.Reserve(size); + while (result.GetSize() < size) result.Append(bool_dist(random_)); + return result; + } + + bool RandomBit() { + std::bernoulli_distribution bool_dist; + return bool_dist(random_); + } - bit_ = bool_dist(random); - bits_1k_.Reserve(vector_size_); - while (bits_1k_.GetSize() < vector_size_) bits_1k_.Append(bool_dist(random)); + template + T RandomInteger() { + static_assert(std::is_integral_v, "T must be an integral type"); + std::uniform_int_distribution value_dist; + return value_dist(random_); + } + + template + std::vector RandomIntegers(std::size_t size) { + static_assert(std::is_integral_v, "T must be an integral type"); + std::uniform_int_distribution value_dist; + std::vector result; + result.reserve(size); + while (result.size() < size) result.emplace_back(value_dist(random_)); + return result; + } + + std::mt19937_64 random_{0}; +}; + +template +class TypedHybridAgmwTest : public testing::Test, + public PartyGenerator, + public SeededRandomnessGenerator { + public: + void SetUp() override { + GenerateParties(false); + GenerateRandomValues(); + } - T value_ = value_dist(random); - values_1k_.reserve(vector_size_); - while (values_1k_.size() < vector_size_) values_1k_.emplace_back(value_dist(random)); + protected: + void GenerateRandomValues() { + bit_ = RandomBit(); + bits_1k_ = RandomBits(vector_size_); + T value_ = RandomInteger(); + values_1k_ = RandomIntegers(vector_size_); } T value_; std::vector values_1k_; bool bit_; encrypto::motion::BitVector<> bits_1k_; - std::vector parties_; std::size_t vector_size_{1000}; + + std::mt19937_64 random_{0}; }; using IntegerTypes = ::testing::Types; -TYPED_TEST_SUITE(TypedAgmwTest, IntegerTypes); +TYPED_TEST_SUITE(TypedHybridAgmwTest, IntegerTypes); -TYPED_TEST(TypedAgmwTest, HybridMultiplication_1_1K_Simd_2_parties) { +TYPED_TEST(TypedHybridAgmwTest, HybridMultiplication_1_1K_Simd_2_parties) { constexpr auto kArithmeticGmw = encrypto::motion::MpcProtocol::kArithmeticGmw; - std::srand(std::time(nullptr)); std::vector> futures; for (auto party_id = 0u; party_id < this->parties_.size(); ++party_id) { @@ -789,4 +831,70 @@ TYPED_TEST(TypedAgmwTest, HybridMultiplication_1_1K_Simd_2_parties) { })); } for (auto& future : futures) future.get(); -} \ No newline at end of file +} + +template +class TypedSignedAgmwTest : public testing::Test, + public PartyGenerator, + public SeededRandomnessGenerator { + public: + void SetUp() override { + GenerateParties(false); + GenerateRandomValues(); + } + + protected: + void GenerateRandomValues() { + values_a_ = RandomIntegers(vector_size_); + values_b_ = RandomIntegers(vector_size_); + } + + std::vector values_a_, values_b_; + std::size_t vector_size_{1000}; +}; + +using SignedIntegerTypes = ::testing::Types; +TYPED_TEST_SUITE(TypedSignedAgmwTest, SignedIntegerTypes); + +TYPED_TEST(TypedSignedAgmwTest, SignedSubtraction_1K_Simd_2_parties) { + constexpr auto kArithmeticGmw = encrypto::motion::MpcProtocol::kArithmeticGmw; + std::vector> futures; + + for (auto party_id = 0u; party_id < this->parties_.size(); ++party_id) { + futures.push_back(std::async(std::launch::async, [this, party_id]() { + encrypto::motion::SecureSignedInteger share_values_a_, share_values_b_; + // If my input - real input, otherwise a dummy 0 (-vector). + // Should not make any difference, just for consistency... + std::vector selected_values_a_ = + party_id == 0 ? this->values_a_ : std::vector(this->values_a_.size(), 0); + std::vector selected_values_b_ = + party_id == 0 ? this->values_b_ : std::vector(this->values_b_.size(), 0); + + share_values_a_ = + this->parties_.at(party_id)->template In( + selected_values_a_, 0); + share_values_b_ = + this->parties_.at(party_id)->template In( + selected_values_b_, 0); + + auto share_sub = share_values_a_ - share_values_b_; + + auto share_output = share_sub.Out(); + + this->parties_.at(party_id)->Run(); + + auto circuit_result = share_output.As>(); + std::vector expected_result; + expected_result.reserve(circuit_result.size()); + for (std::size_t i = 0; i < this->values_a_.size(); ++i) { + expected_result.emplace_back(this->values_a_[i] - this->values_b_[i]); + } + EXPECT_EQ(circuit_result, expected_result); + + this->parties_.at(party_id)->Finish(); + })); + } + for (auto& future : futures) future.get(); +} + +} // namespace \ No newline at end of file diff --git a/src/test/test_bgmw.cpp b/src/test/test_bgmw.cpp index a5e8ff71a..2357fdec5 100644 --- a/src/test/test_bgmw.cpp +++ b/src/test/test_bgmw.cpp @@ -27,9 +27,12 @@ #include "protocols/boolean_gmw/boolean_gmw_gate.h" #include "protocols/boolean_gmw/boolean_gmw_wire.h" #include "protocols/share_wrapper.h" +#include "secure_type/secure_signed_integer.h" #include "test_constants.h" #include "test_helpers.h" +namespace { + using namespace encrypto::motion; TEST(BooleanGmw, InputOutput_1_1K_Simd_2_3_4_5_10_parties) { @@ -860,3 +863,123 @@ TEST(BooleanGmw, Or_64_bit_10_Simd_2_3_parties) { } } } + + + +class PartyGenerator { + protected: + void GenerateParties(bool online_after_setup) { + parties_ = std::move(MakeLocallyConnectedParties(2, kPortOffset)); + for (auto& party : parties_) { + party->GetLogger()->SetEnabled(kDetailedLoggingEnabled); + party->GetConfiguration()->SetOnlineAfterSetup(online_after_setup); + } + } + + std::vector parties_; +}; + +class SeededRandomnessGenerator { + public: + SeededRandomnessGenerator() = default; + SeededRandomnessGenerator(std::size_t seed) { random_.seed(seed); } + + protected: + BitVector<> RandomBits(std::size_t size) { + std::bernoulli_distribution bool_dist; + BitVector<> result; + result.Reserve(size); + while (result.GetSize() < size) result.Append(bool_dist(random_)); + return result; + } + + bool RandomBit() { + std::bernoulli_distribution bool_dist; + return bool_dist(random_); + } + + template + T RandomInteger() { + static_assert(std::is_integral_v, "T must be an integral type"); + std::uniform_int_distribution value_dist; + return value_dist(random_); + } + + template + std::vector RandomIntegers(std::size_t size) { + static_assert(std::is_integral_v, "T must be an integral type"); + std::uniform_int_distribution value_dist; + std::vector result; + result.reserve(size); + while (result.size() < size) result.emplace_back(value_dist(random_)); + return result; + } + + std::mt19937_64 random_{0}; +}; + +template +class TypedSignedBgmwTest : public testing::Test, + public PartyGenerator, + public SeededRandomnessGenerator { + public: + void SetUp() override { + GenerateParties(false); + GenerateRandomValues(); + } + + protected: + void GenerateRandomValues() { + values_a_ = RandomIntegers(vector_size_); + values_b_ = RandomIntegers(vector_size_); + } + + std::vector values_a_, values_b_; + std::size_t vector_size_{1000}; +}; + +using SignedIntegerTypes = ::testing::Types; +TYPED_TEST_SUITE(TypedSignedBgmwTest, SignedIntegerTypes); + +TYPED_TEST(TypedSignedBgmwTest, SignedSubtraction_1K_Simd_2_parties) { + constexpr auto kArithmeticGmw = encrypto::motion::MpcProtocol::kArithmeticGmw; + std::vector> futures; + + for (auto party_id = 0u; party_id < this->parties_.size(); ++party_id) { + futures.push_back(std::async(std::launch::async, [this, party_id]() { + encrypto::motion::SecureSignedInteger share_values_a_, share_values_b_; + // If my input - real input, otherwise a dummy 0 (-vector). + // Should not make any difference, just for consistency... + std::vector selected_values_a_ = + party_id == 0 ? this->values_a_ : std::vector(this->values_a_.size(), 0); + std::vector selected_values_b_ = + party_id == 0 ? this->values_b_ : std::vector(this->values_b_.size(), 0); + + share_values_a_ = + this->parties_.at(party_id)->template In( + ToInput(selected_values_a_), 0); + share_values_b_ = + this->parties_.at(party_id)->template In( + ToInput(selected_values_b_), 0); + + auto share_sub = share_values_a_ - share_values_b_; + + auto share_output = share_sub.Out(); + + this->parties_.at(party_id)->Run(); + + auto circuit_result = share_output.As>(); + std::vector expected_result; + expected_result.reserve(circuit_result.size()); + for (std::size_t i = 0; i < this->values_a_.size(); ++i) { + expected_result.emplace_back(this->values_a_[i] - this->values_b_[i]); + } + EXPECT_EQ(circuit_result, expected_result); + + this->parties_.at(party_id)->Finish(); + })); + } + for (auto& future : futures) future.get(); +} + +} \ No newline at end of file diff --git a/src/test/test_integer_operations.cpp b/src/test/test_integer_operations.cpp index 6334ab6d7..c81372a8f 100644 --- a/src/test/test_integer_operations.cpp +++ b/src/test/test_integer_operations.cpp @@ -22,10 +22,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#include +#include #include +#include #include - -#include +#include #include "algorithm/algorithm_description.h" #include "base/party.h" @@ -34,6 +36,7 @@ #include "protocols/share_wrapper.h" #include "secure_type/secure_unsigned_integer.h" #include "utility/config.h" +#include "utility/helpers.h" #include "test_constants.h" diff --git a/src/test/test_misc.cpp b/src/test/test_misc.cpp index 94d364b63..c87a5670c 100644 --- a/src/test/test_misc.cpp +++ b/src/test/test_misc.cpp @@ -31,6 +31,7 @@ #include "test_constants.h" #include "utility/bit_vector.h" #include "utility/condition.h" +#include "utility/helpers.h" namespace { TEST(Condition, WaitNotifyOne) { @@ -262,4 +263,33 @@ TEST(InputOutputUnVectorization, VectorsOfUnsignedIntegers) { EXPECT_EQ(kV64, v64_check); } +template +class TwosComplementTest : public testing::Test { +}; + +using TwosComplementUnsignedTypes = + ::testing::Types; +TYPED_TEST_SUITE(TwosComplementTest, TwosComplementUnsignedTypes); + +TYPED_TEST(TwosComplementTest, Conversion) { + using U = TypeParam; + using S = typename std::make_signed_t; + constexpr std::size_t num_values{std::min(static_cast(std::numeric_limits::max()), + static_cast(10000))}; + + constexpr S begin{static_cast(0) - static_cast(num_values / 2)}; + + for (S i = begin; (i - begin) < num_values; ++i) { + U u{encrypto::motion::ToTwosComplement(i)}; + S s{encrypto::motion::FromTwosComplement(u)}; + ASSERT_EQ(i, s); + if (std::signbit(i)) { // is negative + U positive_u = -u; + ASSERT_EQ(-i, positive_u); + } else { // is positive + ASSERT_EQ(i, u); + } + } +} + } // namespace \ No newline at end of file