#include "dpf/distributed_point_function.h"
#include <glog/logging.h>
#include <openssl/rand.h>
#include <limits>
#include "dpf/status_macros.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
namespace distributed_point_functions {
namespace {
constexpr absl::uint128 kPrgKeyLeft =
absl::MakeUint128(0x5be037ccf6a03de5ULL, 0x935f08d0a5b6a2fdULL);
constexpr absl::uint128 kPrgKeyRight =
absl::MakeUint128(0xef94b6aedebb026cULL, 0xe2ea1fe0f66f4d0bULL);
constexpr absl::uint128 kPrgKeyValue =
absl::MakeUint128(0x05a5d1588c5423e3ULL, 0x46a31101b21d1c98ULL);
bool ExtractAndClearLowestBit(absl::uint128& x) {
bool bit = ((x & absl::uint128{1}) != 0);
x &= ~absl::uint128{1};
return bit;
}
}
DistributedPointFunction::DistributedPointFunction(
std::unique_ptr<dpf_internal::ProtoValidator> proto_validator,
std::vector<int> blocks_needed, Aes128FixedKeyHash prg_left,
Aes128FixedKeyHash prg_right, Aes128FixedKeyHash prg_value,
absl::flat_hash_map<std::string, ValueCorrectionFunction>
value_correction_functions)
: proto_validator_(std::move(proto_validator)),
parameters_(proto_validator_->parameters()),
tree_levels_needed_(proto_validator_->tree_levels_needed()),
tree_to_hierarchy_(proto_validator_->tree_to_hierarchy()),
hierarchy_to_tree_(proto_validator_->hierarchy_to_tree()),
blocks_needed_(std::move(blocks_needed)),
prg_left_(std::move(prg_left)),
prg_right_(std::move(prg_right)),
prg_value_(std::move(prg_value)),
value_correction_functions_(value_correction_functions) {}
absl::StatusOr<std::vector<Value>>
DistributedPointFunction::ComputeValueCorrection(
int hierarchy_level, absl::Span<const absl::uint128> seeds,
absl::uint128 alpha, const Value& beta, bool invert) const {
int blocks_needed = blocks_needed_[hierarchy_level];
std::vector<absl::uint128> expanded_seeds(2 * blocks_needed);
absl::Span<absl::uint128> expanded_seed_a(&expanded_seeds[0], blocks_needed);
absl::Span<absl::uint128> expanded_seed_b(&expanded_seeds[blocks_needed],
blocks_needed);
DCHECK(seeds.size() == 2);
std::iota(expanded_seed_a.begin(), expanded_seed_a.end(), seeds[0]);
std::iota(expanded_seed_b.begin(), expanded_seed_b.end(), seeds[1]);
DPF_RETURN_IF_ERROR(
prg_value_.Evaluate(expanded_seeds, absl::MakeSpan(expanded_seeds)));
int index_in_block = DomainToBlockIndex(alpha, hierarchy_level);
DPF_ASSIGN_OR_RETURN(
ValueCorrectionFunction func,
GetValueCorrectionFunction(parameters_[hierarchy_level]));
return func(
absl::string_view(reinterpret_cast<const char*>(expanded_seed_a.data()),
blocks_needed * sizeof(absl::uint128)),
absl::string_view(reinterpret_cast<const char*>(expanded_seed_b.data()),
blocks_needed * sizeof(absl::uint128)),
index_in_block, beta, invert);
}
absl::Status DistributedPointFunction::GenerateNext(
int tree_level, absl::uint128 alpha, absl::Span<const Value> beta,
absl::Span<absl::uint128> seeds, absl::Span<bool> control_bits,
absl::Span<DpfKey> keys) const {
CorrectionWord* correction_word = keys[0].add_correction_words();
if (tree_to_hierarchy_.contains(tree_level - 1)) {
int hierarchy_level = tree_to_hierarchy_.at(tree_level - 1);
absl::uint128 alpha_prefix = 0;
int shift_amount = parameters_.back().log_domain_size() -
parameters_[hierarchy_level].log_domain_size();
if (shift_amount < 128) {
alpha_prefix = alpha >> shift_amount;
}
DPF_ASSIGN_OR_RETURN(
std::vector<Value> value_correction,
ComputeValueCorrection(hierarchy_level, seeds, alpha_prefix,
beta[hierarchy_level], control_bits[1]));
for (const Value& value : value_correction) {
*(correction_word->add_value_correction()) = value;
}
}
std::array<std::array<absl::uint128, 2>, 2> expanded_seeds;
DPF_RETURN_IF_ERROR(
prg_left_.Evaluate(seeds, absl::MakeSpan(expanded_seeds[0])));
DPF_RETURN_IF_ERROR(
prg_right_.Evaluate(seeds, absl::MakeSpan(expanded_seeds[1])));
std::array<std::array<bool, 2>, 2> expanded_control_bits;
expanded_control_bits[0][0] = ExtractAndClearLowestBit(expanded_seeds[0][0]);
expanded_control_bits[0][1] = ExtractAndClearLowestBit(expanded_seeds[0][1]);
expanded_control_bits[1][0] = ExtractAndClearLowestBit(expanded_seeds[1][0]);
expanded_control_bits[1][1] = ExtractAndClearLowestBit(expanded_seeds[1][1]);
bool current_bit = 0;
if (parameters_.back().log_domain_size() - tree_level < 128) {
current_bit =
(alpha & (absl::uint128{1}
<< (parameters_.back().log_domain_size() - tree_level))) != 0;
}
bool keep = current_bit, lose = !current_bit;
absl::uint128 seed_correction =
expanded_seeds[lose][0] ^ expanded_seeds[lose][1];
std::array<bool, 2> control_bit_correction;
control_bit_correction[0] = expanded_control_bits[0][0] ^
expanded_control_bits[0][1] ^ current_bit ^ 1;
control_bit_correction[1] =
expanded_control_bits[1][0] ^ expanded_control_bits[1][1] ^ current_bit;
seeds[0] = expanded_seeds[keep][0];
seeds[1] = expanded_seeds[keep][1];
if (control_bits[0]) {
seeds[0] ^= seed_correction;
}
if (control_bits[1]) {
seeds[1] ^= seed_correction;
}
control_bits[0] = expanded_control_bits[keep][0] ^
(control_bits[0] && control_bit_correction[keep]);
control_bits[1] = expanded_control_bits[keep][1] ^
(control_bits[1] && control_bit_correction[keep]);
correction_word->mutable_seed()->set_high(
absl::Uint128High64(seed_correction));
correction_word->mutable_seed()->set_low(absl::Uint128Low64(seed_correction));
correction_word->set_control_left(control_bit_correction[0]);
correction_word->set_control_right(control_bit_correction[1]);
*(keys[1].add_correction_words()) = *correction_word;
return absl::OkStatus();
}
absl::uint128 DistributedPointFunction::DomainToTreeIndex(
absl::uint128 domain_index, int hierarchy_level) const {
int block_index_bits = parameters_[hierarchy_level].log_domain_size() -
hierarchy_to_tree_[hierarchy_level];
DCHECK(block_index_bits < 128);
return domain_index >> block_index_bits;
}
int DistributedPointFunction::DomainToBlockIndex(absl::uint128 domain_index,
int hierarchy_level) const {
int block_index_bits = parameters_[hierarchy_level].log_domain_size() -
hierarchy_to_tree_[hierarchy_level];
DCHECK(block_index_bits < 128);
return static_cast<int>(domain_index &
((absl::uint128{1} << block_index_bits) - 1));
}
absl::StatusOr<DistributedPointFunction::DpfExpansion>
DistributedPointFunction::EvaluateSeeds(
DpfExpansion partial_evaluations, absl::Span<const absl::uint128> paths,
absl::Span<const CorrectionWord* const> correction_words) const {
if (partial_evaluations.seeds.size() !=
partial_evaluations.control_bits.size() ||
partial_evaluations.seeds.size() != paths.size()) {
return absl::InvalidArgumentError(
"partial_evaluations.seeds.size(), "
"partial_evaluations.control_bits.size() and paths.size() must "
"all be equal");
}
auto num_seeds = static_cast<int64_t>(partial_evaluations.seeds.size());
auto num_levels = static_cast<int>(correction_words.size());
constexpr int64_t max_batch_size = Aes128FixedKeyHash::kBatchSize;
DpfExpansion result = std::move(partial_evaluations);
std::vector<absl::uint128> buffer_left, buffer_right;
buffer_left.resize(max_batch_size);
buffer_right.resize(max_batch_size);
BitVector path_bits(max_batch_size), control_bits(max_batch_size);
std::vector<absl::uint128> correction_seeds;
correction_seeds.resize(num_levels);
BitVector correction_controls_left(num_levels),
correction_controls_right(num_levels);
for (int level = 0; level < num_levels; ++level) {
const CorrectionWord& correction = *(correction_words[level]);
correction_seeds[level] =
absl::MakeUint128(correction.seed().high(), correction.seed().low());
correction_controls_left[level] = correction.control_left();
correction_controls_right[level] = correction.control_right();
}
for (int64_t start_block = 0; start_block < num_seeds;
start_block += max_batch_size) {
int64_t current_batch_size =
std::min<int64_t>(num_seeds - start_block, max_batch_size);
for (int level = 0; level < num_levels; ++level) {
absl::Span<const absl::uint128> seeds =
absl::MakeConstSpan(result.seeds)
.subspan(start_block, current_batch_size);
DPF_RETURN_IF_ERROR(prg_left_.Evaluate(
seeds, absl::MakeSpan(buffer_left).subspan(0, current_batch_size)));
DPF_RETURN_IF_ERROR(prg_right_.Evaluate(
seeds, absl::MakeSpan(buffer_right).subspan(0, current_batch_size)));
const int bit_index = num_levels - level - 1;
for (int i = 0; i < current_batch_size; ++i) {
path_bits[i] = 0;
if (bit_index < 128) {
path_bits[i] =
(paths[start_block + i] & (absl::uint128{1} << bit_index)) != 0;
}
if (path_bits[i] == 0) {
result.seeds[start_block + i] = buffer_left[i];
} else {
result.seeds[start_block + i] = buffer_right[i];
}
}
std::copy_n(&result.control_bits[start_block], current_batch_size,
&control_bits[0]);
for (int i = 0; i < current_batch_size; ++i) {
if (control_bits[i]) {
result.seeds[start_block + i] ^= correction_seeds[level];
}
bool current_control_bit =
ExtractAndClearLowestBit(result.seeds[start_block + i]);
if (control_bits[i]) {
if (path_bits[i] == 0) {
current_control_bit ^= correction_controls_left[level];
} else {
current_control_bit ^= correction_controls_right[level];
}
}
result.control_bits[start_block + i] = current_control_bit;
}
}
}
return result;
}
absl::StatusOr<DistributedPointFunction::DpfExpansion>
DistributedPointFunction::ExpandSeeds(
const DpfExpansion& partial_evaluations,
absl::Span<const CorrectionWord* const> correction_words) const {
int num_expansions = static_cast<int>(correction_words.size());
DCHECK(num_expansions < 63);
auto current_level_size =
static_cast<int64_t>(partial_evaluations.seeds.size());
int64_t max_batch_size = Aes128FixedKeyHash::kBatchSize;
int64_t output_size = current_level_size << num_expansions;
std::vector<absl::uint128> prg_buffer_left(max_batch_size),
prg_buffer_right(max_batch_size);
DpfExpansion expansion = partial_evaluations;
expansion.seeds.reserve(output_size);
expansion.control_bits.reserve(output_size);
DpfExpansion next_level_expansion;
next_level_expansion.seeds.reserve(output_size);
next_level_expansion.control_bits.reserve(output_size);
for (int i = 0; i < num_expansions; ++i) {
next_level_expansion.seeds.resize(0);
next_level_expansion.control_bits.resize(0);
absl::uint128 correction_seed = absl::MakeUint128(
correction_words[i]->seed().high(), correction_words[i]->seed().low());
bool correction_control_left = correction_words[i]->control_left();
bool correction_control_right = correction_words[i]->control_right();
for (int64_t start_block = 0; start_block < current_level_size;
start_block += max_batch_size) {
int64_t batch_size =
std::min<int64_t>(current_level_size - start_block, max_batch_size);
DPF_RETURN_IF_ERROR(prg_left_.Evaluate(
absl::MakeConstSpan(expansion.seeds).subspan(start_block, batch_size),
absl::MakeSpan(prg_buffer_left).subspan(0, batch_size)));
DPF_RETURN_IF_ERROR(prg_right_.Evaluate(
absl::MakeConstSpan(expansion.seeds).subspan(start_block, batch_size),
absl::MakeSpan(prg_buffer_right).subspan(0, batch_size)));
for (int64_t j = 0; j < batch_size; ++j) {
int64_t index_expanded = 2 * (start_block + j);
if (expansion.control_bits[start_block + j]) {
prg_buffer_left[j] ^= correction_seed;
prg_buffer_right[j] ^= correction_seed;
}
next_level_expansion.seeds.push_back(prg_buffer_left[j]);
next_level_expansion.seeds.push_back(prg_buffer_right[j]);
next_level_expansion.control_bits.push_back(ExtractAndClearLowestBit(
next_level_expansion.seeds[index_expanded]));
next_level_expansion.control_bits.push_back(ExtractAndClearLowestBit(
next_level_expansion.seeds[index_expanded + 1]));
if (expansion.control_bits[start_block + j]) {
next_level_expansion.control_bits[index_expanded] ^=
correction_control_left;
next_level_expansion.control_bits[index_expanded + 1] ^=
correction_control_right;
}
}
}
std::swap(expansion, next_level_expansion);
current_level_size *= 2;
}
return expansion;
}
absl::StatusOr<DistributedPointFunction::DpfExpansion>
DistributedPointFunction::ComputePartialEvaluations(
absl::Span<const absl::uint128> prefixes, bool update_ctx,
EvaluationContext& ctx) const {
int64_t num_prefixes = static_cast<int64_t>(prefixes.size());
DpfExpansion partial_evaluations;
int start_level = hierarchy_to_tree_[ctx.partial_evaluations_level()];
int stop_level = hierarchy_to_tree_[ctx.previous_hierarchy_level()];
if (ctx.partial_evaluations_size() > 0 && start_level <= stop_level) {
absl::btree_map<absl::uint128, std::pair<absl::uint128, bool>>
previous_partial_evaluations;
for (const PartialEvaluation& element : ctx.partial_evaluations()) {
absl::uint128 prefix =
absl::MakeUint128(element.prefix().high(), element.prefix().low());
int64_t previous_size = previous_partial_evaluations.size();
previous_partial_evaluations.try_emplace(
previous_partial_evaluations.end(), prefix,
std::make_pair(
absl::MakeUint128(element.seed().high(), element.seed().low()),
element.control_bit()));
if (previous_partial_evaluations.size() <= previous_size) {
return absl::InvalidArgumentError(
"Duplicate prefix in `ctx.partial_evaluations()`");
}
}
partial_evaluations.seeds.reserve(prefixes.size());
partial_evaluations.control_bits.reserve(prefixes.size());
for (int64_t i = 0; i < num_prefixes; ++i) {
absl::uint128 previous_prefix = 0;
if (stop_level - start_level < 128) {
previous_prefix = prefixes[i] >> (stop_level - start_level);
}
auto it = previous_partial_evaluations.find(previous_prefix);
if (it == previous_partial_evaluations.end()) {
return absl::InvalidArgumentError(absl::StrCat(
"Prefix not present in ctx.partial_evaluations at hierarchy level ",
ctx.previous_hierarchy_level()));
}
std::pair<absl::uint128, bool> partial_evaluation = it->second;
partial_evaluations.seeds.push_back(partial_evaluation.first);
partial_evaluations.control_bits.push_back(partial_evaluation.second);
}
} else {
partial_evaluations.seeds.resize(
num_prefixes,
absl::MakeUint128(ctx.key().seed().high(), ctx.key().seed().low()));
partial_evaluations.control_bits.resize(
num_prefixes, static_cast<bool>(ctx.key().party()));
start_level = 0;
}
DPF_ASSIGN_OR_RETURN(
partial_evaluations,
EvaluateSeeds(std::move(partial_evaluations), prefixes,
absl::MakeConstSpan(ctx.key().correction_words())
.subspan(start_level, stop_level - start_level)));
ctx.clear_partial_evaluations();
ctx.mutable_partial_evaluations()->Reserve(num_prefixes);
if (update_ctx) {
for (int64_t i = 0; i < num_prefixes; ++i) {
PartialEvaluation* current_element = ctx.add_partial_evaluations();
current_element->mutable_prefix()->set_high(
absl::Uint128High64(prefixes[i]));
current_element->mutable_prefix()->set_low(
absl::Uint128Low64(prefixes[i]));
current_element->mutable_seed()->set_high(
absl::Uint128High64(partial_evaluations.seeds[i]));
current_element->mutable_seed()->set_low(
absl::Uint128Low64(partial_evaluations.seeds[i]));
current_element->set_control_bit(partial_evaluations.control_bits[i]);
}
}
ctx.set_partial_evaluations_level(ctx.previous_hierarchy_level());
return partial_evaluations;
}
absl::StatusOr<DistributedPointFunction::DpfExpansion>
DistributedPointFunction::ExpandAndUpdateContext(
int hierarchy_level, absl::Span<const absl::uint128> prefixes,
EvaluationContext& ctx) const {
DpfExpansion selected_partial_evaluations;
int start_level = 0;
if (prefixes.empty()) {
selected_partial_evaluations.seeds = {
absl::MakeUint128(ctx.key().seed().high(), ctx.key().seed().low())};
selected_partial_evaluations.control_bits = {
static_cast<bool>(ctx.key().party())};
} else {
bool update_ctx =
(hierarchy_level < static_cast<int>(parameters_.size()) - 1);
DPF_ASSIGN_OR_RETURN(selected_partial_evaluations,
ComputePartialEvaluations(prefixes, update_ctx, ctx));
DCHECK(ctx.previous_hierarchy_level() >= 0);
start_level = hierarchy_to_tree_[ctx.previous_hierarchy_level()];
}
int stop_level = hierarchy_to_tree_[hierarchy_level];
DPF_ASSIGN_OR_RETURN(
DpfExpansion expansion,
ExpandSeeds(selected_partial_evaluations,
absl::MakeConstSpan(ctx.key().correction_words())
.subspan(start_level, stop_level - start_level)));
ctx.set_previous_hierarchy_level(hierarchy_level);
return expansion;
}
absl::StatusOr<std::vector<absl::uint128>>
DistributedPointFunction::HashExpandedSeeds(
int hierarchy_level, absl::Span<const absl::uint128> expansion) const {
const auto expansion_size = static_cast<int64_t>(expansion.size());
const int blocks_needed = blocks_needed_[hierarchy_level];
std::vector<absl::uint128> hashed_expansion;
hashed_expansion.reserve(expansion_size * blocks_needed);
for (int64_t i = 0; i < expansion_size; ++i) {
for (int j = 0; j < blocks_needed; ++j) {
hashed_expansion.push_back(expansion[i] + j);
}
}
DPF_RETURN_IF_ERROR(
prg_value_.Evaluate(hashed_expansion, absl::MakeSpan(hashed_expansion)));
return hashed_expansion;
}
absl::StatusOr<std::string>
DistributedPointFunction::SerializeValueTypeDeterministically(
const ValueType& value_type) {
std::string serialized_value_type;
{
::google::protobuf::io::StringOutputStream string_stream(
&serialized_value_type);
::google::protobuf::io::CodedOutputStream coded_stream(&string_stream);
coded_stream.SetSerializationDeterministic(true);
if (!value_type.SerializeToCodedStream(&coded_stream)) {
return absl::InternalError("Serializing value_type to string failed");
}
}
return serialized_value_type;
}
absl::StatusOr<DistributedPointFunction::ValueCorrectionFunction>
DistributedPointFunction::GetValueCorrectionFunction(
const DpfParameters& parameters) const {
std::string serialized_value_type;
DPF_ASSIGN_OR_RETURN(
serialized_value_type,
SerializeValueTypeDeterministically(parameters.value_type()));
auto it = value_correction_functions_.find(serialized_value_type);
if (it == value_correction_functions_.end()) {
return absl::FailedPreconditionError(absl::StrCat(
"No value correction function known for the following parameters:\n",
parameters.DebugString(),
"Did you call RegisterValueType<T>() with your value type?"));
}
return it->second;
}
absl::StatusOr<std::unique_ptr<DistributedPointFunction>>
DistributedPointFunction::Create(const DpfParameters& parameters) {
return CreateIncremental(absl::MakeConstSpan(¶meters, 1));
}
absl::StatusOr<std::unique_ptr<DistributedPointFunction>>
DistributedPointFunction::CreateIncremental(
absl::Span<const DpfParameters> parameters) {
DPF_ASSIGN_OR_RETURN(
std::unique_ptr<dpf_internal::ProtoValidator> proto_validator,
dpf_internal::ProtoValidator::Create(parameters));
std::vector<int> blocks_needed(parameters.size());
for (int i = 0; i < static_cast<int>(parameters.size()); ++i) {
DPF_ASSIGN_OR_RETURN(
int bits_needed,
dpf_internal::BitsNeeded(parameters[i].value_type(),
parameters[i].security_parameter()));
blocks_needed[i] = (bits_needed + 127) / 128;
}
DPF_ASSIGN_OR_RETURN(Aes128FixedKeyHash prg_left,
Aes128FixedKeyHash::Create(kPrgKeyLeft));
DPF_ASSIGN_OR_RETURN(Aes128FixedKeyHash prg_right,
Aes128FixedKeyHash::Create(kPrgKeyRight));
DPF_ASSIGN_OR_RETURN(Aes128FixedKeyHash prg_value,
Aes128FixedKeyHash::Create(kPrgKeyValue));
absl::flat_hash_map<std::string, ValueCorrectionFunction>
value_correction_functions;
DPF_RETURN_IF_ERROR(
RegisterValueTypeImpl<uint8_t>(value_correction_functions));
DPF_RETURN_IF_ERROR(
RegisterValueTypeImpl<uint16_t>(value_correction_functions));
DPF_RETURN_IF_ERROR(
RegisterValueTypeImpl<uint32_t>(value_correction_functions));
DPF_RETURN_IF_ERROR(
RegisterValueTypeImpl<uint64_t>(value_correction_functions));
DPF_RETURN_IF_ERROR(
RegisterValueTypeImpl<absl::uint128>(value_correction_functions));
return absl::WrapUnique(new DistributedPointFunction(
std::move(proto_validator), std::move(blocks_needed), std::move(prg_left),
std::move(prg_right), std::move(prg_value),
std::move(value_correction_functions)));
}
absl::StatusOr<std::pair<DpfKey, DpfKey>>
DistributedPointFunction::GenerateKeysIncremental(
absl::uint128 alpha, absl::Span<const Value> beta) {
if (beta.size() != parameters_.size()) {
return absl::InvalidArgumentError(
"`beta` has to have the same size as `parameters` passed at "
"construction");
}
for (int i = 0; i < static_cast<int>(parameters_.size()); ++i) {
absl::Status status = proto_validator_->ValidateValue(beta[i], i);
if (!status.ok()) {
return status;
}
}
int last_level_log_domain_size = parameters_.back().log_domain_size();
if (last_level_log_domain_size < 128 &&
alpha >= (absl::uint128{1} << last_level_log_domain_size)) {
return absl::InvalidArgumentError(
"`alpha` must be smaller than the output domain size");
}
std::array<DpfKey, 2> keys;
keys[0].set_party(0);
keys[1].set_party(1);
std::array<absl::uint128, 2> seeds;
RAND_bytes(reinterpret_cast<uint8_t*>(&seeds[0]), sizeof(absl::uint128));
RAND_bytes(reinterpret_cast<uint8_t*>(&seeds[1]), sizeof(absl::uint128));
keys[0].mutable_seed()->set_high(absl::Uint128High64(seeds[0]));
keys[0].mutable_seed()->set_low(absl::Uint128Low64(seeds[0]));
keys[1].mutable_seed()->set_high(absl::Uint128High64(seeds[1]));
keys[1].mutable_seed()->set_low(absl::Uint128Low64(seeds[1]));
std::array<bool, 2> control_bits{0, 1};
keys[0].mutable_correction_words()->Reserve(tree_levels_needed_ - 1);
keys[1].mutable_correction_words()->Reserve(tree_levels_needed_ - 1);
for (int i = 1; i < tree_levels_needed_; i++) {
DPF_RETURN_IF_ERROR(GenerateNext(i, alpha, beta, absl::MakeSpan(seeds),
absl::MakeSpan(control_bits),
absl::MakeSpan(keys)));
}
DPF_ASSIGN_OR_RETURN(
std::vector<Value> last_level_value_correction,
ComputeValueCorrection(parameters_.size() - 1, seeds, alpha, beta.back(),
control_bits[1]));
for (const Value& value : last_level_value_correction) {
*(keys[0].add_last_level_value_correction()) = value;
*(keys[1].add_last_level_value_correction()) = value;
}
return std::make_pair(std::move(keys[0]), std::move(keys[1]));
}
absl::StatusOr<EvaluationContext>
DistributedPointFunction::CreateEvaluationContext(DpfKey key) const {
DPF_RETURN_IF_ERROR(proto_validator_->ValidateDpfKey(key));
EvaluationContext result;
for (int i = 0; i < static_cast<int>(parameters_.size()); ++i) {
*(result.add_parameters()) = parameters_[i];
}
*(result.mutable_key()) = std::move(key);
result.set_previous_hierarchy_level(-1);
return result;
}
}