diff --git a/Makefile b/Makefile index 337c8f6..c5c2260 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ docs: @sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_OUTPUT_TYPES%/{r doc/pages/output_types.md' -e 'd}' doc/libdpf_full.md @sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_EVALUATION%/{r doc/pages/evaluation.md' -e 'd}' doc/libdpf_full.md @sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_ITERABLES%/{r doc/pages/iterables.md' -e 'd}' doc/libdpf_full.md - @sed -i -e '/%LIBDPF_INCLUDE_PIR_PIR1%/{r doc/pages/pir1.md' -e 'd}' doc/libdpf_full.md @sed -i -e '/%LIBDPF_INCLUDE_MISC_BUGS%/{r BUGS.md' -e 'd}' doc/libdpf_full.md @sed -i -e '/%LIBDPF_INCLUDE_MISC_CHANGES%/{r CHANGES.md' -e 'd}' doc/libdpf_full.md @sed -i -e '/%LIBDPF_INCLUDE_MISC_TODO%/{r TODO.md' -e 'd}' doc/libdpf_full.md diff --git a/doc/Doxyfile b/doc/Doxyfile index 483ef1b..45b8630 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -291,7 +291,10 @@ TAB_SIZE = 4 # @} or use a double escape (\\{ and \\}) ALIASES = "license=@par License:^^" \ - "complexity=@par Complexity:^^" + "complexity=@par Complexity:^^" \ + "rounds=@par Rounds:^^" \ + "communication=@par Communication:^^" \ + "preprocessing=@par Preprocessing:^^" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -383,7 +386,7 @@ MARKDOWN_STRICT = YES # Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. -TOC_INCLUDE_HEADINGS = 5 +TOC_INCLUDE_HEADINGS = 1 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is @@ -1011,15 +1014,30 @@ INPUT = include/dpf.hpp \ include/grotto.hpp \ include/grotto/ \ doc/libdpf_full.md \ - doc/pages/ppvc.md \ doc/examples.dox \ doc/namespaces.dox \ doc/directories.dox \ doc/assets/assets.dox \ + doc/ideal_functionalities.dox \ + doc/pages/guided_tour.md \ + doc/pages/capabilities.md \ + doc/pages/verifiability.md \ + doc/pages/programmability.md \ + doc/pages/multiparty.md \ + doc/pages/which_dpf.md \ + doc/pages/comparisons.md \ + doc/pages/multipoint.md \ + doc/pages/dealer_free.md \ + doc/pages/beaver.md \ + doc/pages/api.md \ + doc/pages/bibliography.md \ + doc/pages/jet_and_ring.md \ + doc/pages/repr_and_twist.md \ + doc/pages/applications.md \ + doc/pages/ppvc.md \ thirdparty/thirdparty.dox \ examples \ - doc \ - test + doc # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses @@ -1076,7 +1094,8 @@ RECURSIVE = YES # run. EXCLUDE = *.md \ - *.dox + *.dox \ + test # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1303,7 +1322,7 @@ VERBATIM_HEADERS = YES # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. -CLANG_ASSISTED_PARSING = YES +CLANG_ASSISTED_PARSING = NO # If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS # tag is set to YES then Doxygen will add the directory of each input to the @@ -1454,11 +1473,23 @@ HTML_EXTRA_STYLESHEET = thirdparty/doxygen-awesome-css/doxygen-awesome.css \ # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = thirdparty/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ +HTML_EXTRA_FILES = doc/doc-extras.js \ + thirdparty/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ thirdparty/doxygen-awesome-css/doxygen-awesome-paragraph-link.js \ thirdparty/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js \ thirdparty/doxygen-awesome-css/doxygen-awesome-interactive-toc.js \ - thirdparty/doxygen-awesome-css/doxygen-awesome-tabs.js + thirdparty/doxygen-awesome-css/doxygen-awesome-tabs.js \ + doc/papers/boyle-gilboa-ishai-fss-improvements-eprint-2018-707.pdf \ + doc/papers/guo-yang-wang-zhang-xie-zhang-liu-half-tree-eprint-2022-1431.pdf \ + doc/papers/doerner-shelat-scaling-oram-eprint-2017-827.pdf \ + doc/papers/boyle-chandran-gilboa-gupta-ishai-kumar-rathee-mixed-mode-fss-eprint-2020-1392.pdf \ + doc/papers/de-castro-polychroniadou-verifiable-fss-eprint-2021-580.pdf \ + doc/papers/patra-schneider-suresh-yalame-aby2-eprint-2020-1225.pdf \ + doc/papers/zyskind-yanai-pentland-three-party-dpf-eprint-2024-1658.pdf \ + doc/papers/storrier-vadapalli-lyons-henry-grotto-eprint-2023-108.pdf \ + doc/papers/boyle-gilboa-ishai-kolobov-it-dpf-eprint-2023-028.pdf \ + doc/papers/chou-orlandi-simplest-ot-eprint-2015-267.pdf \ + doc/papers/boyar-peralta-aes-sbox-eprint-2011-332.pdf # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. @@ -1471,7 +1502,7 @@ HTML_EXTRA_FILES = thirdparty/doxygen-awesome-css/doxygen-awesome-darkmode # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = AUTO_DARK +HTML_COLORSTYLE = LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1538,7 +1569,7 @@ HTML_CODE_FOLDING = YES # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COPY_CLIPBOARD = YES +HTML_COPY_CLIPBOARD = NO # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by @@ -1809,7 +1840,7 @@ GENERATE_TREEVIEW = YES # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -PAGE_OUTLINE_PANEL = YES +PAGE_OUTLINE_PANEL = NO # When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if # the side bar is limited to only the treeview area (value NO) or if it should @@ -2138,7 +2169,8 @@ PAPER_TYPE = a4 # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = amsmath \ + amssymb # The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for # the generated LaTeX document. The header should contain everything until the @@ -2521,7 +2553,8 @@ SEARCH_INCLUDES = YES # RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = thirdparty \ +INCLUDE_PATH = include \ + thirdparty \ thirdparty/asio/asio/include # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard @@ -2540,10 +2573,17 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = HEDLEY_ALWAYS_INLINE=[[gnu::always_inline]] \ - HEDLEY_PURE=[[gnu::pure]] \ - HEDLEY_CONST=[[gnu::const]] \ - HEDLEY_NO_THROW=[[gnu::nothrow]] \ +PREDEFINED = HEDLEY_ALWAYS_INLINE= \ + HEDLEY_PURE= \ + HEDLEY_CONST= \ + HEDLEY_NO_THROW= \ + HEDLEY_WARN_UNUSED_RESULT= \ + HEDLEY_DEPRECATED_FOR(y,r)= \ + HEDLEY_DIAGNOSTIC_PUSH= \ + HEDLEY_DIAGNOSTIC_POP= \ + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED= \ + HEDLEY_NON_NULL(x)= \ + HEDLEY_PRAGMA(x)= \ psnip_int8_t=int8_t \ psnip_uint8_t=uint8_t \ psnip_int16_t=int16_t \ @@ -2952,7 +2992,7 @@ PLANTUMLFILE_DIRS = # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_GRAPH_MAX_NODES = 50 +DOT_GRAPH_MAX_NODES = 500 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml index 173a04a..2b7de4f 100644 --- a/doc/DoxygenLayout.xml +++ b/doc/DoxygenLayout.xml @@ -1,21 +1,71 @@ - - + + - - - - - - - + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/directories.dox b/doc/directories.dox index 3733f03..f200137 100644 --- a/doc/directories.dox +++ b/doc/directories.dox @@ -10,6 +10,9 @@ /// @dir ./examples/evaluation /// @brief evaluation +/// @dir ./examples/grotto +/// @brief Grotto jets, ring switch, and math gadgets + /// @dir ./examples/input_types /// @brief inputs @@ -25,8 +28,5 @@ /// @dir ./include/dpf /// @brief location of most `libdpf++` headers -/// @dir ./test -/// @brief collection of `GTest` test cases - /// @dir ./thirdparty /// @brief submodules and other third-party content diff --git a/doc/doc-extras.js b/doc/doc-extras.js new file mode 100644 index 0000000..e677d45 --- /dev/null +++ b/doc/doc-extras.js @@ -0,0 +1,130 @@ +(function () { + function copyText(text, button) { + var done = function () { + var previous = button.textContent; + button.textContent = "Copied"; + window.setTimeout(function () { button.textContent = previous; }, 1200); + }; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(done, function () {}); + return; + } + var area = document.createElement("textarea"); + area.value = text; + document.body.appendChild(area); + area.select(); + try { document.execCommand("copy"); done(); } catch (e) {} + document.body.removeChild(area); + } + + document.querySelectorAll(".mwe-copy").forEach(function (button) { + button.addEventListener("click", function () { + var block = button.parentElement.querySelector("pre"); + if (!block) return; + copyText(block.innerText.replace(/\n$/, ""), button); + }); + }); + + // Sidebar order. Destinations match DoxygenLayout.xml, depth-first. + var docPages = [ + ["index.html", "Home"], + ["basics.html", "First program"], + ["which_dpf.html", "Pick a construction"], + ["guided_tour.html", "Guided tour"], + ["listings.html", "Code examples"], + ["input_type_examples.html", "Domain samples"], + ["output_type_examples.html", "Payload samples"], + ["evaluation_examples.html", "Evaluation samples"], + ["grotto_examples.html", "Grotto samples"], + ["iteratable_examples.html", "Iterable samples"], + ["capabilities.html", "Capabilities"], + ["verifiability.html", "Verifiability"], + ["programmability.html", "Programmability"], + ["comparisons.html", "Comparisons"], + ["multipoint_keys.html", "Multipoint"], + ["multiparty.html", "Multiparty"], + ["dealer_free.html", "Dealer-free keygen"], + ["beaver_triples.html", "Beaver triples"], + ["jet_and_ring.html", "Grotto"], + ["repr_and_twist.html", "Representation shift"], + ["ppvc_manual.html", "Programmable vectors"], + ["applications.html", "Application sketches"], + ["getting_started.html", "Manual"], + ["input_types.html", "Domains"], + ["output_types.html", "Payloads"], + ["evaluation.html", "Evaluation"], + ["iterables.html", "Iterables"], + ["api_reference.html", "Call index"], + ["namespaces.html", "Namespaces"], + ["annotated.html", "Class list"], + ["classes.html", "Class index"], + ["files.html", "File list"], + ["bibliography.html", "Bibliography"], + ["ideal_functionalities.html", "Ideal functionalities"], + ["changes.html", "Changelog"], + ["bugs.html", "Bugs"], + ["todo.html", "TODO"], + ["license.html", "License"], + ["authors.html", "Authors"], + ["submodules.html", "Submodules"] + ]; + + function currentPage() { + var name = location.pathname.split("/").pop(); + if (!name || name === "") return "index.html"; + return name; + } + + function pageNav(kind) { + var name = currentPage(); + var index = -1; + for (var i = 0; i < docPages.length; i++) { + if (docPages[i][0] === name) index = i; + } + if (index < 0) return null; + var nav = document.createElement("nav"); + nav.className = "page-nav" + (kind === "bottom" ? " page-nav-bottom" : ""); + nav.setAttribute("aria-label", "Pages"); + function edge(text) { + var span = document.createElement("span"); + span.className = "page-nav-edge"; + span.textContent = text; + return span; + } + if (index > 0) { + var back = document.createElement("a"); + back.href = docPages[index - 1][0]; + back.textContent = "\u2190 " + docPages[index - 1][1]; + nav.appendChild(back); + } else { + nav.appendChild(edge("\u2190")); + } + if (index + 1 < docPages.length) { + var next = document.createElement("a"); + next.className = "page-nav-next"; + next.href = docPages[index + 1][0]; + next.textContent = docPages[index + 1][1] + " \u2192"; + nav.appendChild(next); + } else { + var done = edge("\u2192"); + done.className = "page-nav-edge page-nav-next"; + nav.appendChild(done); + } + return nav; + } + + function mountPageNav() { + var contents = document.querySelector("#doc-content .contents") || document.querySelector(".contents"); + if (!contents || contents.querySelector(".page-nav")) return; + var top = pageNav("top"); + if (!top) return; + contents.insertBefore(top, contents.firstChild); + contents.appendChild(pageNav("bottom")); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", mountPageNav); + } else { + mountPageNav(); + } +})(); diff --git a/doc/examples.dox b/doc/examples.dox index 1d5f8f9..0037fa4 100644 --- a/doc/examples.dox +++ b/doc/examples.dox @@ -66,9 +66,15 @@ /// @example evaluation/eval_full.cpp eval_full.cpp /// @brief an example of `dpf::eval_full` in use +/// @example evaluation/defer_eval.cpp defer_eval.cpp +/// @brief pre-assign `defer_eval_interval` then rotate after input assign + /// @example evaluation/eval_sequence.cpp eval_sequence.cpp /// @brief an example of `dpf::eval_sequence` in use +/// @example evaluation/eval_inner_product.cpp eval_inner_product.cpp +/// @brief fused inner product: scalar range, full domain, paired leaf outputs, ancestor slot, recipe + /// @example evaluation/memoizers.cpp memoizers.cpp /// @brief an example of `dpf::memoizers` in use @@ -78,6 +84,27 @@ /// @example evaluation/buffered_prg.cpp buffered_prg.cpp /// @brief an example of `dpf::randomness::buffered_prg` and `lane_table` +/// @example evaluation/eval_dpf3_point.cpp eval_dpf3_point.cpp +/// @brief three-evaluator point DPF (`make_dpf3` / Shamir open) + +/// @example evaluation/eval_dpf3_doerner_shelat.cpp eval_dpf3_doerner_shelat.cpp +/// @brief dual-spine Doerner–Shelat (2,3) keygen vs dealer `make_dpf3` + +/// @example evaluation/eval_dpf3_cmp_ic.cpp eval_dpf3_cmp_ic.cpp +/// @brief three-party comparison and interval-containment keys + +/// @} + + + +/// @{ + +/// @example grotto/jet_and_ring.cpp jet_and_ring.cpp +/// @brief binomial jet readouts and an exact Z/2^n to Z/M ring switch + +/// @example grotto/repr_and_twist.cpp repr_and_twist.cpp +/// @brief Fibonacci / geometric representation shift and twisted monomials + /// @} @@ -105,10 +132,77 @@ /// @} + +// applications +/// @{ + +/// @example applications/duoram3.cpp duoram3.cpp +/// @brief 3-party Duoram read and update, the DPF step + +/// @example applications/subleq.cpp subleq.cpp +/// @brief MPC SUBLEQ: prepaid unit reads, scaled write, path branch + +/// @example applications/bitmore.cpp bitmore.cpp +/// @brief BitMore query for 2^L servers + +/// @example applications/keyword_pir.cpp keyword_pir.cpp +/// @brief two-server keyword PIR + +/// @example applications/prio.cpp prio.cpp +/// @brief Prio histogram and a Poplar prefix walk + +/// @example applications/idpf_agg.cpp idpf_agg.cpp +/// @brief I-DPF max and k-th order statistic via eval_until + +/// @example applications/llama.cpp llama.cpp +/// @brief LLAMA comparison and degree-0 spline gates + +/// @example applications/pika.cpp pika.cpp +/// @brief Pika function lookup + +/// @example applications/prac.cpp prac.cpp +/// @brief PRAC binary search (one IDPF) and a wide heap leaf + +/// @example applications/express.cpp express.cpp +/// @brief Express mailbox write and one-hot audit + +/// @example applications/splinter.cpp splinter.cpp +/// @brief Splinter private query on public data + +/// @example applications/mastic.cpp mastic.cpp +/// @brief Mastic weighted heavy-hitter prefixes + +/// @example applications/waldo.cpp waldo.cpp +/// @brief Waldo append-only time series and a private-threshold aggregate + +/// @example applications/sabre.cpp sabre.cpp +/// @brief Sabre mailbox write with a verifiable-DPF audit + +/// @example applications/ledger23.cpp ledger23.cpp +/// @brief (2,3) ledger append with a verified point key + +/// @example applications/psi.cpp psi.cpp +/// @brief private set intersection via a DPF oblivious PRF + +/// @example applications/range_count.cpp range_count.cpp +/// @brief range count of secret values on a public interval + +/// @example applications/floram.cpp floram.cpp +/// @brief Floram read and write from address shares + +/// @example applications/pir3.cpp pir3.cpp +/// @brief three-server index PIR on a (2,3) point key + +/// @example applications/it_pir3.cpp it_pir3.cpp +/// @brief three-server index PIR on the information-theoretic DPF + +/// @} + + + /// @{ /// @example mwe/ppvc.cpp ppvc.cpp /// @brief a point-programmable vector commitment -/// @} - +/// @} \ No newline at end of file diff --git a/doc/footer.html b/doc/footer.html index e3bbd3d..18dbf1a 100644 --- a/doc/footer.html +++ b/doc/footer.html @@ -1,18 +1,20 @@ - + - - - - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/doc/header.html b/doc/header.html index 90ea718..677a6fa 100644 --- a/doc/header.html +++ b/doc/header.html @@ -1,17 +1,25 @@ - - + + + - $projectname: $title $title + + + - + + + + + + @@ -34,29 +42,30 @@ $extrastylesheet - - -
+ +
-
- - -
- - - - - - - - - - - + +
+
libdpf++
$searchbox
+ + + + + + + + + +
$searchbox
diff --git a/doc/ideal_functionalities.dox b/doc/ideal_functionalities.dox new file mode 100644 index 0000000..26f557d --- /dev/null +++ b/doc/ideal_functionalities.dox @@ -0,0 +1,619 @@ +/// \page ideal_functionalities Ideal functionalities +/// +/// Each MPC protocol's file page carries one figure: the ideal functionality +/// that protocol realizes. The figure states the parties, the inputs, the +/// outputs, and what is revealed. A protocol may open a masked value or a +/// public offset; that appears in the figure only when the parties learn it. +/// +/// ## Sharing +/// +/// - \ref secret_share.hpp "F_Open" +/// - \ref shamir3.hpp "F_Shamir" +/// +/// ## Dealer keys +/// +/// - \ref dpf_key.hpp "F_DPF" +/// - \ref incremental.hpp "F_IDPF" +/// - \ref grow.hpp "F_Grow" +/// - \ref wildcard.hpp "F_Assign" +/// - \ref dcf.hpp "F_DCF" +/// - \ref blocked_dcf.hpp "F_BDCF" +/// - \ref interval.hpp "F_IC" +/// - \ref multipoint.hpp "F_MPDPF" +/// +/// ## Two-party generation and evaluation +/// +/// - \ref doerner_shelat.hpp "F_DS" +/// - \ref grow_ds.hpp "F_GrowDS" +/// - \ref geneval.hpp "F_GenEval" +/// - \ref beaver.hpp "F_Beaver and F_BeaverAuth" +/// - \ref constrained_cmp.hpp "F_CCMP" +/// - \ref verifiable.hpp "F_VDPF, F_Sketch, and F_OblivHash" +/// +/// ## Three evaluators +/// +/// - \ref dpf3.hpp "F_DPF3" +/// - \ref dpf3_ds.hpp "F_DPF3DS" +/// - \ref dpf3_cmp.hpp "F_DPF3CMP" +/// - \ref dpf3_multipoint.hpp "F_DPF3MP" +/// +/// ## Offset corrections +/// +/// - \ref offset_horner.hpp "F_Horner" +/// - \ref offset_poly.hpp "F_Poly" +/// - \ref offset_jet.hpp "F_Jet" +/// - \ref ring_switch.hpp "F_Switch" +/// - \ref offset_repr.hpp "F_Repr" +/// - \ref offset_twist.hpp "F_Twist" +/// - \ref carry.hpp "F_Carry" + +/// @file dpf/secret_share.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Open" +/// digraph F_Open { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Open
Parties. P0 and P1. Each holds one share.
Input. An additive share, a subtractive share, or an XOR share.
Output. Both parties receive the opened value:
additive is share0 + share1, subtractive is share0 - share1,
XOR is share0 XOR share1.
Leakage. That opened value, and nothing else.
+/// >]; +/// } +/// \enddot + +/// @file dpf/shamir3.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Shamir" +/// digraph F_Shamir { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Shamir
Parties. Evaluators 1, 2, and 3 over fp61.
Input. A secret s. The dealer samples a uniform slope a.
Output. Party i receives s_i = s + a*i.
Any two parties reconstruct s by Lagrange.
A share embeds into a 61-bit XOR string for the (2,3) point key.
Leakage. One share hides s. Two shares reveal it.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dpf_key.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DPF" +/// digraph F_DPF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DPF
Parties. An honest dealer, then evaluators P0 and P1.
Input. A secret point alpha and one or more payloads beta.
A payload may be a wildcard, filled later by F_Assign.
The interior PRG is BGI or Half-Tree. The outputs do not change.
Output. Key k_i to party i.
Eval(x) returns subtractive shares of beta when x = alpha, else 0.
An XOR payload is an XOR share. Several outputs are independent.
Leakage. The keys hide alpha and beta.
Eval of an unassigned wildcard aborts.
+/// >]; +/// } +/// \enddot + +/// @file dpf/incremental.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_IDPF" +/// digraph F_IDPF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_IDPF
Parties. Same dealer and two evaluators as F_DPF.
Input. Secret point alpha.
Each output is placed at a public prefix length.
An optional comparison spec adds an F_DCF channel on the same alpha.
Output. One key per party.
Eval of a prefix slot returns that slot's shares on its programmed domain.
The comparison channel returns F_DCF shares.
Leakage. None beyond those shares. A wildcard slot aborts until F_Assign.
+/// >]; +/// } +/// \enddot + +/// @file dpf/grow.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Grow" +/// digraph F_Grow { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Grow
Parties. An honest dealer holding both F_IDPF keys, then evaluators P0 and P1.
Input. An existing key pair for secret alpha.
+/// extend: the next path bit of alpha and specs whose prefixes equal the new depth.
+/// add_output: specs whose prefixes are already levels of the key.
+/// Optional warm path memoizers supply the on-path seeds (must already be filled).
Output. A new key pair whose type is the old key plus the new material.
+/// Earlier correction words, advice bits, leaves, and comparison words are unchanged share for share.
+/// Eval of an old slot on the new keys matches the old keys. New slots match F_IDPF for those specs.
Leakage. None beyond the new keys. Specs that need a deeper tree, share a packing group with an old slot, or sit on the wrong level abort.
+/// >]; +/// } +/// \enddot + +/// @file dpf/grow_ds.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_GrowDS" +/// digraph F_GrowDS { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_GrowDS
Parties. P0 and P1 hold matching F_IDPF keys and on-path seeds at the frontier.
+/// P2 may deal pads and learns nothing. A joint local simulator holds both keys.
Input. XOR shares of alpha, warm path memoizers through the old depth,
+/// and the same specs as F_Grow. extend_ds opens one new interior correction word.
+/// add_output_ds opens only the new leaf (or comparison) material.
Output. The same grown keys F_Grow would return for that alpha and those specs,
+/// with the same roots and the same public correction words as a dealer extend / add_output.
Leakage. Default: none beyond the keys. P2 never sees alpha or payloads.
+/// Off-path memoizer leftovers are not a valid plant site for secret outputs.
+/// >]; +/// } +/// \enddot + +/// @file dpf/wildcard.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Assign" +/// digraph F_Assign { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Assign
Parties. P0 and P1, holding keys from F_DPF or F_IDPF.
Input. A payload beta, or shares of beta, for a wildcard slot.
A public delta is applied as given.
A subtractive share is converted with that party's coefficient.
Output. The same keys, now evaluating to shares of beta at alpha.
Alpha does not move.
Leakage. None. Eval before Assign aborts.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dcf.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DCF" +/// digraph F_DCF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DCF
Parties. An honest dealer, then evaluators P0 and P1.
Input. Secret point alpha, payloads if_true and if_false,
and a predicate lt, leq, gt, or geq.
A path-paint kind plants one public constant on each sibling subtree.
Output. One key per party.
Eval(x) returns additive shares of if_true when the predicate holds,
and of if_false otherwise.
Leakage. None. The same outputs are realized by F_BDCF.
+/// >]; +/// } +/// \enddot + +/// @file dpf/blocked_dcf.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_BDCF" +/// digraph F_BDCF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_BDCF
Parties. Same dealer and evaluators as F_DCF.
Input. The F_DCF inputs, plus a public checkpoint schedule.
Ring words are stored only at those checkpoints.
A residual tail, when the key sets it, is a table on the node at that height.
Output. Additive shares of the same predicate or path-paint as F_DCF.
Leakage. The schedule is public. It does not reveal alpha.
+/// >]; +/// } +/// \enddot + +/// @file dpf/interval.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_IC" +/// digraph F_IC { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_IC
Parties. An honest dealer, then evaluators P0 and P1.
Input. Secret mask r, public bounds p and q,
and payloads if_true and if_false.
Output. One key per party.
Eval(x) returns additive shares of if_true when
p <= (x - r) mod 2^n <= q, and of if_false otherwise.
Leakage. The bounds are public. r and the payloads stay hidden.
+/// >]; +/// } +/// \enddot + +/// @file dpf/multipoint.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_MPDPF" +/// digraph F_MPDPF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_MPDPF
Parties. An honest dealer, then evaluators P0 and P1.
Input. t distinct points and their payloads.
The verifiable tag selects VDPF buckets.
Output. m = O(t) bucket keys on a cuckoo packing with 3 probes.
Eval(x) sums the three probed buckets and matches the sum of the t point functions.
A batched proof is one 2-lambda token.
Leakage. None beyond the output shares and, when requested, the proof.
+/// >]; +/// } +/// \enddot + +/// @file dpf/doerner_shelat.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DS" +/// digraph F_DS { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DS
Parties. P0 and P1 hold the point. P2 deals pads and learns nothing.
Input. XOR shares of alpha, or additive shares.
Additive shares are converted by a ripple-carry. The sum is not opened.
Beta is public, or additively shared as leaf-key material without opening the payload.
An optional Reveal flag asks for the encoded point (and, for a packed wildcard, the lane).
Output. Each of P0 and P1 receives the F_DPF or F_DCF key make_dpf would emit
for that alpha, the same roots, and the same beaver coins.
When Reveal is set, the encoded point is an explicit output,
and a packed wildcard also returns its lane. Paint comparisons require Reveal.
Leakage. Default leakage is none beyond the keys.
P2 receives neither the point, the prefix, nor the payload.
The tree prefix, the lane, and a shared payload stay hidden unless Reveal is set.
+/// >]; +/// } +/// \enddot + +/// @file dpf/geneval.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_GenEval" +/// digraph F_GenEval { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_GenEval
Parties. P0 and P1. No reusable key is returned.
Input. XOR or additive shares of alpha, a public or shared payload,
and a public query: one point, an interval, a sequence, or the full domain.
Output. Shares of F_DPF on that query.
Leaves are subtractive. Comparison prefixes are additive.
geneval_cmp returns a prefix share at each public endpoint.
The point is not opened. Additive inputs are converted without opening the sum.
Leakage. Evaluators receive the query-trie correction words.
Off-path words are uniform. On-path words match a dealer key and hide the point.
+/// >]; +/// } +/// \enddot + +/// @file dpf/beaver.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Beaver" +/// digraph F_Beaver { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Beaver
Parties. P0 and P1 evaluate. P2 is an honest dealer.
Input. Additive shares of wires, and a public formula:
a polynomial, an inner product, a scale, or a bit-mux.
A later round may reuse a wire. Repeated factors share one blind.
Output. Additive shares of the formula. P2 learns nothing.
Classic triples are the same functionality on fresh wires.
Leakage. None. Opened masks delta = x + lambda are uniform.
+/// >]; +/// } +/// \enddot +/// +/// \dot "Functionality F_BeaverAuth" +/// digraph F_BeaverAuth { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_BeaverAuth
Parties. Same as F_Beaver. A MAC key Delta is fixed before sampling.
Input. The F_Beaver inputs. Every blind, monomial, and value is tagged under Delta.
Output. The same additive shares, together with tag shares.
verify_delta and verify_auth_opening accept only consistent tags.
Leakage. None when the check accepts. A bad tag aborts.
+/// >]; +/// } +/// \enddot + +/// @file dpf/constrained_cmp.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_CCMP" +/// digraph F_CCMP { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_CCMP
Parties. P0 holds x0. P1 holds x1.
Input. Positive integers with absolute difference 1.
Each party forms two bits from the low bits of its input.
Output. Both parties receive the bit 1{x0 < x1}.
The bit is one AND of those derived bits.
Leakage. That bit. If the inputs do not differ by one, the protocol aborts.
+/// >]; +/// } +/// \enddot + +/// @file dpf/verifiable.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_VDPF" +/// digraph F_VDPF { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_VDPF
Parties. P0 and P1, on keys generated under the verifiable tag.
Input. The F_DPF inputs, plus a public evaluation point.
Output. The F_DPF share, and a 2-lambda proof token from each party.
Verify accepts exactly when the path and the output share match:
correction seeds, the leaf correction word, and comparison value words fold into the token.
Leakage. The accept or reject bit. Nothing else.
A tampered seed, leaf, value word, or proof rejects. A zero token rejects.
+/// >]; +/// } +/// \enddot +/// +/// \dot "Functionality F_Sketch" +/// digraph F_Sketch { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Sketch
Parties. P0 and P1, on an extractable key. Default eval does not fold.
Input. Payload shares written during evaluation,
and caller-chosen fp61 challenges, one per payload.
Output. Subtractive shares of three moments (z1, z2, z3).
sketch_verify accepts when z2^2 = z1*z3 after the shares are opened,
that is, when the opened payloads have at most one nonzero point.
Leakage. The accept or reject bit. A second hot point rejects.
+/// >]; +/// } +/// \enddot +/// +/// \dot "Functionality F_OblivHash" +/// digraph F_OblivHash { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_OblivHash
Parties. P0 and P1. Correlated AND triples come from the dealer tape.
The two-party realization is party/oblivious_hash.hpp.
Input. Each party holds the seed it owns,
and a XOR share of the path prefix. The level is public.
Output. Both parties receive H(s0) XOR H(s1),
the same block as hash_node on the joined prefix and the two seeds.
Leakage. That opened block. The prefix is not opened.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dpf3.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DPF3" +/// digraph F_DPF3 { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DPF3
Parties. An honest dealer and evaluators 1, 2, and 3.
Input. Secret point alpha and payload beta in fp61.
The updatable tag keeps the leaf writable.
The verifiable and extractable tags select those checks.
Output. One key per evaluator.
Eval(x) is a degree-1 Shamir share of beta when x = alpha, else 0.
Any two parties reconstruct. Update(beta') rewrites the payload and does not move alpha.
Update on a non-updatable key aborts. verify_dpf3 accepts only a consistent triple of proofs.
Leakage. One key hides alpha and beta. A bad proof rejects.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dpf3_ds.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DPF3DS" +/// digraph F_DPF3DS { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DPF3DS
Parties. Two shareholders of alpha, producing keys for three evaluators.
Input. XOR shares of alpha, after the signed-MSB flip on share 0.
Payload beta in fp61 is a shared input of keygen, not an opened point.
Verifiable, updatable, and extractable select the same options as F_DPF3.
Reveal on a spine is the same optional flag as F_DS.
Output. Three F_DPF3 keys for alpha = x0 XOR x1 and that beta.
Two independent Doerner-Shelat spines carry the Fig. 3 payloads.
Leakage. Neither share alone reveals alpha or beta.
The point and the payload stay shared unless Reveal is set on a spine.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dpf3_cmp.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DPF3CMP" +/// digraph F_DPF3CMP { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DPF3CMP
Parties. Evaluators 1, 2, and 3.
Input. A secret comparison or interval point, and a payload.
Each evaluator holds one DCF half. A Shamir tip of the payload is bookkeeping for updates.
Interval containment also takes the public scale c_x in {-1, 0, 1}.
Output. One additive share of the F_DCF or F_IC predicate value per evaluator,
unreduced in uint64. A complementary pair (1 with 2, or 3 with 2) opens by summation into fp61.
One party holds one DCF share, not both.
Leakage. One evaluator does not learn the point or the clear payload.
+/// >]; +/// } +/// \enddot + +/// @file dpf/dpf3_multipoint.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_DPF3MP" +/// digraph F_DPF3MP { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_DPF3MP
Parties. Same three evaluators as F_DPF3.
Input. t distinct points and fp61 payloads.
Packing matches F_MPDPF. Each bucket is an F_DPF3 key.
Output. Eval(x) sums Shamir shares across the three probes
and reconstructs to the sum of the t point functions.
Update replays the existing cuckoo placement and rewrites each occupied bucket.
It does not draw a new packing.
Leakage. Same as F_DPF3 on each bucket.
+/// >]; +/// } +/// \enddot + +/// @file grotto/offset_horner.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Horner" +/// digraph F_Horner { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Horner
Parties. P0 and P1. The dealer keyed powers 1, c, c^2, c^3 at a hidden center.
Input. Additive shares of x. Public coefficients of a cubic.
The parties open eta = x - r. The center is 2r when wired that way.
Output. Additive shares of the polynomial at the wrapped group element.
The binomial shift by the public carry kappa is local. No further round.
Leakage. eta. Not x, and not the center.
+/// >]; +/// } +/// \enddot + +/// @file grotto/offset_poly.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Poly" +/// digraph F_Poly { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Poly
Parties. P0 and P1, after the same public opening of eta as F_Horner.
Input. A polynomial of runtime degree, at most 16.
Coefficients are public, or additively shared.
Output. Additive shares of f at the wrapped x.
Public coefficients are a local binomial shift and a dot.
Shared coefficients use that local shift and one F_Beaver inner product.
Leakage. eta, and nothing further from F_Beaver.
+/// >]; +/// } +/// \enddot + +/// @file grotto/offset_jet.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Jet" +/// digraph F_Jet { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Jet
Parties. P0 and P1. The dealer keyed binom(center, k) for k = 0..d.
Input. Additive shares of x. The parties open eta = x - r.
Output. Additive shares, in Z/2^64, of binom(x, 0), ..., binom(x, d)
after the public Chu-Vandermonde shift by the carry kappa.
A public dot, forward difference, or hockey-stick prefix is local.
Leakage. eta. Not x, and not the center.
+/// >]; +/// } +/// \enddot + +/// @file grotto/ring_switch.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Switch" +/// digraph F_Switch { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Switch
Parties. P0 and P1. The dealer keyed the wrap comparison and a split of r.
Input. An n-bit limb x, n at most 64, and a public destination modulus.
The parties open eta = x - r.
Destinations are zn64, zn128, field128, and the P-256 scalar field.
Output. Additive shares of x in that residue group.
The wrap indicator stays inside the share. A factor of the modulus reduces locally.
Leakage. eta. Not x, and not the wrap bit in the clear.
+/// >]; +/// } +/// \enddot + +/// @file grotto/offset_repr.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Repr" +/// digraph F_Repr { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Repr
Parties. P0 and P1. The dealer keyed a state vector S_c at the hidden center.
Input. A public invertible matrix M over Z/2^64, or XOR shares for a GF(2) checkpoint.
The parties open eta = x - r. The hot piece has a public carry kappa.
Output. Shares of M^kappa * S_c.
Negative kappa multiplies by M inverse. The determinant must be odd.
Fibonacci, geometric powers, and a CRC jump are this functionality.
Leakage. eta and the public matrix power. Not the state, and not the center.
+/// >]; +/// } +/// \enddot + +/// @file grotto/offset_twist.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Twist" +/// digraph F_Twist { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Twist
Parties. P0 and P1. The dealer keyed c^m * lambda^c in Z/2^64.
Input. Public coefficients a_m and a public unit lambda, or the dyadic tag lambda = 1/2.
The parties open eta = x - r. The hot piece has public carry kappa.
Output. For odd lambda, additive shares of sum a_m (c+kappa)^m lambda^(c+kappa).
For lambda = 1/2, additive shares of sum a_m x^m / 2^x.
The untwisted sum is shifted by a masked low-limb carry. The opened mask is uniform.
The untwisted sum stays shared.
Leakage. eta. Not the untwisted sum, and not the center.
+/// >]; +/// } +/// \enddot + +/// @file grotto/carry.hpp +/// +/// @par Ideal functionality +/// \dot "Functionality F_Carry" +/// digraph F_Carry { +/// graph [bgcolor="transparent"]; +/// node [shape=plaintext, fontname="Helvetica", fontsize=11]; +/// F [label=< +/// +/// +/// +/// +/// +/// +///
F_Carry
Parties. P0 and P1, with optional verifiable comparison keys and a MAC key.
Input. Additive shares of an n-bit limb, a public shift, and,
for the carry-out form, public knowledge that the secret is negative, nonnegative, or unknown.
Output. Additive shares of the matching cleartext oracle:
exact truncate-and-reduce, arithmetic right shift plus the unit correction,
exact fused arithmetic right shift, signed extension,
unknown-sign carry-out, window overflow, or fused same-ring.
Leakage. A fresh masked opening is uniform and hides the secret limb.
A bad path proof or a bad MAC aborts. The secret limb is not learned.
+/// >]; +/// } +/// \enddot diff --git a/doc/libdpf.md b/doc/libdpf.md index c861f0c..cbd0047 100644 --- a/doc/libdpf.md +++ b/doc/libdpf.md @@ -1,31 +1,35 @@ \mainpage notitle
-[TOC] - -![](libdpf-tagline.png) - %LIBDPF_INCLUDE_GETTING_STARTED_INTRODUCTION% -- - - -
← Prev
-
[Next →](@ref basics)
- -\page basics DPF Basics - -[TOC] +\page basics First program %LIBDPF_INCLUDE_GETTING_DPF_BASICS% -- - - -
[← Prev](mainpage)
-
[Next →](@ref getting_started)
+ + + + + + +\page getting_started Manual + +The domain, the payload, and the walk. +Shorter domains make shorter keys. + + - [Domains](@ref input_types) — the secret index + - [Payloads](@ref output_types) — the group at that index + - [Evaluation](@ref evaluation) — point, interval, full domain, sequence + - [Iterables](@ref iterables) — walk the shares a multi-point eval wrote + +Samples for each of those are under [Code examples](@ref listings). @@ -33,69 +37,30 @@ -\page getting_started Getting Started - -[TOC] - -The getting started guide consists of the following subsections. - - - \subpage input_types - - \subpage output_types - - \subpage evaluation - - \subpage iterables - -- - - -
[← Prev](@ref mainpage)
-
[Next →](@ref input_types)
- - - - - - - -\page input_types Input Types - -[TOC] +\page input_types Domains %LIBDPF_INCLUDE_GETTING_STARTED_INPUT_TYPES% -- - - -
[← Prev](@ref getting_started)
-
[Next →](@ref output_types)
- -\page output_types Output Types - -[TOC] +\page output_types Payloads %LIBDPF_INCLUDE_GETTING_STARTED_OUTPUT_TYPES% -- - - -
[← Prev](@ref input_types)
-
[Next →](@ref evaluation)
- -\page evaluation Evaluating DPFs - -[TOC] +\page evaluation Evaluation %LIBDPF_INCLUDE_GETTING_STARTED_EVALUATION% -- - - -
[← Prev](@ref output_types)
-
[Next →](@ref iterables)
- @@ -104,58 +69,8 @@ The getting started guide consists of the following subsections. \page iterables Iterables -[TOC] - %LIBDPF_INCLUDE_GETTING_STARTED_ITERABLES% -- - - -
[← Prev](@ref evaluation)
-
[Next →](@ref pir)
- - - - - -\page pir PIR - -[TOC] - -The getting started guide consists of the following subsections. - - - \subpage pir1 - - \subpage pir2 - -- - - -
[← Prev](@ref iterable)
-
[Next →](@ref pir1)
- -\page pir1 PIR1 - -[TOC] - -%LIBDPF_INCLUDE_PIR_PIR1% - -- - - -
[← Prev](@ref evaluation)
-
[Next →](@ref miscellany)
- -\page miscellany Miscellany - -[TOC] - -The miscellany page consists of the following subpages. - - - \subpage bugs - - \subpage changes - - \subpage todo - - \subpage submodules - - \subpage authors - - \subpage license - -- - - -
[← Prev](@ref pir)
-
[Next →](@ref bugs)
- @@ -164,46 +79,28 @@ The miscellany page consists of the following subpages. \page bugs Bugs -[TOC] - %LIBDPF_INCLUDE_MISC_BUGS% -- - - -
[← Prev](@ref miscellany)
-
[Next →](@ref changes)
- -\page changes CHANGELOG - -[TOC] +\page changes Changelog %LIBDPF_INCLUDE_MISC_CHANGES% -- - - -
[← Prev](@ref bugs)
-
[Next →](@ref todo)
- -\page todo TODO list - -[TOC] +\page todo TODO %LIBDPF_INCLUDE_MISC_TODO% -- - - -
[← Prev](@ref changes)
-
[Next →](@ref submodules)
- @@ -212,14 +109,8 @@ The miscellany page consists of the following subpages. \page submodules Submodules -[TOC] - %LIBDPF_INCLUDE_MISC_SUBMODULES% -- - - -
[← Prev](@ref todo)
-
[Next →](@ref authors)
- @@ -228,14 +119,8 @@ The miscellany page consists of the following subpages. \page authors Authors -[TOC] - %LIBDPF_INCLUDE_MISC_AUTHORS% -- - - -
[← Prev](@ref submodules)
-
[Next →](@ref license)
- @@ -244,26 +129,14 @@ The miscellany page consists of the following subpages. \page license License -[TOC] - %LIBDPF_INCLUDE_MISC_LICENSE% -- - - -
[← Prev](@ref authors)
-
[Next →](@ref listings)
- -\page listings Code Listings - -[TOC] +\page listings Code examples %LIBDPF_INCLUDE_CODE_LISTINGS% - -- - - -
[← Prev](@ref license)
-
Next →
diff --git a/doc/pages/api.md b/doc/pages/api.md new file mode 100644 index 0000000..61d0a41 --- /dev/null +++ b/doc/pages/api.md @@ -0,0 +1,68 @@ +# Call index {#api_reference} + +This is the map of calls. Each row is one sentence and a header. +A complete program for the first rows is the [first program](@ref basics). +The same calls, one line each, continue below. + +The two namespaces are [dpf](@ref dpf) and [grotto](@ref grotto). +In the sidebar this page sits under **API reference** next to the +namespace, class, and file indexes. Start here when you know the call +you want. + +## Build a key + +| Call | What it returns | +| --- | --- | +| [dpf::make_dpf](@ref dpf/incremental.hpp) | Two party keys. A dealer knows the index and the payload. | +| [dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp) | The same key when the parties already share the index. | +| [dpf::geneval_point](@ref dpf/geneval.hpp) | Answer shares for one query. No reusable key. | +| [dpf::geneval_cmp](@ref dpf/geneval.hpp) | Answer shares for a comparison. No reusable key. | +| [dpf::make_dpf3](@ref dpf/dpf3.hpp) | Three keys. Any two open the value. | +| [dpf::make_multipoint](@ref dpf/multipoint.hpp) | One key for many secret points. | +| [dpf::ppvc](@ref ppvc_manual) | A point-programmable vector commitment. The hidden coordinate is set when the commitment is opened. | + +## Evaluate + +| Call | What you pass | +| --- | --- | +| [dpf::eval_point](@ref dpf/eval_point.hpp) | One public input. `*result` is that party's leaf share. | +| [dpf::eval_point](@ref dpf/eval_unified.hpp) `(dpf::cmp, ...)` | One public input on a comparison key. The share is additive. | +| [dpf::eval_interval](@ref dpf/eval_interval.hpp) | An inclusive range, one share per input. | +| [dpf::eval_sequence](@ref dpf/eval_sequence.hpp) | A sorted list of inputs. | +| [dpf::eval_full](@ref dpf/eval_full.hpp) | Every input in the domain. | +| [dpf::reconstruct](@ref dpf/secret_share.hpp) | Both shares. Leaf shares subtract. Comparison shares add. | +| [dpf::eval_point](@ref dpf/interval.hpp) `(dpf::ic, ...)` | One public input on an interval key. | + +## Comparisons and tags + +| Call | Meaning | +| --- | --- | +| [dpf::lt](@ref dpf/dcf.hpp), [leq](@ref dpf/dcf.hpp), [gt](@ref dpf/dcf.hpp), [geq](@ref dpf/dcf.hpp) | The comparison channel. One per key. | +| [dpf::eq](@ref dpf/dcf.hpp) | A point payload, not that channel. | +| [dpf::ic](@ref dpf/interval.hpp) | Public interval on a secret mask. | +| [dpf::at](@ref dpf/placement.hpp) | A value on a prefix, and a value at the leaf. | +| [dpf::verifiable](@ref dpf/verifiable.hpp) | The key carries a proof token. | +| [dpf::vec](@ref dpf/vec.hpp) | Several lanes at one leaf. No carry between lanes. | +| [dpf::wildcard_value](@ref dpf/wildcard.hpp) | Payload filled in after the key exists. Eval throws until then. | + +## Domains and leaves + +The catalogs, with the types that are inputs and the types that are outputs: + +- [Input types](@ref input_types): integers, `modint`, `xint`, `bitstring`, `keyword`, `keyword2`, fixed-point. +- [Output types](@ref output_types): `bit`, `twobit`, `nyble`, fields, curve points, shares, `vec`. + +`bit`, `twobit`, and `nyble` are packed output lanes. `keyword2` is a domain, not a leaf. + +## After the offset is public + +[Grotto](@ref guided_tour) evaluates a function of x once the public offset is open. +The pages are [offset Horner, jets, and ring switch](@ref jet_and_ring) and +[representation shift and twisted jets](@ref repr_and_twist). + +## Where to read next + +- [Which DPF?](@ref which_dpf) if you are still choosing the object. +- [Evaluating DPFs](@ref evaluation) for point, interval, sequence, and full-domain cost. +- [Bibliography](@ref bibliography) for the papers behind the keys. +- [Application mockups](@ref applications) for the DPF step inside a larger protocol. diff --git a/doc/pages/applications.md b/doc/pages/applications.md new file mode 100644 index 0000000..0b654e5 --- /dev/null +++ b/doc/pages/applications.md @@ -0,0 +1,564 @@ +# Application mockups {#applications} + +These programs are the DPF step of a larger protocol. +Each one is a single process. +A dealer stands in where the paper generates keys from shares. +They compile from the repository root: + + c++ -std=c++17 -march=native -I include -I thirdparty examples/applications/duoram3.cpp + +The same line with the other filenames builds the rest. +Optional Python bindings configure with `-DLIBDPF_PYTHON=ON` in the test +build directory, then `make pydpf` (and `make pydpf_pytest`). +`pydpf` exposes point / interval / full / sequence / recipe eval on +`uint8→uint64`, two-leaf multileaf, wildcard assign, 16-bit `eval_until`, +and `it_dpf3`. Not in this module yet: geneval, Doerner–Shelat, VDPF +`prove`/`sketch`, DCF, or grotto. +What those programs had to do by hand is [the library surface underneath](@ref application_gaps). + + +| Sketch | What the DPF step is | +| --- | --- | +| [3-party Duoram](@ref app_duoram) | Unit key, rotate, inner product | +| [SUBLEQ](@ref app_subleq) | Instruction fetch under a secret PC | +| [BitMore](@ref app_bitmore) | `2^L` servers, one hot slot | +| [Keyword PIR](@ref app_keyword) | Cuckoo buckets over keywords | +| [Prio](@ref app_prio) | Heavy-hitter prefix walk | +| [I-DPF max / k-th](@ref app_idpf_agg) | `eval_until` order statistics | +| [LLAMA](@ref app_llama) | Lookup and truncation | +| [Pika](@ref app_pika) | Comparison tree | +| [Express](@ref app_express) | Authenticated memory | +| [PRAC](@ref app_prac) | Range and prefix counts | +| [Splinter](@ref app_splinter) | Function secret-sharing queries | +| [Mastic](@ref app_mastic) | Aggregation | +| [Waldo](@ref app_waldo) | Private search | +| [Sabre](@ref app_sabre) | Robust aggregation | +| [(2,3) ledger](@ref app_ledger23) | Three-party update | +| [PSI](@ref app_psi) | Private set intersection | +| [Range count](@ref app_range_count) | Interval payload | +| [Floram](@ref app_floram) | ORAM read | +| [Three-server PIR](@ref app_pir3) | Information-theoretic DPF | +| [What the walk folds in](@ref application_gaps) | Library surface those programs used to do by hand | + + +## 3-party Duoram {#app_duoram} + +Vadapalli, Henry, and Goldberg ([USENIX Security 2023](@ref bib_duoram)) keep a memory in +shares and read or add at a secret index. +Preprocessing builds unit DPFs at a random index `r`. +Online, the parties open `i* - r` and cyclic-shift the expanded vector. +The read is the dot product of that vector with the memory. +The update adds a payload vector, shifted the same way. + +The program uses one dealer unit key for the read and one payload key +for the update. +`eval_full_inner_product(dpf::paired, key, memory, dpf::rotate{s})` dots +the unit key with the memory read at `(i + s) mod n`, so the caller keeps +one unrotated vector. +The update expands straight into the subtractive memory shares with +`eval_full_add_into(mem, key, dpf::rotate{s})` — no separate expansion, +shift, and add. +A 1-bit leaf at the same index lifts to `+1` or `-1`; its sign share is +recorded at keygen with `dpf::unit_sign`. +A word payload of `1` opens to `+1`. + +This is a `(2,2)` key. +The auxiliary party holds some of those keys. +[dpf::make_dpf3](@ref dpf/dpf3.hpp) shares one point Shamir-style among +three evaluators. + +\include{cpp} applications/duoram3.cpp + +## MPC SUBLEQ {#app_subleq} + +Jiang and Henry ([MSc thesis, University of Calgary](@ref bib_subleq)) emulate the +subtract-and-branch-if-less-than-or-equal-to-zero (SUBLEQ) OISC for +private function evaluation. One instruction is + + D[B] -= D[A] + pc = (D[B] ≤ 0) ? C : pc + 3 + +The DPF work is prepaid. The dealer ships wildcard unit keys +`[[* | 1]]` for the addresses that will be read (and for writing `B`). +Each party expands those keys over the full address domain with +`defer_eval_full` **before** the addresses are known — that is all of +the PRG. Online, the parties open each address into `offset_x`, and +`.get()` on the deferred view rotates the prepaid unit vector. A read +is the Du-Atallah / local dot of that view with the memory shares. The +write reuses the same `e_B` view: add `(-D[A]) · e_B` into the memory +shares (a Beaver multiplication in the protocol; the listing opens the +scale). The branch is a path evaluation only — +`make_dpf(0, dpf::leq(1))` evaluated at `x = D'[B]` — never a full-domain +expand of the word domain. The full protocol can instead assign a +wildcard comparison key to `x` and evaluate at public `0` with `geq` +(same bit). + +Instruction fetch is the same prepaid unit dotted against three sliding +windows of `D` (the three addresses of the instruction). The listing +starts after `(A, B, C)` are already shares. + +An equivalent prepaid form plants the unit at a random `r` and opens +`addr - r`, then folds the shift with `dpf::rotate{s}` on +`eval_full_inner_product` / `eval_full_add_into` (the Duoram / Pika +surface). Same online AES; only the blinding convention differs. + +Still by hand: Du-Atallah blinds, the ABY2 mux of `C` against `pc+3`, +and out-of-bounds prefix-parity checks from the thesis. + +\include{cpp} applications/subleq.cpp + +## BitMore, `2^L` servers {#app_bitmore} + +Hafiz and Henry ([PoPETs 2019](@ref bib_bitmore), §5.2) query `ell = 2^L` servers with `L` +independent 1-bit DPFs, all at the same row. +Server `j` receives key number `j_e` from DPF `e`. +`dpf::pack_bit_columns(keys...)` runs the full-domain bit walk once per +key and writes lane `e` = key `e` into one integer per row, so the +server reads `symbol[row]` instead of unpacking one `int` per bit. +Off the secret row every server computes the same digit. +On the secret row the digits are `symbol(0) XOR j`, a permutation of +`0 .. ell-1`. +When the server count `ℓ` is not a power of two, +`dpf::mod_bit_columns<ℓ>(keys...)` is that same integer modulo `ℓ`. +The first key is still the low bit. The information-theoretic +virtual-bucket response starts from those digits. + +`L = 1` is the two-server member of the same family. +Each party XORs the records its bit selects. +The two answers XOR to the record. + +\include{cpp} applications/bitmore.cpp + +## Keyword PIR {#app_keyword} + +Gilboa and Ishai ([EUROCRYPT 2014](@ref bib_dpf2014)) retrieve one record by a keyword. +[dpf::keyword](@ref dpf/keyword.hpp) is the domain, so the DPF point is +the keyword itself. +Each server walks the dictionary with `eval_sequence` and XORs the +record where its bit share is set. +Off the keyword the two bits match, so the record cancels. +A keyword absent from the dictionary opens to 0. +`eval_sequence` takes the dictionary in nondecreasing order. + +A batch of keywords that should come back as separate records is one +key per keyword. +[dpf::make_multipoint](@ref dpf/multipoint.hpp) adds several secret +points into one output. That is a histogram of hits, or a payload the +client chose. +That packing is de Castro–Polychroniadou (EUROCRYPT 2022): `t` points +into `m ≈ O(t)` cuckoo buckets, each an ordinary GGM key. +S&P 2025 (Boyle, Gilboa, Hamilis, Ishai, Tu) shrinks the dealer message +with a silent OLE / PCG that expands a short seed into many correlated +bucket keys. +That is a different primitive (the same family as silent VOLE), not a +new `multipoint_params` field, and this library does not ship a PCG +stack. + +\include{cpp} applications/keyword_pir.cpp + +## Prio and the heavy-hitter prefix walk {#app_prio} + +Corrigan-Gibbs and Boneh ([NSDI 2017](@ref bib_prio)) aggregate client encodings. +A frequency count is a one-hot vector. +A unit DPF is that vector, compressed. +Each client keys a point at its bin with payload `1` in +[dpf::field64](@ref dpf/field64.hpp), the same prime as libprio `Field64`. +Each server folds a full-domain expansion into its running histogram +share with `eval_full_add_into(hist, key)`. +The opened bin is the count. +Prio's validity proof for a general encoding is a SNIP. +This program is the DPF encoding only. + +The prefix walk is Poplar (Boneh, Boyle, Corrigan-Gibbs, Gilboa, and +Ishai, [ePrint 2021/017](@ref bib_poplar)). +[dpf::idpf](@ref dpf/placement.hpp) plants a `1` on prefix lengths 1, 2, +and 3, counting from the high bit. +Two clients hold `0xA0` and `0xB0`. +They share the length-3 prefix `101` and split at the next bit. +The servers evaluate `out` at one representative of each node +and add the opened values. + +\include{cpp} applications/prio.cpp + +## I-DPF max and k-th {#app_idpf_agg} + +Cheng, Mitrokotsa, Zhang, and Hartmann ([ePrint 2024/1190](@ref bib_idpfagg)) aggregate +secret values with an incremental DPF. +Communication tracks the bit length of the domain, not how many secret +inputs were summed. +Each secret `uint16_t` is one [dpf::idpf](@ref dpf/placement.hpp) with a +unit payload on every prefix length. +Servers resume only the live prefixes with +[dpf::eval_until](@ref dpf/eval_until.hpp). +At each depth they open the two children: max keeps the nonempty +`1`-child; the k-th largest takes the `1`-child when its count is at +least `k`. +`idpf_agg_max` / `idpf_agg_kth` in [dpf/idpf_agg.hpp](@ref dpf/idpf_agg.hpp) +share that walk with the gtest. + +\include{cpp} applications/idpf_agg.cpp + +## LLAMA {#app_llama} + +Gupta, Kumaraswamy, Chandran, and Gupta ([ePrint 2022/793](@ref bib_llama)) evaluate a +nonlinear gate from a dealer key and one opened masked input +`x_hat = x + r`. +The sign test is a comparison: `make_dpf(knot + r, dpf::gt(1))`, +evaluated at `x_hat`. +A degree-0 spline is one [dpf::ic](@ref dpf/interval.hpp) key per piece. +The piece value is the payload. +The same comparison on `int8_t` follows numeric order: `100 > -3` opens +to 1. + +\include{cpp} applications/llama.cpp + +## Pika {#app_pika} + +Wagh ([PoPETs 2022](@ref bib_pika), Fig. 1) looks up `Func(a)` in a table of a bounded +domain. +The dealer keys a unit DPF at a fresh index `r` and the parties open +`x = r - a`. +The inner product of the DPF with the table read at `(i - x) mod n` is +the table entry at `a`; `dpf::rotate{s}` folds that offset into the walk. +A word payload of `1` opens to `+1`. +The paper's early-stop bit leaf opens to `+1` or `-1`; the dealer records +that sign at keygen with `dpf::unit_sign` (the final control bit `Gen` +sees), so the evaluators never open `r`. + +\include{cpp} applications/pika.cpp + +## Express {#app_express} + +Eskandarian, Corrigan-Gibbs, Zaharia, and Boneh ([USENIX Security 2021](@ref bib_express), +§3.1) write one mailbox. +Two servers hold subtractive shares of the mailboxes. +The client sends one key each. +Each server adds its expansion into its share. +The owner opens one address. +An [extractable](@ref dpf/verifiable.hpp) sketch accepts the honest +write and rejects a second hot mailbox. + +The message is [dpf::fp61](@ref dpf/fp61.hpp) because that is the sketch +field. +Each server folds the expansion into its mailbox shares and the one-hot +audit in one walk: `eval_full_add_into(box, key, dpf::sketch(s, r))`. +The extractable full-domain leaf now matches point evaluation on every +lane, so this one call replaces the earlier `eval_point`-per-address +loop and the separate `sketch_fold` pass. + +\include{cpp} applications/express.cpp + +## PRAC {#app_prac} + +Sasy, Vadapalli, and Goldberg ([ePrint 2023/1897](@ref bib_prac)) run dynamic data +structures on a 3-party Duoram. +The new DPF shapes are an incremental key and a wide leaf. + +Binary search on a sorted array reads `lg n` locations. +The accessible set at each depth is a public stride, and the index in +the next stride is the previous index with one comparison bit appended. +One [idpf](@ref dpf/placement.hpp) supplies every stride's unit vector. +The program's array is `[1, 3, 5, 7, 9, 11, 13, 15]` and the needle is +`10`. The public midpoint is index 3. +`eval_prefix_inner_product(out, key, stride)` walks to the +prefix depth once and dots the `2^length` prefix shares with the stride, +replacing one `eval_point` per stride slot. The prefix of length 1 +selects `11` from the stride `{1, 5}`. The prefix of length 2 selects +`9` from `{0, 2, 4, 6}`. The bits `101` are the lower bound, index 5. + +Heapify reads a parent and its two children, three strides at one +index, with one unit key. +The update at that index is a [dpf::vec](@ref dpf/vec.hpp) of three +lanes. +An incremental wide key is `idpf` of those vectors. That call already +evaluates. + +The dealer in this program knows the path and keys it up front. +The protocol appends each comparison bit after the key exists. + +\include{cpp} applications/prac.cpp + +## Splinter {#app_splinter} + +Wang, Yun, Goldwasser, Vaikuntanathan, and Zaharia ([NSDI 2017](@ref bib_splinter)) answer +private queries on public data with two-server FSS. +The client's private `WHERE` value is a unit DPF at that attribute. +Each server dots the selector with a public column pre-aggregated by +attribute, so `eval_full_inner_product(dpf::paired, key, group_sum)` is +`SELECT SUM(value) WHERE attribute = secret`, and the all-ones column is +`COUNT(*)`. +Neither server learns the queried key. + +Still by hand: `MAX` / `TOP-k` and multi-predicate conjunctions are not +one selector DPF; Splinter composes several FSS instances for those. + +\include{cpp} applications/splinter.cpp + +## Mastic {#app_mastic} + +Mastic (private weighted heavy-hitters and attribute-based metrics) is +Poplar's prefix walk with a weight payload. +Each client keys an [idpf](@ref dpf/placement.hpp) whose β on every +prefix length is its weight, not `1`. +`eval_prefixes(out, key)` returns the prefix shares; the +servers sum them across clients and threshold to keep the heavy prefix. +Two clients on `0xA0` and `0xB0` with weights 5 and 3 make prefix `101` +heavy with total weight 8. +`verify_idpf_path` is the one-time VIDPF path check +([path_sketch.hpp](@ref dpf/path_sketch.hpp)). + +\include{cpp} applications/mastic.cpp + +## Waldo {#app_waldo} + +Dauterman, Rathee, Popa, and Stoica ([S&P 2022](@ref bib_waldo)) build a private time-series +database from FSS. +The store is append-only: each event is a fresh unit DPF folded into the +servers' value shares with `eval_full_add_into`, never an update. +A range/threshold aggregate uses the comparison channel: +`eval_full_inner_product(dpf::cmp, key, magnitude)` dots the +per-timestamp `gt` shares with a public magnitude column over the whole +domain, so the SUM over timestamps past a *secret* threshold reveals +neither the threshold nor the matches. The two halves open with +`reconstruct_cmp_halves`. + +Still by hand: Waldo's aggregation trees over several predicates, and +range endpoints that are themselves secret-shared, compose more than one +comparison key. + +\include{cpp} applications/waldo.cpp + +## Sabre {#app_sabre} + +Vadapalli, Storrier, and Henry ([S&P 2022](@ref bib_sabre)) send anonymous messages with a +fast audit. +The write is Express's full-domain add (`eval_full_add_into`). +The audit is a *verifiable* DPF proof rather than Express's `fp61` +sketch: `prove_full(key, dpf::prove(π))` folds a constant-size token per +party, and `dpf::verify(π0, π1)` accepts an honest single-point write and +rejects the mismatched fold a multi-point key produces. + +Still by hand: Sabre's blame / accountability phase that identifies a +cheating client is protocol logic above the DPF proof. + +\include{cpp} applications/sabre.cpp + +## A (2,3) ledger {#app_ledger23} + +A replicated ledger held as (2-of-3) shares by three servers, on this +group's `dpf3` VDPF+ construction. +Each append is one `make_dpf3(slot, amount, dpf::verifiable{})`; a +`verify_dpf3` over the three `prove_dpf3` tokens rejects an append that +is not a single well-formed point before it is applied. +Each server folds the point into its slot shares with +`eval_full_add_into(shares, key)` (the (2,3) full-domain overload), and +any two servers `shamir3::reconstruct` a balance. +Two credits to slot 5 (100 then 7) open to 107. + +Still by hand: the transaction / consensus layer around the append +(ordering, replay protection) is protocol logic above the DPF step. + +\include{cpp} applications/ledger23.cpp + +## Private set intersection {#app_psi} + +Kolesnikov, Kumaresan, Rosulek, and Trieu ([CCS 2016](@ref bib_kkrt)) test membership +with an oblivious PRF. +On this domain the PRF is a table both servers hold. +Each receiver element is a unit DPF. +`eval_full_inner_product(dpf::paired, key, table)` is that server's +share of `PRF(y)`. +The sender publishes `PRF(x)` for each element of their own set. +`y` is in the intersection when the opened tag appears in that image. + +Still by hand: cuckoo hashing in the PSI application layer, and the GGM +puncture that keeps a large domain at `O(n)` communication instead of a +table. The multipoint cuckoo VDMPF stays the GGM packing above; S&P 2025 +DMPF seed packing needs a PCG this library does not provide. + +\include{cpp} applications/psi.cpp + +## Range count {#app_range_count} + +Each secret value is one comparison. +The interval `[lo, hi)` is public. +`eval_point(dpf::cmp, key, q)` opens to 1 when `q` is strictly above the +value, so the two endpoints differ by 1 exactly when +`lo <= v < hi`. +The count is the sum of those bits over the values. +A neighbor just below `lo`, and the open end `hi`, contribute 0. + +Still by hand: a secret interval over a public histogram is two +comparison inner products (the Waldo aggregate, subtracted). +This program is the other direction, secret values and a public range. + +\include{cpp} applications/range_count.cpp + +## Floram {#app_floram} + +Doerner and shelat ([CCS 2017](@ref bib_ds)) read and write an array at a secret +address. +Both parties see the memory. +The address is XOR-shared, so the key is +[dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp) rather than a +dealer key. +The read dots a unit payload with the array. +The write adds a payload key from the same address shares into +subtractive copies of the memory, with `eval_full_add_into`. + +Still by hand: Floram's stash, position map, and refresh. This program +is the FSS access. + +\include{cpp} applications/floram.cpp + +## Three-server PIR {#app_pir3} + +The database is public and replicated on three servers. + +The computational path is one (2,3) point key from +[dpf::make_dpf3](@ref dpf/dpf3.hpp) ([ePrint 2024/1658](@ref bib_dpf3)). +Each server dots its share with the database: +`eval_full_inner_product(key, database)`. +Any two of those dots `shamir3::reconstruct` to the record. + +\include{cpp} applications/pir3.cpp + +The information-theoretic path is +[dpf::make_it_dpf3](@ref dpf/it_dpf3.hpp) ([ePrint 2023/028](@ref bib_itdpf)). +Each server holds an additive share of the characteristic vector; the +three dots sum to the record. Distinct from `make_dpf3`. + +\include{cpp} applications/it_pir3.cpp + +## What the walk now folds in {#application_gaps} + +The calls the eight programs used to build by hand are now the library +surface. See [dpf/eval_walk.hpp](@ref dpf/eval_walk.hpp). + +## Shift, then add {#gap_shift} + +Duoram and Pika rotate a vector by an opened offset, then dot. +`dpf::rotate{s}` on `eval_full_inner_product` reads the weight at +`(i + s) mod 2^n` inside the walk, so the caller keeps one unrotated +vector. `dpf::cyclic_shift(buf, s)` rotates a materialized share buffer. +Duoram's update, Prio's histogram, and Express's mailbox add the +expansion into a buffer the caller already holds with +`eval_full_add_into(buf, key)` (and a `dpf::rotate` or `dpf::sketch` +overload). Express's audit folds in the same pass. + +Still by hand: none for the deferred leaf. +`dpf::leaf_later` expands without the leaf correction word and fills a +parallel control-bit buffer. After `cyclic_shift_pair` (or +`eval_full_add_into(..., leaf_later, rotate{s})`), +`apply_leaf_correction(buf, control, F)` does `buf[i] += F * control[i]`. +[Doerner–Shelat](@ref dpf/doerner_shelat.hpp) already builds a key from +index shares the parties hold. + +## The bit leaf's sign {#gap_sign} + +Duoram's flag vector and Pika's early-stop leaf are 1-bit DPFs that lift +to a ring unit of `+1` or `-1`. `dpf::make_dpf(alpha, dpf::bit::one, +dpf::unit_sign{w0, w1})` records that sign at keygen — `w0 - w1` is the +`±1` unit — from the final control bit `Gen` sees and one key hides. +Absent the tag, keygen is unchanged. + +## Answers that are XORs or bit columns {#gap_xor} + +BitMore stacks `L` full-domain bit vectors and reads them as `L`-bit +digits. `dpf::pack_bit_columns(keys...)` runs the bit walk once per key +and writes one integer per row (lane `e` is key `e`). +`dpf::mod_bit_columns<ℓ>(keys...)` reduces that integer modulo a server +count `ℓ` from 2 through 32768, which is the digit when `ℓ` is not a +power of two. The virtual-bucket response that consumes the digits stays +outside the DPF layer. + +Keyword PIR XORs dictionary records selected by a bit share with +`eval_sequence_xor(key, begin, end, records)`, which folds that loop into +the sequence walk. + +## Prefixes {#gap_prefix} + +Poplar and PRAC score every node at one depth. +`eval_prefixes(out, key)` returns the `2^length` prefix +shares in one walk, and +`eval_prefix_inner_product(out, key, values)` dots them with +a public vector, each replacing one `eval_point` per node. + +`verify_idpf_path` / `sketch_path_level` / +`sketch_path_parent` ([path_sketch.hpp](@ref dpf/path_sketch.hpp)) +check that an incremental key is a single path: each depth is +weight-1 (or one nonzero weight), and each parent equals the sum of its +children. Leaf one-hot remains +[sketch_fold](@ref dpf/verifiable.hpp). + +## A growing index {#gap_stride} + +PRAC's binary search key grows: the next comparison bit is appended to +the index. `dpf::extend` / `dpf::add_output` realize +[F_Grow](@ref ideal_functionalities): a dealer (or joint) view turns an +existing `dpf_key` pair into a richer one. Specs are the same objects +`make_dpf` accepts. Memoizer overloads skip the O(d) rewalk when both +path memoizers are already filled through the frontier. +`extend_ds` / `add_output_ds` realize [F_GrowDS](@ref ideal_functionalities): +one Doerner–Shelat correction-word round on extend, or leaf-open only on +`add_output`, from warm frontiers. + +**Cost (dealer F_Grow).** O(d) rewalk of the spine, or O(1) seed reads +with warm memoizers; one `make_cw` / `advance` on extend; one exterior +leaf plant per new packing group. Zero rounds and zero bytes on the wire. + +**Cost (F_GrowDS).** One interactive level on extend (blinds, CW share, +advice, AND — same shape as one `point_party` level) plus leaf pads when +planting. `local_cw_protocol` opens in-process (no wire). `add_output_ds` +has no interior CW round. + +A wide leaf is a `vec`. +`idpf` of several `vec` payloads is the incremental wide key heapify +uses for every level at once. That call already evaluates. + +## Gates LLAMA still has to assemble {#gap_llama} + +One comparison and one public interval are already gates, and an +`int8_t` comparison follows numeric order. +A spline with several pieces is several `dpf::ic` keys in the program. +One key whose payload is the coefficient vector of the selected piece, +with Horner on the shares and the public `x_hat`, is the Boyle gate +LLAMA cites. +`dpf::gt(hi, lo)` with both payloads a [dpf::vec](@ref dpf/vec.hpp) +carries the vector. That is a wide comparison leaf. +`dpf::gt(hi)` alone uses a zero of the same type as `hi`, so a +`dpf::vec` false payload is the zero vector. +Signed extension and truncate-reduce are +`grotto::sign_extend` / `grotto::truncate_reduce` (wrappers over +`make_carry_keys` + `eval_carry_extend` / `eval_carry_in`). Grotto's +piecewise Horner evaluates a public polynomial from a point key. It is a +different construction. + +## Sketches on the group the protocol writes {#gap_sketch} + +Express audits the same key that writes the mailbox. +`dpf::blob` is the XOR leaf for a row of `N` bytes. A caller fold +`eval_full_add_into(buf, key, fold)` invokes `fold(index, share)` once +per written output in the same walk; `dpf::sketch` remains the `fp61` +weight-1 fold. Pika's malicious check is the same shape over `Z/2^k`, +using their bilinear Schwartz–Zippel lemma. + +`eval_full` on an extractable `fp61` key now matches `eval_point` on +every lane, including both lanes of the packed leaf that holds the +programmed point. Express folds its expansion and audit in one +`eval_full_add_into(box, key, dpf::sketch(...))` walk. +`extractable_full_test` pins the share-for-share match. + +## Punctured PRF {#gap_pprf} + +KKRT's PSI is cuckoo hashing plus a set check in the application. The +library object is `dpf::pprf_master` / `dpf::puncture` / `dpf::pprf_eval` +on the AES PRG, including a 128-bit domain. The programmed value at the +punctured point is stored beside the puncture when the master holder +computes it. The same walk over a set `H` returns a `dpf::pprf_copath`: +every node whose parent lies on a path to `H` and which itself does not, +with shared prefixes stored once. Empty `H` publishes the root; a +full-domain `H` publishes nothing. The walk is O(|H| · n) and never +materializes the domain. An audit opening of a replica-seed pool is that +copath with `program_hidden = false`, so the live seeds stay out. The +one-point layout stays for PSI; `{α}` with programming agrees with it. diff --git a/doc/pages/basics.md b/doc/pages/basics.md index 2ae18dc..52550bd 100644 --- a/doc/pages/basics.md +++ b/doc/pages/basics.md @@ -1,5 +1,81 @@ - +A *point function* is a huge list of zeros with one non-zero entry. +The index of that entry is secret. The value there is secret too. -# Point functions {#point_functions} +A *(2,2) distributed point function* splits that list between two parties. +Each party gets a short key. +Either key alone looks random. +When both parties evaluate at the same public input and combine their shares, +they recover the true value of the point function there. -# DPF Trees {#dpf_trees} +## Point functions {#point_functions} + +Write `f_{α,β}` for the function that returns `β` at `α` and `0` elsewhere. +`make_dpf(α, β)` returns keys `(k0, k1)` such that: + +- `Eval(k0, x)` and `Eval(k1, x)` are shares of `f_{α,β}(x)` +- `|k_i|` is about `O(λ · log |domain|)` for security parameter `λ` + +Leaf payloads use subtractive shares. Comparison payloads use additive shares. +Open with `dpf::reconstruct`. + +## What you pass to `make_dpf` {#make_dpf_args} + +`make_dpf(x, y, ys...)` takes the secret index and one or more payloads. +`dpf::at(y)` plants `y` on the public prefix of length `N`. +`dpf::idpf_at(ys...)` plants one payload per listed prefix. +`dpf::idpf(y0, y1, ...)` is the consecutive prefixes 1, 2, …. +Read a slot with `dpf::eval_point(dpf::out, key, x)`, or +`dpf::out` when `W` is that slot's prefix. + +\code{cpp} +auto [k0, k1] = dpf::make_dpf( + std::uint8_t{0x2a}, + dpf::at<4>(std::uint8_t{5}), + std::uint8_t{9}); +auto hi = *dpf::eval_point(dpf::out<0, 4>, k0, std::uint8_t{0x2a}); +\endcode + +A comparison payload is `dpf::lt`, `dpf::leq`, `dpf::gt`, or `dpf::geq`. +The same four names with `_at` sit on a prefix. Evaluate that channel +with `dpf::cmp`. See [Comparisons and ranges](@ref tour_dcf). + +Let `n` be the input bit length and `λ` the seed width. The key from +`make_dpf` is `Θ(n λ)` bits plus the payloads, and `eval_point` expands +`n` levels. That is the Boyle–Gilboa–Ishai CCS 2016 point key (full +version [ePrint 2018/707](@ref bib_fss2018)): one correction word per level, not their +[EUROCRYPT 2015](@ref bib_fss2015) key of `4n(λ+1)` bits. For a small output group `G`, Remark 3.4 of that full version stops +`ν = log2(λ / log2|G|)` levels early and shortens the key by `ν(λ+2)` +bits. This generator does that: the tree depth is `n` minus the log of +how many copies of `G` fit in one leaf, and those low bits select the lane. +Boyle, Gilboa, Ishai, and Kolobov ([ePrint 2023/028](@ref bib_itdpf)) give a statistically +private 3-server DPF and a perfectly private 4-server DPF; +`dpf::make_it_dpf3` is the additive three-server interface on a +`uint8_t` domain. `make_dpf` is a 2-party PRG key. Interval, sequence, +and full-domain costs are on [Evaluating DPFs](@ref evaluation). + +Width literals (`100_u12`, `7_x12`, `1.5_fixed16`, `1_bit`, `2_twobit`, +`10_nyble`, `_bitstring`) are documented with the types that use them: +[Input types](@ref input_types), [Output types](@ref output_types). + +**Defined in**\n +@ref dpf/dpf_key.hpp, @ref dpf/placement.hpp, @ref dpf/eval_target.hpp + +## DPF Trees {#dpf_trees} + +Keys store a seed and a list of *correction words*. +Evaluation walks a binary tree from the root toward `x`. +At each level a correction word mixes the two children so only the secret +path keeps differing seeds. Off-path nodes match and cancel when the parties +combine. + +Classic keys use a Boyle–Gilboa–Ishai expand (CCS 2016, full version +[ePrint 2018/707](@ref bib_fss2018)). +Half-Tree keys (CCR interior PRG) follow Guo, Yang, Wang, Zhang, Xie, +Zhang, and Liu, [ePrint 2022/1431](@ref bib_halftree): mid-level children are `H(s)` and +`H(s) XOR s`. Their dealer point key keeps the CCS 2016 length and the +`n`-hash point evaluation; they state about `2n+2` random-permutation +calls to generate a key versus about `4n`, and `1.5N` calls for a +full-domain evaluation versus `2N`. See [tree_traits.hpp](@ref dpf/tree_traits.hpp). + +For a slow, friendly walk through every feature, start at the +[guided tour](@ref guided_tour). diff --git a/doc/pages/beaver.md b/doc/pages/beaver.md new file mode 100644 index 0000000..da8da14 --- /dev/null +++ b/doc/pages/beaver.md @@ -0,0 +1,16 @@ +# Beaver triples {#beaver_triples} + +ABY2.0-style sessions open masked wires once (Patra, Schneider, Suresh, +and Yalame, USENIX Security 2021 / [ePrint 2020/1225](@ref bib_aby2)). +A fresh triple follows Beaver, [CRYPTO 1991](@ref bib_beaver): both masked factors are +reconstructed, and the product share is a local correction. +Optional MAC tags are the Shark/SPDZ check. + +One call that samples a list of formulae opens the new wires in one +round. Communication is one masked value per newly opened wire, plus a +tag share of the same width when MACs are on. Preprocessing is one blind +per wire and one product share per monomial. + +**Go deeper:** [beaver.hpp](@ref dpf/beaver.hpp), +[F_Beaver](@ref beaver.hpp), [F_BeaverAuth](@ref beaver.hpp), +and the cost notes in the [guided tour](@ref tour_beaver). diff --git a/doc/pages/bibliography.md b/doc/pages/bibliography.md new file mode 100644 index 0000000..ae41eac --- /dev/null +++ b/doc/pages/bibliography.md @@ -0,0 +1,381 @@ +# Bibliography {#bibliography} + +Each paper is listed once. An ePrint number anywhere else in the manual +links here. **Used in** points back at the pages that rely on that paper. + +ePrint PDFs that IACR posts are cached next to these pages. +Publisher PDFs from Springer, ACM, and USENIX are not. + +[Point functions](@ref bib_sec_point) · +[Generation and OT](@ref bib_sec_ot) · +[Comparisons and proofs](@ref bib_sec_cmp) · +[Three parties](@ref bib_sec_three) · +[Grotto](@ref bib_sec_grotto) · +[Protocols](@ref bib_sec_protocols) + +## Point functions and trees {#bib_sec_point} + +### Improvements and extensions {#bib_fss2018} + +Elette Boyle, Niv Gilboa, and Yuval Ishai. +*Function Secret Sharing: Improvements and Extensions.* +ACM CCS 2016, pp. 1292–1303. Full version, 24 July 2018. +[ePrint 2018/707](https://eprint.iacr.org/2018/707) · +PDF + +The point-function key `make_dpf` follows. Remark 3.4 is the early-stop +packing: the last `ν = log2(λ / log2|G|)` levels are a lane inside the +leaf, not correction words. + +**Used in** [First program](@ref basics) · [Evaluation](@ref evaluation) · [Guided tour](@ref tour_eval) + +### Function Secret Sharing (2015) {#bib_fss2015} + +Elette Boyle, Niv Gilboa, and Yuval Ishai. +*Function Secret Sharing.* +EUROCRYPT 2015, LNCS 9057, pp. 337–367. +Publisher + +The `4n(λ+1)`-bit key named in the manual, as recorded in the +bibliography of ePrint 2018/707. This library does not generate that key. + +**Used in** [First program](@ref basics) · [Evaluation](@ref evaluation) + +### Half-Tree {#bib_halftree} + +Xiaojie Guo, Kang Yang, Xiao Wang, Wenhao Zhang, Xiang Xie, Jiang Zhang, and Zheli Liu. +*Half-Tree: Halving the Cost of Tree Expansion in COT and DPF.* +[ePrint 2022/1431](https://eprint.iacr.org/2022/1431) · +PDF + +`prg::aes128_ccr` selects their dealer point-key expand. Section 5.2 is +a different object: two-party key generation in the COT/OLE hybrid. + +**Used in** [First program](@ref dpf_trees) · [Evaluation](@ref evaluation) · [Guided tour](@ref tour_trees) + +### Information-theoretic DPF {#bib_itdpf} + +Elette Boyle, Niv Gilboa, Yuval Ishai, and Victor I. Kolobov. +*Information-Theoretic Distributed Point Functions.* +[ePrint 2023/028](https://eprint.iacr.org/2023/028) · +PDF + +A statistically private 3-server DPF. `make_it_dpf3` is the additive +interface on a `uint8_t` domain, not the 2-party PRG key. + +**Used in** [Multiparty](@ref multiparty) · [Three-server PIR](@ref app_pir3) · [Evaluation](@ref it_dpf3) + +### Distributed point functions (2014) {#bib_dpf2014} + +Niv Gilboa and Yuval Ishai. +*Distributed Point Functions and Their Applications.* +EUROCRYPT 2014, LNCS 8441, pp. 640–658. +Publisher + +Two-server keyword PIR from a point function on the keyword. + +**Used in** [Keyword PIR](@ref app_keyword) + +## Generation and oblivious transfer {#bib_sec_ot} + +### Scaling ORAM {#bib_ds} + +Jack Doerner and abhi shelat. +*Scaling ORAM for Secure Computation.* +ACM CCS 2017, pp. 523–535. +[ePrint 2017/827](https://eprint.iacr.org/2017/827) · +PDF + +The per-level correction-word opening. `geneval_*` uses that opening on +the public query and does not return their reusable key. Floram's secret +read and write are the same opening. + +**Used in** [Dealer-free keygen](@ref dealer_free) · [Guided tour](@ref tour_ds) · [Floram](@ref app_floram) + +### IKNP OT extension {#bib_iknp} + +Yuval Ishai, Joe Kilian, Kobbi Nissim, and Erez Petrank. +*Extending Oblivious Transfers Efficiently.* +CRYPTO 2003, LNCS 2729, pp. 145–161. +Publisher + +The semi-honest OT extension `dpf::iknp::sample` follows: κ base OTs, +then a correlation-robust hash of the transposed matrix. IKNP is these +four authors. + +**Used in** [Dealer-free keygen](@ref dealer_free) · [Guided tour](@ref tour_iknp) + +### Simplest OT {#bib_chou} + +Tung Chou and Claudio Orlandi. +*The Simplest Protocol for Oblivious Transfer.* +LATINCRYPT 2015. Full version: +[ePrint 2015/267](https://eprint.iacr.org/2015/267) · +PDF + +The P-256 base OT under `dpf::iknp` (`base_sender` / `base_receiver`). + +**Used in** [Guided tour](@ref tour_iknp) + +### AES S-box circuit {#bib_boyar} + +Joan Boyar and René Peralta. +*A depth-16 circuit for the AES S-box.* +[ePrint 2011/332](https://eprint.iacr.org/2011/332) · +PDF + +The 32-AND SubBytes used by `party/oblivious_hash.hpp` +(`aes_bp::and_count = 32`). + +**Used in** [Guided tour](@ref tour_iknp) · [Evaluation](@ref evaluation) + +## Comparisons, proofs, and products {#bib_sec_cmp} + +### Mixed-mode FSS {#bib_dcf} + +Elette Boyle, Nishanth Chandran, Niv Gilboa, Divya Gupta, Yuval Ishai, Nishant Kumar, and Mayank Rathee. +*Function Secret Sharing for Mixed-Mode and Fixed-Point Secure Computation.* +EUROCRYPT 2021, LNCS 12697, pp. 871–900. +[ePrint 2020/1392](https://eprint.iacr.org/2020/1392) · +PDF + +The distributed comparison function, and Figure 3's one-DCF public interval. + +**Used in** [Comparisons](@ref comparisons) · [Evaluation](@ref evaluation) · [Guided tour](@ref tour_dcf) + +### Verifiable FSS {#bib_vdpf} + +Leo de Castro and Antigoni Polychroniadou. +*Lightweight, Maliciously Secure Verifiable Function Secret Sharing.* +EUROCRYPT 2022, pp. 150–179. +[ePrint 2021/580](https://eprint.iacr.org/2021/580) · +PDF + +The 4λ-bit correction seeds and 2λ-bit proof token, and §4's κ = 3 +cuckoo packing. `make_multipoint` follows this GGM multi-point. +S&P 2025 (Boyle, Gilboa, Hamilis, Ishai, and Tu) packs those bucket +keys from a PCG seed. That packing is not a `multipoint_params` tweak +and is not implemented here. + +**Used in** [Verifiability](@ref verifiability) · [Multipoint](@ref multipoint_keys) · [Keyword PIR](@ref app_keyword) · [PSI](@ref app_psi) + +### ABY2.0 {#bib_aby2} + +Arpita Patra, Thomas Schneider, Ajith Suresh, and Hossein Yalame. +*ABY2.0: Improved Mixed-Protocol Secure Two-Party Computation.* +USENIX Security 2021. Full version: +[ePrint 2020/1225](https://eprint.iacr.org/2020/1225) · +PDF + +One public reconstruction per newly opened wire. + +**Used in** [Beaver triples](@ref beaver_triples) · [Guided tour](@ref tour_beaver) + +### Beaver triples {#bib_beaver} + +Donald Beaver. +*Efficient Multiparty Protocols Using Circuit Randomization.* +CRYPTO 1991, LNCS 576, pp. 420–432. +Publisher + +The two-opening product triple, not the ABY2.0 session. + +**Used in** [Beaver triples](@ref beaver_triples) · [Guided tour](@ref tour_beaver) + +### Bit commitment {#bib_naor} + +Moni Naor. +*Bit Commitment Using Pseudorandomness.* +Journal of Cryptology 4(2), 1991, pp. 151–158. +Publisher + +`dpf::ppvc` binds each DPF root with this string commitment: +`G(r) XOR A*rho`, under a public matrix `A`. + +**Used in** [Programmable vectors](@ref ppvc_manual) + +## Three evaluators {#bib_sec_three} + +### Three-party DPF {#bib_dpf3} + +Guy Zyskind, Avishay Yanai, and Alex "Sandy" Pentland. +*High-Throughput Three-Party DPFs with Applications to ORAM and Digital Currencies.* +[ePrint 2024/1658](https://eprint.iacr.org/2024/1658) · +PDF + +Figure 3: each evaluator key is a pair of (2,2)-VDPF+ keys. + +**Used in** [Multiparty](@ref multiparty) · [Guided tour](@ref tour_dpf3) · [Three-server PIR](@ref app_pir3) · [(2,3) ledger](@ref app_ledger23) + +## Grotto {#bib_sec_grotto} + +### Grotto {#bib_grotto} + +Kyle Storrier, Adithya Vadapalli, Allan Lyons, and Ryan Henry. +*Grotto: Screaming fast (2+1)-PC for Z2n via (2,2)-DPFs.* +[ePrint 2023/108](https://eprint.iacr.org/2023/108) · +PDF + +Prefix parity along one key, and Appendix D's degree-0 exact tables. +Offset Horner is not that piecewise-polynomial construction. + +**Used in** [Grotto](@ref jet_and_ring) · [Guided tour](@ref tour_grotto) + +## Protocols {#bib_sec_protocols} + +### Duoram {#bib_duoram} + +Adithya Vadapalli, Ryan Henry, and Ian Goldberg. +*Duoram: A Bandwidth-Efficient Distributed ORAM for 2- and 3-Party Computation.* +USENIX Security 2023. +USENIX + +The 3-party read and update: unit DPFs at a random index, a cyclic +shift by the opened offset, and a dot product with the memory. + +**Used in** [3-party Duoram](@ref app_duoram) + +### BitMore {#bib_bitmore} + +Syed Mahbub Hafiz and Ryan Henry. +*A Bit More Than a Bit Is More Than a Bit Better.* +PoPETs 2019(4), pp. 112–131. +Publisher + +Section 5.2 is the `2^L`-server query: `L` independent 1-bit DPFs, +one key per label bit. + +**Used in** [BitMore](@ref app_bitmore) + +### Prio {#bib_prio} + +Henry Corrigan-Gibbs and Dan Boneh. +*Prio: Private, Robust, and Scalable Computation of Aggregate Statistics.* +NSDI 2017, pp. 259–282. +USENIX + +The frequency count is a one-hot encoding. `dpf::field64` is this +paper's Field64, via libprio. + +**Used in** [Prio](@ref app_prio) + +### Poplar {#bib_poplar} + +Dan Boneh, Elette Boyle, Henry Corrigan-Gibbs, Niv Gilboa, and Yuval Ishai. +*Lightweight Techniques for Private Heavy Hitters.* +[ePrint 2021/017](https://eprint.iacr.org/2021/017) + +The incremental DPF on prefixes of one secret string. EvaluateUntil is +`eval_until`. Mastic is this prefix walk with a weight payload. + +**Used in** [Prio](@ref app_prio) · [I-DPF max and k-th](@ref app_idpf_agg) · [Mastic](@ref app_mastic) · [Guided tour](@ref tour_eval) + +### Incremental aggregation {#bib_idpfagg} + +Nan Cheng, Aikaterini Mitrokotsa, Feng Zhang, and Frank Hartmann. +*Efficient Two-Party Secure Aggregation via Incremental Distributed Point Function.* +[ePrint 2024/1190](https://eprint.iacr.org/2024/1190) + +Communication tracks the bit length of the domain. The max and k-th +walks are `idpf_agg_max` and `idpf_agg_kth`. + +**Used in** [I-DPF max and k-th](@ref app_idpf_agg) + +### LLAMA {#bib_llama} + +Kanav Gupta, Deepak Kumaraswamy, Nishanth Chandran, and Divya Gupta. +*LLAMA: A Low Latency Math Library for Secure Inference.* +[ePrint 2022/793](https://eprint.iacr.org/2022/793) + +Offset comparison and spline gates. + +**Used in** [LLAMA](@ref app_llama) + +### Pika {#bib_pika} + +Sameer Wagh. +*Pika: Secure Computation using Function Secret Sharing over Rings.* +PoPETs 2022(4), pp. 351–377. +Publisher + +Figure 1 is the unit-DPF table lookup. + +**Used in** [Pika](@ref app_pika) + +### Express {#bib_express} + +Saba Eskandarian, Henry Corrigan-Gibbs, Matei Zaharia, and Dan Boneh. +*Express: Lowering the Cost of Metadata-hiding Communication with Cryptographic Privacy.* +USENIX Security 2021, pp. 1775–1792. +USENIX + +Section 3.1 is the DPF mailbox write. + +**Used in** [Express](@ref app_express) · [Sabre](@ref app_sabre) + +### PRAC {#bib_prac} + +Sajin Sasy, Adithya Vadapalli, and Ian Goldberg. +*PRAC: Round-Efficient 3-Party MPC for Dynamic Data Structures.* +[ePrint 2023/1897](https://eprint.iacr.org/2023/1897) + +One incremental DPF replaces the `lg n` point keys of a binary search. +A wide leaf updates a heap node and both children. + +**Used in** [PRAC](@ref app_prac) + +### Splinter {#bib_splinter} + +Frank Wang, Catherine Yun, Shafi Goldwasser, Vinod Vaikuntanathan, and Matei Zaharia. +*Splinter: Practical Private Queries on Public Data.* +NSDI 2017. +USENIX + +A unit DPF selects one public group. The server dots that selector with +a pre-aggregated column. + +**Used in** [Splinter](@ref app_splinter) + +### Waldo {#bib_waldo} + +Emma Dauterman, Mayank Rathee, Raluca Ada Popa, and Ion Stoica. +*Waldo: A Private Time-Series Database from Function Secret Sharing.* +IEEE S&P 2022. +[ePrint 2021/1661](https://eprint.iacr.org/2021/1661) + +Append-only unit DPFs, and a comparison inner product for a secret threshold. + +**Used in** [Waldo](@ref app_waldo) + +### Sabre {#bib_sabre} + +Adithya Vadapalli, Kyle Storrier, and Ryan Henry. +*Sabre: Sender-Anonymous Messaging with Fast Audits.* +IEEE S&P 2022. + +The write is Express's full-domain add. The audit is a verifiable DPF +proof instead of Express's `fp61` sketch. + +**Used in** [Sabre](@ref app_sabre) + +### Batched OPRF / PSI {#bib_kkrt} + +Vladimir Kolesnikov, Ranjit Kumaresan, Mike Rosulek, and Ni Trieu. +*Efficient Batched Oblivious PRF with Applications to Private Set Intersection.* +ACM CCS 2016. +[ePrint 2016/799](https://eprint.iacr.org/2016/799) + +Membership as an oblivious PRF. On this domain the PRF table is public +and each receiver element is a unit DPF. + +**Used in** [Private set intersection](@ref app_psi) + +### Private SUBLEQ {#bib_subleq} + +Jiang and Henry. +MSc thesis, University of Calgary. +A subtract-and-branch instruction for private function evaluation. +The DPF work is a prepaid wildcard unit vector, rotated once the +address is opened. + +**Used in** [MPC SUBLEQ](@ref app_subleq) diff --git a/doc/pages/capabilities.md b/doc/pages/capabilities.md new file mode 100644 index 0000000..c6d3e7b --- /dev/null +++ b/doc/pages/capabilities.md @@ -0,0 +1,15 @@ +# Capabilities {#capabilities} + +Everything past a plain two-party point key. +The home page is the short version. Open a page for the snippet and the header. + +- [Verifiability & authenticity](@ref verifiability) +- [Programmability](@ref programmability) +- [Comparisons & ranges](@ref comparisons) +- [Multipoint keys](@ref multipoint_keys) +- [Multiparty & 3-server](@ref multiparty) +- [Dealer-free keygen](@ref dealer_free) +- [Beaver triples](@ref beaver_triples) +- [Grotto](@ref jet_and_ring) +- [Point-programmable vector commitments](@ref ppvc_manual) +- [Application sketches](@ref applications) diff --git a/doc/pages/comparisons.md b/doc/pages/comparisons.md new file mode 100644 index 0000000..05dd422 --- /dev/null +++ b/doc/pages/comparisons.md @@ -0,0 +1,26 @@ +# Comparisons & ranges {#comparisons} + +A distributed comparison function returns a payload when a predicate holds +on the secret point. Shares are additive: `reconstruct` adds them. + +| Call | When it is hot | +| --- | --- | +| `dpf::lt` / `leq` / `gt` / `geq` | The public query is on that side of the secret point | +| `dpf::eq` | The public query equals the secret point | +| `dpf::ic` | The secret point lies in a public interval | +| `dpf::idcf` / `cmp_prefix` | A correction at every depth, or only the first `L` bits | +| `dpf::block_width` | Ring words only at checkpoints `B` levels apart | + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{40}, dpf::gt(std::uint64_t{1})); +auto opened = dpf::reconstruct( + dpf::eval_point(dpf::cmp, k0, std::uint8_t{50}), + dpf::eval_point(dpf::cmp, k1, std::uint8_t{50})); +// opened == 1 +``` + +A wildcard comparison payload is filled later with `assign_cmp`. +Eval before that throws. + +**Go deeper:** [dcf.hpp](@ref dpf/dcf.hpp), [interval.hpp](@ref dpf/interval.hpp), +[blocked_dcf.hpp](@ref dpf/blocked_dcf.hpp), [F_DCF](@ref dcf.hpp). diff --git a/doc/pages/dealer_free.md b/doc/pages/dealer_free.md new file mode 100644 index 0000000..7ce5c67 --- /dev/null +++ b/doc/pages/dealer_free.md @@ -0,0 +1,23 @@ +# Dealer-free keygen {#dealer_free} + +The two parties already hold shares of the secret index. +Nobody sends `alpha` to a dealer. + +| Call | What you get | +| --- | --- | +| [dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp) | A reusable two-party key from XOR shares of `alpha` (Doerner–Shelat, CCS 2017 / [ePrint 2017/827](@ref bib_ds)) | +| [dpf::geneval_point](@ref dpf/geneval.hpp) / `geneval_interval` / `geneval_cmp` | Answer shares for one query. No reusable key | +| IKNP pads | The same Doerner–Shelat walk after Chou–Orlandi base OT ([ePrint 2015/267](@ref bib_chou)), with no pad dealer | + +```cpp +const std::uint8_t alpha = 42; +const std::uint8_t x0 = 0x13; +const std::uint8_t x1 = static_cast(alpha ^ x0); +auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, std::uint64_t{7}); +``` + +Three evaluators have the same shape: `make_dpf3_doerner_shelat`. +Socket sessions live under `party/dist_ds.hpp` and `party/iknp_deal.hpp`. + +**Go deeper:** [Doerner–Shelat](@ref tour_ds), [geneval.hpp](@ref dpf/geneval.hpp), +[Multiparty & 3-server](@ref multiparty). diff --git a/doc/pages/evaluation.md b/doc/pages/evaluation.md index 328fe7a..64a8c60 100644 --- a/doc/pages/evaluation.md +++ b/doc/pages/evaluation.md @@ -1,5 +1,18 @@ +On this page: +[wildcard assign](@ref wildcard_assign), +[memoizers](@ref memoizers), +[buffers](@ref output_buffers), +[point](@ref eval_point), +[interval](@ref eval_interval), +[full domain](@ref eval_full), +[deferred input](@ref defer_eval), +[inner product](@ref eval_inner_product), +[sequence](@ref eval_sequence), +[three-party](@ref dpf3), +[IT 3-server](@ref it_dpf3). + `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 @@ -10,6 +23,122 @@ additive shares: `reconstruct` computes `share0 + share1`. A single-output `[from, to]` is inclusive. The points passed to `eval_sequence` are a nondecreasing range; an unsorted range throws `std::runtime_error`. +Let `n` be the input bit length and `λ` the PRG seed width (128 bits for +`prg::aes128`). A `make_dpf` key stores one `λ`-bit root and, on each of +the `n` levels, one `λ`-bit correction word and an advice bit, plus one +leaf per output slot. Key size is `Θ(n λ)` bits plus those payloads. +That layout is Boyle, Gilboa, and Ishai, CCS 2016 (full version +[ePrint 2018/707](@ref bib_fss2018)), whose stated length is `n(λ+2)+λ+⌈log2|G|⌉` bits, not +their [EUROCRYPT 2015](@ref bib_fss2015) key of `4n(λ+1)` bits. For a small output group `G`, Remark 3.4 of the full version stops +`ν = log2(λ / log2|G|)` levels early and shortens the key by `ν(λ+2)` +bits. This generator does that: `depth` is `n - lg(outputs_per_leaf)`, +where `outputs_per_leaf` is how many copies of `G` fit in one `λ`-bit +block. Those low input bits select the lane. A comparison still writes +a value word on each remaining level. The default expand is that CCS 2016 +tree. Guo, Yang, Wang, Zhang, Xie, Zhang, and Liu ([ePrint 2022/1431](@ref bib_halftree)) keep +the same key length and the same `n`-hash point evaluation, and state +about `2n+2` random-permutation calls for key generation versus about +`4n`, and `1.5N` calls for a full-domain evaluation versus `2N`. +`prg::aes128_ccr` selects that Half-Tree expand. +Keygen expands once per level. `at` and `idpf` add leaves; the spine +is still `n` levels. A comparison adds one payload word per level. +`block_width` stores one word every `B` levels instead, and the seed +spine stays `Θ(n λ)`. That is ahead of Elette Boyle, Nishanth Chandran, +Niv Gilboa, Divya Gupta, Yuval Ishai, Nishant Kumar, and Mayank Rathee +(EUROCRYPT 2021, [ePrint 2020/1392](@ref bib_dcf)), who publish a value word on every +level: about `B` times fewer payload words, paid for by expanding the +siblings between checkpoints at evaluation. See the +[bibliography](@ref bibliography). + +`eval_point` expands those `n` levels. A basic path memoizer keeps the +`n` nodes (`Θ(n λ)` bits) and the next call expands only the suffix after +the common prefix. The nonmemoizing memoizer keeps one node. `reconstruct` +of two shares, or of two or three Shamir shares, is a constant amount of +group arithmetic. + +`eval_point` and `eval_point(dpf::out, key, x)` read output slot `I`. +`dpf::out` is that slot with prefix length `W` checked against the +key. A bare leaf's prefix is the input bit length. `dpf::at` uses `N`. +`dpf::prefix_deduce` (the default of `out`) reads the prefix from the key. +`eval_point(dpf::cmp, key, x)` reads the comparison channel. +`eval_point(dpf::cmp_prefix, key, x)` reads the first `L` bits of an +`idcf` key. The [eval_point](@ref eval_point) section has the calls. + +On an `idpf` / multilevel key, `eval_prefixes(out, key)` walks from +the root and materializes all `2^N` prefix shares (Poplar-style). +`idpf_eval_ctx` / `eval_until` ([ePrint 2021/017](@ref bib_poplar)'s EvaluateUntil) resume +under a live prefix list so the saved node count stays +`O(|prefixes|)`, not `O(2^N)`. The I-DPF max / k-th walk in +[dpf/idpf_agg.hpp](@ref dpf/idpf_agg.hpp) ([ePrint 2024/1190](@ref bib_idpfagg)) is that +caller. See [eval_until](@ref dpf/eval_until.hpp) and the application +mockup [I-DPF max and k-th](@ref app_idpf_agg). + +## Assigning a wildcard leaf {#wildcard_assign} + +An output wildcard is a placeholder for a payload filled after keygen. +The type and the `dpf::wildcards` names are on +[Output types](@ref output_types). Evaluation of an unassigned slot throws +`std::runtime_error`. + +In one process, each party holds a share of the payload and the two keys. +Slot `I` is `std::get(key.leaf_nodes)` (`party_key` is the key). The +exchange is three calls: + +\code{cpp} +auto & w0 = std::get<0>(k0.leaf_nodes); +auto & w1 = std::get<0>(k1.leaf_nodes); +auto b0 = w0.compute_and_get_blinded_output_share(share0); +auto b1 = w1.compute_and_get_blinded_output_share(share1); +auto l0 = w0.compute_and_get_leaf_share(b1); +auto l1 = w1.compute_and_get_leaf_share(b0); +w0.reconstruct_correction_word(l1); +w1.reconstruct_correction_word(l0); +\endcode + +`share0` and `share1` are the two parties' shares of the payload. +`compute_and_get_blinded_output_share` also accepts a `secret_share`. +A wrapper that is already ready takes `begin_update()` before the same +three calls; that assign adds a difference onto the payload already +installed. + +Over a socket, `key.async_assign_leaf(peer, share, token)` (and +`async_assign_leaf`) runs that exchange. The method is present when +the library is built with ASIO. That is two rounds: blinded output +shares, then leaf shares. Each message is one share of the output (the +second round sends the packed leaf). The Beaver triple on the wildcard +slot is the preprocessing; keygen stored it. Local time is linear in +the leaf width. + +`assign_cmp` on a pair of keys rewrites the `n` value-correction words +in place and sends nothing. `assign_cmp_local` is that patch on one key +once `δ` and the addend share are already known. An input wildcard is +one round: the parties exchange one `n`-bit offset share and reconstruct +locally. See [Input types](@ref input_types). + +A wildcard *comparison* payload is different. Generate with +`dpf::lt(dpf::wildcard_value{})` (or `leq` / `gt` / `geq`), then +`dpf::assign_cmp(k0, k1, if_true, if_false)` on the pair. Eval before +that throws. `dpf::assign_cmp_local(key, delta, addend_share)` is the +one-key form: both parties pass the same public `delta` and additive +shares of the absorb target. + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint8_t{40}, + dpf::lt(dpf::wildcard_value{})); +dpf::assign_cmp(k0, k1, std::uint64_t{7}); +auto y0 = dpf::eval_point(dpf::cmp, k0, std::uint8_t{10}); +\endcode + +An input wildcard masks the index. The calls are +`offset_x.compute_and_get_share` and `offset_x.reconstruct` on +[Input types](@ref input_types). Eager `eval_interval` / `eval_full` require +that offset to be ready. To expand **before** assign (full-domain identity, +then rotate once `δ` is known), use +[Deferred input evaluation](@ref defer_eval). + +**Defined in**\n +@ref dpf/leaf_wrapper.hpp, @ref dpf/dpf_key.hpp, @ref dpf/incremental.hpp + 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 @@ -18,7 +147,7 @@ later call should reuse them. The factories either party's type accepts both parties. Name that type with `dpf::unwrap_party_key_t>`. -# Memoizers {#memoizers} +## Memoizers {#memoizers} ## Path memoizers {#path_memoizers} @@ -31,7 +160,8 @@ keeps one node and starts from the root on every call. A one-off 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. +restarts the path. One point evaluation is `Θ(n)` expands. The basic +memoizer's extra memory is the `n` saved nodes. **Code samples**\n
@@ -57,6 +187,15 @@ interval rebuilds into the same allocation. Passing only the memoizer still allocates a fresh output buffer and returns `std::pair(buffer, iterable)`. +Let `L` be the number of inputs in the inclusive interval. The node count +at a level is the width of that interval shifted up to the level, and the +walk expands each of those nodes once, `Θ(n + L)` expands in total. The +output buffer is `L` slots per selected output. A basic memoizer keeps two +levels, `O(L)` nodes. A full-tree memoizer keeps every level, still +`Θ(n + L)` nodes. `eval_full` is the same walk with `L = 2^n`: time and +the output buffer are `Θ(2^n)` slots, and the basic full memoizer holds +two levels of that tree. + ## Sequence memoizers {#sequence_memoizers} `make_sequence_recipe(begin, end)` compiles a sorted point list into a @@ -75,7 +214,16 @@ level and reverses direction as it descends. `make_full_tree_sequence_memoizer(recipe)` retains every level. A key whose depth differs from the recipe throws `std::logic_error`. -# Output buffers {#output_buffers} +Let `m` be the number of listed points. `make_sequence_recipe` makes one +pass per level and binary-searches each live block, so the build is +`O(n m log m)` comparisons in the worst case. The recipe stores one step +per block per level, `O(n m)` records when every level splits. Evaluating +it expands one node per step, at most `O(n m)` expands, and writes `m` +shares. The double-space memoizer keeps two levels of that traversal +(`O(m)` nodes at the widest level). The inplace memoizer keeps one level. +The full-tree memoizer keeps every level. + +## Output buffers {#output_buffers} `output_buffer` is move-only storage with `size`, iterators, `data`, and `operator[]`. Build it with the factory that matches the evaluation: @@ -94,6 +242,9 @@ the evaluation overwrites every slot it is responsible for. 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. +The buffer holds one slot per input in the interval, or one slot per +listed point. Its space is that many slots times the slot width. Packed +`bit`, `twobit`, and `nyble` slots share words. 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 @@ -111,16 +262,35 @@ rule.
-# dpf::eval_point {#eval_point} +## dpf::eval_point {#eval_point} `eval_point(key, x)` evaluates output 0 at one input. `eval_point(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, key, x, path)` and `eval_point(dpf::cmp, key, x, path)` -are the same walk with an explicit channel. Comparison results are additive -shares. +`eval_point(dpf::out, key, x, path)` and +`eval_point(dpf::out, key, x, path)` are the same walk with an +explicit slot. `W` must equal that slot's prefix. +`eval_point(dpf::cmp, key, x, path)` reads the comparison channel. +`eval_point(dpf::cmp_prefix, key, x, path)` reads an `idcf` prefix of +`L` bits. Comparison results are additive shares. An unassigned wildcard +comparison throws until `assign_cmp`; see +[Assigning a wildcard leaf](@ref wildcard_assign). + +\code{cpp} +auto [k0, k1] = dpf::make_dpf( + std::uint8_t{0x2a}, + dpf::at<4>(std::uint8_t{5}), + std::uint8_t{9}); +auto hi = *dpf::eval_point(dpf::out<0, 4>, k0, std::uint8_t{0x2a}); +auto leaf = *dpf::eval_point(dpf::out<1, 8>, k0, std::uint8_t{0x2a}); + +auto [c0, c1] = dpf::make_dpf(std::uint8_t{40}, + dpf::idcf(dpf::gt(std::uint64_t{1}))); +auto full = dpf::eval_point(dpf::cmp, c0, std::uint8_t{50}); +auto pref = dpf::eval_point(dpf::cmp_prefix<4>, c0, std::uint8_t{50}); +\endcode **Code samples**\n
@@ -129,7 +299,7 @@ shares.
-# dpf::eval_interval {#eval_interval} +## dpf::eval_interval {#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 @@ -144,7 +314,7 @@ selected output.
-# dpf::eval_full {#eval_full} +## dpf::eval_full {#eval_full} `eval_full(key)` is the closed interval from `std::numeric_limits::min()` through `max()`. The buffer and @@ -159,7 +329,120 @@ size both for that domain.
-# dpf::eval_sequence {#eval_sequence} +## Deferred input evaluation {#defer_eval} + +Additive input blinding evaluates in tree coordinates `x ↦ x + δ`, where +`δ` is reconstructed by `assign_wildcard_input` into `offset_x`. Eager +`eval_interval` folds that map into the traversed range and throws if the +offset is not ready. + +When `δ` is still unknown, call `defer_eval_interval` or `defer_eval_full`: + +1. Expand the **full** input domain at identity into a full-sized buffer + (`make_output_buffer_for_full`). +2. After assign, `.get()` on the returned `deferred_rotated_subinterval` + rotates by `offset_x(0)` and yields the logical `[from, to]` (including + wrap-around when `from > to` in domain-walk order). + +Interior-only prep (assigned input, leaf still a wildcard) is +`defer_traverse_interval`. Full-domain interior with both still unset is +`defer_traverse_full`. + +Keep the deferred object alive for the lifetime of any `.get()` range. +Buffers must outlive both. + +**Code samples**\n +
+ + - defer_eval.cpp \include{cpp} evaluation/defer_eval.cpp + +
+ +**Defined in**\n +@ref dpf/deferred_rotated_subinterval.hpp, @ref dpf/eval_interval.hpp, +@ref dpf/eval_full.hpp + +## dpf::eval_inner_product {#eval_inner_product} + +`eval_inner_product` multiply-accumulates DPF shares against another vector +during the walk. It does not write the output vector. + +Three local forms: + +- **Batched leaf walk** (no tag): `eval_inner_product(key, from, to, weights, memo)`. + One output, weights in `eval_interval` layout, same batched exterior AES as + that walk, O(1) accumulator. +- **`dpf::paired`** (row-wise): one weight row per input. A scalar pairs with + one output; a `tuple` / `array` zips several outputs (leaf slots or an + ancestor prefix plus the leaf) off one path. Products are summed. +- **`dpf::columns`** (transposed): one output, several weight streams, one + accumulator per stream. Products stay apart. A stream is `w[i]` or `w(i)`. + `dpf::project` maps the share first; `dpf::also` sees it unmapped. + +A single-stream `columns` result matches `paired` on that stream. A +single-output batched leaf walk matches `paired` when the interval is +leaf-aligned so covering-leaf weights equal the clipped domain points. +Unaligned intervals still weight every lane of the covering leaves (same +layout as one-key `eval_interval` buffers). Cohort evaluation uses a separate +*interleaved* leaf layout (`cohort_index`); `interleave_leaves` builds that +order from per-key buffers (see `dpf/interleave_leaves.hpp` and +`dpf/cohort.hpp`). + +Pass `dpf::paired`. One element of the other vector is consumed per input, in +the same order as `eval_interval` or `eval_sequence`. + +A scalar element pairs with one output: + +```cpp +eval_inner_product(dpf::paired, key, from, to, weights); +eval_full_inner_product(dpf::paired, key, weights); +``` + +A `std::tuple` or `std::array` element pairs componentwise with the output +indices in the template pack. Those outputs may be several slots on the same +leaf, or an ancestor prefix slot and the leaf. Both are read from one path: + +```cpp +eval_inner_product<0, 1>(dpf::paired, key, from, to, rows); +eval_sequence_inner_product<0, 1>(key, begin, end, rows); +eval_sequence_inner_product<0, 1>(key, recipe, begin, end, rows); +``` + +`share * component` must be defined. The product type must support `+`. +The walk costs the same as the interval or sequence it follows. Each +input adds a constant amount of arithmetic per paired output or per +column. The accumulator is the only result; there is no output vector +of shares. +The point list for a sequence or recipe is sorted nondecreasing, same as +`eval_sequence`. The recipe overload also checks that the list length matches +the recipe. + +`dpf::columns` is the same walk with the products kept apart. One output, +several weight streams, one accumulator per stream: + +```cpp +auto [sum, dot, sq] = eval_sequence_inner_product<0>( + dpf::columns, key, begin, end, std::tie(ones, r, r2)); +``` + +A stream is anything with `w[i]` or `w(i)`, so a challenge can be a function +of the list index instead of a stored vector. `i` is the position in the +point list, or in the interval in `eval_interval` order. `dpf::project(fn)` +maps the share before the multiply. `dpf::also(fn)` is called as +`fn(i, x, share)` on the share before that map, which is how a mailbox slot +is updated in the same walk. Pass either tag, both, or neither. + +The older single-output range walk (no `dpf::paired`) is still the batched +leaf inner product: `eval_inner_product(key, from, to, weights, memo)`. + +**Code samples**\n +
+ + - eval_inner_product.cpp \include{cpp} evaluation/eval_inner_product.cpp + +
+ +## dpf::eval_sequence {#eval_sequence} `eval_sequence(key, begin, end, tag)` evaluates a sorted list. `dpf::return_output_only_tag_` stores one share per listed point. @@ -170,6 +453,13 @@ The iterable still yields one share per listed point, in list order. `memo` is a sequence memoizer bound to `recipe`. Omit `memo` to allocate a `double_space` workspace for that call. +`eval_sequence_breadth_first(key, begin, end, buffer)` writes one share +per listed point, expanding a level at a time. The list is still sorted. +`eval_sequence_breadth_first(dpf::out, key, begin, end)` allocates the +buffer and returns it. The `out` form checks the prefix the same way +`eval_point` does. The cost is the same order as recipe `eval_sequence` +on that list: at most `O(n m)` expands and `m` output slots. + **Code samples**\n
@@ -177,7 +467,7 @@ The iterable still yields one share per listed point, in list order.
-# Buffered PRG {#buffered_prg} +## Buffered PRG {#buffered_prg} `dpf::randomness::buffered_prg` (alias `dpf::randomness::aes_buffered_prg`) is a forward cursor with one @@ -190,9 +480,100 @@ it is. `sampled()` is how far `get` and `fill` have advanced. roles. `value_at(role, index)` and `mask_at(role, index)` are independent streams, and a repeated index returns the same element. +`get` and `fill` of `q` elements do `Θ(q)` PRG work. The cursor keeps +`per_stream_buffer_elems` elements per stream. `at` reads one absolute +index and does not move the cursor. A `lane_table` keeps one cache +window per role (the constructor's `window`, default 256) for the value +stream and one for the mask stream. A repeated index returns the same +element. `fill_values` / `fill_masks` of `q` elements are `Θ(q)`. + **Code samples**\n
- buffered_prg.cpp \include{cpp} evaluation/buffered_prg.cpp
+ +## Three-party (2,3) DPF {#dpf3} + +`make_dpf3(α, β)` builds three evaluator keys after Guy Zyskind, Avishay Yanai, and Alex "Sandy" Pentland, [ePrint 2024/1658](@ref bib_dpf3), Figure 3: +two VDPF+ spines plus Shamir embedding in `fp61`. `eval_point` returns a +field share; open with `dpf::reconstruct` on any two (or all three) +`dpf::as_share` values. Tags: `verifiable`, `extractable`, `updatable` +(Fig. 10 in-place payload update). + +`make_dpf3_doerner_shelat(x0, x1, β)` is the dual-spine Doerner–Shelat path: +XOR shares of `α`, same clear `β`. Socket orchestration lives in +`party/dist_dpf3.hpp` (`dist_with_dpf3_key`): after keygen, role **p0** holds +party 1, **p2** holds party 2, **p1** holds party 3. Path bits open to p0/p1; +p2 does not learn `α`. Dist keys are verifiable; payload updates use dealer +`updatable` keys or `remake_dpf3`. + +Comparison / interval: `make_dpf3_cmp`, `make_dpf3_cmp_blocked`, `make_dpf3_ic`. +Multipoint: `make_multipoint3` (cuckoo buckets of point keys). + +`make_dpf3` is local dealer work. Each evaluator key is two VDPF+ spines, +`Θ(n λ)` bits with a constant factor over one two-party key. `eval_point` +is two walks, `Θ(n)` expands, then a field scale. Opening two or three +`as_share` values is a constant number of `fp61` operations. An +`updatable` rewrite is four leaf patches and an offset refresh, `O(λ)` +and independent of `n`. `make_dpf3_doerner_shelat` is two +Doerner–Shelat spines: twice the rounds and the pad tape of one +two-party opening (see [Doerner–Shelat](@ref tour_ds)). Comparison, +interval, and multipoint forms add the same extra material as the +two-party comparison, interval, or cuckoo packing, on top of those spines. + +## Information-theoretic 3-server DPF {#it_dpf3} + +`make_it_dpf3(α, β)` ([ePrint 2023/028](@ref bib_itdpf)) is a different object from +`make_dpf3`. Each of three parties holds an additive share of the +characteristic vector on `{0..255}`; the **sum** of all three +`eval_it_dpf3` values is the point function. Any single key is +independent of `(α, β)`. For this domain size the key is a full +truth-table share (`N = 256` words), not the paper's matching-vector +packing. PIR is `eval_it_dpf3_inner_product` on each server; the three +dots sum to the record. See [it_dpf3.hpp](@ref dpf/it_dpf3.hpp) and +[Three-server PIR](@ref app_pir3). + +Socket (2+1) keygen lives in `party/dist_ds.hpp`. The two-party peer that +replaces the pad dealer is IKNP (`party/iknp_deal.hpp`, +`dist_with_*_iknp`): same Doerner–Shelat walk after Jack Doerner and abhi +shelat, CCS 2017 ([ePrint 2017/827](@ref bib_ds)), with pads from `dpf::iknp::sample` +(Ishai, Kilian, Nissim, and Petrank, CRYPTO 2003; Chou–Orlandi base OT, +[ePrint 2015/267](@ref bib_chou)). There is no two-party `(2,3)` Shamir path — +`dist_dpf3` needs three key holders. Fig-10 `updatable` payload rewrites +also stay on `dist_dpf3`. + +**IKNP vs dealer vs Half-Tree (costs).** With input bit length `n` and +seed width `λ = 128`, a reveal point tape has length `T = Θ(n)` +(`ncw = n`, `nblock = 2n + n_leaf`). An oblivious (non-reveal) tape adds +`n · 40960` bit×block pads because each level runs the Boyar–Peralta +32-AND S-box hash ([ePrint 2011/332](@ref bib_boyar); see `hash_level_and_count()`). + +- Dealer `make_dpf`: 0 rounds, 0 bytes, `Θ(n)` AES expands. +- DS + p2 (`dist_with_*`): dealer sends `Θ(n λ)` bits of pads offline; + online is `n` rounds and `Θ(n λ)` bits of peer opens (guided tour + [Doerner–Shelat](@ref tour_ds)). +- Half-Tree §5.2 ([ePrint 2022/1431](@ref bib_halftree)): `n+3` rounds in the COT/OLE hybrid + with no beaver-pad dealer. This library's IKNP path does **not** use + that hybrid; it keeps the per-level DS open after OT-sampled pads. +- IKNP + DS: two Chou–Orlandi sessions of `κ = 128` base OTs, then two + OT-extension directions whose U-matrix and correction traffic is + `Θ(κ T)` bits, then the same `n` DS opens as the dealer walk (no p2 + frames). Local work adds `Θ(κ)` P-256 scalar muls and the AES column + expands of the extension. + +One localhost measurement (`party_bench --case iknp_geneval_point +--repeat 1 --warmup 0`, `uint8` so `n = 8`, reveal): ≈ 1.08 s wall; +about 10–16 KiB per direction on the p0–p1 link; harness `rounds = 47` +and `prg_evals = 2431`. That is a measurement of this harness, not a +claimed speedup over dealer keygen or over Half-Tree §5.2. + +**Code samples**\n +
+ + - eval_dpf3_point.cpp \include{cpp} evaluation/eval_dpf3_point.cpp + - eval_dpf3_doerner_shelat.cpp \include{cpp} evaluation/eval_dpf3_doerner_shelat.cpp + - eval_dpf3_cmp_ic.cpp \include{cpp} evaluation/eval_dpf3_cmp_ic.cpp + +
diff --git a/doc/pages/guided_tour.md b/doc/pages/guided_tour.md new file mode 100644 index 0000000..7dca21f --- /dev/null +++ b/doc/pages/guided_tour.md @@ -0,0 +1,634 @@ +# A guided tour of libdpf++ {#guided_tour} + +This page is a map of the library in plain words. +It shows short code, then points you to the full manuals when you want more. +To choose which object to build, follow [which DPF](@ref which_dpf). + +[TOC] + +## What problem does this solve? {#tour_why} + +Imagine a big table of zeros, with one non-zero cell at a secret place. +You want two helpers to hold that table. +Each helper gets a small key, not the whole table. +Neither helper learns where the non-zero cell is. +Together, their answers add up to the true table. + +That secret table is a *point function*. +A *distributed point function* (DPF) is a way to share it with short keys. +`libdpf++` builds those keys and evaluates them quickly in C++17. + +People use DPFs for private lookup (PIR), multi-party computation (MPC), +and other privacy tools. See also the [ideal functionalities](@ref ideal_functionalities) +for what each protocol is allowed to learn. + +## Prior work {#tour_prior} + +Elette Boyle, Niv Gilboa, and Yuval Ishai gave the point-function keys +this library generates, and the later sections name Jack Doerner and +abhi shelat, Xiaojie Guo, Kang Yang, Xiao Wang, Wenhao Zhang, Xiang Xie, +Jiang Zhang, and Zheli Liu, and the other authors, next to the +construction they described. The [bibliography](@ref bibliography) lists +each paper once. + +## Your first DPF {#tour_first} + +Include one header. Build a key for each of two parties. +Evaluate at a point. Open the two shares. + +```cpp +#include "dpf.hpp" + +const std::uint8_t alpha = 42; // secret index +const std::uint64_t beta = 7; // secret payload +auto [k0, k1] = dpf::make_dpf(alpha, beta); + +auto y0 = *dpf::eval_point(k0, alpha); +auto y1 = *dpf::eval_point(k1, alpha); +std::uint64_t opened = dpf::reconstruct(y0, y1); // 7 +``` + +Leaf outputs open with subtraction: `share0 - share1`. +Comparison outputs open with addition: `share0 + share1`. +`dpf::reconstruct` picks the right rule from the share types. + +**Try next** +- Full example: [eval_point.cpp](@ref evaluation/eval_point.cpp) +- Eval manual: [Evaluating DPFs](@ref evaluation) +- Key API: [dpf_key.hpp](@ref dpf/dpf_key.hpp) + +## The feature zoo {#tour_zoo} + +The list on the home page is the short version. +Here is the full map. + +## Inputs: where the secret point lives {#tour_inputs} + +The *input type* is the type of the index `alpha`. +Shorter inputs mean shorter keys and faster walks. + +| Kind | What it is for | Go deeper | +| --- | --- | --- | +| `uintN_t` / `intN_t` | Widths 8, 16, 32, 64 | [Input types](@ref input_types) | +| 128-bit integers | Big domains | [extended types](@ref input_types) | +| `modint` / `modintN_t` | Unsigned residue mod `2^N` | [modint.hpp](@ref dpf/modint.hpp) | +| `xint` / `xintN_t` | XOR ring of width `N` | [xor_wrapper.hpp](@ref dpf/xor_wrapper.hpp) | +| `dpf::bitstring` | Opaque bit strings | [bitstring.hpp](@ref dpf/bitstring.hpp) | +| `dpf::keyword` / `keyword2` | Dictionary keys, ranked patterns | [keyword2.hpp](@ref dpf/keyword2.hpp) | +| `grotto::fixedpoint` | Fixed-point domain | [fixedpoint.hpp](@ref grotto/fixedpoint.hpp) | +| `wildcard_value` | Secret index assigned later | [wildcard.hpp](@ref dpf/wildcard.hpp) | +| Custom types | Your own domain | [custom input rules](@ref custom_input_types) | + +```cpp +using X = dpf::modint<20>; // domain size 2^20 +auto [k0, k1] = dpf::make_dpf(X{1000}, std::uint64_t{1}); +// same index: 1000_u20 +``` + +Width literals sit next to those types: `100_u12` (`modint`), `7_x12` +(`xint`), `dpf::literals::operator""_bitstring`, and `1.5_fixed16` through +`_fixed64`. `dpf::bit`, `dpf::twobit`, and `dpf::nyble` are outputs, not +domains. See [Input types](@ref input_types). + +## Outputs: what sits at that point {#tour_outputs} + +The *output type* is the group element at `alpha`. +Many outputs can share one leaf when they fit. + +| Kind | Group | Go deeper | +| --- | --- | --- | +| `uintN_t` / `intN_t` / `modint` | Additive | [Output types](@ref output_types) | +| `xint` / `xor_wrapper` | Word XOR | [xor_wrapper.hpp](@ref dpf/xor_wrapper.hpp) | +| `dpf::bit` | One XOR bit, packed | [bit.hpp](@ref dpf/bit.hpp) | +| `dpf::twobit` | Z/4Z, packed 2-bit lanes | [twobit.hpp](@ref dpf/twobit.hpp) | +| `dpf::nyble` | Z/16Z, packed nibbles | [nyble.hpp](@ref dpf/nyble.hpp) | +| `dpf::bitstring` | XOR string | [bitstring.hpp](@ref dpf/bitstring.hpp) | +| `grotto::fixedpoint` | Fixed-point raw word | [fixedpoint.hpp](@ref grotto/fixedpoint.hpp) | +| `dpf::vec` | `N` lanes, no carry between them | [vec.hpp](@ref dpf/vec.hpp) | +| `dpf::wildcard_value` | Fill later | [wildcard.hpp](@ref dpf/wildcard.hpp) | +| `field64` / `field128` | Prime fields | [field64.hpp](@ref dpf/field64.hpp) | +| `fp61` | Field for 3-party DPFs | [fp61.hpp](@ref dpf/fp61.hpp) | +| `p256` / `p256_scalar` | Curve and order | [p256.hpp](@ref dpf/p256.hpp) | +| Typed shares | (2,2) additive and subtractive, (3,3) additive, (2,3) replicated | [secret_share.hpp](@ref dpf/secret_share.hpp) | + +```cpp +auto [k0, k1] = dpf::make_dpf( + std::uint8_t{12}, + dpf::wildcard_value{}); +// assign the payload later; eval before assign throws +``` + +Packed output literals are `1_bit`, `2_twobit`, and `10_nyble`. +`dpf::vec` is `N` lanes of an ordinary output with no carry between +lanes; the construction is on [Output types](@ref output_types). +Assigning a wildcard leaf is +[leaf_nodes](@ref wildcard_assign): `compute_and_get_blinded_output_share`, +`compute_and_get_leaf_share`, `reconstruct_correction_word`, or +`async_assign_leaf` over a socket. The socket path is two rounds, one +output-width share each way per round. The Beaver triple on the slot +is preprocessing from keygen. `assign_cmp` rewrites `n` words locally +and sends nothing. + +## Many leaves and prefix slots {#tour_leaves} + +One key can carry several payloads. +`dpf::at(value)` plants a value on a public prefix of length `N`. +That is an *incremental* DPF: one path can read an ancestor and a leaf. + +```cpp +auto [k0, k1] = dpf::make_dpf( + std::uint8_t{0x2a}, + dpf::at<4>(std::uint8_t{5}), // high nibble + std::uint8_t{9}); // full leaf, prefix 8 +auto hi = *dpf::eval_point(dpf::out<0, 4>, k0, std::uint8_t{0x2a}); +auto leaf = *dpf::eval_point(dpf::out<1, 8>, k0, std::uint8_t{0x2a}); + +// one payload per listed prefix; idpf(y0, y1) is prefixes 1, 2, ... +auto [h0, h1] = dpf::make_dpf(std::uint8_t{0x2a}, + dpf::idpf_at<4, 8>(std::uint8_t{5}, std::uint8_t{9})); +``` + +`dpf::out` reads slot `I` and takes the prefix from the key. +`dpf::out` checks that the prefix is `W`. + +**Go deeper:** [incremental.hpp](@ref dpf/incremental.hpp), +[placement.hpp](@ref dpf/placement.hpp), +[F_IDPF](@ref incremental.hpp). + +## How you evaluate {#tour_eval} + +| Call | What it does | +| --- | --- | +| `eval_point` | One input | +| `eval_interval` | Inclusive range `[from, to]` | +| `eval_full` | Whole domain | +| `eval_sequence` | Sorted list of points | +| `eval_inner_product` | Dot with weights during the walk | +| `eval_until` / `idpf_eval_ctx` | Compact prefix shares up to a public bit depth | +| Memoizers | Keep tree nodes between calls | +| Output buffers | Hold the written shares | + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{42}, std::uint64_t{7}); +std::vector w(11, 1); +auto s0 = dpf::eval_inner_product(dpf::paired, k0, std::uint8_t{40}, + std::uint8_t{50}, w); +auto s1 = dpf::eval_inner_product(dpf::paired, k1, std::uint8_t{40}, + std::uint8_t{50}, w); +// reconstruct(s0, s1) == 7 * w[2] +``` + +Let `n` be the input bit length and `λ` the seed width (128 bits for +the default AES PRG). A key is `Θ(n λ)` bits plus its payloads. +That is the Boyle–Gilboa–Ishai CCS 2016 point key (full version +[ePrint 2018/707](@ref bib_fss2018)): one correction word per level. For a small output group their Remark 3.4 stops +`ν = log2(λ / log2|G|)` levels early. This generator does that: +`depth` is `n` minus the log of how many outputs pack in one leaf, and +those low bits select the lane. `eval_point` is `Θ(n)` expands. An inclusive interval of `L` inputs is +`Θ(n + L)` expands and `L` output slots. A sorted list of `m` points is +at most `O(n m)` expands and `m` slots. A full-domain eval is `Θ(2^n)`. +An inner product does that same walk and keeps only the accumulator. +Memoizer and buffer sizes are on the [eval manual](@ref evaluation). + +**Go deeper:** [Evaluating DPFs](@ref evaluation), +[eval_inner_product.hpp](@ref dpf/eval_inner_product.hpp), +[eval_until.hpp](@ref dpf/eval_until.hpp), +examples under `examples/evaluation/`. + +`idpf_eval_ctx` plus `eval_until` ([ePrint 2021/017](@ref bib_poplar) EvaluateUntil) emit +compact prefix shares up to a public bit depth without a full-domain +walk. `idpf_agg_max` / `idpf_agg_kth` ([ePrint 2024/1190](@ref bib_idpfagg)) drive that +API for order statistics — see [I-DPF max and k-th](@ref app_idpf_agg). + +## Iterables {#tour_iterables} + +After a multi-point eval, you often want to walk only the hot leaves, +or zip two parties' buffers. +`eval_interval` and `eval_full` return a `subinterval_iterable`. +`eval_sequence` returns a `subsequence_iterable`. +`indices_set_in` walks the set bits of a bit-output iterable. +`advice_bits_of` yields the low bit of each element. +`batch_of` steps several bit arrays together. +`tuple_as_zip` walks iterables in lockstep. +`rotated_by` rotates a container. + +**Go deeper:** [Iterables](@ref iterables). + +## Comparisons and ranges {#tour_dcf} + +A *distributed comparison function* (DCF) returns a payload when a predicate +holds on the secret point. The four predicates are `dpf::lt`, `dpf::leq`, +`dpf::gt`, and `dpf::geq`. Each takes the true payload and an optional false +payload (zero by default). `lt_at` and the same `_at` forms for +`leq`, `gt`, and `geq` plant the channel on a prefix of length `N`. +Evaluate with `dpf::cmp`. Shares are additive. + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{40}, dpf::gt(std::uint64_t{1})); +// hot when the query is greater than 40 +auto y0 = dpf::eval_point(dpf::cmp, k0, std::uint8_t{50}); +auto y1 = dpf::eval_point(dpf::cmp, k1, std::uint8_t{50}); +auto opened = dpf::reconstruct(y0, y1); // 1 +``` + +A wildcard comparison payload is filled later with `assign_cmp`. Eval +before that throws. + +```cpp +auto [w0, w1] = dpf::make_dpf(std::uint8_t{40}, + dpf::lt(dpf::wildcard_value{})); +dpf::assign_cmp(w0, w1, std::uint64_t{7}, std::uint64_t{0}); +``` + +`dpf::idcf(dpf::gt(beta))` (any of the four predicates) stores a correction +at every depth. `dpf::cmp` is the full point; `dpf::cmp_prefix` is the +first `L` bits. `dpf::eq(if_true, if_false)` is equality, with `eq_at` +on a prefix. `dpf::block_width(dpf::lt(beta))` keeps ring words only at +checkpoints `B` levels apart. + +```cpp +auto [i0, i1] = dpf::make_dpf(std::uint8_t{40}, + dpf::ic(10, 20, std::uint64_t{1})); +// hot when (x - 40) mod 256 is between 10 and 20 +auto in0 = dpf::eval_point(dpf::ic, i0, std::uint8_t{55}); +``` + +A comparison key keeps the `n`-level seed spine and one payload word +per level (`Θ(n λ + n w)` bits for a `w`-bit payload). That is the DCF +of Boyle, Chandran, Gilboa, Gupta, Ishai, Kumar, and Rathee, EUROCRYPT +2021 ([ePrint 2020/1392](@ref bib_dcf)). `block_width` stores a payload word every +`B` levels, `Θ(n/B)` words, and the spine is unchanged. Against that +EUROCRYPT 2021 comparison, which publishes a value word on every level, +`block_width` is ahead on payload size: about `B` times fewer value +words, with the same seed spine. Point evaluation expands the siblings +between checkpoints to recover the same comparison. Point eval is one path, +`Θ(n)` expands, plus an `O(n)` sum of those words. `ic` is one such key, +the public-interval gate in their Figure 3, evaluated at two public +shifts, still `Θ(n)`. + +Path paints put a unit on sibling subtrees and scale it by +`if_true - if_false`. The canned ones are `lcp`, `common_prefix`, +`prefix_mask`, `diverge_one_hot`, `break_bit`, and `prefix_with_length`. +`path_paint(fn, if_true, if_false)` supplies the unit. Each has an `_at` +form. + +```cpp +auto [p0, p1] = dpf::make_dpf(std::uint8_t{40}, + dpf::common_prefix(std::uint64_t{1})); +``` + +**Go deeper:** [dcf.hpp](@ref dpf/dcf.hpp), +[blocked_dcf.hpp](@ref dpf/blocked_dcf.hpp), +[interval.hpp](@ref dpf/interval.hpp), +ideal figures [F_DCF](@ref dcf.hpp), [F_BDCF](@ref blocked_dcf.hpp), [F_IC](@ref interval.hpp). + +## Verifiable and extractable keys {#tour_vdpf} + +Pass `dpf::verifiable{}` or `dpf::extractable{}` as an extra `make_dpf` +argument. `verifiable` follows de Castro and Polychroniadou, EUROCRYPT +2022 ([ePrint 2021/580](@ref bib_vdpf)): one extra correction seed per level (their hash +outputs `4λ` bits), a `2λ`-bit proof token, and the proof fold is part +of the same `Θ(n)` walk. The stored seed is `Θ(n λ)` more bits. +`extractable` adds one `fp61` sketch token to that walk. +Each party folds a short proof token on a verifiable key. +Equal tokens mean the walk used honest correction seeds. +`extractable` is a weight-1 sketch over `fp61`. A second hot point fails +the check. `dpf::updatable{}` keeps a beaver leaf so a later assign can +rewrite the payload. + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{42}, std::uint64_t{7}, + dpf::verifiable{}); +auto [e0, e1] = dpf::make_dpf(std::uint8_t{42}, std::uint64_t{7}, + dpf::extractable{}); +``` + +**Go deeper:** [verifiable.hpp](@ref dpf/verifiable.hpp), +[F_VDPF](@ref verifiable.hpp), [F_Sketch](@ref verifiable.hpp). + +## Many points at once {#tour_multipoint} + +`make_multipoint(alphas, betas)` packs many points into cuckoo buckets, +following de Castro and Polychroniadou, EUROCRYPT 2022, §4 (ePrint +2021/580): `κ = 3` hashes, one point key per bucket. +Pass `dpf::verifiable{}` before the optional `multipoint_params` for a +batched proof: one token. There is one point key per bucket, and the +bucket count is linear in the number of points `m`, so the keys are +`Θ(m n λ)` bits. Eval probes three buckets: `Θ(n)` expands. + +```cpp +std::vector alphas{1, 2, 3}; +std::vector betas{4, 5, 6}; +auto [k0, k1] = dpf::make_multipoint(alphas, betas); +``` + +**Go deeper:** [multipoint.hpp](@ref dpf/multipoint.hpp), [F_MPDPF](@ref multipoint.hpp). + +## A vector with one programmable coordinate {#tour_ppvc} + +`dpf::ppvc` commits to a vector in `(Z/2^s Z)^n` before the hidden +coordinate is chosen. The commitment binds both roots of `s` aligned +1-bit DPF pairs. Opening one side of each pair writes that coordinate, +or the sum of the vector, and a shift moves it onto a public index. + +```cpp +using scheme = dpf::ppvc; +const auto pp = scheme::setup(); +const auto [com, st] = scheme::commit(pp); +const auto op = scheme::open(st, 0, 0x5a, std::uint8_t{40}); +``` + +`dpf::k_ppvc` is `K` of those commitments. The full account is +[Point-programmable vector commitments](@ref ppvc_manual). + +## Tree shapes: classic and Half-Tree {#tour_trees} + +The default interior PRG walks a Boyle–Gilboa–Ishai tree (CCS 2016, +full version [ePrint 2018/707](@ref bib_fss2018)). +Select `prg::aes128_ccr` as the *interior* PRG to use Half-Tree expands +(`H(s)` and `H(s) XOR s`), following Guo, Yang, Wang, Zhang, Xie, Zhang, +and Liu, [ePrint 2022/1431](@ref bib_halftree). Keys stay compatible with the same eval API. +The number of levels is still `n`, and a key is still `Θ(n λ)` bits. +Their dealer scheme keeps that length and the `n`-hash point evaluation, +and states about `2n+2` random-permutation calls to generate a key versus +about `4n`, and `1.5N` calls for a full-domain evaluation versus `2N`. + +**Go deeper:** [tree_traits.hpp](@ref dpf/tree_traits.hpp), +[prg_aes_ccr.hpp](@ref dpf/prg_aes_ccr.hpp). + +## Two-party keygen without a dealer: Doerner–Shelat {#tour_ds} + +Two parties hold XOR (or additive) shares of `alpha`. +A third party deals pads and learns nothing. +The result matches what an honest `make_dpf` would emit. + +`geneval_*` does keygen and eval in one pass on a public query trie. +You get shares of the answer without a reusable key. + +```cpp +const std::uint8_t alpha = 42; +const std::uint8_t x0 = 0x13; +const std::uint8_t x1 = static_cast(alpha ^ x0); +const std::uint64_t beta = 7; +auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, beta); + +// index is x0 + x1; payload shares y0 + y1 reconstruct to beta +const std::uint64_t y0 = 3; +const std::uint64_t y1 = beta - y0; +auto [a0, a1] = dpf::make_dpf_doerner_shelat( + dpf::arith_input, x0, x1, beta); +auto [b0, b1] = dpf::make_dpf_doerner_shelat( + dpf::arith_output, x0, x1, y0, y1); +``` + +`geneval_point`, `geneval_interval`, +`geneval_full`, and `geneval_sequence` do that keygen and the eval in one +pass. They take the two shares, the public query, a `dpf::ds_randomness` +tape (root sampler and pad stream), and the payload. + +The convenience `make_dpf_doerner_shelat` samples its pad tape locally +and returns both keys with no messages. The opening follows Jack Doerner +and abhi shelat, CCS 2017 ([ePrint 2017/827](@ref bib_ds)). A networked opening does one +round per level, `n` rounds, because the next seeds depend on that +level's correction word. Each round sends a constant number of `λ`-bit +blinds and returns one `λ`-bit correction word plus advice and AND bits. +Xiaojie Guo, Kang Yang, Xiao Wang, Wenhao Zhang, Xiang Xie, Jiang Zhang, and Zheli Liu ([ePrint 2022/1431](@ref bib_halftree), §5.2) generate a DPF in the COT/OLE hybrid +in `n+3` rounds with no beaver-pad dealer; this opening uses that dealer +tape. `geneval_*` is the same per-level opening on the public query trie. +It does not return their reusable key, and with a local tape it sends +nothing. +Preprocessing is one correction-word pad and two AND pads per level, +`Θ(n λ)` bits. `geneval_*` itself sends nothing: each live level samples +those pads from the `ds_randomness` tape and folds the frontier into one +correction word. Local expand time is `O(n F)` PRG expands, where `F` is +the number of distinct query leaves on the trie (the call rejects more +than `2^20`). That is `Θ(n)` for one point and follows the trie for an +interval, a listed sequence, or a full domain. No reusable key is stored. `arith_input` first runs an `n`-step +ripple carry: `n` bit-multiplication triples and `n` bit openings, then +the same tree rounds. `arith_output` splits the payload and does not add +a round of its own beyond that split. + +### Two-party socket walk without p2 (IKNP) {#tour_iknp} + +When there is no pad dealer, p0 and p1 sample the same Doerner–Shelat +pad correlations with semi-honest OT extension after Yuval Ishai, Joe +Kilian, Kobbi Nissim, and Erez Petrank, CRYPTO 2003 (`dpf::iknp::sample`), +seeded by the Chou–Orlandi base OT of Tung Chou and Claudio Orlandi, +LATINCRYPT 2015 ([ePrint 2015/267](@ref bib_chou)) on P-256, and install them into a local +inbox. The tree walk is the same as `dist_with_*` in `party/dist_ds.hpp`. +Entry points: + +- `dist_with_point_key_iknp` — point / half-tree / wildcard / additive input +- `dist_with_extractable_point_key_iknp` +- `dist_with_cmp_key_iknp` — comparison (reveal or oblivious) +- `dist_with_ic_key_iknp` — interval containment + +A wildcard leaf is the two-party payload bind: keygen does not learn β, and +`assign` writes it afterwards on the p0–p1 link. Fig-10 `updatable` rewrites +are a `(2,3)` operation and stay on `dist_dpf3`. + +**Ideal (semi-honest).** Each party inputs its share of the point (XOR or +additive) and the payload; each outputs its DPF key. With +`RevealPoint`/`Reveal`, the reconstructed point (or comparison/interval +prefix) may be opened because the caller asked. Without reveal, α, the +peer seed, the peer payload share, and pad bits that would open the path +stay hidden. Correction-word `gamma` pads are XOR shares of +`(bit1·rand0)⊕(bit0·rand1)` so neither party learns the peer pad bit +(learning that bit plus the opened blind would open the peer path bit). + +**Costs (asymptotic, then one measured case).** Let `n` be the input bit +length and `λ = 128` the seed width. Write `T` for the XOR-pad tape +length that `iknp_deal::add_*` demands: for a reveal point key, +`T = Θ(n)` (`ncw = n` correction-word pads and `nblock = 2n + n_leaf` +bit×block pads). An oblivious (non-reveal) walk also buys +`n · hash_level_and_count()` bit×block pads, and +`hash_level_and_count() = 8×16×10×32 = 40960` follows the Boyar–Peralta +32-AND AES S-box ([ePrint 2011/332](@ref bib_boyar)) inside `party/oblivious_hash.hpp`. + +| Path | Rounds | Communication | Local work | +| --- | --- | --- | --- | +| Dealer `make_dpf` | 0 | 0 | `Θ(n)` AES PRG expands | +| DS + p2 dealer (`dist_with_*`) | `n` online (one CW open per level) | Dealer tape `Θ(n λ)` bits offline; peer opens `Θ(n λ)` bits | `Θ(n)` expands | +| Half-Tree §5.2 ([ePrint 2022/1431](@ref bib_halftree)) | `n+3` in the COT/OLE hybrid | Hybrid COT/OLE (paper §5.2; not this library's opening) | CCR expands | +| IKNP + DS (`dist_with_*_iknp`) | Constant-round base OT + extension, then the same `n` DS opens | Base: two Chou–Orlandi sessions of `κ = 128` OTs (33-byte P-256 points). Extension (two directions): each sends `κ ⌈T/8⌉` bytes of U plus `T · λ/8` bytes of OT correction, then `ncw · λ/8` bytes of `gamma`; B2A pads add one more extension of length `nb2a`. Peer DS messages match the dealer walk (no p2 frames). | `Θ(κ)` P-256 scalar muls for base OT; `Θ(κ T / w)` AES column expands for the extension (`w` is the chunk width); then `Θ(n)` tree expands | + +Compared to the p2 dealer path, IKNP removes the third party and replaces +the offline `Θ(n λ)`-bit pad send with OT whose dominant term is +`Θ(κ T)` bits for a reveal walk (`T = Θ(n)`). It does **not** beat +Half-Tree §5.2's `n+3` round count: this library still opens one level +per round after the pads exist. It also does not claim a wall-clock +speedup over dealer keygen; dealer `make_dpf` stays local and free of +public-key work. + +**Measurement** (`party_bench --case iknp_geneval_point --repeat 1 +--warmup 0`, `uint8` domain so `n = 8`, reveal point, localhost sockets, +one run): wall ≈ 1.08 s; p0 sent 9805 B / received 16499 B; p1 sent +11245 B / received 15059 B; harness `rounds = 47` (IKNP frames plus the +`n` DS opens and the flow's eval openings); `prg_evals = 2431` each. +The same harness on `iknp_wildcard_leaf` (non-reveal wildcard, so the +Boyar–Peralta oblivious hash tape is live) moved tens of megabytes and +took ≈ 1.8 s on one run — that is the `n · 40960` pad blow-up, not a +claim about payload assign alone. + +`(2,3)` Shamir DPF (`party/dist_dpf3.hpp`) still needs a third key holder; +there is no pure 2-party `dpf3` entry point. + +**Go deeper:** [doerner_shelat.hpp](@ref dpf/doerner_shelat.hpp), +[geneval.hpp](@ref dpf/geneval.hpp), +[F_DS](@ref doerner_shelat.hpp), [F_GenEval](@ref geneval.hpp), +party mesh [trio.hpp](@ref dpf/net/trio.hpp). +[iknp.hpp](@ref dpf/iknp.hpp), `party/iknp_deal.hpp`. +[Bibliography](@ref bibliography). + +## Multiplication and circuits: Beaver {#tour_beaver} + +ABY2.0-style sessions open masked wires once, following Patra, Schneider, +Suresh, and Yalame, USENIX Security 2021 (full version [ePrint 2020/1225](@ref bib_aby2)). +A fresh triple follows Donald Beaver, [CRYPTO 1991](@ref bib_beaver), which reconstructs +both masked factors. +Polynomials, dots, scales, and bit-muxes share blinds. +Optional MAC tags give Shark/SPDZ-style checks. + +One call that samples a list of formulae opens the new wires in one +round. Communication is one masked value per newly opened wire, of that +wire's width, plus a tag share of the same width when MACs are on. +Preprocessing is one blind per wire and one product share per monomial. +A later round reuses blinds it already holds and samples only the new +monomials. The dealer keeps each full blind; the parties receive the +additive splits. + +**Go deeper:** [beaver.hpp](@ref dpf/beaver.hpp), +[F_Beaver](@ref beaver.hpp), [F_BeaverAuth](@ref beaver.hpp), +[constrained_cmp.hpp](@ref dpf/constrained_cmp.hpp) for `F_CCMP`. + +## Three evaluators {#tour_dpf3} + +`(2,3)` point keys follow Zyskind, Yanai, and Pentland, [ePrint 2024/1658](@ref bib_dpf3), +Figure 3: each evaluator key is a pair of `(2,2)`-VDPF+ keys. +Each key is a Shamir share in `fp61`. +Any two parties open. Keys can be verifiable, extractable, or updatable. +Dealer `make_dpf3` is local. Each key is two spines, `Θ(n λ)` bits. +Their evaluation section records about `2×` the key size of one two-party +DPF, which is this pair of spines. +Eval is two point walks, `Θ(n)`. Opening the field shares is `O(1)` +`fp61` arithmetic. An updatable payload rewrite is `O(λ)`, independent +of `n`. The dual-spine Doerner–Shelat form costs two copies of the +two-party opening in [that section](@ref tour_ds). + +```cpp +auto [k1, k2, k3] = dpf::make_dpf3(std::uint8_t{42}, dpf::fp61{7}); +auto y1 = dpf::eval_point(k1, std::uint8_t{42}); +auto y2 = dpf::eval_point(k2, std::uint8_t{42}); +auto y3 = dpf::eval_point(k3, std::uint8_t{42}); +auto opened = dpf::reconstruct( + dpf::as_share(k1, y1), dpf::as_share(k2, y2), dpf::as_share(k3, y3)); +``` + +Dual-spine Doerner–Shelat builds the same three keys from XOR shares of +`alpha`. Comparison, interval, blocked, and multipoint forms are +`make_dpf3_cmp`, `make_dpf3_ic`, `make_dpf3_cmp_blocked`, and +`make_multipoint3`. `remake_dpf3` replaces an updatable payload. + +```cpp +const std::uint8_t alpha = 42; +const std::uint8_t x0 = 0x13; +const std::uint8_t x1 = static_cast(alpha ^ x0); +auto [s1, s2, s3] = dpf::make_dpf3_doerner_shelat(x0, x1, dpf::fp61{7}); +auto opened_ds = dpf::reconstruct( + dpf::as_share(s1, dpf::eval_point(s1, alpha)), + dpf::as_share(s2, dpf::eval_point(s2, alpha)), + dpf::as_share(s3, dpf::eval_point(s3, alpha))); +``` + +**Go deeper:** [dpf3.hpp](@ref dpf/dpf3.hpp), +[dpf3_ds.hpp](@ref dpf/dpf3_ds.hpp), +[dpf3_cmp.hpp](@ref dpf/dpf3_cmp.hpp), +[dpf3_multipoint.hpp](@ref dpf/dpf3_multipoint.hpp), +examples `eval_dpf3_*.cpp`. + +`make_it_dpf3` is a different three-server object ([ePrint 2023/028](@ref bib_itdpf)): +additive truth-table shares on `{0..255}`, not Shamir spines. The sum of +three `eval_it_dpf3` values is the point function. See +[Information-theoretic 3-server DPF](@ref it_dpf3) and +[Three-server PIR](@ref app_pir3). + +## Grotto: math after a public offset {#tour_grotto} + +Open `eta = x - r`. Then cheap public corrections give rich functions of `x` +without another tree walk. + +| Tool | Idea | +| --- | --- | +| Offset Horner / poly | Powers of the center, then a binomial shift | +| Binomial jet | Shares of `\binom{x}{k}`; free public dots | +| Ring switch | Exact move into `Z/M`, fields, or P-256 scalars | +| `nmod` | Truncated Barrett reduction by a public modulus | +| Representation shift | Advance a linear recurrence by public `\kappa` | +| Twisted jets | `c^m \lambda^c`, including dyadic `1/2` | +| Carry | Truncate, arithmetic shift, extend on shared limbs | +| Prefix parity | XOR or signed prefix sums along a key | +| LUTs | Constant, easy, dyadic, range, window, principal | +| Closed form / exact steps | Compositions and digit or bit counts | +| `fixed_mul` | Fixed-point product into a chosen width | + +```cpp +#include "grotto.hpp" +const std::uint8_t center = 12; +const std::uint8_t r = 200; +auto jet_keys = grotto::make_offset_jet_keys(center, 3); +// after eta opens: offset_jet_shares, then offset_jet_dot +auto ring = grotto::make_ring_switch_keys>(r); +auto horner = grotto::make_offset_horner_keys(center); +auto poly = grotto::make_offset_poly_keys(center, 4); +using q16 = grotto::fixedpoint<16, std::int32_t>; +auto prod = grotto::fixed_mul<16, 16>(q16{1.5}, q16{2}); +``` + +For degree `d` and `K` knots, offset Horner, poly, jet, and twist each +store one comparison (`d ≤ 3` for Horner, `d ≤ 16` otherwise). The +payload is a `dpf::vec` of the powers, so the `λ`-bit seed spine is +stored once and the value-correction words grow with the vector. Key +size is `Θ(n λ)` for the spine plus `Θ(d n)` bits of value words. +Storrier, Vadapalli, Lyons, and Henry ([ePrint 2023/108](@ref bib_grotto)) evaluate a +public piecewise polynomial from one point key by prefix parity; offset +Horner is this secret-center comparison, not that spline. After `η` is +public, each party runs one sequence-shaped walk on those knots, the +same order as `eval_sequence`, then `O(d^2)` local arithmetic. A ring switch +is one `lt` key and one point eval, `Θ(n)`. `nmod` and `fixed_mul` are +local fixed-width arithmetic (`fixed_mul` uses at most 8 limbs). Carry +keys are one comparison per live recipe flag, on the limb width, plus +one Beaver bit-opening when the recipe multiplies share MSBs. Prefix +parity on `m` endpoints is one resumed path walk, `O(m n)` expands in +the worst case; that walk follows [ePrint 2023/108](@ref bib_grotto). The degree-0 exact +LUTs follow Appendix D of the same paper. Other LUT calls are a knot +search plus a constant-size Horner; exact steps loop over the word. +Detail is on the Grotto pages. + +**Go deeper:** [jet_and_ring](@ref jet_and_ring), +[repr_and_twist](@ref repr_and_twist), +[grotto.hpp](@ref grotto.hpp), +ideal figures on the offset headers under [Ideal functionalities](@ref ideal_functionalities). + +## JSON and async I/O {#tour_io} + +Serialize keys with the JSON helpers. +Ship keys over ASIO peers with `dpf::asio::make_dpf`. + +**Go deeper:** [json.hpp](@ref dpf/json.hpp), [asio.hpp](@ref dpf/asio.hpp). + +## Running protocols {#tour_party} + +The `party/` programs run a three-role mesh (`p0`, `p1`, dealer `p2`). +Flows cover Beaver, geneval, DCF, DPF3, Grotto, and adversarial checks. +Use `--list` and `--tag` to filter. + +**Go deeper:** [trio.hpp](@ref dpf/net/trio.hpp), +`party/registry.hpp` (in-tree). + +## Suggested reading order {#tour_order} + +1. [First program](@ref basics), then this tour if you want the map in prose. +2. [Capabilities](@ref capabilities): verifiability, programmability, comparisons, multipoint, three servers, dealer-free keygen, Beaver, Grotto. +3. [Domains](@ref input_types) and [Payloads](@ref output_types). +4. [Evaluation](@ref evaluation) and the [code examples](@ref listings). +5. [Application sketches](@ref applications). diff --git a/doc/pages/input_types.md b/doc/pages/input_types.md index 693e1e7..23eaf46 100644 --- a/doc/pages/input_types.md +++ b/doc/pages/input_types.md @@ -1,20 +1,33 @@ - +An *input type* is the domain of the secret index. +Shorter domains make shorter keys and faster walks. +Prefer `std::uint16_t` over `unsigned short` so the depth is obvious. -An *input type* is the type used as the domain for the `x`-coordinate of a -DPF. `libdpf++` ships with native support for a number of convenient input -types, which are enumerated below. See also the [formal requirements](@ref custom_input_types) -for a type not listed below to be used as an input type. +| Kind | Use it when | Header | +| --- | --- | --- | +| `uintN_t` / `intN_t` | Width 8, 16, 32, or 64 | `` | +| 128-bit integers | A domain that does not fit in 64 bits | `simde_uint128` | +| `modint` | Unsigned residue mod `2^N`, `N` from 1 to 256. Literal `100_u12` | [modint.hpp](@ref dpf/modint.hpp) | +| `xint` | XOR ring of width `N`. Literal `7_x12` | [xor_wrapper.hpp](@ref dpf/xor_wrapper.hpp) | +| `bitstring` | Opaque bits that are not a number | [bitstring.hpp](@ref dpf/bitstring.hpp) | +| `keyword` / `keyword2` | A short string, or a ranked pattern | [keyword2.hpp](@ref dpf/keyword2.hpp) | +| `fixedpoint` | A fixed-point domain | [fixedpoint.hpp](@ref grotto/fixedpoint.hpp) | +| `wildcard_value` | The index is chosen later | [wildcard.hpp](@ref dpf/wildcard.hpp) | -Within the `libdpf++` source, the (typically deduced) template parameter -`typename InputT` indicates the input type of the DPF under consideration. -Moreover, the `dpf::dpf_key` class (and some others) publicly expose the -clause +`dpf::bit`, `dpf::twobit`, and `dpf::nyble` are packed outputs, not domains. +A domain of that width is `modint` or `xint`. +Rules for a type of your own are in [custom input types](@ref custom_input_types). +A full program for each kind is under [Code examples](@ref input_type_examples). + +```cpp +using X = dpf::modint<20>; +auto [k0, k1] = dpf::make_dpf(X{1000}, std::uint64_t{1}); ``` - using input_type = InputT; -``` -providing an easy way to programmatically determine the input type. -# Integer scalar types +The notes below are closed. Open one when you need the rules. + +
+Integer scalar types + Any integer scalar type—that is, any type `T` such that `std::numeric_limits::is_integer == true`—may be used as an input type. In general, you should always opt for the "shortest" such type that @@ -28,9 +41,23 @@ for specifying input types. For example, use `uint16_t` in place of `unsigned short`, or `int32_t` in place of `int`. Doing so improves portability and makes it easier to keep track of the resulting DPF depth. +Three names cover every integer width: + +- `std::uintN_t` and `std::intN_t` for `N` in `{8, 16, 32, 64}` (and + `simde_uint128` / `simde_int128` for 128 bits). Signed inputs flip the + high bit before the walk so the tree order matches two's complement. +- `dpf::modint` (`dpf::modints::modintN_t`, literal `N_uN`) for an + unsigned residue modulo `2^N`, `N` from 1 through 256. +- `dpf::xint` (`dpf::xints::xintN_t`, literal `N_xN`) for the same width + in `GF(2)^N`. See `xor_wrapper` below. + +`dpf::bit`, `dpf::twobit`, and `dpf::nyble` are packed output lanes of width +1, 2, and 4. They are not domains. A domain of that width is `modint<1>`, +`modint<2>`, or `modint<4>` (or the matching `xint`). + **See also**\n -The `dpf::modint` class template for custom-bitlength integer types that -allow tighter control over size of DPF keys. +[dpf::modint](@ref dpf/modint.hpp), [dpf::xint](@ref dpf/xor_wrapper.hpp), +and [Output types](@ref output_types) for the packed lanes. **Code samples**\n
@@ -38,8 +65,11 @@ allow tighter control over size of DPF keys. - integral_types.cpp \include{cpp} input_types/integral_types.cpp
+
+ +
+Extended-precision integer scalar types -# Extended-precision integer scalar types The extended-precision (`128`-bit) integer scalar types provided as compiler extensions by most major C++ compilers (e.g., `__int128` and `unsigned __int128`), including `g++` and @@ -58,8 +88,11 @@ to declare such 128-bit integers in compiler-independent way. - extended_types.cpp \include{cpp} input_types/extended_types.cpp
+ + +
+dpf::modint<Nbits> -# dpf::modint Arbitrary-, yet fixed-bitlength unsigned integer types. `dpf::modint` is a lightweight class template that adapts one of the above-mentioned integer @@ -75,7 +108,9 @@ discarded. **Pro tip**\n Choose the smallest `Nbits` possible to get DPFs of the shortest length -- -and with the fastest evaluations -- possible. +and with the fastest evaluations -- possible. `dpf::modints::modintN_t` is +`modint` for `N` from 1 through 256. With `using namespace dpf::literals`, +`100_u12` is a `modint<12>`. **Defined in**\n @ref dpf/modint.hpp @@ -87,8 +122,11 @@ Here are some examples of arithmetic operations with `dpf::modint`: - modint.cpp \include{cpp} input_types/modint.cpp +
+ +
+dpf::bitstring<Nbits> -# dpf::bitstring Arbitrary-, yet fixed- bitlength binary strings types. `dpf::bitstring` is a class template that represents a binary string of any given length. @@ -110,7 +148,8 @@ shortest length -- and with the fastest evaluations -- possible. @ref dpf/bitstring.hpp **See also**\n -`dpf::bit` and `dpf::static_bit_array` +`dpf::bit`, `dpf::static_bit_array`, and the aliases `dpf::bitN_t` for +`N` from 1 through 128 (`bitstring`). A literal is `dpf::literals::operator""_bitstring`. **Code samples**\n
@@ -118,8 +157,11 @@ shortest length -- and with the fastest evaluations -- possible. - bitstring.cpp \include{cpp} input_types/bitstring.cpp
+
+ +
+dpf::keyword<Alphabet, N> -# dpf::keyword Fixed-length strings over restricted alphabets. `dpf::keyword` is an alias for the class template `dpf::basic_fixed_length_string`, which represents @@ -171,8 +213,11 @@ The `dpf::alphabets` namespace for a catalog of predefined alphabets. - keyword.cpp \include{cpp} input_types/keyword.cpp +
+ +
+dpf::xor_wrapper<T> -# dpf::xor_wrapper An element of `GF(2)^N` for `N=8*sizeof(T)`. `xor_wrapper` is a lightweight class template that adapts "integer-like" types so that @@ -182,6 +227,11 @@ becomes a nop). This is useful for cases where instances of the input type are to be XOR-shared among two parties. `xor_wrapper` may be specialized with any of the above-mentioned input types. +`dpf::xint` is `xor_wrapper>`. `dpf::xints::xintN_t` names that +type for `N` from 1 through 256, and `7_x12` (in `dpf::literals`) is an +`xint<12>`. Use `xint` when the index itself is an XOR share. Use `modint` +when the index is an ordinary integer of the same width. + **Defined in**\n @ref dpf/xor_wrapper.hpp @@ -191,10 +241,117 @@ with any of the above-mentioned input types. - xor_wrapper \include{cpp} input_types/xor_wrapper.cpp +
+ +
+dpf::keyword2<Pattern> + + +A ranked string whose language is a static pattern. `dpf::keyword2` is the +replacement for `dpf::keyword`: the pattern is the type, and each accepted +string has one rank in `0 .. |L|-1`. That rank is the DPF input. The type +stores the rank as a `dpf::modint` of width `keyword2::bits`. + +The pattern is a null-terminated array with static storage. It is an anchored +expression: literals, character classes, `.`, concatenation, alternation, +`?`, and `{n}` / `{n,m}`. There is no unbounded `*` or `+`. A pad is explicit, +as in `pad('0')[0-9]{0,4}`. The quoted character is digit 0. Leading pads do +not change the rank, and the domain size is still `radix^max`. + +Rank order is structural. Concatenation is mixed-radix with the left piece +in the high place. A pad field sorts by its integer value. A repetition +`{n,m}` is shortlex. Alternatives occupy contiguous blocks in source order +and must be unambiguous. + +The type is refused at compile time when a pad or a variable repetition is +not delimited, when two spellings share a value, when the automaton would +exceed 64 states, when a spelling is longer than 64, or when the language +does not fit in 256 bits. `dpf::keyword2_status()` reports that +diagnostic without defining the type. + +`keyword2` is a domain. It is not a leaf type: leaf arithmetic is not +specialized for it. A payload of the rank's width is a `modint` of +`keyword2::bits`. + +\code{cpp} +inline constexpr char digits[] = "pad('0')[0-9]{0,4}"; +using kw = dpf::keyword2; +kw alpha = "42"; +auto [k0, k1] = dpf::make_dpf(alpha, std::uint64_t{1}); +\endcode + +**Defined in**\n +@ref dpf/keyword2.hpp + +**See also**\n +`dpf::keyword` for the older fixed-alphabet string. +
+ +
+grotto::fixedpoint<FractionalBits, IntegralType> + + +A fixed-point value stored in an integer backend. `FractionalBits` is the +number of bits after the binary point. `IntegralType` defaults to +`uint64_t`, so `grotto::fixedpoint<16>` is a Q48.16 value in a 64-bit word. +The DPF depth is the backend width, not the fractional width. + +`fixedpoint(3)` is the mathematical value 3. The raw encoding is +`3 << FractionalBits`. `from_raw` stores a bit-exact word. A `double` +constructor rounds with `nearbyint` in the current rounding mode. +`grotto::fixedpoint_literals` provides `1.5_fixed16` through `_fixed64` +(and `_fixed0`) on the default `uint64_t` backend. + +Signed backends (`int32_t`, `simde_int128`, and so on) flip the high bit +before the walk so negative values order below non-negative ones. + +The same type is an output. Leaf addition and subtraction are the backend +integer's leaf operations on the raw word. Leaf scaling multiplies by that +raw word and does not shift the binary point back. + +\code{cpp} +using fp = grotto::fixedpoint<16>; // Q48.16 +fp one = 1; // raw 1 << 16 +fp half = fp::from_raw(0x00008000); +auto [k0, k1] = dpf::make_dpf(half, std::uint64_t{1}); +\endcode + +**Defined in**\n +@ref grotto/fixedpoint.hpp +
+ +
+dpf::wildcard_value<T> as a domain + + +A wildcard input defers the secret index. `make_dpf(dpf::wildcard_value{}, beta)` plants a random mask in each key's `offset_x`. The public value the parties later open is `mask - alpha`, not `alpha`. + +Each party calls `offset_x.compute_and_get_share` with its share of `alpha`, exchanges that share, then calls `offset_x.reconstruct` with the other party's share. Evaluation before that throws `std::runtime_error` (`offset not set` / unassigned wildcard). After it, `eval_point` at the real `alpha` opens `beta`. + +To expand a range **before** the offset is assigned, use +`defer_eval_interval` / `defer_eval_full` (full-domain identity eval, then a +rotated view after assign). See [Deferred input evaluation](@ref defer_eval). + +`dpf::wildcards` names the usual placeholders (`wildcards::uint32`, `wildcards::xint`, `wildcards::modint`, `wildcards::bitstring`). `dpf::wildcard` is an empty `wildcard_value`. + +Output wildcards are a different wrapper. See [Output types](@ref output_types). + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{7}); +\endcode + +**Defined in**\n +@ref dpf/wildcard.hpp and @ref dpf/offset_wrapper.hpp + +`dpf::vec` is not a domain. It is a packed output of `N` lanes. See [Output types](@ref output_types). - - - +
+ +\anchor custom_input_types +
+Custom input type requirements -# Custom input type requirements {#custom_input_types} A type can be a DPF input when the library can walk its bits and, for interval or full-domain evaluation, order its values. @@ -234,3 +391,4 @@ template <> struct mod_pow_2 { }; } \endcode +
diff --git a/doc/pages/introduction.md b/doc/pages/introduction.md index 56606d7..af1192b 100644 --- a/doc/pages/introduction.md +++ b/doc/pages/introduction.md @@ -1,34 +1,38 @@ -# Introduction +\htmlonly +

