libdpf/doc/pages/evaluation.md

7.7 KiB

make_dpf(x, y) returns one key per party. Evaluation of a key yields that party's share. Leaf outputs are subtractive shares: open them with dpf::reconstruct, which computes share0 - share1. Comparison outputs are additive shares: reconstruct computes share0 + share1. A single-output eval_point returns a small handle; *handle is the share. An unassigned dpf::wildcard output throws std::runtime_error.

[from, to] is inclusive. The points passed to eval_sequence are a nondecreasing range; an unsorted range throws std::runtime_error.

Memoizers hold interior nodes between calls. Output buffers hold the shares a multi-point evaluation writes. Pass both as mutable named objects when a later call should reuse them. The factories make_basic_path_memoizer, make_basic_interval_memoizer, and make_*_sequence_memoizer unwrap party_key, so a workspace built from either party's type accepts both parties. Name that type with dpf::unwrap_party_key_t<std::decay_t<decltype(key)>>.

Memoizers

Path memoizers

eval_point walks one root-to-leaf path. make_basic_path_memoizer<Key>() keeps every node of the previous point, and the next point recomputes only the suffix after the common prefix. make_nonmemoizing_path_memoizer<Key>() keeps one node and starts from the root on every call. A one-off eval_point(key, x) uses the nonmemoizing memoizer.

Pass the memoizer as a mutable lvalue. The default argument is a new temporary, so it has no previous point to resume from. Keep a separate memoizer for each key you are in the middle of evaluating. A different root restarts the path.

Code samples\n

  • memoizers.cpp \include{cpp} evaluation/memoizers.cpp

Interval memoizers

eval_interval and eval_full expand every leaf in a range. make_basic_interval_memoizer<Key>(from, to) stores two levels of that range. That is the workspace the convenience overloads allocate. make_full_tree_interval_memoizer<Key>(from, to) keeps every level. make_basic_full_memoizer<Key>() and make_full_tree_full_memoizer<Key>() are the same workspaces sized for the whole domain.

Size the memoizer for the widest interval you will pass to it. A wider interval throws std::length_error. The same key and the same endpoints leave the final interior level in place. A different key or a different interval rebuilds into the same allocation.

Passing only the memoizer still allocates a fresh output buffer and returns std::pair(buffer, iterable).

Sequence memoizers

make_sequence_recipe<Key>(begin, end) compiles a sorted point list into a traversal. The recipe depends on the input type, and one recipe serves every key of that type.

A sequence memoizer stores a reference to the recipe object it was built from and checks later calls by address. Pass that same object, and keep the recipe alive for as long as the memoizer is used. A copy of the recipe throws std::logic_error.

make_double_space_sequence_memoizer<Key>(recipe) keeps two levels. It is what eval_sequence(key, recipe, buffer) allocates when you omit the memoizer. make_inplace_reversing_sequence_memoizer<Key>(recipe) keeps one level and reverses direction as it descends. make_full_tree_sequence_memoizer<Key>(recipe) retains every level. A key whose depth differs from the recipe throws std::logic_error.

Output buffers

output_buffer<T> is move-only storage with size, iterators, data, and operator[]. Build it with the factory that matches the evaluation:

  • make_output_buffer_for_interval(key, from, to)
  • make_output_buffer_for_full(key)
  • make_output_buffer_for_subsequence(key, begin, end, tag)
  • make_output_buffer_for_recipe_subsequence(key, recipe, tag)

On a party_key, leaf slots are subtractive_shares and comparison slots are additive_shares. dpf::bit, dpf::twobit, and dpf::nyble slots are packed. Trivially default-constructible slot types are left uninitialized; the evaluation overwrites every slot it is responsible for.

eval_interval and recipe eval_sequence take the buffer as a non-const reference, so the argument is a named object. The returned iterable refers into that buffer. Read it while the buffer is alive, and only over the points the iterable covers. The next evaluation overwrites those slots.

For one output, the convenience overload returns the buffer itself as the first element of the pair. For several output indices it returns a tuple of buffers.

make_output_buffer(dpf::out<I>, key, from, to) and make_output_buffer(dpf::cmp, key, n) size a buffer for one channel of a multi-output or comparison key. The slot types follow the same party-share rule.

Code samples\n

  • output_buffers.cpp \include{cpp} evaluation/output_buffers.cpp

dpf::eval_point

eval_point(key, x) evaluates output 0 at one input. eval_point<I>(key, x) selects another output. Two or more indices, eval_point<0, 1>(key, x), return a tuple of shares rather than handles. eval_point(key, x, path) continues a path memoizer.

eval_point(dpf::out<I>, key, x, path) and eval_point(dpf::cmp, key, x, path) are the same walk with an explicit channel. Comparison results are additive shares.

Code samples\n

  • eval_point.cpp \include{cpp} evaluation/eval_point.cpp

dpf::eval_interval

eval_interval(key, from, to) evaluates every input from from through to. The iterable yields one share per input, in that order. Optional arguments are an output buffer and then an interval memoizer. An output index pack, eval_interval<0, 1>(key, from, to, buffers, memo), writes each selected output.

Code samples\n

  • eval_interval.cpp \include{cpp} evaluation/eval_interval.cpp

dpf::eval_full

eval_full(key) is the closed interval from std::numeric_limits<Input>::min() through max(). The buffer and full-domain memoizer overloads match eval_interval. make_output_buffer_for_full(key) and make_basic_full_memoizer<Key>() size both for that domain.

Code samples\n

  • eval_full.cpp \include{cpp} evaluation/eval_full.cpp

dpf::eval_sequence

eval_sequence(key, begin, end, tag) evaluates a sorted list. dpf::return_output_only_tag_ stores one share per listed point. dpf::return_entire_node_tag_ stores whole leaves; it is the default. The iterable still yields one share per listed point, in list order.

eval_sequence(key, recipe, buffer, memo, tag) repeats that list. memo is a sequence memoizer bound to recipe. Omit memo to allocate a double_space workspace for that call.

Code samples\n

  • eval_sequence.cpp \include{cpp} evaluation/eval_sequence.cpp

Buffered PRG

dpf::randomness::buffered_prg<PRG, Ts...> (alias dpf::randomness::aes_buffered_prg<Ts...>) is a forward cursor with one PRG stream per value type. get<I>() and fill<I>(out, n) consume the cursor. at<I>(index) reads an absolute index and leaves the cursor where it is. sampled<I>() is how far get and fill have advanced. per_stream_buffer_elems is at least 1.

dpf::randomness::lane_table<T> is the seekable form for a runtime set of roles. value_at(role, index) and mask_at(role, index) are independent streams, and a repeated index returns the same element.

Code samples\n

  • buffered_prg.cpp \include{cpp} evaluation/buffered_prg.cpp