Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of the initialization, setup, and execution phase of the reach-only protocol. #1129

Merged
merged 10 commits into from
Aug 4, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "wfa/measurement/common/crypto/encryption_utility_helper.h"

#include <memory>
#include <utility>

#include "absl/status/status.h"
Expand Down Expand Up @@ -47,26 +48,6 @@ absl::StatusOr<ElGamalCiphertext> ExtractElGamalCiphertextFromString(
std::string(str.substr(kBytesPerEcPoint, kBytesPerEcPoint)));
}

absl::StatusOr<std::vector<std::string>> GetBlindedRegisterIndexes(
absl::string_view data, ProtocolCryptor& protocol_cryptor) {
ASSIGN_OR_RETURN(size_t register_count,
GetNumberOfBlocks(data, kBytesPerCipherRegister));
std::vector<std::string> blinded_register_indexes;
blinded_register_indexes.reserve(register_count);
for (size_t index = 0; index < register_count; ++index) {
// The size of data_block is guaranteed to be equal to
// kBytesPerCipherText
absl::string_view data_block =
data.substr(index * kBytesPerCipherRegister, kBytesPerCipherText);
ASSIGN_OR_RETURN(ElGamalCiphertext ciphertext,
ExtractElGamalCiphertextFromString(data_block));
ASSIGN_OR_RETURN(std::string decrypted_el_gamal,
protocol_cryptor.DecryptLocalElGamal(ciphertext));
blinded_register_indexes.push_back(std::move(decrypted_el_gamal));
}
return blinded_register_indexes;
}