libdpf++ is a header-only C++17 library of distributed point functions: short keys that hide one secret index, then answer at public points as secret shares.

+

What it does

+ +\endhtmlonly -## Synopsis {#synopsis} +## A complete program {#first_program} -`libdpf++` is a fast and extensible, header-only C++17 implementation of -`(2,2)-`*distributed point functions* (or *DPFs*). In addition to core DPF -functionality, `libdpf++` provides a plethora of data structures, helper -functions, and syntactic sugar designed to facilitate seamless integration -into higher-level cryptographic protocols and primitives. With its focus on -speed and ease of use, `libdpf++` is an ideal building block for implementing -private information retrieval (PIR), secure multiparty computation (MPC), -zero-knowledge arguments, anonymous messaging, and more. +Leaf shares are subtractive. `reconstruct` is `share0 - share1`. +The same program is `examples/mwe/point.cpp`. +More programs are on [Pick a construction](@ref which_dpf) and [Code examples](@ref listings). -## Features {#features} +\htmlonly +
#include "dpf.hpp"
 
-  -  input types
-  -  output types
-  -  wildcards
-  -  multiple leaves
-  -  evaluation types
-  -  memoizers
-  -  json serialization
-  -  asynchronous I/O
-  -  [point-programmable vector commitments](@ref ppvc_manual)
+const std::uint8_t alpha = 42;
+const std::uint64_t beta = 7;
+auto [k0, k1] = dpf::make_dpf(alpha, beta);
 