absl::StatusOr<KeyCountPairCipherText> ExtractKeyCountPairFromSubstring(
absl::string_view str) {
if (str.size() != kBytesPerCipherText * 2) {
Expand Down Expand Up @@ -121,6 +102,17 @@ absl::Status WriteEcPointPairToString(const ElGamalEcPointPair& ec_point_pair,
return absl::OkStatus();
}

absl::StatusOr<ElGamalEcPointPair> GetEcPointPairFromString(
absl::string_view str, int curve_id) {
Context ctx;
ASSIGN_OR_RETURN(ECGroup ec_group, ECGroup::Create(curve_id, &ctx));
ASSIGN_OR_RETURN(ElGamalCiphertext ciphertext,
ExtractElGamalCiphertextFromString(str));
ASSIGN_OR_RETURN(ElGamalEcPointPair ec_point,
GetElGamalEcPoints(ciphertext, ec_group));
return ec_point;
}

absl::StatusOr<std::vector<std::string>> GetCountValuesPlaintext(
int maximum_value, int curve_id) {
if (maximum_value < 1) {
Expand All @@ -142,4 +134,31 @@ absl::StatusOr<std::vector<std::string>> GetCountValuesPlaintext(
return result;
}

absl::Status EncryptCompositeElGamalAndAppendToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, std::string& data) {
ASSIGN_OR_RETURN(
ElGamalCiphertext key,
protocol_cryptor.EncryptCompositeElGamal(plaintext_ec, composite_type));
data.append(key.first);
data.append(key.second);
return absl::OkStatus();
}

absl::Status EncryptCompositeElGamalAndWriteToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, size_t pos, std::string& result) {
if (pos + kBytesPerCipherText > result.size()) {
return absl::InvalidArgumentError("result is not long enough to write.");
}
ASSIGN_OR_RETURN(
ElGamalCiphertext key,
protocol_cryptor.EncryptCompositeElGamal(plaintext_ec, composite_type));

result.replace(pos, kBytesPerEcPoint, key.first);
result.replace(pos + kBytesPerEcPoint, kBytesPerEcPoint, key.second);

return absl::OkStatus();
}

} // namespace wfa::measurement::common::crypto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

namespace wfa::measurement::common::crypto {

using ::wfa::measurement::common::crypto::CompositeType;

// A pair of ciphertexts which store the key and count values of a liquidlegions
// register.
struct KeyCountPairCipherText {
Expand All @@ -41,11 +43,6 @@ absl::StatusOr<size_t> GetNumberOfBlocks(absl::string_view data,
absl::StatusOr<ElGamalCiphertext> ExtractElGamalCiphertextFromString(
absl::string_view str);

// Blinds the last layer of ElGamal Encryption of register indexes, and return
// the deterministically encrypted results.
absl::StatusOr<std::vector<std::string>> GetBlindedRegisterIndexes(
absl::string_view data, ProtocolCryptor& protocol_cryptor);

// Extracts a KeyCountPairCipherText from a string_view.
absl::StatusOr<KeyCountPairCipherText> ExtractKeyCountPairFromSubstring(
absl::string_view str);
Expand All @@ -66,10 +63,30 @@ absl::Status AppendEcPointPairToString(const ElGamalEcPointPair& ec_point_pair,
absl::Status WriteEcPointPairToString(const ElGamalEcPointPair& ec_point_pair,
size_t pos, std::string& result);

// Extract a ElGamalEcPointPair from a string_view.
absl::StatusOr<ElGamalEcPointPair> GetEcPointPairFromString(
absl::string_view str, int curve_id);

// Returns the vector of ECPoints for count values from 1 to maximum_value.
absl::StatusOr<std::vector<std::string>> GetCountValuesPlaintext(
int maximum_value, int curve_id);

// Encrypts plaintext and appends bytes of the cipher text to a target string.
// The length of bytes appened is kBytesPerCipherText = kBytesPerEcPoint * 2.
absl::Status EncryptCompositeElGamalAndAppendToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, std::string& data);

// Encrypts plaintext and writes bytes of the cipher text to a target string at
// a certain position.
// Bytes are written by replacing content of the string starting at pos. The
// length of bytes written is kBytesPerCipherText = kBytesPerEcPoint * 2.
// Returns a Status with code `INVALID_ARGUMENT` when the result string is not
// long enough.
absl::Status EncryptCompositeElGamalAndWriteToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, size_t pos, std::string& result);

} // namespace wfa::measurement::common::crypto

#endif // SRC_MAIN_CC_WFA_MEASUREMENT_COMMON_CRYPTO_ENCRYPTION_UTILITY_HELPER_H_
56 changes: 55 additions & 1 deletion src/main/cc/wfa/measurement/common/crypto/protocol_cryptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class ProtocolCryptorImpl : public ProtocolCryptor {
CompositeType composite_type) override;
absl::StatusOr<ElGamalCiphertext> EncryptCompositeElGamal(
absl::string_view plain_ec_point, CompositeType composite_type) override;
absl::StatusOr<std::string> EncryptIntegerToStringCompositeElGamal(
int64_t value) override;
absl::StatusOr<ElGamalCiphertext> ReRandomize(
const ElGamalCiphertext& ciphertext,
CompositeType composite_type) override;
Expand Down Expand Up @@ -173,6 +175,58 @@ absl::StatusOr<ElGamalCiphertext> ProtocolCryptorImpl::EncryptCompositeElGamal(
: partial_composite_el_gamal_cipher_->Encrypt(plain_ec_point);
}

absl::StatusOr<std::string>
ProtocolCryptorImpl::EncryptIntegerToStringCompositeElGamal(int64_t value) {
Context ctx;
std::string ciphertext;
ciphertext.resize(kBytesPerCipherText);
if (value < 0) {
return absl::InvalidArgumentError(
absl::StrCat("The value should be non-negative, but is ", value));
}
if (value == 0) {
ASSIGN_OR_RETURN(
ElGamalEcPointPair zero_ec,
EncryptIdentityElementToEcPointsCompositeElGamal(CompositeType::kFull));

if (absl::StatusOr<std::string> result = zero_ec.u.ToBytesCompressed();
result.ok()) {
ciphertext.replace(0, kBytesPerEcPoint, *result);
} else {
return result.status();
}

if (absl::StatusOr<std::string> result = zero_ec.e.ToBytesCompressed();
result.ok()) {
ciphertext.replace(kBytesPerEcPoint, kBytesPerEcPoint, *result);
} else {
return result.status();
}
} else {
ASSIGN_OR_RETURN(ElGamalEcPointPair one_ec,
EncryptPlaintextToEcPointsCompositeElGamal(
kUnitECPointSeed, CompositeType::kFull));
ASSIGN_OR_RETURN(
ElGamalEcPointPair point_ec,
MultiplyEcPointPairByScalar(one_ec, ctx.CreateBigNum(value)));

if (absl::StatusOr<std::string> result = point_ec.u.ToBytesCompressed();
result.ok()) {
ciphertext.replace(0, kBytesPerEcPoint, *result);
} else {
return result.status();
}

if (absl::StatusOr<std::string> result = point_ec.e.ToBytesCompressed();
result.ok()) {
ciphertext.replace(kBytesPerEcPoint, kBytesPerEcPoint, *result);
} else {
return result.status();
}
}
return ciphertext;
}

absl::StatusOr<ElGamalCiphertext> ProtocolCryptorImpl::ReRandomize(
const ElGamalCiphertext& ciphertext, CompositeType composite_type) {
ASSIGN_OR_RETURN(
Expand Down Expand Up @@ -251,7 +305,7 @@ absl::Status ProtocolCryptorImpl::BatchProcess(absl::string_view data,
ASSIGN_OR_RETURN(std::string temp, DecryptLocalElGamal(ciphertext));
// The first part of the ciphertext is the random number which is still
// required to decrypt the other layers of ElGamal encryptions (at the
// subsequent duchies. So we keep it.
// subsequent duchies). So we keep it.
result.replace(pos, kBytesPerEcPoint, ciphertext.first);
pos += kBytesPerEcPoint;
result.replace(pos, kBytesPerEcPoint, temp);
Expand Down
4 changes: 4 additions & 0 deletions src/main/cc/wfa/measurement/common/crypto/protocol_cryptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class ProtocolCryptor {
// Encrypts the plain EcPoint using the full or partial composite ElGamal Key.
virtual absl::StatusOr<ElGamalCiphertext> EncryptCompositeElGamal(
absl::string_view plain_ec_point, CompositeType composite_type) = 0;
// Maps the integer onto the curve and then encrypts the EcPoint with the full
// composite ElGamal Key, returns the string representation of the ciphertext.
virtual absl::StatusOr<std::string> EncryptIntegerToStringCompositeElGamal(
int64_t value) = 0;
// Encrypts the Identity Element using the full or partial composite ElGamal
// Key, returns the result as an ElGamalEcPointPair.
virtual absl::StatusOr<ElGamalEcPointPair>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("@wfa_common_jvm//build:defs.bzl", "test_target")
package(default_visibility = [
":__pkg__",
test_target(":__pkg__"),
"//src/main/cc/wfa/measurement:__subpackages__",
"//src/main/swig/protocol:__subpackages__",
])

Expand Down Expand Up @@ -36,6 +37,33 @@ cc_library(
],
)

cc_library(
name = "reach_only_liquid_legions_v2_encryption_utility",
srcs = [
"reach_only_liquid_legions_v2_encryption_utility.cc",
],
hdrs = [
"reach_only_liquid_legions_v2_encryption_utility.h",
],
strip_include_prefix = _INCLUDE_PREFIX,
deps = [
":multithreading_helper",
":noise_parameters_computation",
"//src/main/cc/wfa/measurement/common/crypto:constants",
"//src/main/cc/wfa/measurement/common/crypto:encryption_utility_helper",
"//src/main/cc/wfa/measurement/common/crypto:protocol_cryptor",
"//src/main/proto/wfa/measurement/internal/duchy/protocol:reach_only_liquid_legions_v2_encryption_methods_cc_proto",
"@any_sketch//src/main/cc/estimation:estimators",
"@any_sketch//src/main/cc/math:distributed_discrete_gaussian_noiser",
"@any_sketch//src/main/cc/math:distributed_geometric_noiser",
"@com_google_absl//absl/algorithm:container",
"@com_google_private_join_and_compute//private_join_and_compute/crypto:commutative_elgamal",
"@wfa_common_cpp//src/main/cc/common_cpp/jni:jni_wrap",
"@wfa_common_cpp//src/main/cc/common_cpp/macros",
"@wfa_common_cpp//src/main/cc/common_cpp/time:started_thread_cpu_timer",
],
)

cc_library(
name = "liquid_legions_v2_encryption_utility_wrapper",
srcs = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ using ::wfa::measurement::common::crypto::ProtocolCryptorOptions;
using ::wfa::measurement::internal::duchy::ElGamalPublicKey;
using ::wfa::measurement::internal::duchy::protocol::LiquidLegionsV2NoiseConfig;

// Blinds the last layer of ElGamal Encryption of register indexes, and return
// the deterministically encrypted results.
absl::StatusOr<std::vector<std::string>> GetBlindedRegisterIndexes(
absl::string_view data, MultithreadingHelper& helper) {
ASSIGN_OR_RETURN(size_t register_count,
GetNumberOfBlocks(data, kBytesPerCipherRegister));
std::vector<std::string> blinded_register_indexes;
blinded_register_indexes.resize(register_count);

absl::AnyInvocable<absl::Status(ProtocolCryptor&, size_t)> f =
[&](ProtocolCryptor& cryptor, size_t index) -> absl::Status {
absl::string_view data_block =
data.substr(index * kBytesPerCipherRegister, kBytesPerCipherText);
ASSIGN_OR_RETURN(ElGamalCiphertext ciphertext,
ExtractElGamalCiphertextFromString(data_block));
ASSIGN_OR_RETURN(std::string decrypted_el_gamal,
cryptor.DecryptLocalElGamal(ciphertext));
blinded_register_indexes[index] = std::move(decrypted_el_gamal);
return absl::OkStatus();
};
RETURN_IF_ERROR(helper.Execute(register_count, f));

return blinded_register_indexes;
}

// Merge all the counts in each group using the SameKeyAggregation algorithm.
// The calculated (flag_1, flag_2, flag_3, count) tuple is appended to the
// response. 'sub_permutation' contains the locations of the registers belonging
Expand Down Expand Up @@ -240,39 +265,6 @@ absl::StatusOr<std::vector<ElGamalEcPointPair>> GetSameKeyAggregatorMatrixBase(
return std::move(result);
}

absl::Status EncryptCompositeElGamalAndAppendToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, std::string& data) {
ASSIGN_OR_RETURN(
ElGamalCiphertext key,
protocol_cryptor.EncryptCompositeElGamal(plaintext_ec, composite_type));
data.append(key.first);
data.append(key.second);
return absl::OkStatus();
}

// Encrypts plaintext and writes bytes of the cipher text to a target string at
// a certain position.
// Bytes are written by replacing content of the string starting at pos. The
// length of bytes written is kBytesPerCipherText = kBytesPerEcPoint * 2.
// Returns a Status with code `INVALID_ARGUMENT` when the result string is not
// long enough.
absl::Status EncryptCompositeElGamalAndWriteToString(
ProtocolCryptor& protocol_cryptor, CompositeType composite_type,
absl::string_view plaintext_ec, size_t pos, std::string& result) {
if (pos + kBytesPerCipherText > result.size()) {
return absl::InvalidArgumentError("result is not long enough to write.");
}
ASSIGN_OR_RETURN(
ElGamalCiphertext key,
protocol_cryptor.EncryptCompositeElGamal(plaintext_ec, composite_type));

result.replace(pos, kBytesPerEcPoint, key.first);
result.replace(pos + kBytesPerEcPoint, kBytesPerEcPoint, key.second);

return absl::OkStatus();
}

// Adds encrypted blinded-histogram-noise registers to the end of data.
// returns the number of such noise registers added.
absl::StatusOr<int64_t> AddBlindedHistogramNoise(
Expand Down Expand Up @@ -939,10 +931,9 @@ CompleteExecutionPhaseOneAtAggregator(
MultithreadingHelper::CreateMultithreadingHelper(
request.parallelism(), protocol_cryptor_options));

ASSIGN_OR_RETURN(
std::vector<std::string> blinded_register_indexes,
GetBlindedRegisterIndexes(request.combined_register_vector(),
multithreading_helper->GetProtocolCryptor()));
ASSIGN_OR_RETURN(std::vector<std::string> blinded_register_indexes,
GetBlindedRegisterIndexes(request.combined_register_vector(),
*multithreading_helper));

// Create a sorting permutation of the blinded register indexes, such that we
// don't need to modify the sketch data, whose size could be huge. We only
Expand Down
Loading