-## Credits {#credits}
+const std::uint64_t at = dpf::reconstruct(
+    *dpf::eval_point(k0, alpha),
+    *dpf::eval_point(k1, alpha));
+// at == 7; any other public point opens to 0
 
-  - Adithya Vadapalli (IIT Kanpur)
-  - Kyle Storrier (UCalgary)
-  - Allan Lyons (UCalgary)
-
-## Disclaimer {#disclaimer}
-
-We bet you $50 that there is at least one security bug in this code base. If you are you use this code, it is on you to verify that bug does not cross any of the same code paths as you.
\ No newline at end of file
+// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/point.cpp
+
+\endhtmlonly diff --git a/doc/pages/iterables.md b/doc/pages/iterables.md index 367742a..a5cdf16 100644 --- a/doc/pages/iterables.md +++ b/doc/pages/iterables.md @@ -1,13 +1,160 @@ -# dpf::setbit_index_iterable - -# dpf::subsequence_iterable +Multi-point evaluation returns an iterable over the shares it wrote. +The helpers below walk a subset of that range, or of a `bit_array`, +without copying the underlying storage. Read the iterable while the +buffer it refers to is alive. Walking `k` steps is `Θ(k)` time and +`O(1)` extra memory beyond that buffer. `indices_set_in` inspects the +words of its bit view and skips all-zero words, so a dense view is +linear in the bit length. `offset_iterable`'s constructor is one binary +search on a sorted range. `batch_of` steps one bit at a time across its +arrays. # dpf::subinterval_iterable -# dpf::zip_iterable +`eval_interval` and `eval_full` return one of these (or a tuple of them, +one per selected output). `begin()` / `end()` walk the inclusive +`[from, to]` in input order. -# dpf::parallel_bit_iterable +You can also clip an existing iterator. The constructor is +`(iterator, buf_size, from, to, preclip, outputs_per_leaf)`. +`from` and `to` are indices into that iterator. `preclip` is how many +steps `begin()` skips. `outputs_per_leaf` is the packing width; pass 0 +for a plain bit array. -# dpf::advice_bit_iterable \ No newline at end of file +\code{cpp} +auto [buf, leaves] = dpf::eval_interval(k0, std::uint8_t{10}, std::uint8_t{20}); +for (auto share : leaves) { /* one share per input */ } + +dpf::dynamic_bit_array<> bits(128); +dpf::subinterval_iterable view(bits.begin(), bits.size(), + 0, bits.size() - 1, 0, 0); +\endcode + +**Defined in**\n +@ref dpf/subinterval_iterable.hpp + +## dpf::subsequence_iterable + +`eval_sequence(key, begin, end)` returns one. Each step is the share for +the next listed point, in list order. The recipe overload returns a +`recipe_subsequence_iterable` over `recipe.output_indices()`. + +The constructor `(out_it, begin, end)` binds an output iterator and the +point list. The usual way to obtain one is the eval call, which also +owns the buffer. + +\code{cpp} +std::vector points{1, 4, 9}; +auto [buf, listed] = dpf::eval_sequence(k0, points.begin(), points.end()); +for (auto share : listed) { /* one share per listed point */ } +\endcode + +**Defined in**\n +@ref dpf/subsequence_iterable.hpp + +## dpf::setbit_index_iterable + +`indices_set_in(iter)` walks the positions whose bit is set. +`iter` is a `subinterval_iterable` of bit iterators: the iterable +`eval_full` / `eval_interval` returns for a `dpf::bit` output, or a +view of a `dynamic_bit_array`. `for_each_set_index(iter, fn)` calls +`fn` on each index. + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint16_t{0xAAAA}, dpf::bit::one); +auto [buf, leaves] = dpf::eval_full(k0); +for (auto index : dpf::indices_set_in(leaves)) { /* set-bit positions */ } +\endcode + +**Defined in**\n +@ref dpf/setbit_index_iterable.hpp + +## dpf::zip_iterable + +`tuple_as_zip` takes an lvalue `std::tuple` of iterables and walks them +together. Each step is a `std::tuple` of the dereferenced values. +`for_each_in_zip(tuple, fn)` does the same with a callback. The tuple +must stay alive for the walk. + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, std::uint64_t{7}); +auto [buf0, it0] = dpf::eval_full(k0); +auto [buf1, it1] = dpf::eval_full(k1); +auto rows = std::make_tuple(it0, it1); +for (auto [a, b] : dpf::tuple_as_zip(rows)) +{ + auto opened = dpf::reconstruct(a, b); +} +\endcode + +**Defined in**\n +@ref dpf/zip_iterable.hpp + +## dpf::parallel_bit_iterable + +`batch_of(a, b, ...)` steps several `bit_array`s in lockstep. Each +value is one bit-column, an `std::array` of the lane elements. +`batch_of(it)` does the same from an iterator of `N` arrays. +`for_each_bit_parallel` applies a function to each column. + +\code{cpp} +dpf::dynamic_bit_array<> a(64), b(64); +for (auto column : dpf::batch_of(a, b)) { /* column[0], column[1] */ } +\endcode + +**Defined in**\n +@ref dpf/parallel_bit_iterable.hpp + +## dpf::advice_bit_iterable + +`advice_bits_of(iterable)` yields the least significant bit of each +element. `for_each_advice_bit(iterable, fn)` applies `fn` to each bit. +`bit_array_from_advice_bits` packs those bits into a +`dynamic_bit_array`. + +\code{cpp} +std::vector nodes{1, 2, 4}; +for (auto bit : dpf::advice_bits_of(nodes)) { /* low bit of each word */ } +auto packed = dpf::bit_array_from_advice_bits(dpf::advice_bits_of(nodes)); +\endcode + +**Defined in**\n +@ref dpf/advice_bit_iterable.hpp + +**Code samples**\n +
+ + - advice_bit_iterable.cpp \include{cpp} iterables/advice_bit_iterable.cpp + +
+ +## dpf::rotation_iterable + +`rotated_by(container, n)` walks `container` starting `n` elements in, +then wraps. `for_each_rotated_by(begin, end, n, fn)` calls +`fn(index, value)` in that order; `index` is the element's original +position. The constructor is `(begin, end, rotate_by)`. + +\code{cpp} +std::vector values{1, 2, 3}; +for (auto x : dpf::rotated_by(values, 1)) { /* 2, 3, 1 */ } +\endcode + +**Defined in**\n +@ref dpf/rotation_iterable.hpp + +## grotto::offset_iterable + +A sorted range, rotated so the first entry is the first value strictly +greater than `offset`, with `offset` subtracted from each element. +Prefix-parity walks use this view. + +\code{cpp} +#include "grotto.hpp" +std::vector knots{0, 10, 40}; +grotto::offset_iterable shifted(knots.begin(), knots.end(), 10); +\endcode + +**Defined in**\n +@ref grotto/offset_iterable.hpp diff --git a/doc/pages/jet_and_ring.md b/doc/pages/jet_and_ring.md new file mode 100644 index 0000000..c9e743d --- /dev/null +++ b/doc/pages/jet_and_ring.md @@ -0,0 +1,425 @@ +# Jet and exact ring switch {#jet_and_ring} + +One opened offset `eta = x - r` drives two cheap corrections. The binomial +jet returns shares of \f$\binom{x}{0},\ldots,\binom{x}{d}\f$ after a public +Chu–Vandermonde shift. The ring switch returns shares of `x` in any residue +group whose comparison payload is the destination modulus. + +The same offset also drives [offset Horner](@ref offset_horner), +[offset polynomials](@ref offset_poly), and [carry](@ref carry). +[Prefix parity](@ref prefix_parity) reads a key's path. +[Cleartext maps](@ref grotto_luts) evaluate fixed-point functions with no tree. + +## Binomial jet {#offset_jet} + +`make_offset_jet_keys(center, degree)` keys one incremental `gt` whose +payload is the vector of \f$\binom{\mathrm{center}}{k}\f$ in +\f$\mathbb{Z}/2^{64}\f$. After `eta` opens, +the same knot shift and carry cut as offset poly refine the pieces. On the +piece with carry `kappa`, + +\f[ +\binom{c+\kappa}{k} + =\sum_j\binom{c}{j}\binom{\kappa}{k-j}. +\f] + +`make_offset_jet_keys` writes one incremental comparison for degree `d` +(`d ≤ 16`). The seed spine is `Θ(n λ)` bits, with `n` the center's bit +length and `λ` the seed width. Value words grow with the `d+1` binomial +lanes. After `η` is public, `offset_jet_shares` evaluates that one key +on the `K` knots, the same order as one `eval_sequence` on the knots. +The Chu–Vandermonde +update after those walks is `Θ(P · d²)` arithmetic, where `P` is the +number of refined pieces (the knots, plus the domain minimum, plus the +carry cut when the input width is at most 62). `offset_jet_dot` is +`Θ(d)`. No further round when the coefficients are public. + +`offset_jet_shares` returns that shifted jet. Public dots are free: + +- value of \f$\sum a_k\binom{x}{k}\f$ via `offset_jet_dot`; +- forward difference via `offset_jet_difference_coeff` (Pascal); +- hockey-stick prefix via `offset_jet_prefix_coeff`. + +The prefix needs \f$\binom{x}{k+1}\f$, so the key degree must be one larger +than the polynomial degree. Degree 16 therefore prefix-sums polynomials +through degree 15. + +Binomials modulo \f$2^{64}\f$ use a falling factorial modulo +\f$2^{64+v_2(k!)}\f$ (\f$v_2(16!)=15\f$), then multiply by the inverse of the +odd part of \f$k!\f$. Dividing by \f$k!\f$ inside \f$\mathbb{Z}/2^{64}\f$ alone +is not exact. + +A Padé pair or one Newton correction is two dots against the same jet and +one reciprocal after the shares are opened. Those are not separate APIs. + +**Code samples**\n +
+ + - jet_and_ring.cpp \include{cpp} grotto/jet_and_ring.cpp + +
+ +## Exact ring switch {#ring_switch} + +For an unsigned \f$n\f$-bit limb (\f$n\le 64\f$) with representatives in +\f$[0,2^n)\f$, + +\f[ +\eta + r = x + w\cdot 2^n,\qquad +w=\mathbf{1}[r+\eta\ge 2^n]. +\f] + +In any modulus \f$M\f$, + +\f[ +x \equiv \eta + (r\bmod M) - w\cdot(2^n\bmod M)\pmod M. +\f] + +A `uint64` comparison share is not a share mod \f$M\f$. The payload of the +wrap comparison is the destination element \f$2^n\bmod M\f$. The dealer keys +`lt(2^n \bmod M)` at the secret `r` and stores an additive split of `r` in +the residue group. After `eta` opens, each party evaluates at the public +query \f$2^n-1-\eta\f$. That indicator is hot exactly on wrap, including the +`eta = 0` case. Party 0 adds public `eta`. + +Destination groups: + +- `grotto::zn64` and `grotto::zn128` ([residue.hpp](@ref grotto/residue.hpp)); +- `dpf::field128`; +- `dpf::p256_scalar` (NIST P-256 order, not the point group). + +`ring_switch_factor` reduces a share when `Factor` divides the +modulus. One switch into an lcm yields every factor by local reduction. + +The dealer material is one `lt` key on that limb, `Θ(n λ)` bits for +limb width `n ≤ 64` and seed width `λ`, plus two residue shares of `r`. After `η` is +public, each party does one point evaluation (`Θ(n)` expands) and a +constant amount of arithmetic in the destination group. +`ring_switch_factor` is local. + +This is the exact neighbour of truncated Barrett `nmod`. +`grotto::nmod(x_raw, x_bits, recip_raw, recip_bits, residue_bits)` splits +`x / M` when `recip_raw / 2^recip_bits` is a positive approximation of `1/M`. +The result is an `nmod_result`: `quotient` is `floor(x/M)`, and `residue` +is the fractional part truncated onto `residue_bits`. +`nmod_pow2(x_raw, x_bits, exp, residue_bits)` is the same split when the +modulus is a power of two. Both are one product by a reciprocal of at +most 128 bits, so time and extra memory are constant in the word size. + +\code{cpp} +auto split = grotto::nmod_pow2(raw, 16, 0, 16); +\endcode + +**Defined in**\n +@ref grotto/nmod.hpp + +See also [representation shift and twisted jets](@ref repr_and_twist). + +## Offset Horner {#offset_horner} + +`make_offset_horner_keys(center)` keys one `gt` whose +payload is `center^m` for `m = 0 .. Degree`. `Degree` is at most 3 +(`offset_horner_max_degree`). Pass `dpf::verifiable{}` for proof tokens. +After `eta` opens, `offset_horner_eval` returns that party's +share of the cubic at the wrapped point. Coefficients are one +`std::array` per knot, low degree first. + +\code{cpp} +const std::uint8_t center = 12; +auto mat = grotto::make_offset_horner_keys(center); +std::vector knots{0}; +std::vector> coeff{{4, 2, 1}}; +auto s0 = grotto::offset_horner_eval<0, 2>(mat, knots, coeff, eta); +\endcode + +`geneval_offset_horner` runs the same cubic from Jack Doerner and abhi shelat shares of +`x` and of the center, on a `dpf::ds_randomness` tape. + +Degree is at most 3. The seed spine is one comparison, `Θ(n λ)` bits, +and the value words hold the four powers. Evaluation after `η` opens is +one sequence-shaped walk on the knots plus `O(1)` arithmetic. The +geneval form generates that same comparison once, opens one correction +word per level, as in [geneval](@ref tour_ds), and does not store a +reusable key. + +**Defined in**\n +@ref grotto/offset_horner.hpp + +## Offset polynomial {#offset_poly} + +`make_offset_poly_keys(center, degree)` is offset Horner at a runtime +degree, at most 16 (`offset_poly_max_degree`). One incremental `gt` +whose payload is the vector of powers. `offset_poly_eval` dots the shifted powers. +`offset_poly_clear` is the same polynomial in the clear. +`offset_poly_kappas` is the public carry of each piece. +Shared coefficients use `offset_poly_shift_share` (the binomial map is +linear) and `offset_poly_beaver_share` for the dot. + +Degree `d` is at most 16: one key, `Θ(n λ)` bits of seed spine plus +value words that grow with `d`. The clear and public-coefficient evals +are one sequence-shaped walk on the `K` knots, then `O(d^2)` arithmetic. A shared-coefficient dot is one Beaver +inner product: one opening round of the masked vectors, communication +linear in the flattened length (pieces times `d+1` coefficients), and +one product share per coefficient in preprocessing. The shift of each +party's coefficient share is local. + +\code{cpp} +auto mat = grotto::make_offset_poly_keys(std::uint8_t{12}, 4); +std::vector knots{0}; +std::vector> coeff{{4, 2, 1, 0, 0}}; +auto s0 = grotto::offset_poly_eval<0>(mat, knots, coeff, eta); +auto opened = s0 + grotto::offset_poly_eval<1>(mat, knots, coeff, eta); +\endcode + +**Defined in**\n +@ref grotto/offset_poly.hpp + +## Carry {#carry} + +A `carry_request` names the source width `n`, the shift `s`, the output +width `out_n`, a `carry_mode` (`truncate_reduce`, `same_ring`, `extend`, +`window`), and a `sign_knowledge` (`unknown`, `nonnegative`, `negative`). +`plan_carry` returns a `carry_recipe` whose flags are the steps that are +still live. `plan_carry_in(n, s)`, `plan_carry_out(n, s, sign)`, and +`plan_carry_fused(n, s, out_n, sign)` fill the common requests. + +`make_carry_keys(recipe)` (and `make_carry_in_keys`, `make_carry_out_keys`, +`make_carry_fused_keys`) builds the dealer keys. `finalize_carry_in_blinds` +adjusts a truncate-reduce split. Online, `eval_carry_in(keys, party, opened)` +returns a `carry_eval_share` whose `value` is that party's share. +`opened` is `(x0 + x1 + rin) mod 2^n`. The other online entry points are +`eval_carry_out_known`, `eval_carry_out_unknown`, `eval_carry_extend`, +`eval_carry_window`, and `eval_carry_fused`. + +Cleartext twins, for tests and for a public limb, are `eval_carry_clear`, +`carry_in_clear`, `carry_out_clear`, `carry_asr`, and `carry_mask`. +`plan_carry` is a constant-time inspection of the request. Each live +comparison flag becomes one DPF key whose domain is the limb width `w` +of that comparison, `Θ(w λ)` bits, and the online step is one point +walk of that key. A share-MSB AND adds one Beaver bit triple in +preprocessing and one opening round of a bit. + +\code{cpp} +auto keys = grotto::make_carry_in_keys(32, 8); +auto share = grotto::eval_carry_in(keys, /*party*/ 0, opened); +auto clear = grotto::eval_carry_clear(keys.recipe, x0, x1); +\endcode + +**Defined in**\n +@ref grotto/carry_plan.hpp, @ref grotto/carry.hpp + +## Prefix parity {#prefix_parity} + +`prefix_parities(key, endpoints)` walks a key to the sorted endpoints and +returns XOR shares of the prefix parities, plus the index of the first +endpoint on the wrap. `segment_parities` turns those into one share per +segment. `all_segment_parities_from_prefix_parities` is the same conversion +when you already hold the prefix array. + +`signed_prefix_parities(key, endpoints)` needs a comparison channel +(assigned, if the payload was a wildcard). It returns one additive +`uint64_t` share per endpoint: for `dpf::gt(1)` that share opens to 1 when +the secret point is below the endpoint. `signed_prefix_parities_into` +writes a runtime-length buffer. + +The prefix walk follows Storrier, Vadapalli, Lyons, and Henry, ePrint +2023/108: one key's prefix parity in place of a comparison per piece. +On `m` endpoints the walk resumes one path memoizer (`Θ(n)` nodes, `n` +the key depth). Expands are the nodes on those paths, `O(m n)` in the +worst case, and less when endpoints share a prefix or the zero-suffix +stop hits. `signed_prefix_parities` adds an `O(n)` sum of +value-correction words on each endpoint. Both calls are local. +`segment_parities` is the prefix walk plus an `O(m)` XOR of those bits. + +\code{cpp} +std::array ends{10, 40}; +auto [bits, first] = grotto::prefix_parities(k0, ends); +auto segs = grotto::segment_parities(k0, ends); +auto signs = grotto::signed_prefix_parities(cmp0, ends); +\endcode + +**Defined in**\n +@ref grotto/prefix_parity.hpp + +## Cleartext maps {#grotto_luts} + +These functions take a raw fixed-point word (`n << fractional_bits`) and +return a raw word. They do not build a DPF. The type +`grotto::fixedpoint` itself is a domain and an output; see +[Input types](@ref input_types) and [Output types](@ref output_types). + +## Fixed-point product {#fixedpoint_mul} + +`fixed_mul(lhs, rhs)` multiplies two +`fixedpoint` values and keeps that many integer bits (including the sign) +and fraction bits. Bits below the fraction are floored. The product type +is the `result_type` of `fixed_mul_plan`. The plan uses at most 8 +limbs and refuses a wider window, so the product is a constant amount +of 64-bit arithmetic and `O(1)` extra memory. + +\code{cpp} +using q16 = grotto::fixedpoint<16, std::int32_t>; +auto prod = grotto::fixed_mul<16, 16>(q16{1.5}, q16{2.0}); +\endcode + +**Defined in**\n +@ref grotto/fixedpoint_mul.hpp + +## Lookup tables {#lookup_tables} + +Constant, easy, principal, range, and window tables are included from +`grotto.hpp`. The dyadic table comes in through `exact_steps.hpp`, which +`grotto.hpp` also includes. + +- **Constant.** `make_exact_constant_lut(exact_constant::signum, fractional_bits)` + and the other `exact_constant` names (`positive`, `negative`, `nonneg`, + `nonpos`, `zero`, `nonzero`, `ilogb`, `ceil_ilogb`, `ilog10`, `clz`, + `clrsb`). `make_threshold_lut`, `make_interval_lut`, and + `make_clipped_quotient_lut` build a `constant_lut` you call as + `table(raw)`. +- **Easy.** Few-piece polynomials with integer knots: + `make_abs_lut`, `make_relu_lut`, `make_clip_lut`, `make_hardsigmoid_lut`, + `make_hardswish_lut`, `make_leaky_relu_hundredth_lut`, and the other + `make_*_lut` factories in [easy_lut.hpp](@ref grotto/easy_lut.hpp). + The result is an `easy_lut`. `make_leaky_relu_lut(shift)` is the + dyadic slope `1/2^shift`. The Appendix D leaky ReLU is slope `1/100`. +- **Dyadic.** Exact steps on powers of two: `make_signum_lut`, + `make_msb_lut(index)`, `make_ilogb_lut`, `make_ilog10_lut`, `make_clz_lut`, + `make_clrsb_lut`, and the sign predicates `make_positive_lut` through + `make_nonzero_lut`. `ilog_of_zero` is the sentinel for a zero argument. + `msb_bit_limit` is 8. +- **Range.** `eval_reduced(reduced::ln, fractional_bits, raw)` and the + other `reduced` names (`lg`, `log10`, `exp`, `exp2`, `exp10`, `sin`, + `cos`, `tan`, `cot`, `sec`, `csc`, the hyperbolics, `sqrt`, `inv`, + `rsqrt`, `invsq`, `expm1`, `log1p`). `split_positive` is the dyadic + mantissa split those reductions use. +- **Window.** `eval_window(window::gelu, fractional_bits, raw)`. The + `window` names cover `smoothstep`, `sigmoid`, `tanh`, `erf`, `erfc`, + `softplus`, `gelu`, `silu`, `asin`, `acos`, `probit`, `hardelish`, + `lecun_tanh`, `one_minus_sigmoid`, and the rest of the enum in + [window_lut.hpp](@ref grotto/window_lut.hpp). +- **Principal.** `eval_principal(principal::sin, fractional_bits, raw)` on + the closed principal interval. Precisions are 8, 12, …, 32 + (`principal_precision`). Names: `ln`, `exp`, `sin`, `tanf`, `tang`, + `sinh`, `cosh`, `sqrt`, `coth`, `sec`, `gsec`, `csch`, `inv`, `rsqrt`, + `invsq`. + +\code{cpp} +auto sign = grotto::make_exact_constant_lut( + grotto::exact_constant::signum, 0); +auto s = sign(std::int32_t{-3}); +auto relu = grotto::make_relu_lut(8); +auto ln = grotto::eval_reduced(grotto::reduced::ln, 16, raw); +auto gelu = grotto::eval_window(grotto::window::gelu, 16, raw); +auto sine = grotto::eval_principal(grotto::principal::sin, 16, raw); +\endcode + +The degree-0 exact tables follow Storrier, Vadapalli, Lyons, and Henry, +[ePrint 2023/108](@ref bib_grotto), Appendix D. +Sign predicates are a constant number of cuts, `Θ(1)`. `clz` and +`ilogb` cut once per bit of the raw width, `Θ(w)`. `ilog10` binary-searches +the raw domain once per decimal exponent, `Θ(w²)` probes. `make_msb_lut` +emits `Θ(2^index)` cuts and rejects `index` at or above 8. +`make_clipped_quotient_lut` is linear in `(high-low)/modulus`, capped at +`2^16` pieces. Calling a constant or easy table binary-searches its `P` +pieces, `O(log P)`. `eval_principal` and `eval_window` binary-search the +static knots and then run one cubic. `eval_reduced` adds a short series +on the small interval (`expm1` 24 terms, `log1p` 80). No DPF and no +communication. + +## Appendix D maps that were still cleartext {#appendix_d_gaps} + +Appendix D of [ePrint 2023/108](@ref bib_grotto) lists HardELiSH, LeCun tanh, and leaky +ReLU with slope `1/100`. Those three now have fixed-point evaluators. +`one_minus_sigmoid` is the sigmoid table complemented, which rounds out +the logistic pair. + +The cubics were built on mocha2. Sollya chose the longest pieces whose +absolute error stays within half an ulp. Mathematica (Remez), Maple +(`numapprox[minimax]`), and MATLAB/Chebfun (`minimax`) fitted each +piece, and the shipped polynomial is the one with the lowest error +after the coefficients are rounded to `k+16` fraction bits. Of the 443 +cubics, Sollya won 229, Maple 83, MATLAB 74, and Mathematica 57. + +Half an ulp at 16 fraction bits is `2^{-17} ≈ 7.63e-6`. Appendix D's +own columns are tighter (`4.2e-8`) and therefore use more pieces +(HardELiSH 38, LeCun tanh 89). The counts below are this library's +half-ulp partitions. + +| map | degree | pieces at k = 8, 12, 16, 20, 24, 28, 32 | evaluation | +| --- | --- | --- | --- | +| `window::hardelish` | 3 on `(-1, 0)`; exact quadratic on `[0, 1]` | 1, 2, 3, 6, 12, 23, 45 | `Θ(log P)` knot search and one cubic on `(-1, 0)`. Elsewhere `Θ(1)`: `0`, `round(x(x+1)/2)`, or `x` | +| `window::lecun_tanh` | 3 | 3, 6, 11, 22, 44, 88, 177 | `Θ(log P)` on the positive knots of the absolute value, then a sign. Past the last knot the value is the constant `±round(1.7159 · 2^k)` | +| `window::one_minus_sigmoid` | 3 | same as `sigmoid`: 8, 16, 64, 128, 256, 1024, 2048 | one sigmoid evaluation and one subtraction. `Θ(log P)` | +| `make_leaky_relu_hundredth_lut` | 1 | 2 | `Θ(1)`. Identity on the right, `round(x/100)` on the left. Error at most half a unit in the last place | + +`make_leaky_relu_lut(shift)` is still the dyadic slope `1/2^shift`. +The hundredth factory is the Appendix D slope and does not depend on +the fractional width. + +`grotto::polynomials::eval_horner` evaluates a `poly_constant`, +`poly_linear`, `poly_quadratic`, or `poly_cubic` (a `std::array` of +coefficients, constant term first). `piecewise_eval(polys, bounds, x)` +picks the piece and calls that Horner step. + +**Defined in**\n +@ref grotto/constant_lut.hpp, @ref grotto/easy_lut.hpp, +@ref grotto/dyadic_lut.hpp, @ref grotto/range_lut.hpp, +@ref grotto/window_lut.hpp, @ref grotto/principal_lut.hpp, +@ref grotto/piecewise.hpp + +## Closed form {#closed_form} + +`eval_closed(closed::atanh, fractional_bits, raw)` composes +`eval_reduced` and `eval_window`. The `closed` names are the inverse +hyperbolics and inverse trig functions, `selu`, `elu`, `celu`, +`softsign`, `tanhshrink`, the `logistic` / `exponential` / `laplace` / +`cauchy` quantiles, `sinc`, and the extra powers `cbrt`, `qtrt`, +`icbrt`, `iqtrt`, `pow_m01`, `pow_p15`, `pow_m3`. Precision is one of +8, 12, …, 32. Each call runs a constant number of `eval_reduced` or +`eval_window` evaluations. + +\code{cpp} +auto y = grotto::eval_closed(grotto::closed::atan, 16, raw); +\endcode + +**Defined in**\n +@ref grotto/closed_form.hpp + +## Exact steps {#exact_steps} + +Counts and booleans come back as fixed-point integers, +`n << fractional_bits`. + +\code{cpp} +auto floor_x = grotto::eval_dec_floor(raw, 16); +auto digits = grotto::eval_dec_width(raw, 16); +auto bits = grotto::eval_bit_width(raw, 16); +\endcode + +Decimal digit counts divide in a loop, so the time follows the number +of digits. `eval_bit_width`, `eval_bit_floor`, `eval_bit_ceil`, +`eval_countl_one`, and `eval_has_single_bit` scan the raw width, +`Θ(width)` bit operations and at most 64 shifts. `eval_logstar` is five +magnitude comparisons, `Θ(1)`. `eval_deg2rad` and `eval_rad2deg` are one +scale each. + +Also `eval_dec_ceil`, `eval_oct_width`, `eval_b64_width`, +`eval_value_length` (base 8, 10, or 64), `eval_has_single_digit`, +`eval_bit_floor`, `eval_bit_ceil`, `eval_countl_one`, `eval_has_single_bit`, +`eval_deg2rad`, and `eval_rad2deg`. `make_exact_step_lut` builds the +matching `easy_lut`. + +**Defined in**\n +@ref grotto/exact_steps.hpp + +## Gadget functors {#grotto_gadgets} + +`grotto/gadgets.hpp` still includes the decimal and exponential reference +headers. The functors that those headers used to provide are deprecated: +call `eval_reduced`, `eval_window`, `make_*_lut`, or `exact_constant` +instead. `gadget_hints` holds the old domain, degree, and pole notes +for a functor type. + +**Defined in**\n +@ref grotto/gadgets.hpp, @ref grotto/gadget_hints.hpp diff --git a/doc/pages/listings.md b/doc/pages/listings.md index d9a6764..4c9717a 100644 --- a/doc/pages/listings.md +++ b/doc/pages/listings.md @@ -3,9 +3,10 @@ - \subpage input_type_examples - \subpage output_type_examples - \subpage evaluation_examples + - \subpage grotto_examples - \subpage iteratable_examples -\page input_type_examples input_types +\page input_type_examples Domain samples - \subpage input_types_2integral_types_8cpp - \subpage input_types_2extended_types_8cpp @@ -36,7 +37,7 @@ \page "input_types_2custom_8cpp" input_types/custom.cpp \include{cpp} input_types/custom.cpp -\page output_type_examples output_types +\page output_type_examples Payload samples - \subpage output_types_2integral_types_8cpp - \subpage output_types_2extended_types_8cpp @@ -67,14 +68,20 @@ \page "output_types_2custom_8cpp" output_types/custom.cpp \include{cpp} output_types/custom.cpp -\page evaluation_examples evaluation +\page evaluation_examples Evaluation samples - \subpage evaluation_2eval_point_8cpp - \subpage evaluation_2eval_interval_8cpp - \subpage evaluation_2eval_full_8cpp + - \subpage evaluation_2defer_eval_8cpp - \subpage evaluation_2eval_sequence_8cpp - \subpage evaluation_2memoizers_8cpp - \subpage evaluation_2output_buffers_8cpp + - \subpage evaluation_2eval_inner_product_8cpp + - \subpage evaluation_2buffered_prg_8cpp + - \subpage evaluation_2eval_dpf3_point_8cpp + - \subpage evaluation_2eval_dpf3_doerner_shelat_8cpp + - \subpage evaluation_2eval_dpf3_cmp_ic_8cpp \page "evaluation_2eval_point_8cpp" evaluation/eval_point.cpp \include{cpp} evaluation/eval_point.cpp @@ -85,6 +92,9 @@ \page "evaluation_2eval_full_8cpp" evaluation/eval_full.cpp \include{cpp} evaluation/eval_full.cpp + \page "evaluation_2defer_eval_8cpp" evaluation/defer_eval.cpp + \include{cpp} evaluation/defer_eval.cpp + \page "evaluation_2eval_sequence_8cpp" evaluation/eval_sequence.cpp \include{cpp} evaluation/eval_sequence.cpp @@ -94,7 +104,33 @@ \page "evaluation_2output_buffers_8cpp" evaluation/output_buffers.cpp \include{cpp} evaluation/output_buffers.cpp -\page iteratable_examples iterables + \page "evaluation_2eval_inner_product_8cpp" evaluation/eval_inner_product.cpp + \include{cpp} evaluation/eval_inner_product.cpp + + \page "evaluation_2buffered_prg_8cpp" evaluation/buffered_prg.cpp + \include{cpp} evaluation/buffered_prg.cpp + + \page "evaluation_2eval_dpf3_point_8cpp" evaluation/eval_dpf3_point.cpp + \include{cpp} evaluation/eval_dpf3_point.cpp + + \page "evaluation_2eval_dpf3_doerner_shelat_8cpp" evaluation/eval_dpf3_doerner_shelat.cpp + \include{cpp} evaluation/eval_dpf3_doerner_shelat.cpp + + \page "evaluation_2eval_dpf3_cmp_ic_8cpp" evaluation/eval_dpf3_cmp_ic.cpp + \include{cpp} evaluation/eval_dpf3_cmp_ic.cpp + +\page grotto_examples Grotto samples + + - \subpage grotto_2jet_and_ring_8cpp + - \subpage grotto_2repr_and_twist_8cpp + + \page "grotto_2jet_and_ring_8cpp" grotto/jet_and_ring.cpp + \include{cpp} grotto/jet_and_ring.cpp + + \page "grotto_2repr_and_twist_8cpp" grotto/repr_and_twist.cpp + \include{cpp} grotto/repr_and_twist.cpp + +\page iteratable_examples Iterable samples - \subpage iterables_2setbit_index_iterable_8cpp - \subpage iterables_2advice_bit_iterable_8cpp diff --git a/doc/pages/multiparty.md b/doc/pages/multiparty.md new file mode 100644 index 0000000..90588f8 --- /dev/null +++ b/doc/pages/multiparty.md @@ -0,0 +1,30 @@ +# Multiparty & 3-server {#multiparty} + +Two-party keys are the default. The library also builds three-evaluator +`(2,3)` keys, information-theoretic three-server DPFs, and dealer-free +two-party keygen when the index is already shared. + +| Construction | Parties | Header | +| --- | --- | --- | +| [dpf::make_dpf3](@ref dpf/dpf3.hpp) | Any two of three open (Shamir / dual spine, [ePrint 2024/1658](@ref bib_dpf3)) | `dpf3.hpp` | +| [dpf::make_dpf3_doerner_shelat](@ref dpf/dpf3_ds.hpp) | Same keys from XOR shares of `alpha` | `dpf3_ds.hpp` | +| `make_dpf3_cmp` / `make_dpf3_ic` / `make_multipoint3` | Comparisons, intervals, multipoint | `dpf3_cmp.hpp`, `dpf3_multipoint.hpp` | +| [dpf::make_it_dpf3](@ref dpf/it_dpf3.hpp) | Information-theoretic 3-server DPF ([ePrint 2023/028](@ref bib_itdpf)) | `it_dpf3.hpp` | +| [dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp) | Two parties, shared index, no dealer for the point | `doerner_shelat.hpp` | +| [dpf::geneval_*](@ref dpf/geneval.hpp) | Answer shares for one query, no reusable key | `geneval.hpp` | + +```cpp +auto [k1, k2, k3] = dpf::make_dpf3(std::uint8_t{42}, dpf::fp61{7}); +auto opened = dpf::reconstruct( + dpf::as_share(k1, dpf::eval_point(k1, std::uint8_t{42})), + dpf::as_share(k2, dpf::eval_point(k2, std::uint8_t{42})), + dpf::as_share(k3, dpf::eval_point(k3, std::uint8_t{42}))); +``` + +Party meshes and socket walks live under [trio.hpp](@ref dpf/net/trio.hpp) +and the `party/` harnesses. Application-shaped sketches include +[3-party Duoram](@ref app_duoram) and [three-server PIR](@ref app_pir3). + +**Go deeper:** [guided tour — three evaluators](@ref tour_dpf3), +[Doerner–Shelat](@ref tour_ds), [evaluating DPFs](@ref evaluation), +[bibliography](@ref bibliography). diff --git a/doc/pages/multipoint.md b/doc/pages/multipoint.md new file mode 100644 index 0000000..cf0ce42 --- /dev/null +++ b/doc/pages/multipoint.md @@ -0,0 +1,20 @@ +# Multipoint keys {#multipoint_keys} + +`make_multipoint(alphas, betas)` packs many secret points into cuckoo +buckets (de Castro–Polychroniadou, EUROCRYPT 2022, §4 / [ePrint 2021/580](@ref bib_vdpf)): +`κ = 3` hashes, one point key per bucket. The bucket count is linear in +the number of points `m`, so the keys are `Θ(m n λ)` bits. Eval probes +three buckets. + +Pass `dpf::verifiable{}` for one batched proof token over the whole set. +Three-evaluator packing is `make_multipoint3`. + +```cpp +std::vector alphas{1, 2, 3}; +std::vector betas{4, 5, 6}; +auto [k0, k1] = dpf::make_multipoint(alphas, betas); +``` + +**Go deeper:** [multipoint.hpp](@ref dpf/multipoint.hpp), +[dpf3_multipoint.hpp](@ref dpf/dpf3_multipoint.hpp), +[F_MPDPF](@ref multipoint.hpp). diff --git a/doc/pages/output_types.md b/doc/pages/output_types.md index a70186b..7c17e8d 100644 --- a/doc/pages/output_types.md +++ b/doc/pages/output_types.md @@ -1,13 +1,32 @@ - +An output type is the group element at the secret index. +Every output on one key has the same width, and the type is trivially copyable. +Leaf addition is the group operation. -An output type is the group element stored at the programmed input. Every -output of one DPF must have the same `dpf::utils::bitlength_of_output` width, -and each output type must be trivially copyable and standard layout. Leaf -addition and subtraction are the group operation; leaf multiplication scales -a leaf by one output element (used by wildcard Beaver triples and inner -products). +| Kind | Group | Header | +| --- | --- | --- | +| `uintN_t` / `intN_t` / `modint` | Addition | ``, [modint.hpp](@ref dpf/modint.hpp) | +| `xint` | XOR | [xor_wrapper.hpp](@ref dpf/xor_wrapper.hpp) | +| `bit` / `twobit` / `nyble` | Packed lanes (XOR, Z/4Z, Z/16Z) | [bit.hpp](@ref dpf/bit.hpp) | +| `bitstring` | XOR string | [bitstring.hpp](@ref dpf/bitstring.hpp) | +| `vec` | `N` lanes, no carry between them | [vec.hpp](@ref dpf/vec.hpp) | +| `wildcard_value` | Filled in later | [wildcard.hpp](@ref dpf/wildcard.hpp) | +| `field64` / `field128` / `fp61` | Prime field | [fp61.hpp](@ref dpf/fp61.hpp) | +| `p256` / `p256_scalar` | Curve point or scalar | [p256.hpp](@ref dpf/p256.hpp) | +| Shares | (2,2), (3,3), or (2,3) replicated | [secret shares](@ref secret_shares) | +| `fixedpoint` | Fixed-point word | [fixedpoint.hpp](@ref grotto/fixedpoint.hpp) | + +`bool` is an 8-bit integer. A one-bit payload is `dpf::bit`. +A full program for each kind is under [Code examples](@ref output_type_examples). + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::bit::one); +``` + +The notes below are closed. Open one when you need the rules. + +
+Integer scalar types -# Integer scalar types Fixed-width integers (`uint32_t`, `int64_t`, and the other `psnip` widths) are an additive group. Leaves use SIMD add, subtract, and multiply, including @@ -15,63 +34,290 @@ are an additive group. Leaves use SIMD add, subtract, and multiply, including 64 bits. `bool` is an 8-bit integer, not a packed bit. Use `dpf::bit` for one bit. -`dpf::modint` is an output as well as an input. Packed leaves add the -underlying word; values wider than one AES block add with `operator+`. +`std::uintN_t` and `std::intN_t` (`N` in `{8, 16, 32, 64}`) are the standard +widths. `dpf::modint` (`dpf::modints::modintN_t`, literal `N_uN`) is an +output of any width from 1 through 256, as well as an input. Packed leaves +add the underlying word; values wider than one AES block add with +`operator+`. `dpf::xint` is the XOR ring of the same width; see +`xor_wrapper` below. +
+ +\anchor secret_shares +
+Secret shares -# Secret shares {#secret_shares} `dpf::additive_share` and `dpf::subtractive_share` are -layout-identical wrappers around a number-like `T` (`Party` is `0` or `1`). +(2,2) shares, layout-identical to `T` (`Party` is `0` or `1`). Reconstruction is `share0 + share1` for additive shares and `share0 - share1` -for subtractive shares. Creating from a plaintext puts the value on party 0 -and zero on party 1. +for subtractive shares. -Leaf evaluation returns subtractive shares of the payload. Comparison -(`lt`/`leq`/`gt`/`geq`) returns additive shares. Public plaintexts absorb on -party 0 only. Mixing additive and subtractive shares at the same party flips -the differing-scheme operand on party 1 so the opened secret stays correct. -Use `raw()` / `from_raw` / `retag` for intentional bit-level escapes. +`dpf::additive3_share` is a (3,3)-additive share (`Party` is `0`, +`1`, or `2`), also one word. Reconstruction is `share0 + share1 + share2`. +All three shares are required. + +`dpf::replicated_share` is a (2,3) replicated share. Party `i` +holds `(x_i, x_{i+1 mod 3})` of a (3,3)-additive sharing (`own` and `next`), +so the secret is `x_0 + x_1 + x_2`. Any two parties reconstruct. +`as_additive3()` is that party's (3,3) component. `add_replicated` folds a +(3,3) sharing in by updating both holders of each component. + +Creating from a plaintext puts the value on party 0. For a replicated share +that value is component `x_0`, which party 2 also stores as `next`. + +Leaf evaluation of a `party_key` returns (2,2) subtractive shares. Comparison +(`lt`/`leq`/`gt`/`geq`) returns (2,2) additive shares. Those two schemes mix +at the same party by flipping the differing-scheme operand on party 1. +Public plaintexts absorb on party 0 for a one-word share. For a replicated +share the plaintext is added to `x_0` only: party 0 updates `own`, party 2 +updates `next`, and party 1 is unchanged. Use `raw()` / `from_raw` / `retag` +for intentional bit-level escapes on a one-word share. `make_dpf` returns a `party_key` pair so each party's eval result is typed. -Pass plaintext domain points and payloads; use `reconstruct` (or `raw()`) -when you already hold shares. +Pass plaintext domain points and payloads. `additively_share`, +`additively_share3`, and `share_replicated` draw uniform sharings. +`make_additive_shares`, `make_subtractive_shares`, `make_additive3_shares`, +and `make_replicated_shares` are the deterministic splits. A two-party +comparison addend or wildcard Beaver absorb stays a (2,2) share. + +Conversions that keep the secret on one party are `a2b`, `b2a`, `a2fss`, +`fss2a`, `b2fss`, and `fss2b` (`a` additive, `b` subtractive, `fss` the leaf +share), plus `rss2y` and `y2rss` between a replicated share and its (3,3) +components (`y`). `s2y`, `y2s`, `s2rss`, and `rss2s` open a reconstructing +set and split again (`s` is (2,3) Shamir, `shamir_share`, points 1, 2, 3). +A cast that changes how many parties hold the secret, including anything +that would need a garbled circuit, is not a conversion. `rss_mul` is the +replicated product: each party forms `x_i y_i + x_i y_{i+1} + x_{i+1} y_i`, +and the three terms are an additive sharing of the product. +
+ +
+Extended-precision integer scalar types -# Extended-precision integer scalar types `simde_int128`, `simde_uint128`, `uint128_t`, and `uint256_t` are additive. Their leaf arithmetic is ordinary addition of those integers. +
+ +
+dpf::bit -# dpf::bit A one-bit output. The group is XOR: `operator+` and `operator-` are both XOR, and a leaf multiply is AND with an all-zero or all-one mask. Many `dpf::bit` -outputs are packed into each leaf. +outputs are packed into each leaf, low bit first. `dpf::bit::zero` and +`dpf::bit::one` are the two values. `1_bit` is the literal in +`dpf::literals`. `bool` is not this type: a `bool` leaf is an 8-bit integer. + +`dpf::bit` is not a domain. A 1-bit index is `dpf::modint<1>` or +`dpf::xint<1>`. + +**Defined in**\n +@ref dpf/bit.hpp + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::bit::one); +\endcode +
+ +
+dpf::twobit + + +A 2-bit output in the ring Z/4Z. Values are `0` through `3` +(`twobit::zero` .. `twobit::three`). Scalar `+` and `-` wrap modulo 4. +A leaf packs one lane every two bits, low lane in the low bits of the first +byte, matching `dpf::bit`. Leaf addition is not XOR: a carry stays inside +the 2-bit lane. `2_twobit` is the literal in `dpf::literals`. + +`dpf::twobit` is not a domain. A 2-bit index is `dpf::modint<2>` or +`dpf::xint<2>`. + +**Defined in**\n +@ref dpf/twobit.hpp + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::twobit::two); +\endcode +
+ +
+dpf::nyble + + +A 4-bit output in the ring Z/16Z. Values are `0` through `15`. Scalar `+` +and `-` wrap modulo 16. A leaf packs one lane every four bits, low nibble +first. Leaf addition is not XOR and is not a byte add: a carry must not +cross into the next nibble. `dpf::to_nyble` accepts an integer or one hex +digit. `10_nyble` is the literal in `dpf::literals`. + +`dpf::nyble` is not a domain. A 4-bit index is `dpf::modint<4>` or +`dpf::xint<4>`. + +**Defined in**\n +@ref dpf/nyble.hpp + +\code{cpp} +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::to_nyble(0xau)); +\endcode +
+ +
+dpf::bitstring<Nbits> -# dpf::bitstring A fixed string of bits in the XOR group. `operator+`, `operator-`, and leaf addition are XOR. The leftmost character of a literal or of `to_string` is the most significant bit, matching `0b` notation. Bits above `Nbits` are not -part of the value. +part of the value. `dpf::bitN_t` is `bitstring` for `N` from 1 through +128. +
-# dpf::wildcard +
+dpf::vec<T, N> -`dpf::wildcard_value` is a placeholder. The leaf group is the group of -`T`. The value can be filled in later; until then evaluation of that output -throws. `operator()` accepts both lvalues and rvalues. + +`N` lanes of an ordinary output `T`, stored with lane 0 in the least-significant +place. `T` is an integer, `modint`, `xint` / `xor_wrapper`, `fixedpoint`, +`twobit`, or `nyble`. `+`, `-`, and `*` on a `vec` run per lane and do not +carry into the next lane. Leaf addition and subtraction do the same. The +output width is `N` times the width of `T`. + +`dpf::vec` is not a domain. + +\code{cpp} +dpf::vec beta; +beta[0] = 9; +beta[1] = 8; +beta[2] = 7; +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, beta); +\endcode + +**Defined in**\n +@ref dpf/vec.hpp +
+ +
+dpf::wildcard_value<T> + + +A placeholder for an output of type `T`. The leaf group is the group of `T`. +`make_dpf` accepts an empty `wildcard_value{}` (or `dpf::wildcard`, or +a name in `dpf::wildcards`). Evaluation of that output throws +`std::runtime_error` until the parties assign a payload. A key can mix +concrete outputs and wildcards; only the wildcard slots throw. + +Assignment is a share of the payload, exchanged the way a Beaver opening is: +`compute_and_get_blinded_output_share`, then `compute_and_get_leaf_share` on +the peer's blinded share, then `reconstruct_correction_word`. A second +assign adds a difference onto the payload already installed. `async_assign_leaf` +does the same exchange over a socket. + +`operator()(value)` returns a `wildcard_value` that already holds `value`. +`operator()()` with no argument draws `dpf::uniform_sample()` and returns +that sample together with an additive sharing of it. + +`dpf::wildcards` names the common placeholders and their types: + +- `bit`, `uint8` / `int8` / `xint8`, and the same at 16, 32, and 64 bits +- `uint128`, `xint128`, `uint256`, `xint256` +- `modint`, `xint`, `bitstring` +- `ieee_float` and `ieee_double` `float` and `double` wildcards are bitwise, not IEEE arithmetic. Leaf -addition is XOR of the representation and leaf scaling is AND, which is an +addition is XOR of the representation and leaf scaling is AND. That is an exact group. It is not floating-point addition. -# dpf::xor_wrapper +A wildcard *input* is separate: it masks the secret index. See +[Input types](@ref input_types). + +**Defined in**\n +@ref dpf/wildcard.hpp + +**Code samples**\n +
+ + - wildcard.cpp \include{cpp} output_types/wildcard.cpp + +
+
+ +
+dpf::xor_wrapper<T> + An element of `GF(2)^n` for `n = 8 * sizeof(T)`, or `n = N` for `dpf::xint`. `operator+` and `operator-` are XOR, `operator*` is AND, and `++` / `--` flip the low bit (the same XOR with 1). Leaf addition is XOR and leaf scaling is AND. -# Custom output type requirements {#custom_output_types} +`dpf::xint` is `xor_wrapper>`, the XOR ring of width `N` from +1 through 256. `dpf::xints::xintN_t` names it, and `7_x12` (in +`dpf::literals`) is an `xint<12>`. The additive ring of the same width is +`modint` / `N_uN`. +
+ +
+Prime fields and P-256 + + +`dpf::field64` is GF(2^64 − 2^32 + 1), the same prime as libprio `Field64`. +`dpf::field128` is GF(340282366920938462946865773367900766209), the same +prime as libprio `Field128`. Both are additive output groups: leaf addition +is field addition, and leaf scaling is field multiplication. A raw PRG block +is reduced into the field on the first leaf operation. + +```cpp +auto [k0, k1] = dpf::make_dpf(alpha, dpf::field64{42}); +auto [k0, k1] = dpf::make_dpf(alpha, dpf::field128{42}); +``` + +`dpf::p256` is a NIST P-256 point in SEC1 compressed form (33 bytes). The +identity is 33 zero bytes. `p256{k}` is `k` times the generator, so +`p256{1}` is the generator. `p256::from_compressed` accepts a 33-byte SEC1 +encoding. Leaf addition is point addition. A PRG block that is not already a +point is mapped onto the curve. There is no point×point product, so a P-256 +output is not a wildcard leaf. + +```cpp +auto [k0, k1] = dpf::make_dpf(alpha, dpf::p256{1}); +``` + +`lt`, `leq`, `gt`, and `geq` take the same types. The comparison payload is +an element of that group: field addition for the two primes, point addition +for P-256. Any trivially copyable group with `from_seed`, `operator+`, and +unary `operator-` works the same way, including an `idcf` prefix of that +comparison and dealerless generation. Blocked checkpoints and path-paint +recipes still use the integer limb channel. +
+ +
+grotto::fixedpoint<FractionalBits, IntegralType> + + +A fixed-point output stored in an integer backend. `FractionalBits` is the +number of bits after the binary point. `IntegralType` defaults to +`uint64_t`. The leaf group is the additive group of that backend: leaf +addition and subtraction add the raw words, and leaf scaling multiplies by +the raw word. Neither operation shifts the binary point. + +`fixedpoint(3)` is the value 3 (`3 << FractionalBits` in the raw word). +`from_raw` is bit-exact. The same type is a domain whose depth is the +backend width. See [Input types](@ref input_types). + +\code{cpp} +using fp = grotto::fixedpoint<16>; +auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, fp{1}); +\endcode + +**Defined in**\n +@ref grotto/fixedpoint.hpp +
+ +\anchor custom_output_types +
+Custom output type requirements + Specialize `dpf::leaf_arithmetic::add_t`, `subtract_t`, and `multiply_t` for the exterior node type (`simde__m128i` for the default AES PRG, and @@ -88,3 +334,4 @@ component-wise `+` of one output object. Outputs must be trivially copyable and standard layout. `dpf::utils::make_from_integral_value` should build `T` from the integer `1` when tests or `make_default` need a nonzero payload. See `test/tests/helpers/custom_output_type_small.hpp`. +
diff --git a/doc/pages/ppvc.md b/doc/pages/ppvc.md index a8ab139..2ea999e 100644 --- a/doc/pages/ppvc.md +++ b/doc/pages/ppvc.md @@ -5,7 +5,8 @@ A point-programmable vector commitment binds a vector after the commitment is published. `n` is a power of two, the bit length of the input type, and at most -2^16. `s` is the `Width` parameter, from 1 to 64. +2^20, because evaluation stores one entry per domain point. `s` is +the `Width` parameter, from 1 to 64. The manual construction is `dpf::ppvc`. `dpf::k_ppvc` is `K` independent copies of that object. @@ -64,6 +65,12 @@ exactly one place where the two keys disagree, at the recorded index, with payload 1. `audit` expands a seed and accepts when the published commitment matches that expansion and the replica is well formed. +When many replica seeds sit as leaves of a GGM tree, the audit opening of +the pool is a [`dpf::pprf_copath`](@ref dpf/pprf.hpp) built by +`dpf::puncture(master, live…, /*program_hidden=*/false)`: every audited +leaf re-expands with `dpf::pprf_eval`, and a live seed is never among the +published nodes. Sampling the audit set and combining live copies stay in +the protocol, not in this library. `k_ppvc` asks for the same checks on every copy, and for distinct hidden indices. `combine_rotated` adds the rotated vectors in `Z/2^s Z`. @@ -79,7 +86,6 @@ The generator is `dpf::prg::aes128` unless another 128-bit PRG is named. **Try**\n @ref mwe/ppvc.cpp -Naor's string commitment is Moni Naor, "Bit Commitment Using -Pseudorandomness," Journal of Cryptology 4(2), 1991, pp. 151–158. +Naor's string commitment is Moni Naor, [Bit Commitment Using Pseudorandomness](@ref bib_naor), Journal of Cryptology 1991. The point keys are the Boyle–Gilboa–Ishai construction named in [DPF basics](@ref point_functions). diff --git a/doc/pages/programmability.md b/doc/pages/programmability.md new file mode 100644 index 0000000..b3f9dae --- /dev/null +++ b/doc/pages/programmability.md @@ -0,0 +1,29 @@ +# Programmability {#programmability} + +Fill in a secret index or payload after the key exists, rewrite an +updatable leaf, or commit to a vector before choosing which coordinate +to open. + +| Mechanism | What it does | +| --- | --- | +| [dpf::wildcard_value](@ref dpf/wildcard.hpp) | Placeholder input or leaf. Eval throws until you assign. | +| Leaf assign | `compute_and_get_blinded_output_share`, `reconstruct_correction_word`, `async_assign_leaf`, `assign_cmp` — see [wildcard assign](@ref wildcard_assign). | +| [dpf::updatable](@ref dpf/placement.hpp) | Beaver leaf so a later rewrite replaces the payload without regenerating the spine. | +| [dpf::ppvc](@ref ppvc_manual) | Point-programmable vector commitment: bind `(Z/2^s Z)^n`, then open one hidden coordinate (or the sum). | +| [dpf::at](@ref dpf/placement.hpp) / `idpf` | Plant values on public prefixes as well as the leaf. | + +```cpp +auto [k0, k1] = dpf::make_dpf( + std::uint8_t{12}, + dpf::wildcard_value{}); +// assign the payload later; eval before assign throws + +using scheme = dpf::ppvc; +const auto pp = scheme::setup(); +const auto [com, st] = scheme::commit(pp); +const auto op = scheme::open(st, 0, 0x5a, std::uint8_t{40}); +``` + +**Go deeper:** [guided tour — wildcards](@ref tour_outputs), +[PPVC](@ref tour_ppvc), [PPVC manual](@ref ppvc_manual), +[input types](@ref input_types), [output types](@ref output_types). diff --git a/doc/pages/repr_and_twist.md b/doc/pages/repr_and_twist.md new file mode 100644 index 0000000..61b7614 --- /dev/null +++ b/doc/pages/repr_and_twist.md @@ -0,0 +1,116 @@ +# Representation shift and twisted jets {#repr_and_twist} + +One opened offset `eta = x - r` also drives linear-recurrence checkpoints and +twisted monomials. Representation shift advances a dealer-keyed state vector +by a public matrix power. Twisted jets key \f$c^{m}\lambda^{c}\f$ and correct +with a public Pascal shift plus \f$\lambda^{\kappa}\f$. + +## Representation shift {#offset_repr} + +Offset Horner is the unipotent (Pascal) case of a shift-invariant module. +Here the dealer keys an arbitrary state +\f$S_c\in(\mathbb{Z}/2^{64})^d\f$ at the hidden center. After `eta` opens, each +refined piece has a public carry `kappa`, and the parties apply + +\f[ +S_{c+\kappa}=M^{\kappa}S_c. +\f] + +Negative `kappa` uses \f$M^{-1}\f$ (the determinant must be odd, hence a unit +in \f$\mathbb{Z}/2^{64}\f$). The wrap branch is multiplication by the public +constant \f$M^{\mp 2^n}\f$; when \f$M^{2^n}=I\f$ it is free. + +Built-in examples: + +- **Fibonacci.** Companion matrix of \f$T^2-T-1\f$ with + \f$S_n=(F_{n+1},F_n)\f$. The Lucas addition formula is exactly + \f$S_{c+\kappa}=M^{\kappa}S_c\f$. Helpers: + `offset_repr_fibonacci_matrix`, `offset_repr_fibonacci_state`. +- **Geometric.** The \f$1\times 1\f$ matrix \f$[\lambda]\f$ advances + \f$\lambda^{c}\f$ by the public factor \f$\lambda^{\kappa}\f$. +- **CRC / LFSR.** Over \f$\mathrm{GF}(2)\f$ the same checkpoint uses XOR + shares. `offset_repr_crc32_jump` is the cleartext public twin + (ISO / Ethernet polynomial); keyed CRC is deferred to XOR payload shares. + +A state of `s` lanes (`s ≤ offset_repr_max_dim`, which is 8) is one +incremental comparison. The seed spine is `Θ(n λ)` bits with `λ` the +seed width, and the value words grow with the `s` lanes. +`offset_repr_eval` is one +sequence-shaped walk on the knots. `offset_repr_matrix_pow` then +squares the `s × s` matrix once per bit of `|kappa|` (`Θ(s³)` per +squaring, at most 63 squarings) and applies it on every refined piece, +`Θ(P · bitlength(kappa) · s³)` field operations. Negative exponents need +`M^{-1}`, so the determinant has to be odd. + +`make_offset_repr_keys(center, state)` keys one incremental `gt` of the state vector. +`offset_repr_eval` returns one party's share of the advanced state. +`offset_repr_matrix_pow` is the public \f$M^{e}\f$ used after `eta` opens. + +**Code samples**\n +
+ + - repr_and_twist.cpp \include{cpp} grotto/repr_and_twist.cpp + +
+ +## Twisted jets {#offset_twist} + +The dealer keys one comparison whose payload is the vector of twisted powers +\f$c^{m}\lambda^{c}\f$ in \f$\mathbb{Z}/2^{64}\f$. After `eta` opens, the segment +walk returns those shares on the hot piece. A public binomial shift of the +coefficient vector by `kappa`, followed by a public factor \f$\lambda^{\kappa}\f$, +yields + +\f[ +\sum_{m}a_m(c+\kappa)^{m}\lambda^{c+\kappa} + =\lambda^{\kappa}\sum_{m}q_m\,c^{m}\lambda^{c}, +\f] + +where \f$q=\mathrm{Pascal}(\kappa)\,a\f$. Odd \f$\lambda\f$ are units, so negative +`kappa` is \f$(\lambda^{-1})^{|\kappa|}\f$. + +Dyadic decay \f$\lambda=1/2\f$ is the tag `twist_half`. A right shift does +not distribute over additive shares, so keygen plants \f$c^{m}\f$ and +`offset_twist_eval` returns shares of the untwisted +\f$\sum a_m(c+\kappa)^{m}\f$. After opening, a public right shift by the +wrapped point yields \f$\sum a_m x^{m}/2^{x}\f$. `offset_twist_clear` with +`twist_half` evaluates that dyadic target in the clear. + +The closed form + +\f[ +\sum_{k=1}^{n}k\lambda^{k} + =\lambda\frac{1-(n+1)\lambda^{n}+n\lambda^{n+1}}{(1-\lambda)^{2}} +\f] + +(for odd \f$\lambda\neq 1\f$) is `offset_twist_arithmetico_geometric`. It is a +readout of the same twisted table (degree-1 coefficients against +\f$\lambda^{k}\f$ powers). + +`make_offset_twist_keys(center, degree, lambda)` and the `twist_half` +overload key the table. `lambda` must be odd. `offset_twist_eval` +returns one party's share of the twisted polynomial at the wrapped point. +`offset_twist_clear` is the same value in the clear. + +Degree `d` is at most 16: one comparison key. The seed spine is +`Θ(n λ)` bits with `λ` the seed width, and the value words grow with +`d`. Then one sequence-shaped walk on the knots and +an `O(d^2)` Pascal shift. `twist_half` skips the public multiply by +the odd base and leaves a shift for after the shares are opened. +`offset_twist_arithmetico_geometric` is a constant amount of arithmetic +on its two public arguments. + +\code{cpp} +const std::uint8_t center = 10; +const std::uint8_t eta = 5; +auto twist_keys = grotto::make_offset_twist_keys( + center, 2, std::uint64_t{3}); +std::vector knots{0}; +std::vector coeff{2, 5, 1}; +auto s0 = grotto::offset_twist_eval<0>(twist_keys, knots, coeff, eta); +auto half_keys = grotto::make_offset_twist_keys( + center, 2, grotto::twist_half); +\endcode + +Offset Horner, offset polynomials, carry, prefix parity, and the cleartext +LUTs are on [jet and ring](@ref jet_and_ring). diff --git a/doc/pages/verifiability.md b/doc/pages/verifiability.md new file mode 100644 index 0000000..c3dd2a5 --- /dev/null +++ b/doc/pages/verifiability.md @@ -0,0 +1,27 @@ +# Verifiability & authenticity {#verifiability} + +Prove that a DPF walk used honest correction seeds, or that a weight-1 +sketch over the path is consistent. The tags ride along as extra +`make_dpf` arguments; eval still returns the usual leaf share. + +| Tag / call | What you get | +| --- | --- | +| [dpf::verifiable](@ref dpf/verifiable.hpp) | Proof token folded on the path (de Castro–Polychroniadou, EUROCRYPT 2022 / [ePrint 2021/580](@ref bib_vdpf)). Equal tokens across parties mean honest seeds. | +| [dpf::extractable](@ref dpf/verifiable.hpp) | Weight-1 `fp61` sketch on the same walk. A second hot point fails the check. | +| [dpf::output_mac](@ref dpf/verifiable.hpp) / `mac_authenticate` | Authenticated leaf shares and batch checks. | +| Path sketches | [path_sketch.hpp](@ref dpf/path_sketch.hpp) for prefix / parent sketches used by application mockups. | + +```cpp +auto [k0, k1] = dpf::make_dpf(std::uint8_t{42}, std::uint64_t{7}, + dpf::verifiable{}); +auto [e0, e1] = dpf::make_dpf(std::uint8_t{42}, std::uint64_t{7}, + dpf::extractable{}); +``` + +Multipoint keys take the same `dpf::verifiable{}` tag for a batched +proof (one token for the cuckoo set). Three-party keys can be +verifiable or extractable as well; see [Multiparty & 3-server](@ref multiparty). + +**Go deeper:** [guided tour](@ref tour_vdpf), ideal figures +[F_VDPF](@ref verifiable.hpp) / [F_Sketch](@ref verifiable.hpp), +[bibliography](@ref bibliography). diff --git a/doc/pages/which_dpf.md b/doc/pages/which_dpf.md new file mode 100644 index 0000000..6dcfac5 --- /dev/null +++ b/doc/pages/which_dpf.md @@ -0,0 +1,27 @@ +# Which DPF? {#which_dpf} + +Click a card. The next question appears under it, and the last card is a +complete program. Each program is also a file under `examples/mwe/` and +compiles from the repository root: + + c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/point.cpp + +The same choices are listed as links under the cards. +Types in full are on [Input types](@ref input_types) and [Output types](@ref output_types). + +\htmlinclude mwe/chooser.html + +## The same choices, as links {#which_links} + +- A dealer knows `alpha` and `beta`. Two parties each get a key: [dpf::make_dpf](@ref dpf/incremental.hpp). +- The parties already share `alpha` and want a reusable key: [dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp). +- The parties want the answer and no key: [dpf::geneval_point](@ref dpf/geneval.hpp), [dpf::geneval_interval](@ref dpf/geneval.hpp), [dpf::geneval_cmp](@ref dpf/geneval.hpp). +- Three evaluators: [dpf::make_dpf3](@ref dpf/dpf3.hpp) and [dpf::make_dpf3_doerner_shelat](@ref dpf/dpf3_ds.hpp). +- One point, a comparison, or a public interval: [dpf::gt](@ref dpf/dcf.hpp) and the other predicates, and [dpf::ic](@ref dpf/interval.hpp). +- Many secret points: [dpf::make_multipoint](@ref dpf/multipoint.hpp). +- A vector whose hidden coordinate is chosen after the commitment: [point-programmable vector commitments](@ref ppvc_manual). +- Several lanes at one leaf: [dpf::vec](@ref dpf/vec.hpp). +- A payload filled in later: [dpf::wildcard_value](@ref dpf/wildcard.hpp). +- A proof the key is well formed: [dpf::verifiable](@ref dpf/verifiable.hpp). + +The call index, with one line each, is the [API reference](@ref api_reference). diff --git a/doc/papers/boyar-peralta-aes-sbox-eprint-2011-332.pdf b/doc/papers/boyar-peralta-aes-sbox-eprint-2011-332.pdf new file mode 100644 index 0000000..db34ada Binary files /dev/null and b/doc/papers/boyar-peralta-aes-sbox-eprint-2011-332.pdf differ diff --git a/doc/papers/boyle-chandran-gilboa-gupta-ishai-kumar-rathee-mixed-mode-fss-eprint-2020-1392.pdf b/doc/papers/boyle-chandran-gilboa-gupta-ishai-kumar-rathee-mixed-mode-fss-eprint-2020-1392.pdf new file mode 100644 index 0000000..685d338 Binary files /dev/null and b/doc/papers/boyle-chandran-gilboa-gupta-ishai-kumar-rathee-mixed-mode-fss-eprint-2020-1392.pdf differ diff --git a/doc/papers/boyle-gilboa-ishai-fss-improvements-eprint-2018-707.pdf b/doc/papers/boyle-gilboa-ishai-fss-improvements-eprint-2018-707.pdf new file mode 100644 index 0000000..3420067 Binary files /dev/null and b/doc/papers/boyle-gilboa-ishai-fss-improvements-eprint-2018-707.pdf differ diff --git a/doc/papers/boyle-gilboa-ishai-kolobov-it-dpf-eprint-2023-028.pdf b/doc/papers/boyle-gilboa-ishai-kolobov-it-dpf-eprint-2023-028.pdf new file mode 100644 index 0000000..5d17d13 Binary files /dev/null and b/doc/papers/boyle-gilboa-ishai-kolobov-it-dpf-eprint-2023-028.pdf differ diff --git a/doc/papers/chou-orlandi-simplest-ot-eprint-2015-267.pdf b/doc/papers/chou-orlandi-simplest-ot-eprint-2015-267.pdf new file mode 100644 index 0000000..f6a9bb3 Binary files /dev/null and b/doc/papers/chou-orlandi-simplest-ot-eprint-2015-267.pdf differ diff --git a/doc/papers/de-castro-polychroniadou-verifiable-fss-eprint-2021-580.pdf b/doc/papers/de-castro-polychroniadou-verifiable-fss-eprint-2021-580.pdf new file mode 100644 index 0000000..8a64dec Binary files /dev/null and b/doc/papers/de-castro-polychroniadou-verifiable-fss-eprint-2021-580.pdf differ diff --git a/doc/papers/doerner-shelat-scaling-oram-eprint-2017-827.pdf b/doc/papers/doerner-shelat-scaling-oram-eprint-2017-827.pdf new file mode 100644 index 0000000..7dab8e7 Binary files /dev/null and b/doc/papers/doerner-shelat-scaling-oram-eprint-2017-827.pdf differ diff --git a/doc/papers/guo-yang-wang-zhang-xie-zhang-liu-half-tree-eprint-2022-1431.pdf b/doc/papers/guo-yang-wang-zhang-xie-zhang-liu-half-tree-eprint-2022-1431.pdf new file mode 100644 index 0000000..01d4bc9 Binary files /dev/null and b/doc/papers/guo-yang-wang-zhang-xie-zhang-liu-half-tree-eprint-2022-1431.pdf differ diff --git a/doc/papers/patra-schneider-suresh-yalame-aby2-eprint-2020-1225.pdf b/doc/papers/patra-schneider-suresh-yalame-aby2-eprint-2020-1225.pdf new file mode 100644 index 0000000..c4faad2 Binary files /dev/null and b/doc/papers/patra-schneider-suresh-yalame-aby2-eprint-2020-1225.pdf differ diff --git a/doc/papers/storrier-vadapalli-lyons-henry-grotto-eprint-2023-108.pdf b/doc/papers/storrier-vadapalli-lyons-henry-grotto-eprint-2023-108.pdf new file mode 100644 index 0000000..ba0888b Binary files /dev/null and b/doc/papers/storrier-vadapalli-lyons-henry-grotto-eprint-2023-108.pdf differ diff --git a/doc/papers/zyskind-yanai-pentland-three-party-dpf-eprint-2024-1658.pdf b/doc/papers/zyskind-yanai-pentland-three-party-dpf-eprint-2024-1658.pdf new file mode 100644 index 0000000..408d758 Binary files /dev/null and b/doc/papers/zyskind-yanai-pentland-three-party-dpf-eprint-2024-1658.pdf differ diff --git a/doc/stylesheet.css b/doc/stylesheet.css index ba212eb..42c9b54 100644 --- a/doc/stylesheet.css +++ b/doc/stylesheet.css @@ -1,3 +1,55 @@ +/* Layout carried over from the v2.2.0 theme patches. + Sidebar width matches TREEVIEW_WIDTH. The header is tall enough for the + 116px wordmark plus the search row; the theme's 120px header clipped it. + Tree arrows stay visible. Doxygen 1.14+ draws them as arrowheads. */ +html { + --side-nav-fixed-width: 350px; + --top-height: 185px; + --side-nav-arrow-opacity: 0.9; + --side-nav-arrow-hover-opacity: 0.9; +} + +/* The theme caps logo images at 2x the title size. The wordmark is 330x116, + so the sidebar header padding is tightened so the mark is not clipped. */ +#titlearea { + padding-left: 8px; + padding-right: 8px; +} + +#projectlogo img { + max-height: none; + width: 330px; + height: 116px; +} + +#titlearea table { + width: 100%; +} + +@media screen and (min-width: 768px) { + /* Keep the dark-mode toggle on the search row. The theme appends it + inside the search cell, and a block-level search box would wrap it. */ + #titlearea tr:last-child > td { + display: flex; + align-items: center; + } + + #MSearchBox { + display: block; + flex: 1 1 auto; + width: auto; + min-width: 0; + } + + #MSearchField { + width: calc(100% - 80px); + } + + #nav-sync { + left: 308px; + } +} + .github-corner svg { fill: var(--primary-light-color); color: var(--page-background-color); @@ -178,3 +230,189 @@ dpf trees: \e4bd .iterables_2zip_iterable_8cpp\.html::before { content: "\f1c9"; } + +/* Click-through on Which DPF. A checked radio reveals the next question + and the matching program. The programs are the files under examples/mwe. */ +.chooser { + margin: 1.2rem 0 1.6rem; +} +.chooser .q { + font-weight: 600; + margin: 1rem 0 0.45rem; +} +.chooser input { + position: absolute; + opacity: 0; + width: 1px; + height: 1px; +} +.chooser .choice { + display: inline-block; + vertical-align: top; + width: 14rem; + margin: 0 0.6rem 0.6rem 0; + padding: 0.7rem 0.8rem; + border: 1px solid var(--separator-color); + border-radius: 8px; + background: var(--fragment-background); + cursor: pointer; +} +.chooser .choice strong { + display: block; + margin-bottom: 0.2rem; +} +.chooser .choice span { + display: block; + font-size: 0.92rem; + color: var(--page-secondary-foreground-color, #555); +} +.chooser input:checked + .choice, +.chooser input:focus-visible + .choice { + border-color: var(--primary-color); + box-shadow: inset 0 0 0 1px var(--primary-color); +} +.chooser .branch, +.chooser .result { + display: none; +} +#h-dealer:checked ~ .branch-dealer, +#h-share:checked ~ .branch-share, +#h-answer:checked ~ .branch-answer, +#h-three:checked ~ .branch-three { + display: block; +} +#d-point:checked ~ .result-point, +#d-cmp:checked ~ .result-cmp, +#d-ic:checked ~ .result-ic { + display: block; +} +.chooser .result { + margin-top: 0.8rem; + padding: 0.9rem 1rem 1rem; + border: 1px solid var(--separator-color); + border-radius: 8px; +} +.chooser .result h3 { + margin-top: 0; +} +.chooser pre.mwe { + overflow: auto; + padding: 0.8rem 1rem; + border-radius: 6px; + background: var(--fragment-background); + border: 1px solid var(--separator-color); +} +.mwe-copy { + cursor: pointer; + border: 1px solid var(--separator-color); + border-radius: 6px; + background: var(--page-background-color); + color: var(--page-foreground-color); + padding: 0.25rem 0.7rem; + margin-bottom: 0.6rem; +} +.mwe-cmd { + font-size: 0.92rem; +} + +.hero-lead { + font-size: 1.2rem; + line-height: 1.45; + max-width: 40rem; + margin-top: 0.2rem; +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 0.75rem; + margin: 0.4rem 0 1.6rem; +} + +.feature-card { + display: block; + padding: 0.85rem 0.95rem 0.95rem; + border: 1px solid var(--separator-color); + border-radius: 10px; + background: var(--fragment-background); + text-decoration: none; + color: var(--page-foreground-color); + line-height: 1.35; +} + +.feature-card:hover { + border-color: var(--primary-color); +} + +.feature-card strong, +.feature-card b { + display: block; + margin: 0.2rem 0 0.35rem; + font-size: 1.02rem; +} + +.feature-card p { + margin: 0; + color: var(--page-secondary-foreground-color); + font-size: 0.92rem; +} + +.feature-kicker { + display: block; + font-size: 0.72rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--primary-color); + font-weight: 700; +} + +details.type-note { + border-bottom: 1px solid var(--separator-color); + margin: 0; +} + +details.type-note summary { + cursor: pointer; + font-weight: 650; + padding: 0.55rem 0; +} + +details.type-note > p:first-of-type { + margin-top: 0.2rem; +} + +.page-nav { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin: 0 0 1.1rem; + padding: 0.45rem 0 0.7rem; + border-bottom: 1px solid var(--separator-color); +} + +.page-nav-bottom { + margin: 2rem 0 0.25rem; + padding-top: 0.85rem; + border-bottom: 0; + border-top: 1px solid var(--separator-color); +} + +.page-nav a { + text-decoration: none; + font-weight: 650; + max-width: 46%; +} + +.page-nav a.page-nav-next { + margin-left: auto; + text-align: right; +} + +pre.fragment { + overflow: auto; + padding: 0.9rem 1rem; + border-radius: 8px; + background: var(--fragment-background); + border: 1px solid var(--separator-color); +}