From 0dff6df8ed4aa2816b4a510d175dc651180df987 Mon Sep 17 00:00:00 2001 From: Ryan Henry Date: Thu, 24 Sep 2026 23:18:10 -0600 Subject: [PATCH] Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges. Co-authored-by: Cursor --- doc/Doxyfile | 912 +++++++----- doc/assets/assets.dox | 1 + include/dpf.hpp | 8 + include/dpf/advice_bit_iterable.hpp | 3 + include/dpf/aligned_allocator.hpp | 1 + include/dpf/asio.hpp | 3 +- include/dpf/beaver.hpp | 327 +++-- include/dpf/bit.hpp | 20 +- include/dpf/bit_array.hpp | 117 +- include/dpf/bitstring.hpp | 56 +- include/dpf/blocked_dcf.hpp | 26 +- include/dpf/buffered_prg.hpp | 17 +- include/dpf/cmp_group.hpp | 548 +++++++ include/dpf/constrained_cmp.hpp | 110 ++ include/dpf/dcf.hpp | 124 +- include/dpf/doerner_shelat.hpp | 476 +++++- include/dpf/dpf_key.hpp | 580 ++++++-- include/dpf/emplace.hpp | 10 + include/dpf/eval_common.hpp | 38 +- include/dpf/eval_full.hpp | 8 +- include/dpf/eval_inner_product.hpp | 31 +- include/dpf/eval_interval.hpp | 79 +- include/dpf/eval_point.hpp | 82 +- include/dpf/eval_sequence.hpp | 48 +- include/dpf/eval_target.hpp | 21 +- include/dpf/eval_unified.hpp | 26 +- include/dpf/fp61.hpp | 241 ++++ include/dpf/geneval.hpp | 301 +++- include/dpf/incremental.hpp | 1276 +++++++++++++++-- include/dpf/interval.hpp | 319 ++++- include/dpf/interval_memoizer.hpp | 65 +- include/dpf/json.hpp | 918 +++++++++--- include/dpf/keyword.hpp | 61 +- include/dpf/keyword2.hpp | 5 + include/dpf/leaf_arithmetic.hpp | 11 +- include/dpf/leaf_node.hpp | 24 +- include/dpf/leaf_wrapper.hpp | 35 +- include/dpf/modint.hpp | 97 +- include/dpf/multipoint.hpp | 560 ++++++++ include/dpf/nyble.hpp | 15 + include/dpf/offset_wrapper.hpp | 41 +- include/dpf/output_buffer.hpp | 33 +- include/dpf/packed_array.hpp | 6 +- include/dpf/packed_lane.hpp | 5 + include/dpf/packed_lane_arithmetic.hpp | 5 +- include/dpf/parallel_bit_iterable.hpp | 3 +- include/dpf/parallel_bit_iterable_helpers.hpp | 16 +- include/dpf/path_memoizer.hpp | 31 +- include/dpf/placement.hpp | 127 +- include/dpf/prg.hpp | 18 +- include/dpf/prg_aes.hpp | 39 +- include/dpf/prg_aes_ccr.hpp | 254 ++++ include/dpf/prg_chacha.hpp | 64 +- include/dpf/prg_dummy.hpp | 8 +- include/dpf/prg_lowmc.hpp | 12 +- include/dpf/random.hpp | 13 +- include/dpf/rotated_iterable.hpp | 245 ---- include/dpf/secret_share.hpp | 37 +- include/dpf/sequence_memoizer.hpp | 40 +- include/dpf/sequence_recipe.hpp | 22 +- include/dpf/sequence_utils.hpp | 6 +- include/dpf/setbit_index_iterable.hpp | 3 +- include/dpf/subsequence_iterable.hpp | 2 +- include/dpf/tree_traits.hpp | 385 +++++ include/dpf/twiddle.hpp | 5 +- include/dpf/twobit.hpp | 15 + include/dpf/utils.hpp | 148 +- include/dpf/vec.hpp | 277 ++++ include/dpf/verifiable.hpp | 366 +++++ include/dpf/wildcard.hpp | 6 +- include/dpf/xor_wrapper.hpp | 10 +- include/dpf/zip_iterable.hpp | 2 +- include/grotto/constant_lut.hpp | 54 +- include/grotto/dyadic_lut.hpp | 21 +- include/grotto/easy_lut.hpp | 32 +- include/grotto/fixedpoint.hpp | 95 +- include/grotto/fixedpoint_mul.hpp | 51 +- include/grotto/gadget_hints.hpp | 5 +- include/grotto/gadgets.hpp | 7 +- include/grotto/gadgets/activations.hpp | 2 +- include/grotto/gadgets/activations/celu.hpp | 2 +- include/grotto/gadgets/activations/elish.hpp | 10 +- include/grotto/gadgets/activations/elu.hpp | 2 +- include/grotto/gadgets/activations/gelu.hpp | 10 +- .../grotto/gadgets/activations/hardelish.hpp | 2 +- .../grotto/gadgets/activations/hardshrink.hpp | 10 +- .../gadgets/activations/hardsigmoid.hpp | 10 +- .../grotto/gadgets/activations/hardswish.hpp | 10 +- .../grotto/gadgets/activations/hardtanh.hpp | 10 +- .../grotto/gadgets/activations/leakyrelu.hpp | 10 +- .../grotto/gadgets/activations/lecun_tanh.hpp | 2 +- .../grotto/gadgets/activations/logsigmoid.hpp | 10 +- include/grotto/gadgets/activations/mish.hpp | 10 +- .../gadgets/activations/one_minus_sigmoid.hpp | 11 +- include/grotto/gadgets/activations/relu.hpp | 7 +- include/grotto/gadgets/activations/relu6.hpp | 10 +- include/grotto/gadgets/activations/selu.hpp | 2 +- include/grotto/gadgets/activations/serf.hpp | 10 +- .../grotto/gadgets/activations/sigmoid.hpp | 10 +- include/grotto/gadgets/activations/silu.hpp | 10 +- .../grotto/gadgets/activations/smoothstep.hpp | 10 +- .../grotto/gadgets/activations/softminus.hpp | 10 +- .../grotto/gadgets/activations/softplus.hpp | 10 +- .../grotto/gadgets/activations/softshrink.hpp | 10 +- .../grotto/gadgets/activations/softsign.hpp | 2 +- .../gadgets/activations/squared_relu.hpp | 10 +- .../grotto/gadgets/activations/starrelu.hpp | 2 +- .../grotto/gadgets/activations/tanhexp.hpp | 10 +- .../grotto/gadgets/activations/tanhshrink.hpp | 2 +- include/grotto/gadgets/binary.hpp | 2 +- include/grotto/gadgets/binary/bit.hpp | 4 +- include/grotto/gadgets/binary/countl_zero.hpp | 8 +- include/grotto/gadgets/binary/prefix.hpp | 2 +- include/grotto/gadgets/binary/sgn.hpp | 12 +- include/grotto/gadgets/decimal.hpp | 2 +- include/grotto/gadgets/decimal/dec_ceil.hpp | 2 +- include/grotto/gadgets/decimal/dec_floor.hpp | 2 +- include/grotto/gadgets/decimal/dec_width.hpp | 2 +- .../gadgets/decimal/has_single_digit.hpp | 2 +- include/grotto/gadgets/elementary.hpp | 2 +- include/grotto/gadgets/elementary/abs.hpp | 10 +- include/grotto/gadgets/elementary/approx.hpp | 2 +- include/grotto/gadgets/elementary/boxcar.hpp | 2 +- include/grotto/gadgets/elementary/clip.hpp | 12 +- include/grotto/gadgets/elementary/eq.hpp | 2 +- include/grotto/gadgets/elementary/geq.hpp | 2 +- include/grotto/gadgets/elementary/gt.hpp | 2 +- .../grotto/gadgets/elementary/identity.hpp | 2 +- include/grotto/gadgets/elementary/leq.hpp | 4 +- include/grotto/gadgets/elementary/lt.hpp | 2 +- .../grotto/gadgets/elementary/negative.hpp | 4 +- include/grotto/gadgets/elementary/neq.hpp | 2 +- .../grotto/gadgets/elementary/nonnegative.hpp | 4 +- .../grotto/gadgets/elementary/nonpositive.hpp | 4 +- include/grotto/gadgets/elementary/nonzero.hpp | 4 +- include/grotto/gadgets/elementary/pmone.hpp | 2 +- .../grotto/gadgets/elementary/positive.hpp | 4 +- include/grotto/gadgets/elementary/rect.hpp | 2 +- include/grotto/gadgets/elementary/step.hpp | 2 +- include/grotto/gadgets/elementary/ternary.hpp | 2 +- include/grotto/gadgets/elementary/zero.hpp | 4 +- include/grotto/gadgets/exponential.hpp | 2 +- include/grotto/gadgets/exponential/exp.hpp | 8 +- include/grotto/gadgets/exponential/exp10.hpp | 8 +- include/grotto/gadgets/exponential/exp2.hpp | 8 +- include/grotto/gadgets/hyperbolic.hpp | 2 +- include/grotto/gadgets/hyperbolic/acosh.hpp | 2 +- include/grotto/gadgets/hyperbolic/acoth.hpp | 2 +- include/grotto/gadgets/hyperbolic/acsch.hpp | 2 +- include/grotto/gadgets/hyperbolic/asech.hpp | 2 +- include/grotto/gadgets/hyperbolic/asinh.hpp | 2 +- include/grotto/gadgets/hyperbolic/atanh.hpp | 2 +- include/grotto/gadgets/hyperbolic/cosh.hpp | 10 +- include/grotto/gadgets/hyperbolic/coth.hpp | 10 +- include/grotto/gadgets/hyperbolic/csch.hpp | 10 +- include/grotto/gadgets/hyperbolic/sech.hpp | 10 +- include/grotto/gadgets/hyperbolic/sinh.hpp | 10 +- include/grotto/gadgets/hyperbolic/tanh.hpp | 10 +- include/grotto/gadgets/logarithm.hpp | 2 +- include/grotto/gadgets/logarithm/ilog10.hpp | 24 +- include/grotto/gadgets/logarithm/ilog16.hpp | 2 +- include/grotto/gadgets/logarithm/ilog256.hpp | 2 +- include/grotto/gadgets/logarithm/ilogb.hpp | 10 +- include/grotto/gadgets/logarithm/lg.hpp | 10 +- include/grotto/gadgets/logarithm/ln.hpp | 10 +- include/grotto/gadgets/logarithm/log10.hpp | 10 +- include/grotto/gadgets/logarithm/logn.hpp | 2 +- include/grotto/gadgets/logarithm/logstar.hpp | 2 +- include/grotto/gadgets/misc.hpp | 2 +- include/grotto/gadgets/misc/entropy.hpp | 2 +- include/grotto/gadgets/misc/erf.hpp | 10 +- include/grotto/gadgets/misc/erfc.hpp | 14 +- include/grotto/gadgets/misc/expint.hpp | 2 +- include/grotto/gadgets/misc/gamma.hpp | 2 +- include/grotto/gadgets/misc/i0.hpp | 2 +- include/grotto/gadgets/misc/lgamma.hpp | 2 +- include/grotto/gadgets/misc/reimann_zeta.hpp | 2 +- include/grotto/gadgets/misc/sinc.hpp | 2 +- include/grotto/gadgets/powers.hpp | 2 +- include/grotto/gadgets/powers/cbrt.hpp | 2 +- include/grotto/gadgets/powers/icbrt.hpp | 2 +- include/grotto/gadgets/powers/iqtrt.hpp | 2 +- include/grotto/gadgets/powers/isqrt.hpp | 10 +- include/grotto/gadgets/powers/qtrt.hpp | 2 +- include/grotto/gadgets/powers/reciprocal.hpp | 10 +- include/grotto/gadgets/powers/sqrt.hpp | 10 +- include/grotto/gadgets/quantile.hpp | 2 +- include/grotto/gadgets/quantile/cauchy.hpp | 4 +- .../grotto/gadgets/quantile/chisquared.hpp | 4 +- .../grotto/gadgets/quantile/exponential.hpp | 4 +- include/grotto/gadgets/quantile/gamma.hpp | 4 +- include/grotto/gadgets/quantile/gaussian.hpp | 4 +- include/grotto/gadgets/quantile/geometric.hpp | 4 +- include/grotto/gadgets/quantile/laplace.hpp | 4 +- include/grotto/gadgets/quantile/logistic.hpp | 4 +- include/grotto/gadgets/quantile/lognormal.hpp | 4 +- include/grotto/gadgets/quantile/pareto.hpp | 4 +- include/grotto/gadgets/quantile/poisson.hpp | 4 +- include/grotto/gadgets/quantile/powerlaw.hpp | 4 +- .../grotto/gadgets/quantile/standard_t.hpp | 4 +- include/grotto/gadgets/trigonometric.hpp | 2 +- include/grotto/gadgets/trigonometric/acos.hpp | 10 +- include/grotto/gadgets/trigonometric/acot.hpp | 2 +- include/grotto/gadgets/trigonometric/acsc.hpp | 2 +- include/grotto/gadgets/trigonometric/asec.hpp | 2 +- include/grotto/gadgets/trigonometric/asin.hpp | 10 +- include/grotto/gadgets/trigonometric/atan.hpp | 2 +- include/grotto/gadgets/trigonometric/cos.hpp | 10 +- include/grotto/gadgets/trigonometric/cot.hpp | 12 +- include/grotto/gadgets/trigonometric/csc.hpp | 10 +- .../grotto/gadgets/trigonometric/deg2rad.hpp | 2 +- .../grotto/gadgets/trigonometric/rad2deg.hpp | 4 +- include/grotto/gadgets/trigonometric/sec.hpp | 10 +- include/grotto/gadgets/trigonometric/sin.hpp | 10 +- include/grotto/gadgets/trigonometric/tan.hpp | 10 +- include/grotto/hexfloat.hpp | 16 +- include/grotto/nmod.hpp | 24 +- include/grotto/offset_horner.hpp | 143 +- include/grotto/offset_iterable.hpp | 20 +- include/grotto/prefix_parity.hpp | 43 +- include/grotto/principal_lut.hpp | 14 +- include/grotto/range_lut.hpp | 167 ++- include/grotto/window_lut.hpp | 21 +- test/CMakeLists.txt | 16 + test/tests/arith_payload_test.cpp | 370 +++++ test/tests/beaver_test.cpp | 426 +++++- test/tests/blocked_dcf_test.cpp | 86 ++ test/tests/constrained_cmp_test.cpp | 64 + test/tests/context_blast_test.cpp | 15 +- test/tests/corner_gaps_test.cpp | 3 + test/tests/dyadic_lut_test.cpp | 14 + test/tests/fp61_test.cpp | 57 + test/tests/geneval_test.cpp | 15 + test/tests/half_tree_test.cpp | 322 +++++ test/tests/ic_test.cpp | 123 ++ test/tests/incremental_json_test.cpp | 203 ++- test/tests/incremental_test.cpp | 30 + test/tests/lane_blast_test.cpp | 30 + test/tests/multipoint_test.cpp | 180 +++ test/tests/nmod_test.cpp | 13 + test/tests/packed_lane_test.cpp | 6 + test/tests/path_recipe_test.cpp | 3 + test/tests/prg_aes_ccr_test.cpp | 135 ++ test/tests/prg_chacha_test.cpp | 38 + test/tests/range_lut_test.cpp | 100 ++ test/tests/stress_scenarios_test.cpp | 41 +- test/tests/types_test.cpp | 30 + test/tests/verifiable_test.cpp | 211 +++ test/tests/wide_payload_test.cpp | 345 +++++ thirdparty/thirdparty.dox | 1 + 250 files changed, 12199 insertions(+), 1981 deletions(-) create mode 100644 doc/assets/assets.dox create mode 100644 include/dpf/cmp_group.hpp create mode 100644 include/dpf/constrained_cmp.hpp create mode 100644 include/dpf/fp61.hpp create mode 100644 include/dpf/multipoint.hpp create mode 100644 include/dpf/prg_aes_ccr.hpp delete mode 100644 include/dpf/rotated_iterable.hpp create mode 100644 include/dpf/tree_traits.hpp create mode 100644 include/dpf/vec.hpp create mode 100644 include/dpf/verifiable.hpp create mode 100644 test/tests/arith_payload_test.cpp create mode 100644 test/tests/constrained_cmp_test.cpp create mode 100644 test/tests/fp61_test.cpp create mode 100644 test/tests/half_tree_test.cpp create mode 100644 test/tests/multipoint_test.cpp create mode 100644 test/tests/prg_aes_ccr_test.cpp create mode 100644 test/tests/verifiable_test.cpp create mode 100644 test/tests/wide_payload_test.cpp create mode 100644 thirdparty/thirdparty.dox diff --git a/doc/Doxyfile b/doc/Doxyfile index b0efa8b..9a86ae7 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1,7 +1,7 @@ -# Doxyfile 1.10.0 +# Doxyfile 1.15.0 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -15,10 +15,10 @@ # # Note: # -# Use doxygen to compare the used configuration file with the template +# Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] -# Use doxygen to compare the used configuration file with the template +# Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] @@ -51,7 +51,7 @@ PROJECT_NAME = libdpf++ PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "tree-mendously speedy DPFs in C++" @@ -71,17 +71,18 @@ PROJECT_ICON = doc/assets/libdpf-logo.svg # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = doc -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO @@ -97,7 +98,7 @@ CREATE_SUBDIRS = NO CREATE_SUBDIRS_LEVEL = 8 -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. @@ -106,36 +107,28 @@ CREATE_SUBDIRS_LEVEL = 8 ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -166,13 +159,13 @@ ABBREVIATE_BRIEF = "The $name class" \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. @@ -180,7 +173,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = YES -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -190,11 +183,11 @@ FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @@ -208,41 +201,42 @@ STRIP_FROM_PATH = STRIP_FROM_INC_PATH = include -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. +# interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -254,10 +248,10 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = YES -# By default Python docstrings are displayed as preformatted text and doxygen's +# By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES @@ -268,7 +262,7 @@ PYTHON_DOCSTRING = YES INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. @@ -285,16 +279,16 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = "license=@par License:\n" \ "complexity=@par Complexity:\n" @@ -339,40 +333,54 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files +# default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add +# the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES +# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as +# Markdown formatted also in cases where Doxygen's native markup format +# conflicts with that of Markdown. This is only relevant in cases where +# backticks are used. Doxygen's native markup style allows a single quote to end +# a text fragment started with a backtick and then treat it as a piece of quoted +# text, whereas in Markdown such text fragment is treated as verbatim and only +# ends when a second matching backtick is found. Also, Doxygen's native markup +# format requires double quotes to be escaped when they appear in a backtick +# section, whereas this is not needed for Markdown. +# The default value is: YES. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_STRICT = YES + # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. +# 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 @@ -388,20 +396,29 @@ TOC_INCLUDE_HEADINGS = 5 MARKDOWN_ID_STYLE = DOXYGEN -# When enabled doxygen tries to link words that correspond to documented +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# +# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO @@ -413,16 +430,16 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. +# Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. @@ -431,7 +448,7 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first +# tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. @@ -489,26 +506,26 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you +# which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. +# Minimum value: 0, maximum value: 512, default value: 1. NUM_PROC_THREADS = 1 @@ -524,7 +541,7 @@ TIMESTAMP = NO # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -590,7 +607,7 @@ EXTRACT_ANON_NSPACES = YES RESOLVE_UNNAMED_PARAMS = YES -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. @@ -598,7 +615,7 @@ RESOLVE_UNNAMED_PARAMS = YES HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect @@ -607,14 +624,22 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -628,16 +653,16 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = YES -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option +# Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. @@ -645,21 +670,27 @@ INTERNAL_DOCS = YES CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -672,7 +703,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = YES -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. @@ -684,14 +715,14 @@ FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. @@ -699,7 +730,7 @@ SORT_MEMBER_DOCS = NO SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -711,7 +742,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = YES -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. @@ -728,11 +759,11 @@ SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. @@ -802,24 +833,25 @@ SHOW_FILES = YES SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from +# Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = doc/DoxygenLayout.xml @@ -834,19 +866,35 @@ LAYOUT_FILE = doc/DoxygenLayout.xml CITE_BIB_FILES = doc/libdpf.bib +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -854,44 +902,60 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = YES -# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about -# undocumented enumeration values. If set to NO, doxygen will accept +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = YES -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves -# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless @@ -902,19 +966,33 @@ WARN_IF_UNDOC_ENUM_VAL = YES WARN_AS_ERROR = NO -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = doc/doxygen.log @@ -930,16 +1008,20 @@ WARN_LOGFILE = doc/doxygen.log INPUT = include/dpf.hpp \ include/dpf/ \ + include/grotto.hpp \ + include/grotto/ \ doc/libdpf_full.md \ doc/examples.dox \ doc/namespaces.dox \ doc/directories.dox \ + doc/assets/assets.dox \ + thirdparty/thirdparty.dox \ examples \ doc \ test # 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 +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. @@ -949,12 +1031,12 @@ INPUT = include/dpf.hpp \ INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files -# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default -# INPUT_ENCODING) if there is a match. The character encodings are a list of the -# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding -# "INPUT_ENCODING" for further information on supported encodings. +# INPUT_ENCODING if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. INPUT_FILE_ENCODING = @@ -964,16 +1046,16 @@ INPUT_FILE_ENCODING = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. +# read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, -# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, -# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to -# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.cpp \ @@ -989,7 +1071,7 @@ RECURSIVE = YES # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = *.md \ @@ -1045,7 +1127,7 @@ EXAMPLE_RECURSIVE = YES IMAGE_PATH = doc/assets/ -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # @@ -1060,16 +1142,16 @@ IMAGE_PATH = doc/assets/ # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # -# Note that doxygen will use the data processed and written to standard output +# Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -1080,7 +1162,7 @@ INPUT_FILTER = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. FILTER_PATTERNS = @@ -1102,10 +1184,19 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = doc/libdpf_full.md +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The @@ -1135,7 +1226,7 @@ SOURCE_BROWSER = YES INLINE_SOURCES = YES -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -1173,7 +1264,7 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. @@ -1187,14 +1278,14 @@ SOURCE_TOOLTIPS = YES # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. @@ -1202,26 +1293,28 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the # clang parser (see: # http://clang.llvm.org/) for more accurate parsing at the cost of reduced # performance. This can be particularly helpful with template rich C++ code for -# which doxygen's built-in parser lacks the necessary type information. -# Note: The availability of this option depends on whether or not doxygen was +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = YES -# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to -# YES then doxygen will add the directory of each input to the include path. +# 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 +# include path. # The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_ADD_INC_PATHS = YES # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories +# the include paths will already be set by Doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. @@ -1240,7 +1333,7 @@ CLANG_OPTIONS = -std=c++17 \ # specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. -# Note: The availability of this option depends on whether or not doxygen was +# Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = @@ -1269,7 +1362,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1290,40 +1383,40 @@ HTML_OUTPUT = ../build/docs HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = doc/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = doc/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1333,7 +1426,7 @@ HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. @@ -1368,11 +1461,11 @@ HTML_EXTRA_FILES = thirdparty/doxygen-awesome-css/doxygen-awesome-darkmode # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. -# Possible values are: LIGHT always generate light mode output, DARK always -# generate dark mode output, AUTO_LIGHT automatically set the mode according to -# the user preference, use light mode if no preference is set (the default), -# AUTO_DARK automatically set the mode according to the user preference, use -# dark mode if no preference is set and TOGGLE allow to user to switch between +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1381,7 +1474,7 @@ HTML_COLORSTYLE = AUTO_DARK # 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 -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1391,7 +1484,7 @@ HTML_COLORSTYLE = AUTO_DARK HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1435,7 +1528,7 @@ HTML_DYNAMIC_SECTIONS = YES HTML_CODE_FOLDING = YES -# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: @@ -1448,7 +1541,7 @@ HTML_COPY_CLIPBOARD = YES # 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 -# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1472,7 +1565,7 @@ HTML_INDEX_NUM_ENTRIES = 100 # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML +# create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at @@ -1491,6 +1584,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1513,14 +1613,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1540,7 +1644,7 @@ CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1574,6 +1678,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1632,7 +1746,7 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1676,30 +1790,39 @@ DISABLE_INDEX = YES # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has more details information than the tab index, you +# could consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES -# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the -# FULL_SIDEBAR option determines if the side bar is limited to only the treeview -# area (value NO) or if it should extend to the full height of the window (value -# YES). Setting this to YES gives a layout similar to -# https://docs.readthedocs.io with more room for contents, but less room for the -# project logo, title, and description. If either GENERATE_TREEVIEW or -# DISABLE_INDEX is set to NO, this option has no effect. +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = YES + +# 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 +# extend to the full height of the window (value YES). Setting this to YES gives +# a layout similar to e.g. https://docs.readthedocs.io with more room for +# contents, but less room for the project logo, title, and description. If +# GENERATE_TREEVIEW is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1708,6 +1831,12 @@ FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1715,14 +1844,21 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 350 -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = YES -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. @@ -1735,7 +1871,7 @@ HTML_FORMULA_FORMAT = svg # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1759,53 +1895,90 @@ FORMULA_MACROFILE = USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2, MathJax_3 and MathJax_4. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4 +# (see: +# https://docs.mathjax.org/en/v4.0/output/index.htm). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the +# destination directory should contain the MathJax.js script. For instance, if +# the mathjax directory is located at the same level as the HTML output +# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3 +# and 4 the destination directory should contain the tex-.js script +# (where is either chtml or svg). The default value points to the +# MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from https://www.mathjax.org before deployment. The default +# value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html -# #tex-and-latex-extensions): +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# https://docs.mathjax.org/en/v3.2/input/tex/extensions/): +# MATHJAX_EXTENSIONS = ams +# For example for MathJax version 4 (see +# https://docs.mathjax.org/en/v4.0/input/tex/extensions/): +# MATHJAX_EXTENSIONS = units +# Note that for Mathjax version 4 quite a few extensions are already +# automatically loaded. To disable a package in Mathjax version 4 one can use +# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS += +# -textmacros) # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the Mathjax site +# for more details: +# - MathJax version 2 (see: +# https://docs.mathjax.org/en/v2.7/) +# - MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/) +# - MathJax version 4 (see: +# https://docs.mathjax.org/en/v4.0/) For an example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1824,7 +1997,7 @@ SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and +# setting. When disabled, Doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing # and searching needs to be provided by external tools. See the section # "External Indexing and Searching" for details. @@ -1833,7 +2006,7 @@ SEARCHENGINE = YES SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the @@ -1878,7 +2051,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1892,7 +2065,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1937,7 +2110,7 @@ MAKEINDEX_CMD_NAME = makeindex LATEX_MAKEINDEX_CMD = makeindex -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# If the COMPACT_LATEX tag is set to YES, Doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1966,36 +2139,38 @@ PAPER_TYPE = a4 EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# 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 +# first chapter. If it is left blank Doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that Doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank Doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that Doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen # will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the @@ -2021,7 +2196,7 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as # specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX # files. Set this option to YES, to get a higher quality PDF documentation. # @@ -2031,15 +2206,22 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2049,7 +2231,7 @@ LATEX_HIDE_INDICES = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. +# The default value is: plainnat. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain @@ -2066,7 +2248,7 @@ LATEX_EMOJI_DIRECTORY = # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, Doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -2081,7 +2263,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -2101,38 +2283,36 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load stylesheet definitions from file. Syntax is similar to Doxygen's # configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be +# similar to Doxygen's configuration file. A template extensions file can be # generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_SOURCE_CODE = NO +RTF_EXTRA_FILES = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -2163,7 +2343,7 @@ MAN_EXTENSION = .3 MAN_SUBDIR = -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without # them the man command would be unable to find the correct page. @@ -2176,7 +2356,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, Doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -2190,7 +2370,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, Doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -2199,7 +2379,7 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include # namespace members in file scope as well, matching the HTML output. # The default value is: NO. # This tag requires that the tag GENERATE_XML is set to YES. @@ -2210,7 +2390,7 @@ XML_NS_MEMB_FILE_SCOPE = NO # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -2224,20 +2404,11 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an # AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. @@ -2249,8 +2420,8 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- -# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 -# database with symbols found by doxygen stored in tables. +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. # The default value is: NO. GENERATE_SQLITE3 = NO @@ -2264,7 +2435,7 @@ GENERATE_SQLITE3 = NO SQLITE3_OUTPUT = sqlite3 # The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db -# database file will be recreated with each doxygen run. If set to NO, doxygen +# database file will be recreated with each Doxygen run. If set to NO, Doxygen # will warn if a database file is already found and not modify it. # The default value is: YES. # This tag requires that the tag GENERATE_SQLITE3 is set to YES. @@ -2275,7 +2446,7 @@ SQLITE3_RECREATE_DB = YES # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, Doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -2283,7 +2454,7 @@ SQLITE3_RECREATE_DB = YES GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, Doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -2313,13 +2484,13 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names # in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. @@ -2345,7 +2516,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = thirdparty \ @@ -2393,7 +2565,7 @@ PREDEFINED = HEDLEY_ALWAYS_INLINE=[[gnu::always_inline]] \ EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have # an all uppercase name, and do not end with a semicolon. Such function macros # are typically used for boiler-plate code, and will confuse the parser if not @@ -2417,12 +2589,12 @@ SKIP_FUNCTION_MACROS = YES # section "Linking to external documentation" for more information about the use # of tag files. # Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is +# the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. TAGFILES = thirdparty/cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/ -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. @@ -2453,41 +2625,34 @@ EXTERNAL_PAGES = YES # Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# If you set the HAVE_DOT tag to YES then Doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: # https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = YES -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 Doxygen will base this on the number of # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. -# Minimum value: 0, maximum value: 32, default value: 0. +# Minimum value: 0, maximum value: 512, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. DOT_NUM_THREADS = 0 # DOT_COMMON_ATTR is common attributes for nodes, edges and labels of # subgraphs. When you want a differently looking font in the dot files that -# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. # For details please see Node, # Edge and Graph Attributes specification You need to make sure dot is able # to find the font, which can be done by putting it in a standard location or by @@ -2521,19 +2686,24 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the # class with other documented classes. Explicit enabling a collaboration graph, @@ -2545,15 +2715,18 @@ CLASS_GRAPH = YES COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. See also the chapter Grouping -# in the manual. +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2574,10 +2747,19 @@ UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 -# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# If the UML_LOOK tag is enabled, field labels are shown along the edge between +# two class nodes. If there are many fields and many nodes the graph may become +# too cluttered. The UML_MAX_EDGE_LABELS threshold limits the number of items to +# make the size more manageable. Set this to 0 for no limit. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_MAX_EDGE_LABELS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and # methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS -# tag is set to YES, doxygen will add type and arguments for attributes and -# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen # will not generate fields with class member information in the UML graphs. The # class diagrams will look similar to the default class diagrams but using UML # notation for the relationships. @@ -2605,7 +2787,7 @@ DOT_WRAP_THRESHOLD = 17 TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented # files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, # can be accomplished by means of the command \includegraph. Disabling an @@ -2616,7 +2798,7 @@ TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented # files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set # to NO, can be accomplished by means of the command \includedbygraph. Disabling @@ -2627,7 +2809,7 @@ INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2639,7 +2821,7 @@ INCLUDED_BY_GRAPH = YES CALL_GRAPH = YES -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2651,14 +2833,14 @@ CALL_GRAPH = YES CALLER_GRAPH = YES -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the # files in the directories. Explicit enabling a directory graph, when @@ -2670,28 +2852,40 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: # https://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). +# +# Note the formats svg:cairo and svg:cairo:cairo cannot be used in combination +# with INTERACTIVE_SVG (the INTERACTIVE_SVG will be set to NO). # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus, +# png:gdiplus:gdiplus, svg:cairo, svg:cairo:cairo, svg:svg, svg:svg:core, +# gif:cairo, gif:cairo:gd, gif:cairo:gdiplus, gif:gdiplus, gif:gdiplus:gdiplus, +# gif:gd, gif:gd:gd, jpg:cairo, jpg:cairo:gd, jpg:cairo:gdiplus, jpg:gd, +# jpg:gd:gd, jpg:gdiplus and jpg:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = svg -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. +# If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option +# can be set to YES to enable generation of interactive SVG images that allow +# zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. +# +# Note This option will be automatically disabled when DOT_IMAGE_FORMAT is set +# to svg:cairo or svg:cairo:cairo. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2710,7 +2904,7 @@ DOT_PATH = DOTFILE_DIRS = -# You can include diagrams made with dia in doxygen documentation. Doxygen will +# You can include diagrams made with dia in Doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. @@ -2723,28 +2917,34 @@ DIA_PATH = DIAFILE_DIRS = -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. PLANTUML_CFG_FILE = -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = +# The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that +# contain PlantUml files that are included in the documentation (see the +# \plantumlfile command). + +PLANTUMLFILE_DIRS = + # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct +# larger than this value, Doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. @@ -2774,28 +2974,30 @@ MAX_DOT_GRAPH_DEPTH = 0 DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. # -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. DOT_CLEANUP = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will # use a built-in version of mscgen tool to produce the charts. Alternatively, # the MSCGEN_TOOL tag can also specify the name an external tool. For instance, -# specifying prog as the value, doxygen will call the tool as prog -T +# specifying prog as the value, Doxygen will call the tool as prog -T # -o . The external tool should support # output file formats "png", "eps", "svg", and "ismap". diff --git a/doc/assets/assets.dox b/doc/assets/assets.dox new file mode 100644 index 0000000..45b602b --- /dev/null +++ b/doc/assets/assets.dox @@ -0,0 +1 @@ +// Registers doc/assets with Doxygen so `@dir` can document the image directory. diff --git a/include/dpf.hpp b/include/dpf.hpp index 4bfb64e..afd0d10 100644 --- a/include/dpf.hpp +++ b/include/dpf.hpp @@ -116,6 +116,14 @@ #include "dpf/uint256_t.hpp" +#include "dpf/fp61.hpp" + +#include "dpf/verifiable.hpp" + +#include "dpf/multipoint.hpp" + +#include "dpf/vec.hpp" + #include "dpf/interval.hpp" #endif // LIBDPF_INCLUDE_DPF_HPP__ diff --git a/include/dpf/advice_bit_iterable.hpp b/include/dpf/advice_bit_iterable.hpp index a50da4c..c6edd4d 100644 --- a/include/dpf/advice_bit_iterable.hpp +++ b/include/dpf/advice_bit_iterable.hpp @@ -339,12 +339,15 @@ auto bit_array_from_advice_bits_simde(Iterator first, Iterator last, static_assert(CHAR_BIT == 8, "CHAR_BIT not equal to 8"); auto ret = dynamic_bit_array(bits); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::size_t bits_per_byte = CHAR_BIT, bytes = (bits-1)/bits_per_byte + 1, bits_per_word = ret.bits_per_word, bits_per_simde = dpf::utils::bitlength_of_v, bytes_per_simde = sizeof(simde_type), words_per_simde = bits_per_simde / bits_per_word; + HEDLEY_PRAGMA(GCC diagnostic pop) std::size_t curbits = 0, pos = 0; std::array in = {0}; diff --git a/include/dpf/aligned_allocator.hpp b/include/dpf/aligned_allocator.hpp index 9885893..244bba5 100644 --- a/include/dpf/aligned_allocator.hpp +++ b/include/dpf/aligned_allocator.hpp @@ -54,6 +54,7 @@ class aligned_allocator /// @brief a `deleter` functor for use by `std::unique_ptr` to free /// memory allocated when the `std::unique_ptr` was /// constructed + /// @tparam Pointer pointer type stored in the deleter template struct deleter { diff --git a/include/dpf/asio.hpp b/include/dpf/asio.hpp index 919fd1a..b99420e 100644 --- a/include/dpf/asio.hpp +++ b/include/dpf/asio.hpp @@ -1,6 +1,5 @@ /// @file dpf/asio.hpp -/// @brief -/// @details +/// @brief ASIO helpers for shipping DPF keys and assigning wildcard inputs. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2023 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; diff --git a/include/dpf/beaver.hpp b/include/dpf/beaver.hpp index 4883a3a..61885f9 100644 --- a/include/dpf/beaver.hpp +++ b/include/dpf/beaver.hpp @@ -14,7 +14,9 @@ /// A polynomial is a sum of monomials in several wires. /// `2 + 3*x + 4*y + 5*x*y + 6*pow(x, 2) + pow(x, 2)*y + x*y*z` /// is one round. `λ_x²` is stored once whether it appears as `x²`, -/// inside `x² y`, or in a second polynomial. Wires that occur with the +/// inside `x² y`, or in a second polynomial. An inner product is that +/// sum: `dot({x0,x1}, {y0,y1})` is `x0*y0 + x1*y1`, and the pair +/// products share one preprocessing value. Wires that occur with the /// same exponents in every term, as in `a3*(x*z)^3 + a2*(x*z)^2 + a1*(x*z) + a0`, /// are multiplied first and the univariate polynomial is a later round. /// A factor shared by every term, such as a sign or a piecewise scale, @@ -70,8 +72,9 @@ namespace dpf namespace beavers { -/// Ring operations used to build and consume triples. -/// Specialize for a ring whose multiplicative identity is not `Ring{1}`. +/// @brief Ring operations used to build and consume triples. +/// @details Specialize for a ring whose multiplicative identity is not `Ring{1}`. +/// @tparam Ring payload ring template struct ring_traits { @@ -90,7 +93,8 @@ struct ring_traits static Ring sample() { return dpf::uniform_sample(); } }; -/// Bitwise AND uses the all-ones word as its multiplicative identity. +/// @brief Bitwise AND uses the all-ones word as its multiplicative identity. +/// @tparam T value type template struct ring_traits> { @@ -121,7 +125,8 @@ struct default_sampler Ring operator()() const { return ring_traits::sample(); } }; -/// Additive (2,2) split. `open()` is `p0 + p1`. +/// @brief Additive (2,2) split. `open()` is `p0 + p1`. +/// @tparam Ring payload ring template struct split { @@ -157,9 +162,10 @@ struct split template class session; -/// A value in a session. Copying a wire copies its id; it does not copy the +/// @brief A value in a session. Copying a wire copies its id; it does not copy the /// blind. The ring argument is on the type so `a * x * x` can build an /// expression without naming the session. +/// @tparam Ring payload ring template class wire { @@ -192,15 +198,16 @@ private: std::uint32_t id_ = 0; }; -/// Unevaluated sum of monomials. `*` distributes over `+`. A public +/// @brief Unevaluated sum of monomials. `*` distributes over `+`. A public /// coefficient scales a term. Nothing is sampled until `session::operator()`. +/// @tparam Ring payload ring template struct expr { struct term { Ring coeff{}; - /// Positive exponents, sorted by wire id. + /// @brief Positive exponents, sorted by wire id. std::vector> powers; }; @@ -226,10 +233,20 @@ expr wire_expr(wire w); template expr horner_expr(wire x, std::initializer_list coeffs); -/// One PRG lane per blind role, plus the share-mask stream for that role. -/// `blind(role, index)` and `share(role, index, value)` do not depend on +namespace detail +{ + +template +expr dot_expr(const ContX & xs, const ContY & ys); + +} // namespace detail + +/// @brief One PRG lane per blind role, plus the share-mask stream for that role. +/// @details `blind(role, index)` and `share(role, index, value)` do not depend on /// call order. Walking `index` forward stays inside a refilled window. /// `PRG` defaults to `dpf::prg::aes128`. +/// @tparam Ring payload ring +/// @tparam PRG pseudorandom generator template class oracle { @@ -241,7 +258,7 @@ public: using seed_type = typename PRG::block_type; using traits = ring_traits; - /// Monomial roles sit above wire ids. Dot-cross roles sit above those. + /// @brief Monomial roles sit above wire ids. Dot-cross roles sit above those. static constexpr std::uint32_t mono_role_base = 0x40000000u; static constexpr std::uint32_t dot_role_base = 0x80000000u; @@ -260,7 +277,7 @@ public: return dot_role_base + gate; } - /// Fused within-polynomial λ combinations (Appendix E groupings). + /// @brief Fused within-polynomial λ combinations (Appendix E groupings). static constexpr std::uint32_t bundle_role_base = 0xC0000000u; HEDLEY_NO_THROW @@ -290,7 +307,11 @@ public: return lanes_.mask_at(role, index); } - /// Additive split of `value`. The mask is the role's mask stream at `index`. + /// @brief Additive split of `value`. The mask is the role's mask stream at `index`. + /// @param role the `role` + /// @param index the index + /// @param value the value to convert or store + /// @return Additive split of `value` split share(std::uint32_t role, std::uint64_t index, const Ring & value) const { Ring p0 = mask(role, index); @@ -311,20 +332,22 @@ private: dpf::randomness::lane_table lanes_; }; -/// Shares produced for one copy index of a recorded formula. +/// @brief Shares produced for one copy index of a recorded formula. +/// @tparam Ring payload ring template struct prg_material { std::vector> lambda; std::vector> monomial; - /// Fused λ-combinations for polynomial gates, in bundle index order. + /// @brief Fused λ-combinations for polynomial gates, in bundle index order. std::vector> bundles; - /// Parallel to the session's gates. Empty split when the gate is not a dot. + /// @brief Parallel to the session's gates. Empty split when the gate is not a dot. std::vector> dot_cross; }; -/// Dealer session: record formulae, `sample` blinds and monomials, `bind` +/// @brief Dealer session: record formulae, `sample` blinds and monomials, `bind` /// input secrets, `evaluate` every round. +/// @tparam Ring payload ring template class session { @@ -338,7 +361,7 @@ public: using exp_list = std::vector>; using wire = ::dpf::beavers::wire; - /// One factor of a monomial query: `{{x, 2}, {a, 1}}`. + /// @brief One factor of a monomial query: `{{x, 2}, {a, 1}}`. struct power { wire base{}; @@ -351,29 +374,34 @@ public: session(session &&) = delete; session & operator=(session &&) = delete; - /// Arithmetic input. Its blind is sampled once and then reused. + /// @brief Arithmetic input. Its blind is sampled once and then reused. + /// @return Arithmetic input HEDLEY_WARN_UNUSED_RESULT wire input() { return emplace_wire(0, true, false); } - /// Sample this wire's blind even if no recorded formula opens it. - /// One-shot triples use this for the product wire, so it can be reused. + /// @brief Sample this wire's blind even if no recorded formula opens it. + /// @details One-shot triples use this for the product wire, so it can be reused. + /// @param w the `w` void pin(wire w) { wires_[check(w)].pinned = true; } - /// 0/1 wire in this ring. `bind` accepts only `zero()` or `one()` + /// @brief 0/1 wire in this ring. `bind` accepts only `zero()` or `one()` /// (`1` for integer rings, the all-ones word for `xor_wrapper`). + /// @return 0/1 wire in this ring HEDLEY_WARN_UNUSED_RESULT wire bit() { return emplace_wire(0, true, true); } - /// One-round product. Repeated wires share a blind. + /// @brief One-round product. Repeated wires share a blind. + /// @param factors the `factors` + /// @return One-round product HEDLEY_WARN_UNUSED_RESULT wire product(std::initializer_list factors) { @@ -396,7 +424,10 @@ public: return commit_product({check(a), check(b), check(c)}); } - /// Record a sum of monomials. Like terms share one blind product. + /// @brief Record a sum of monomials. Like terms share one blind product. + /// @param e the `e` + /// @return Record a sum of monomials + /// @throws std::invalid_argument if `beaver expression is from a different session` HEDLEY_WARN_UNUSED_RESULT wire operator()(const expr & e) { @@ -405,15 +436,23 @@ public: return commit_poly(e); } - /// `c[0] + c[1] x + c[2] x^2 + ...` in one round. + /// @brief `c[0] + c[1] x + c[2] x^2 + ...` in one round. + /// @param x the `x` + /// @param coeffs the public coefficients + /// @return `c[0] + c[1] x + c[2] x^2 + ...` in one round HEDLEY_WARN_UNUSED_RESULT wire horner(wire x, std::initializer_list coeffs) { return (*this)(horner_expr(x, coeffs)); } - /// Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round + /// @brief Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round /// when `sign` and `x` are inputs. + /// @param sign the sign bit or sign value + /// @param x the `x` + /// @param coeffs the public coefficients + /// @return Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round when `sign` + /// and `x` are inputs HEDLEY_WARN_UNUSED_RESULT wire horner(wire sign, wire x, std::initializer_list coeffs) { @@ -426,7 +465,10 @@ public: return product(x, x); } - /// One-round `a * x * x` (one blind for `x`). + /// @brief One-round `a * x * x` (one blind for `x`). + /// @param a the `a` + /// @param x the `x` + /// @return One-round `a * x * x` (one blind for `x`) HEDLEY_WARN_UNUSED_RESULT wire mul_square(wire a, wire x) { @@ -437,31 +479,21 @@ public: HEDLEY_WARN_UNUSED_RESULT wire dot(const ContX & xs, const ContY & ys) { - std::vector x; - std::vector y; - for (const auto & w : xs) - x.push_back(check(w)); - for (const auto & w : ys) - y.push_back(check(w)); - return commit_dot(std::move(x), std::move(y)); + return finish_dot(detail::dot_expr(xs, ys)); } HEDLEY_WARN_UNUSED_RESULT wire dot(std::initializer_list xs, std::initializer_list ys) { - std::vector x; - std::vector y; - x.reserve(xs.size()); - y.reserve(ys.size()); - for (auto w : xs) - x.push_back(check(w)); - for (auto w : ys) - y.push_back(check(w)); - return commit_dot(std::move(x), std::move(y)); + return finish_dot(detail::dot_expr(xs, ys)); } - /// `z_i = scalar * lanes[i]`, one output wire per lane. The scalar blind + /// @brief `z_i = scalar * lanes[i]`, one output wire per lane. The scalar blind /// is shared. Each lane gets its own `λ_s λ_i` share. + /// @tparam Cont cont + /// @param scalar the `scalar` + /// @param lanes the lane values + /// @return `z_i = scalar * lanes[i]`, one output wire per lane template HEDLEY_WARN_UNUSED_RESULT std::vector scale(wire scalar, const Cont & lanes) @@ -482,7 +514,11 @@ public: return out; } - /// `bit * scalar`. `bit` must come from `bit()`. + /// @brief `bit * scalar`. `bit` must come from `bit()`. + /// @param selector the `selector` + /// @param scalar the `scalar` + /// @return `bit * scalar` + /// @throws std::invalid_argument if `bit_mul selector must come from bit()` HEDLEY_WARN_UNUSED_RESULT wire bit_mul(wire selector, wire scalar) { @@ -492,7 +528,11 @@ public: return commit_product({id, check(scalar)}); } - /// One-round `selector ? when1 : when0`, i.e. `when0 + selector * (when1 - when0)`. + /// @brief One-round `selector ? when1 : when0`, i.e. `when0 + selector * (when1 - when0)`. + /// @param selector the `selector` + /// @param when1 the `when1` + /// @param when0 the `when0` + /// @return One-round `selector ? when1 : when0`, i.e HEDLEY_WARN_UNUSED_RESULT wire mux(wire selector, wire when1, wire when0) { @@ -515,8 +555,11 @@ public: return out; } - /// Sample every missing wire blind and every missing monomial. - /// Blinds already sampled are left alone. + /// @brief Sample every missing wire blind and every missing monomial. + /// @details Blinds already sampled are left alone. + /// @tparam Sample sample + /// @param sampler the randomness sampler + /// @throws std::logic_error if `beaver blind is missing` template void sample(Sample && sampler) { @@ -572,9 +615,12 @@ public: sample(default_sampler{}); } - /// Install missing blinds and product shares from copy `index` of `src`. - /// Already-sampled wires keep their λ. New product shares are built from + /// @brief Install missing blinds and product shares from copy `index` of `src`. + /// @details Already-sampled wires keep their λ. New product shares are built from /// those stored blinds, then split with the oracle's share lane. + /// @tparam PRG pseudorandom generator + /// @param src the source + /// @param index the index template void sample_from(const oracle & src, std::uint64_t index = 0) { @@ -614,9 +660,13 @@ public: } } - /// Every wire, monomial, and dot cross of this formula at copy `index`. - /// Does not change the session. Copies are independent lanes samples, so + /// @brief Every wire, monomial, and dot cross of this formula at copy `index`. + /// @details Does not change the session. Copies are independent lanes samples, so /// `material_at(src, 5)` does not depend on having asked for 0..4. + /// @tparam PRG pseudorandom generator + /// @param src the source + /// @param index the index + /// @return Every wire, monomial, and dot cross of this formula at copy `index` template prg_material material_at(const oracle & src, std::uint64_t index) const { @@ -665,7 +715,13 @@ public: return out; } - /// Split `secret` into fresh additive shares and bind them to an input. + /// @brief Split `secret` into fresh additive shares and bind them to an input. + /// @tparam Sample sample + /// @param w the `w` + /// @param secret the secret value + /// @param sampler the randomness sampler + /// @throws std::invalid_argument if `only input wires can be bound` + /// @throws std::logic_error if `input wire is already bound` template void bind(wire w, Ring secret, Sample && sampler) { @@ -686,7 +742,12 @@ public: bind(w, secret, default_sampler{}); } - /// Bind shares the caller already holds. Their sum is the secret. + /// @brief Bind shares the caller already holds. Their sum is the secret. + /// @param w the `w` + /// @param p0 the `p0` + /// @param p1 the `p1` + /// @throws std::invalid_argument if `only input wires can be bound` + /// @throws std::logic_error if `input wire is already bound` void bind_shares(wire w, Ring p0, Ring p1) { auto id = check(w); @@ -700,9 +761,10 @@ public: wires_[id].value_ready = true; } - /// Open every ready round. Inputs used by round-1 gates are opened + /// @brief Open every ready round. Inputs used by round-1 gates are opened /// together; a gate output is a later round's input and keeps the blind /// chosen in `sample`. + /// @throws std::logic_error if `beaver wire is not ready to open` void evaluate() { int max_round = 0; @@ -774,7 +836,10 @@ public: return wires_[id].lambda; } - /// Share of `Π λ_i^{e_i}`. A lone `λ_w` is the wire blind itself. + /// @brief Share of `Π λ_i^{e_i}`. A lone `λ_w` is the wire blind itself. + /// @param spec the specification + /// @return Share of `Π λ_i^{e_i}` + /// @throws std::invalid_argument if `beaver power is zero` split monomial(const std::vector & spec) const { std::vector> raw; @@ -807,7 +872,10 @@ public: return traits::add(v.p0, v.p1); } - /// Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire. + /// @brief Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire. + /// @param w the `w` + /// @return Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire + /// @throws std::logic_error if `beaver wire has not been opened` Ring delta(wire w) const { auto id = check(w); @@ -819,12 +887,35 @@ public: split dot_cross(wire w) const { auto id = check(w); - int g = wires_[id].gate; - if (g < 0 || gates_[static_cast(g)].kind != gate_kind::dot) + if (!wires_[id].dot_output) throw std::invalid_argument("wire is not a dot output"); - if (!gates_[static_cast(g)].cross_ready) + int g = wires_[id].gate; + if (g < 0) + throw std::invalid_argument("wire is not a dot output"); + const auto & gate = gates_[static_cast(g)]; + if (gate.kind == gate_kind::dot) + { + if (!gate.cross_ready) + throw std::logic_error("call sample() before reading a dot cross term"); + return gate.cross; + } + Ring s0 = traits::zero(); + Ring s1 = traits::zero(); + bool found = false; + for (const auto & step : gate.steps) + { + if (step.bundle < 0 || !step.delta.empty()) + continue; + const auto & bundle = bundles_[static_cast(step.bundle)]; + if (!bundle.ready) + throw std::logic_error("call sample() before reading a dot cross term"); + s0 = traits::add(s0, traits::mul(step.scale, bundle.share.p0)); + s1 = traits::add(s1, traits::mul(step.scale, bundle.share.p1)); + found = true; + } + if (!found) throw std::logic_error("call sample() before reading a dot cross term"); - return gates_[static_cast(g)].cross; + return split{s0, s1}; } int round_of(wire w) const @@ -835,16 +926,19 @@ public: HEDLEY_NO_THROW std::size_t wire_count() const noexcept { return wires_.size(); } - /// Product shares beyond the per-wire blinds: subset monomials from + /// @brief Product shares beyond the per-wire blinds: subset monomials from /// `product` gates, plus one fused bundle per public-δ class in a /// polynomial (Appendix E). A lone mask is not counted. + /// @return Product shares beyond the per-wire blinds: subset monomials from `product` gates, + /// plus one fused bundle per public-δ class in a polynomial (Appendix E) HEDLEY_NO_THROW std::size_t monomial_count() const noexcept { return monos_.size() + bundles_.size(); } - /// Wire blinds that the recorded formulae actually open, plus product shares. + /// @brief Wire blinds that the recorded formulae actually open, plus product shares. + /// @return Wire blinds that the recorded formulae actually open, plus product shares std::size_t preprocessing_count() const { std::size_t n = monomial_count(); @@ -865,7 +959,7 @@ private: std::vector factors; }; - /// One λ-monomial in a fused preprocessing share. + /// @brief One λ-monomial in a fused preprocessing share. struct bundle_part { Ring coeff{}; @@ -879,7 +973,7 @@ private: bool ready = false; }; - /// Online: `public(δ) * scale * share`, where share is a wire mask, + /// @brief Online: `public(δ) * scale * share`, where share is a wire mask, /// a raw monomial, or a fused sum of monomials. struct poly_step { @@ -896,6 +990,7 @@ private: bool is_input = false; bool is_bit = false; bool pinned = false; + bool dot_output = false; bool lambda_ready = false; bool value_ready = false; bool delta_ready = false; @@ -1369,25 +1464,10 @@ private: return last; } - wire commit_dot(std::vector xs, std::vector ys) + wire finish_dot(const expr & e) { - if (xs.empty() || xs.size() != ys.size()) - throw std::invalid_argument( - "beaver dot operands must have the same non-zero length"); - int round = 1; - for (std::size_t i = 0; i < xs.size(); ++i) - { - round = std::max(round, wires_[xs[i]].ready_round + 1); - round = std::max(round, wires_[ys[i]].ready_round + 1); - } - auto out = emplace_wire(round, false, false); - gate g; - g.kind = gate_kind::dot; - g.out = out.id_; - g.lhs = std::move(xs); - g.rhs = std::move(ys); - gates_.push_back(std::move(g)); - wires_[out.id_].gate = static_cast(gates_.size() - 1); + auto out = (*this)(e); + wires_[out.id_].dot_output = true; return out; } @@ -1572,8 +1652,10 @@ private: return false; } - /// Group λ-monomials that share a public δ monomial into one share. - /// A bucket that is only `c · λ_i` reuses the wire blind. + /// @brief Group λ-monomials that share a public δ monomial into one share. + /// @details A bucket that is only `c · λ_i` reuses the wire blind. + /// @param terms the polynomial terms + /// @return Group λ-monomials that share a public δ monomial into one share std::vector compile_poly(const std::vector & terms) { struct bucket @@ -1665,9 +1747,20 @@ private: steps.push_back(std::move(step)); continue; } + Ring scale = parts[0].coeff; + for (const auto & part : parts) + { + if (!(part.coeff == scale)) + scale = traits::one(); + } + if (!(scale == traits::one())) + { + for (auto & part : parts) + part.coeff = traits::one(); + } poly_step step; step.delta = delta; - step.scale = traits::one(); + step.scale = scale; step.bundle = require_bundle(std::move(parts)); steps.push_back(std::move(step)); } @@ -1852,8 +1945,40 @@ Ring coeff_of(Coeff value) return Ring{value}; } +template +expr dot_expr(const ContX & xs, const ContY & ys) +{ + std::vector> x; + std::vector> y; + for (const auto & w : xs) + x.push_back(w); + for (const auto & w : ys) + y.push_back(w); + if (x.empty() || x.size() != y.size()) + throw std::invalid_argument( + "beaver dot operands must have the same non-zero length"); + expr acc = wire_expr(x[0]) * wire_expr(y[0]); + for (std::size_t i = 1; i < x.size(); ++i) + acc = add_exprs(std::move(acc), wire_expr(x[i]) * wire_expr(y[i])); + return acc; +} + } // namespace detail +template +HEDLEY_WARN_UNUSED_RESULT +expr dot(std::initializer_list> xs, std::initializer_list> ys) +{ + return detail::dot_expr(xs, ys); +} + +template +HEDLEY_WARN_UNUSED_RESULT +expr dot(const std::vector> & xs, const std::vector> & ys) +{ + return detail::dot_expr(xs, ys); +} + template HEDLEY_WARN_UNUSED_RESULT expr wire_expr(wire w) @@ -1895,7 +2020,13 @@ expr horner_expr(wire x, std::initializer_list coeffs) return e; } -/// `coeff * v0 * v1 * ...`, with repeated wires counting as a power. +/// @brief `coeff * v0 * v1 * ...`, with repeated wires counting as a power. +/// @tparam Ring payload ring +/// @tparam Wires wires +/// @param coeff the public coefficient +/// @param first the first element of the range +/// @param rest the remaining arguments +/// @return `coeff * v0 * v1 * ...`, with repeated wires counting as a power template HEDLEY_WARN_UNUSED_RESULT expr monomial(Ring coeff, wire first, Wires... rest) @@ -2128,8 +2259,10 @@ expr operator+(expr e, Coeff coeff) // `out` is the ABY2.0 blind of the product wire, for a later round. // --------------------------------------------------------------------------- -/// `subset[mask - 1]` is `Π λ_i` over the bits set in `mask` (bit i selects +/// @brief `subset[mask - 1]` is `Π λ_i` over the bits set in `mask` (bit i selects /// `in[i]`). Singleton masks are the wire blinds themselves. +/// @tparam Arity arity +/// @tparam Ring payload ring template struct fresh_beaver { @@ -2259,9 +2392,14 @@ struct mux_beaver split out{}; }; -/// One fresh Beaver pair from copy `index` of an oracle. -/// Roles match a session that records `input, input, product`: wires 0 and 1, +/// @brief One fresh Beaver pair from copy `index` of an oracle. +/// @details Roles match a session that records `input, input, product`: wires 0 and 1, /// the product wire, and monomial 0. +/// @tparam Ring payload ring +/// @tparam PRG pseudorandom generator +/// @param src the source +/// @param index the index +/// @return One fresh Beaver pair from copy `index` of an oracle template HEDLEY_WARN_UNUSED_RESULT beaver2 beaver2_at(const oracle & src, std::uint64_t index) @@ -2278,7 +2416,14 @@ beaver2 beaver2_at(const oracle & src, std::uint64_t index) src.share(oracle::wire_role(2), index, out)}; } -/// `n` copies starting at `begin`. Each role is one contiguous lane read. +/// @brief `n` copies starting at `begin`. Each role is one contiguous lane read. +/// @tparam Ring payload ring +/// @tparam PRG pseudorandom generator +/// @param src the source +/// @param begin the iterator to the first query +/// @param out the output buffer +/// @param n the `n` +/// @throws std::invalid_argument if `beaver2 output is null` template void fill_beaver2(const oracle & src, std::uint64_t begin, beaver2 * out, std::size_t n) diff --git a/include/dpf/bit.hpp b/include/dpf/bit.hpp index 65944b2..0ed00e6 100644 --- a/include/dpf/bit.hpp +++ b/include/dpf/bit.hpp @@ -52,7 +52,7 @@ enum bit : bool /// equal to `dpf::bit::one` if the *least-significant bit* of /// `value` is `1` and `dpf::bit::zero` otherwise. /// @param value the `int` to convert -/// @returns `static_cast(value & 1)` +/// @return `static_cast(value & 1)` HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -64,6 +64,8 @@ static constexpr dpf::bit to_bit(int value) noexcept /// @brief converts the least-significant bit of an integer literal to a `dpf::bit` /// @details This overload exists so `operator""_bit` does not select the /// character converter, which is an exact match for `unsigned long long`. +/// @param value the value to convert or store +/// @return the returned `dpf::bit` HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -77,7 +79,7 @@ static constexpr dpf::bit to_bit(unsigned long long value) noexcept /// equal to `dpf::bit::one` if `value==true` and `dpf::bit::zero` /// otherwise. /// @param value the `bool` to convert -/// @returns `static_cast(value)` +/// @return `static_cast(value)` HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -90,10 +92,12 @@ static constexpr dpf::bit to_bit(bool value) noexcept /// @details Convert a character to a `dpf::bit`. The resulting `dpf::bit` is /// equal to `dpf::bit::one` if `value==one` and `dpf::bit::zero` /// otherwise. +/// @tparam CharT character type +/// @tparam Traits character traits /// @param value the character to convert /// @param zero character used to represent `0` (default: ``CharT('0')``) /// @param one character used to represent `1` (default: ``CharT('1')``) -/// @returns `static_cast(0)` if `value==0` or +/// @return `static_cast(0)` if `value==0` or /// `static_cast(1)` if `value==1` /// @throws std::domain_error if `value != zero && value != one` template to_string( /// characters to use for zero and one are obtained from the /// currently-imbued locale by calling `os.widen()` with `0` and `1` /// as the arguments. +/// @tparam CharT character type +/// @tparam Traits character traits /// @param os a character output stream /// @param value the `dpf::bit` to insert into the output stream /// @return `os` @@ -162,6 +171,8 @@ operator<<(std::basic_ostream & os, const dpf::bit & value) /// stored in `value`. The characters to use for zero and one are /// obtained from the currently-imbued locale by calling `is.widen()` /// with `0` and `1` as the arguments. +/// @tparam CharT character type +/// @tparam Traits character traits /// @param is a character input stream /// @param value the `dpf::bit` to extract from the input stream /// @return `is` @@ -190,6 +201,9 @@ inline constexpr dpf::bit operator+(dpf::bit lhs, dpf::bit rhs) noexcept } /// @brief GF(2) subtraction. Identical to `operator+`. +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return GF(2) subtraction HEDLEY_NO_THROW inline constexpr dpf::bit operator-(dpf::bit lhs, dpf::bit rhs) noexcept { diff --git a/include/dpf/bit_array.hpp b/include/dpf/bit_array.hpp index fe81f86..5b31ae0 100644 --- a/include/dpf/bit_array.hpp +++ b/include/dpf/bit_array.hpp @@ -1,6 +1,5 @@ /// @file dpf/bit_array.hpp -/// @brief -/// @details +/// @brief Packed bit arrays, static and dynamic, with bit proxies and iterators. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -68,6 +67,8 @@ class const_bit_iterator; // forward reference /// @brief a base class for classes representing a sequence of bits /// @details A `bit_array` represents a sequence of bits. The underlying /// storage is an array of integers of type `dpf::bit_array::word_type`. +/// @tparam ConcreteBitArrayT concrete bit array type +/// @tparam WordT word used to pack bits template class bit_array_base { @@ -143,10 +144,12 @@ class bit_array_base ~bit_array_base() = default; /// @brief default copy assignment + /// @return `*this` inline constexpr bit_array_base & operator=(const bit_array_base &) = default; /// @brief defaulted move assignment + /// @return `*this` HEDLEY_NO_THROW inline constexpr bit_array_base & operator=(bit_array_base &&) noexcept = default; @@ -202,7 +205,7 @@ class bit_array_base /// significant to most significant) /// @note Unlike `test` and `at`, does not throw exceptions: the behavior /// is undefined if `pos` is out of bounds - /// @returns an object of type `dpf::bit_array_base::reference`, which + /// @return an object of type `dpf::bit_array_base::reference`, which /// allows writing to the requested bit /// @complexity `O(1)` HEDLEY_NO_THROW @@ -215,9 +218,9 @@ class bit_array_base /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) + /// @return the value of the requested bit /// @note Unlike `test` and `at`, does not throw exceptions: the behavior /// is undefined if `pos` is out of bounds - /// @returns the value of the requested bit /// @complexity `O(1)` HEDLEY_NO_THROW inline constexpr const_reference operator[](size_type pos) const noexcept @@ -235,7 +238,7 @@ class bit_array_base /// significant to most significant) /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` - /// @returns an object of type `dpf::bit_array_base::reference`, which + /// @return an object of type `dpf::bit_array_base::reference`, which /// allows writing to the requested bit /// @complexity `O(1)` constexpr reference at(size_type pos) @@ -249,9 +252,9 @@ class bit_array_base /// @details accesses the bit at position `pos` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) + /// @return the value of the requested bit /// @throws std::out_of_range if `pos` does not correspond to a valid /// position within the `bit_array_base` - /// @returns the value of the requested bit /// @complexity `O(1)` constexpr const_reference at(size_type pos) const { @@ -264,7 +267,7 @@ class bit_array_base /// @brief returns an iterator to the first bit /// @{ - /// @returns iterator to the first element + /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr iterator begin() noexcept @@ -273,7 +276,7 @@ class bit_array_base if (p == nullptr) return iterator{}; return iterator{p, word_type(1)}; } - /// @returns iterator to the first element + /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator begin() const noexcept @@ -282,7 +285,7 @@ class bit_array_base if (p == nullptr) return const_iterator{}; return const_iterator{p, word_type(1)}; } - /// @returns iterator to the first element + /// @return iterator to the first element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator cbegin() const noexcept @@ -293,7 +296,7 @@ class bit_array_base /// @brief returns an iterator to the end (one past the last bit) /// @{ - /// @returns iterator to the element following the last element + /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr iterator end() noexcept @@ -303,7 +306,7 @@ class bit_array_base return iterator{p + (size() >> lg_bits_per_word), static_cast(word_type(1) << (size() % bits_per_word))}; } - /// @returns iterator to the element following the last element + /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator end() const noexcept @@ -313,7 +316,7 @@ class bit_array_base return const_iterator{p + (size() >> lg_bits_per_word), static_cast(word_type(1) << (size() % bits_per_word))}; } - /// @returns iterator to the element following the last element + /// @return iterator to the element following the last element /// @complexity `O(1)` HEDLEY_NO_THROW constexpr const_iterator cend() const noexcept @@ -325,7 +328,7 @@ class bit_array_base /// @brief checks if the specified bit is set to `true` /// @param pos the 0-based position of the bit to return (least /// significant to most significant) - /// @returns `true` if the requested bit is set, `false` otherwise + /// @return `true` if the requested bit is set, `false` otherwise /// @complexity `O(1)` bool test(size_type pos) const { @@ -357,10 +360,9 @@ class bit_array_base } /// @details checks if all bits in a range are set to `true` - /// @param first,last the range of elements under consideration /// @tparam Iterator an iterator type - /// @return `true` if all of the bits in the given range are set to - /// `true`, otherwise `false` + /// @param first,last the range of elements under consideration + /// @return `true` if all of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW @@ -394,10 +396,9 @@ class bit_array_base } /// @details checks if any bits in a range are set to `true` - /// @param first,last the range of elements under consideration /// @tparam Iterator an iterator type - /// @return `true` if any of the bits in the given range are set to - /// `true`, otherwise `false` + /// @param first,last the range of elements under consideration + /// @return `true` if any of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW @@ -422,10 +423,9 @@ class bit_array_base } /// @details checks if none bits in a range are set to `true` - /// @param first,last the range of elements under consideration /// @tparam Iterator an iterator type - /// @return `true` if none of the bits in the given range are set to - /// `true`, otherwise `false` + /// @param first,last the range of elements under consideration + /// @return `true` if none of the bits in the given range are set to `true`, otherwise `false` /// @complexity `O(last-first)` template HEDLEY_NO_THROW @@ -438,7 +438,7 @@ class bit_array_base /// @brief returns the number of bits set to `true` /// @{ /// @details counts the number of bits that are set to `true` - /// @returns the number of bits set to `true` + /// @return the number of bits set to `true` /// @complexity `O(size())` HEDLEY_NO_THROW size_type count() const noexcept @@ -456,8 +456,8 @@ class bit_array_base return sum; } /// @details counts the number of bits in a range that are set to `true` - /// @param first,last the range of elements under consideration /// @tparam Iterator an iterator type + /// @param first,last the range of elements under consideration /// @return the number of bits in the given range that are set to `true` /// @complexity `O(last-first)` template @@ -477,7 +477,7 @@ class bit_array_base /// @brief returns the parity of all stored bits /// @{ /// @details counts the parity of all stored bits - /// @returns the parity of all stored bits + /// @return the parity of all stored bits /// @complexity `O(size())` HEDLEY_NO_THROW size_type parity() const noexcept @@ -496,8 +496,8 @@ class bit_array_base } /// @details counts the parity of bits in a range - /// @param first,last the range of elements under consideration /// @tparam Iterator an iterator type + /// @param first,last the range of elements under consideration /// @return the parity of all bits in the given range /// @complexity `O(last-first)` template @@ -515,7 +515,7 @@ class bit_array_base /// @} /// @brief returns the number of bits - /// @returns number of bits that the `bit_array_base` holds + /// @return number of bits that the `bit_array_base` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE @@ -633,9 +633,12 @@ class bit_array_base /// contains `size()` characters with the first character /// corresponding to the last `(size()-1th)` bit and the last /// character corresponding tot he first `(0th)` bit. + /// @tparam CharT character type + /// @tparam Traits character traits + /// @tparam Allocator allocator type /// @param zero character to use to represent `false`/`0` (default: ``CharT('0')``) /// @param one character to use to represent `true`/`1` (default: ``CharT('1')``) - /// @returns the converted string + /// @return the converted string /// @throws May throw `std::bad_alloc` from the `std::string` constructor. /// @complexity `O(size())` template (static_cast(lhs) ^ static_cast(rhs)); @@ -746,7 +752,7 @@ class bit_array_base /// @{ /// @details sets `*this` to the result of binary AND on `*this` and `b` /// @param b the other bit - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -758,7 +764,7 @@ class bit_array_base /// @details sets `*this` to the result of binary OR on `*this` and `b` /// @param b the other bit - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -770,7 +776,7 @@ class bit_array_base /// @details sets `*this` to the result of binary XOR on `*this` and `b` /// @param b the other bit - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -780,8 +786,8 @@ class bit_array_base return *this; } - /// @details returns a temporary copy of `*this` with its value - /// flipped (binary NOT) + /// @details returns a temporary copy of `*this` with its value flipped (binary NOT) + /// @return the flipped bit /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -792,7 +798,7 @@ class bit_array_base /// @} /// @brief sets to the referenced bit to 1 - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -805,7 +811,7 @@ class bit_array_base } /// @brief unsets the referenced bit to 0 - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -818,7 +824,8 @@ class bit_array_base } /// @brief assigns `b ? 1 : 0` to the referenced bit - /// @returns `*this` + /// @param b the `b` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -832,7 +839,7 @@ class bit_array_base } /// @brief flips the referenced bit - /// @returns `*this` + /// @return `*this` /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -846,6 +853,8 @@ class bit_array_base /// @brief Exchange the bits named by two proxies, including temporaries /// returned from `operator[]` and `operator*`. + /// @param a the `a` + /// @param b the `b` HEDLEY_NO_THROW friend constexpr void swap(bit_reference a, bit_reference b) noexcept { @@ -897,6 +906,8 @@ class bit_array_base static constexpr word_type sentinel = ~word_type(0); /// @brief Low `n` bits set. `n == 0` yields 0. `n >= bits_per_word` yields all ones. + /// @param n the `n` + /// @return Low `n` bits set HEDLEY_NO_THROW static constexpr word_type low_bits_mask(size_type n) noexcept { @@ -913,6 +924,8 @@ class bit_array_base } /// @brief Bits strictly below the single set bit in `mask`. + /// @param mask the bit mask + /// @return Bits strictly below the single set bit in `mask` HEDLEY_NO_THROW static constexpr word_type bits_below(word_type mask) noexcept { @@ -920,6 +933,8 @@ class bit_array_base } /// @brief Bits at and above the single set bit in `mask`. + /// @param mask the bit mask + /// @return Bits at and above the single set bit in `mask` HEDLEY_NO_THROW static constexpr word_type bits_at_and_above(word_type mask) noexcept { @@ -929,6 +944,11 @@ class bit_array_base /// @brief Invoke `fn(masked_bits, relevant_mask)` for each limb touched by /// `[first, last)`. Does not dereference a one-past-the-end word. /// `fn` returns false to stop early. + /// @tparam Iterator iterator type + /// @tparam Fn fn + /// @param first the first element of the range + /// @param last the past-the-end element of the range + /// @param fn the `fn` template void for_each_span(Iterator first, Iterator last, Fn fn) const { @@ -975,6 +995,8 @@ class bit_array_base /// @brief a base class provided to simplify the definition of /// `bit_iterator` and `const_bit_iterator` +/// @tparam ConcreteBitArrayT concrete bit array type +/// @tparam WordT word used to pack bits template class bit_iterator_base @@ -1461,6 +1483,7 @@ class alignas(utils::max_align_v) static_bit_array final } /// @brief constructs a `static_bit_array` from the low bits of `val` + /// @param val the `val` inline constexpr explicit static_bit_array(std::size_t val) : data_{} { @@ -1503,7 +1526,7 @@ class alignas(utils::max_align_v) static_bit_array final } /// @brief returns the number of bits - /// @returns number of bits that the `static_bit_array` holds + /// @return number of bits that the `static_bit_array` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE @@ -1534,6 +1557,8 @@ class dynamic_bit_array using unique_ptr = typename allocator::unique_ptr; public: /// @brief constructs a zeroed `dynamic_bit_array` that holds `nbits` bits + /// @param nbits the width in bits + /// @param alloc the `alloc` /// @throws std::bad_alloc if allocating storage fails inline explicit dynamic_bit_array(std::size_t nbits, allocator alloc = allocator{}) @@ -1605,6 +1630,7 @@ class dynamic_bit_array } /// @brief direct access to the underlying data array + /// @param i the `i` /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -1625,6 +1651,7 @@ class dynamic_bit_array } /// @brief direct access to the underlying data array + /// @param i the `i` /// @return a pointer to the start of the data array HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -1643,7 +1670,7 @@ class dynamic_bit_array } /// @brief returns the number of bits - /// @returns number of bits that the `dynamic_bit_array` holds + /// @return number of bits that the `dynamic_bit_array` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE @@ -1654,7 +1681,7 @@ class dynamic_bit_array } private: - /// Store zeros through `volatile` so the wipe is not deleted as a dead store. + /// @brief Store zeros through `volatile` so the wipe is not deleted as a dead store. HEDLEY_NO_THROW void wipe() noexcept { @@ -1671,7 +1698,10 @@ class dynamic_bit_array unique_ptr data_; }; -/// @brief +/// @brief Exchanges the bits named by two `dynamic_bit_array` proxies. +/// @tparam WordT word used to pack bits +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand template HEDLEY_NO_THROW inline constexpr void swap(typename dynamic_bit_array::reference lhs, @@ -1682,6 +1712,11 @@ inline constexpr void swap(typename dynamic_bit_array::reference lhs, rhs = tmp; } +/// @brief Exchanges the bits named by two `static_bit_array` proxies. +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand template HEDLEY_NO_THROW diff --git a/include/dpf/bitstring.hpp b/include/dpf/bitstring.hpp index 80b929b..0c33ba4 100644 --- a/include/dpf/bitstring.hpp +++ b/include/dpf/bitstring.hpp @@ -68,6 +68,7 @@ namespace dpf /// `dpf::bit_array_base` and is parametrized on `Nbits`, which is /// the length of the bitstring. /// @tparam Nbits the bitlength of the string +/// @tparam WordT word used to pack bits template > class bitstring : public bit_array_base, WordT> @@ -81,6 +82,7 @@ class bitstring : public bit_array_base, WordT> using const_pointer = typename base::const_pointer; using size_type = typename base::size_type; static constexpr auto bits_per_word = base::bits_per_word; + static constexpr bool dpf_bitstring = true; private: /// @brief the number of `word_type`s are being used to represent the /// `num_bits_` bits @@ -135,11 +137,15 @@ class bitstring : public bit_array_base, WordT> /// and length `len` can be provided, as well as characters /// denoting alternate values for set (`one`) and unset (`zero`) /// bits. + /// @tparam CharT character type + /// @tparam Traits character traits + /// @tparam Alloc allocator type /// @param str `string` used to initialize the `dpf::bitstring` /// @param pos a starting offset into `str` /// @param len number of characters to use from `str` /// @param zero character used to represent `0` (default: `CharT('0')`) /// @param one character used to represent `1` (default: `CharT('1')`) + /// @throws std::out_of_range template @@ -166,10 +172,12 @@ class bitstring : public bit_array_base, WordT> /// `CharT *` `str`. An optional starting position `pos` and length /// `len` can be provided, as well as characters denoting alternate /// values for set (`one`) and unset (`zero`) bits. + /// @tparam CharT character type /// @param str string used to initialize the `dpf::bitstring` /// @param len number of characters to use from `str` /// @param zero character used to represent `false`/`0` (default: ``CharT('0')``) /// @param one character used to represent `true`/`1` (default: ``CharT('1')``) + /// @throws std::invalid_argument if `null string` template explicit bitstring(const CharT * str, typename std::basic_string::size_type len @@ -293,6 +301,7 @@ class bitstring : public bit_array_base, WordT> /// @brief shifts the bit mask to the right by the given number of /// bits /// @param shift_by number of bits to shift the mask to the right + /// @param mask the bit mask /// @return a reference to the modified `dpf::bitstring::bit_mask` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -306,6 +315,7 @@ class bitstring : public bit_array_base, WordT> /// @brief shifts the bit mask to the left by the given number of /// bits /// @param shift_by number of bits to shift the mask to the right + /// @param mask the bit mask /// @return a reference to the modified `dpf::bitstring::bit_mask` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -342,6 +352,8 @@ class bitstring : public bit_array_base, WordT> } /// @brief Inequality of the defined bits. + /// @param rhs the right-hand operand + /// @return Inequality of the defined bits HEDLEY_ALWAYS_INLINE constexpr bool operator!=(const bitstring & rhs) const { @@ -349,6 +361,8 @@ class bitstring : public bit_array_base, WordT> } /// @brief Less than, most-significant bit first. + /// @param rhs the right-hand operand + /// @return Less than, most-significant bit first HEDLEY_ALWAYS_INLINE constexpr bool operator<(const bitstring & rhs) const { @@ -356,6 +370,8 @@ class bitstring : public bit_array_base, WordT> } /// @brief Less than or equal, most-significant bit first. + /// @param rhs the right-hand operand + /// @return Less than or equal, most-significant bit first HEDLEY_ALWAYS_INLINE constexpr bool operator<=(const bitstring & rhs) const { @@ -363,6 +379,8 @@ class bitstring : public bit_array_base, WordT> } /// @brief Greater than, most-significant bit first. + /// @param rhs the right-hand operand + /// @return Greater than, most-significant bit first HEDLEY_ALWAYS_INLINE constexpr bool operator>(const bitstring & rhs) const { @@ -501,7 +519,7 @@ class bitstring : public bit_array_base, WordT> } /// @brief returns the number of bits - /// @returns number of bits that the `bitstring` holds + /// @return number of bits that the `bitstring` holds /// @complexity `O(1)` HEDLEY_NO_THROW HEDLEY_PURE @@ -517,6 +535,7 @@ class bitstring : public bit_array_base, WordT> std::array data_{}; /// @brief Mask of the bits that belong to this string in the high word. + /// @return the returned `word_type` HEDLEY_NO_THROW static constexpr word_type defined_high_mask() noexcept { @@ -537,6 +556,8 @@ class bitstring : public bit_array_base, WordT> /// @brief Most-significant word first. Unused high bits are ignored. /// Every limb is visited, so the time does not depend on where /// the strings differ. + /// @param rhs the right-hand operand + /// @return Most-significant word first constexpr int compare(const bitstring & rhs) const { if constexpr (data_length_ == 0) return 0; @@ -558,6 +579,12 @@ class bitstring : public bit_array_base, WordT> } /// @brief Last character is bit 0. `len` must be at most `Nbits`. + /// @tparam CharT character type + /// @param str the source string + /// @param len the number of bytes + /// @param zero the character used for 0 + /// @param one the character used for 1 + /// @throws std::out_of_range if `string longer than Nbits` template void assign_msb_string(const CharT * str, std::size_t len, CharT zero, CharT one) { @@ -615,6 +642,8 @@ namespace utils { /// @brief specializes `dpf::utils::bitlength_of` for `dpf::bitstring` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template struct bitlength_of> @@ -622,6 +651,8 @@ struct bitlength_of> { }; /// @brief specializes `dpf::utils::msb_of` for `dpf::bitstring` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template struct msb_of> @@ -633,6 +664,8 @@ struct msb_of> /// @brief specializes `dpf::utils::countl_zero_symmetric_difference` for /// `dpf::bitstring` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template struct countl_zero_symmetric_difference> @@ -909,6 +942,9 @@ namespace bitstrings /// @brief Build a bitstring from characters. The first character is the /// most significant bit of the digit string (same order as `0b...`). /// Digits shorter than `Bitstring::size()` occupy the low bits. +/// @tparam Bitstring bitstring +/// @tparam bits bits +/// @return the returned `Bitstring` template constexpr Bitstring bitstring_literal() { @@ -925,6 +961,8 @@ constexpr Bitstring bitstring_literal() /// @details The leftmost character is the most significant bit, matching /// `0b` integer literals. `10101001_bitstring` equals /// `dpf::bitstring<8>(0b10101001)`. +/// @tparam bits bits +/// @return user-defined numeric literal for creating `dpf::bitstring` objects template constexpr static auto operator "" _bitstring() { @@ -937,7 +975,9 @@ constexpr static auto operator "" _bitstring_u8() return bitstring_literal, bits...>(); } -/// Alias used by the test suite: word type `uint8_t`, not length 8. +/// @brief Alias used by the test suite: word type `uint8_t`, not length 8. +/// @tparam bits bits +/// @return Alias used by the test suite: word type `uint8_t`, not length 8 template constexpr static auto operator "" _bitstring_8() { @@ -1123,6 +1163,8 @@ namespace std /// @{ /// @details specializes `std::numeric_limits` for `dpf::bitstring` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template class numeric_limits> @@ -1174,20 +1216,26 @@ class numeric_limits> }; /// @details specializes `std::numeric_limits` for `dpf::bitstring const` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template class numeric_limits const> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `dpf::bitstring volatile` +/// @brief `dpf::bitstring volatile` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template class numeric_limits volatile> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `dpf::bitstring const volatile` +/// @brief `dpf::bitstring const volatile` +/// @tparam Nbits width in bits +/// @tparam WordT word used to pack bits template class numeric_limits const volatile> diff --git a/include/dpf/blocked_dcf.hpp b/include/dpf/blocked_dcf.hpp index 39f45a5..156636f 100644 --- a/include/dpf/blocked_dcf.hpp +++ b/include/dpf/blocked_dcf.hpp @@ -22,6 +22,7 @@ #include "dpf/dcf.hpp" #include "dpf/path_memoizer.hpp" +#include "dpf/tree_traits.hpp" #include "dpf/twiddle.hpp" #include "dpf/utils.hpp" @@ -79,11 +80,11 @@ struct schedule }; template -HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE uint64_t rho_of(const Node & node, uint64_t mask) noexcept { - auto kids = PRG::eval01(dpf::unset_lo_2bits(node)); + auto kids = dpf::tree_traits::expand_value(node); return dcf_impl::convert_node(kids[0], mask); } @@ -107,7 +108,10 @@ constexpr uint64_t mul_sgn(int sgn, uint64_t v, uint64_t mask) noexcept return 0; } -/// Group element `sgn` (`+1`, `-1`, or `0`) used as an `assign_cmp` coefficient. +/// @brief Group element `sgn` (`+1`, `-1`, or `0`) used as an `assign_cmp` coefficient. +/// @param sgn the `sgn` +/// @param mask the bit mask +/// @return Group element `sgn` (`+1`, `-1`, or `0`) used as an `assign_cmp` coefficient HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -136,6 +140,7 @@ uint64_t checkpoint_word(const Node & n0, const Node & n1, uint64_t beta, } template +HEDLEY_PURE HEDLEY_NO_THROW uint64_t checkpoint_coeff(const Node & n0, const Node & n1, uint64_t mask) noexcept @@ -160,7 +165,7 @@ void suffix_masks(const Node & seed, std::size_t q, uint64_t mask, std::size_t m = 0; for (std::size_t i = 0; i < n; ++i) { - auto kids = PRG::eval01(dpf::unset_lo_2bits(cur[i])); + auto kids = dpf::tree_traits::expand_value(cur[i]); nxt[m++] = kids[0]; nxt[m++] = kids[1]; } @@ -219,8 +224,11 @@ uint64_t add_frontier(uint64_t acc, const typename KeyT::interior_node & seed, uint64_t mask, int party) { using node = typename KeyT::interior_node; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::vector cur; std::vector nxt; + HEDLEY_PRAGMA(GCC diagnostic pop) cur.push_back(seed); for (std::size_t lvl = from_depth; lvl < to_depth; ++lvl) { @@ -228,9 +236,10 @@ uint64_t add_frontier(uint64_t acc, const typename KeyT::interior_node & seed, nxt.reserve(cur.size() * 2); const node cw0 = dpf.correction_word(lvl, false); const node cw1 = dpf.correction_word(lvl, true); + const bool is_last = KeyT::tree::is_last_level(lvl, KeyT::depth); for (const node & fs : cur) { - auto kids = KeyT::traverse_interior01(fs, cw0, cw1); + auto kids = KeyT::traverse_interior01(fs, cw0, cw1, is_last); nxt.push_back(kids[0]); nxt.push_back(kids[1]); } @@ -334,7 +343,8 @@ uint64_t eval_share(const KeyT & dpf, InputT tx, PathMemoizer & path) const bool xi = !!(bit_mask & tx); const node & parent = path[level]; const node right = KeyT::traverse_interior(parent, - dpf.correction_word(level, true), true); + dpf.correction_word(level, true), true, + KeyT::tree::is_last_level(level, KeyT::depth)); if (!xi) { pend[npend].seed = right; @@ -358,6 +368,7 @@ uint64_t eval_share(const KeyT & dpf, InputT tx, PathMemoizer & path) } template +HEDLEY_PURE HEDLEY_NO_THROW bool memo_has(const Memo & memo, Integral prefix, std::size_t depth, Integral from_lane, Integral to_excl) noexcept @@ -422,7 +433,8 @@ uint64_t eval_share_memo(const KeyT & dpf, Integral lane, if (!xi) { const node right = KeyT::traverse_interior(parent, - dpf.correction_word(level, true), true); + dpf.correction_word(level, true), true, + KeyT::tree::is_last_level(level, KeyT::depth)); pend[npend].seed = right; pend[npend].prefix = sib; pend[npend].depth = level + 1; diff --git a/include/dpf/buffered_prg.hpp b/include/dpf/buffered_prg.hpp index 7e61cf7..221566b 100644 --- a/include/dpf/buffered_prg.hpp +++ b/include/dpf/buffered_prg.hpp @@ -65,7 +65,12 @@ struct lane_codec return out; } - /// Element `index` is the packed byte range `[index * sizeof(T), ...)`. + /// @brief Element `index` is the packed byte range `[index * sizeof(T), ...)`. + /// @param seed the PRG seed + /// @param index the index + /// @param out the output buffer + /// @param count the number of blocks + /// @throws std::invalid_argument if `prg lane index is out of range` static void fill(block_type seed, std::uint64_t index, T * out, std::size_t count) { if (count == 0) @@ -85,8 +90,8 @@ struct lane_codec HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::aligned_allocator alloc; - auto blocks = alloc.allocate_unique_ptr(static_cast(nblocks)); HEDLEY_PRAGMA(GCC diagnostic pop) + auto blocks = alloc.allocate_unique_ptr(static_cast(nblocks)); PRG::eval(seed, blocks.get(), static_cast(nblocks), static_cast(start)); auto * bytes = reinterpret_cast(blocks.get()); @@ -167,13 +172,15 @@ typename PRG::block_type sample_master_seed() return dpf::uniform_sample(); } -/// Forward cursor over one PRG stream per value type. +/// @brief Forward cursor over one PRG stream per value type. /// /// `get()` and `fill()` consume the cursor. `at(index)` reads an /// absolute index and leaves the cursor where it is. `sampled()` reports /// how far `get` and `fill` have advanced. `per_stream_buffer_elems` is at /// least 1. /// @snippet evaluation/buffered_prg.cpp buffered-prg +/// @tparam PRG pseudorandom generator +/// @tparam Ts ts template class buffered_prg { @@ -248,12 +255,14 @@ private: template using aes_buffered_prg = buffered_prg; -/// Seekable value and mask streams for a runtime set of roles. +/// @brief Seekable value and mask streams for a runtime set of roles. /// /// `value_at(role, index)` and `mask_at(role, index)` are independent of /// call order. A repeated index returns the same element. `window` is at /// least 1. /// @snippet evaluation/buffered_prg.cpp lane-table +/// @tparam T value type +/// @tparam PRG pseudorandom generator template class lane_table { diff --git a/include/dpf/cmp_group.hpp b/include/dpf/cmp_group.hpp new file mode 100644 index 0000000..54301e1 --- /dev/null +++ b/include/dpf/cmp_group.hpp @@ -0,0 +1,548 @@ +/// @file dpf/cmp_group.hpp +/// @brief Comparison-payload group for types that do not fit in a masked `uint64_t`. +/// @details Payloads of at most 64 bits that already convert to `uint64_t` +/// stay on that path. Everything else — wider integers, `modint`, +/// `fixedpoint`, `xor_wrapper`, `bitstring`, and `dpf::vec` — is a +/// little-endian limb vector. Lanes of a `vec` add (or XOR) apart, +/// with no carry from one lane into the next. A PRG stretch fills a +/// group element from one GGM node, so the element is uniform even +/// when it is wider than 64 bits. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license. + +#ifndef LIBDPF_INCLUDE_DPF_CMP_GROUP_HPP__ +#define LIBDPF_INCLUDE_DPF_CMP_GROUP_HPP__ + +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" + +#include "dpf/twiddle.hpp" +#include "dpf/utils.hpp" +#include "dpf/wildcard.hpp" + +namespace dpf +{ +namespace detail +{ + +struct group_elem +{ + static constexpr std::size_t cap = 4; + uint64_t limb[cap]{}; + std::uint16_t lane_bits = 64; + std::uint16_t lanes = 1; + bool xor_group = false; +}; + +template +struct is_modint_tag : std::false_type {}; +template +struct is_modint_tag> + : std::bool_constant {}; + +template +struct is_bitstring_tag : std::false_type {}; +template +struct is_bitstring_tag> + : std::bool_constant {}; + +template +struct is_vec_tag : std::false_type {}; +template +struct is_vec_tag> + : std::bool_constant {}; + +template +struct has_integral_representation : std::false_type {}; +template +struct has_integral_representation().integral_representation())>> + : std::true_type {}; + +template +struct cmp_lane_of { using type = T; }; +template +struct cmp_lane_of { using type = typename T::lane_type; }; + +template +struct cmp_group_info +{ + using type = concrete_type_t>; + static constexpr bool is_vec = is_vec_tag::value; + using lane = typename cmp_lane_of::type; + static constexpr bool lane_xor = + utils::is_xor_wrapper_v || is_bitstring_tag::value; + static constexpr std::size_t lanes = []() constexpr { + if constexpr (is_vec) + return type::lane_count; + else + return std::size_t{1}; + }(); + static constexpr std::size_t lane_bits = utils::bitlength_of_v; + static constexpr std::size_t total_bits = lanes * lane_bits; + /// @brief `uint64_t` ring, including a fixed-point value whose raw word fits. + static constexpr bool narrow_ring = + !is_vec && !lane_xor && lane_bits <= 64; + static constexpr bool custom = !narrow_ring; + + static_assert(!custom || total_bits <= 256, + "comparison payload exceeds 256 bits"); + static_assert(!custom || total_bits > 0, + "comparison payload has no bits"); +}; + +template +HEDLEY_ALWAYS_INLINE +group_elem group_layout() +{ + using info = cmp_group_info; + group_elem g; + g.lane_bits = static_cast(info::lane_bits); + g.lanes = static_cast(info::lanes); + g.xor_group = info::lane_xor; + return g; +} + +HEDLEY_ALWAYS_INLINE +group_elem group_zero(const group_elem & layout) +{ + group_elem g; + g.lane_bits = layout.lane_bits; + g.lanes = layout.lanes; + g.xor_group = layout.xor_group; + return g; +} + +inline bool bit_at(const uint64_t limb[4], std::size_t bit) noexcept +{ + return ((limb[bit / 64u] >> (bit % 64u)) & 1ull) != 0; +} + +inline void set_bit(uint64_t limb[4], std::size_t bit, bool on) noexcept +{ + const std::size_t i = bit / 64u; + const uint64_t m = 1ull << (bit % 64u); + if (on) + limb[i] |= m; + else + limb[i] &= ~m; +} + +inline void mask_limbs(uint64_t limb[4], std::size_t bits) noexcept +{ + if (bits >= 256) + return; + for (std::size_t b = bits; b < 256; ++b) + set_bit(limb, b, false); +} + +inline void read_lane(const group_elem & g, std::size_t lane, uint64_t out[4]) noexcept +{ + std::memset(out, 0, 4 * sizeof(uint64_t)); + const std::size_t base = lane * g.lane_bits; + for (std::size_t b = 0; b < g.lane_bits; ++b) + set_bit(out, b, bit_at(g.limb, base + b)); +} + +inline void write_lane(group_elem & g, std::size_t lane, const uint64_t in[4]) noexcept +{ + const std::size_t base = lane * g.lane_bits; + for (std::size_t b = 0; b < g.lane_bits; ++b) + set_bit(g.limb, base + b, bit_at(in, b)); +} + +inline void add_lane(uint64_t a[4], const uint64_t b[4], std::size_t bits) noexcept +{ + unsigned carry = 0; + const std::size_t n = (bits + 63u) / 64u; + for (std::size_t i = 0; i < n; ++i) + { + const unsigned __int128 sum = + static_cast(a[i]) + b[i] + carry; + a[i] = static_cast(sum); + carry = static_cast(sum >> 64); + } + mask_limbs(a, bits); +} + +inline void neg_lane(uint64_t a[4], std::size_t bits) noexcept +{ + uint64_t one[4] = {1, 0, 0, 0}; + for (std::size_t i = 0; i < 4; ++i) + a[i] = ~a[i]; + mask_limbs(a, bits); + add_lane(a, one, bits); +} + +/// @brief Bit-serial product. Used when a wildcard coefficient scales δ, and when +/// interval containment multiplies δ by a small public integer. +/// @param a the `a` +/// @param b the `b` +/// @param bits the packed bits +/// @param out the output buffer +inline void mul_lane(const uint64_t a[4], const uint64_t b[4], std::size_t bits, + uint64_t out[4]) noexcept +{ + std::memset(out, 0, 4 * sizeof(uint64_t)); + for (std::size_t bit = 0; bit < bits; ++bit) + { + if (!bit_at(b, bit)) + continue; + uint64_t shifted[4]{}; + for (std::size_t s = 0; s < bits; ++s) + { + if (s + bit < bits && bit_at(a, s)) + set_bit(shifted, s + bit, true); + } + add_lane(out, shifted, bits); + } +} + +inline group_elem group_apply(const group_elem & a, const group_elem & b, + void (*lane_op)(uint64_t *, const uint64_t *, std::size_t)) +{ + group_elem out = group_zero(a); + for (std::size_t i = 0; i < a.lanes; ++i) + { + uint64_t la[4]{}, lb[4]{}; + read_lane(a, i, la); + read_lane(b, i, lb); + if (a.xor_group) + { + for (std::size_t k = 0; k < 4; ++k) + la[k] ^= lb[k]; + mask_limbs(la, a.lane_bits); + } + else + { + lane_op(la, lb, a.lane_bits); + } + write_lane(out, i, la); + } + return out; +} + +inline group_elem group_add(const group_elem & a, const group_elem & b) +{ + return group_apply(a, b, add_lane); +} + +inline group_elem group_neg(const group_elem & a) +{ + group_elem out = group_zero(a); + if (a.xor_group) + return a; + for (std::size_t i = 0; i < a.lanes; ++i) + { + uint64_t la[4]{}; + read_lane(a, i, la); + neg_lane(la, a.lane_bits); + write_lane(out, i, la); + } + return out; +} + +inline group_elem group_sub(const group_elem & a, const group_elem & b) +{ + if (a.xor_group) + return group_add(a, b); + return group_add(a, group_neg(b)); +} + +inline group_elem group_mul(const group_elem & a, const group_elem & b) +{ + group_elem out = group_zero(a); + for (std::size_t i = 0; i < a.lanes; ++i) + { + uint64_t la[4]{}, lb[4]{}, lc[4]{}; + read_lane(a, i, la); + read_lane(b, i, lb); + if (a.xor_group) + { + for (std::size_t k = 0; k < 4; ++k) + lc[k] = la[k] & lb[k]; + mask_limbs(lc, a.lane_bits); + } + else + { + mul_lane(la, lb, a.lane_bits, lc); + } + write_lane(out, i, lc); + } + return out; +} + +inline group_elem group_sgn(bool t, const group_elem & a) +{ + return t ? group_neg(a) : a; +} + +/// @brief Multiplicative identity: `1` in each additive lane, all-ones in an XOR lane. +/// @param layout the `layout` +/// @return Multiplicative identity: `1` in each additive lane, all-ones in an XOR lane +inline group_elem group_one(const group_elem & layout) +{ + group_elem g = group_zero(layout); + for (std::size_t i = 0; i < g.lanes; ++i) + { + uint64_t lane[4]{}; + if (g.xor_group) + { + for (std::size_t b = 0; b < g.lane_bits; ++b) + set_bit(lane, b, true); + } + else + { + lane[0] = 1; + } + write_lane(g, i, lane); + } + return g; +} + +/// @brief Integer `s` in every lane. Negative `s` is the group negation of `|s|`. +/// @param s the `s` +/// @param layout the `layout` +/// @return Integer `s` in every lane +inline group_elem group_scalar(int s, const group_elem & layout) +{ + if (layout.xor_group) + { + if ((s & 1) == 0) + return group_zero(layout); + return group_one(layout); + } + const bool neg = s < 0; + const auto mag = static_cast(neg ? -s : s); + group_elem g = group_zero(layout); + for (std::size_t i = 0; i < g.lanes; ++i) + { + uint64_t lane[4] = {mag, 0, 0, 0}; + mask_limbs(lane, g.lane_bits); + if (neg) + neg_lane(lane, g.lane_bits); + write_lane(g, i, lane); + } + return g; +} + +inline group_elem group_from_bytes(const unsigned char * bytes, std::size_t nbytes, + const group_elem & layout) +{ + group_elem g = group_zero(layout); + const std::size_t need = (static_cast(layout.lanes) * layout.lane_bits + + 7u) / 8u; + if (nbytes < need) + throw std::invalid_argument("comparison group stretch was short"); + std::size_t bit = 0; + for (std::size_t lane = 0; lane < layout.lanes; ++lane) + { + uint64_t raw[4]{}; + for (std::size_t b = 0; b < layout.lane_bits; ++b, ++bit) + { + const unsigned char byte = bytes[bit / 8u]; + const bool on = ((byte >> (bit % 8u)) & 1u) != 0; + set_bit(raw, b, on); + } + write_lane(g, lane, raw); + } + return g; +} + +template +group_elem group_from_node(Node node, const group_elem & layout) +{ + auto seed = dpf::unset_lo_2bits(node); + auto kids = PRG::eval01(seed); + unsigned char bytes[64]{}; + constexpr std::size_t nb = sizeof(kids[0]); + static_assert(nb <= 32, "comparison stretch expects a 128- or 256-bit block"); + std::memcpy(bytes, &kids[0], nb); + std::memcpy(bytes + nb, &kids[1], nb); + return group_from_bytes(bytes, nb * 2, layout); +} + +template +Word group_to_word(const group_elem & g) +{ + Word w{}; + static_assert(sizeof(Word) <= sizeof(g.limb), + "comparison word is wider than 256 bits"); + std::memcpy(&w, g.limb, sizeof(Word)); + return w; +} + +template +group_elem group_from_word(const Word & w, const group_elem & layout) +{ + group_elem g = group_zero(layout); + std::memcpy(g.limb, &w, sizeof(Word) < sizeof(g.limb) ? sizeof(Word) : sizeof(g.limb)); + mask_limbs(g.limb, static_cast(layout.lanes) * layout.lane_bits); + return g; +} + +template +void store_raw_integer(group_elem & g, std::size_t lane, const T & value) +{ + uint64_t tmp[4]{}; + if constexpr (has_integral_representation::value) + { + auto raw = value.integral_representation(); + std::memcpy(tmp, &raw, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + } + else if constexpr (is_modint_tag::value) + { + auto raw = static_cast(value); + std::memcpy(tmp, &raw, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + } + else if constexpr (is_bitstring_tag::value) + { + auto raw = utils::to_integral_type{}(value); + std::memcpy(tmp, &raw, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + } + else if constexpr (utils::is_xor_wrapper_v) + { + auto raw = value.data(); + std::memcpy(tmp, &raw, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + } + else + { + static_assert(std::is_trivially_copyable_v, + "comparison payload must be trivially copyable"); + static_assert(sizeof(T) <= sizeof(tmp), + "comparison payload exceeds 256 bits"); + std::memcpy(tmp, &value, sizeof(T)); + } + mask_limbs(tmp, g.lane_bits); + write_lane(g, lane, tmp); +} + +template +group_elem group_from_beta(const Beta & value) +{ + using C = std::decay_t; + group_elem g = group_layout(); + if constexpr (is_vec_tag::value) + { + for (std::size_t i = 0; i < C::lane_count; ++i) + { + auto lane = group_from_beta(value.lanes[i]); + uint64_t raw[4]{}; + read_lane(lane, 0, raw); + write_lane(g, i, raw); + } + } + else + { + store_raw_integer(g, 0, value); + } + return g; +} + +template +Beta group_to_beta(const group_elem & g) +{ + using C = std::decay_t; + if constexpr (is_vec_tag::value) + { + C out{}; + for (std::size_t i = 0; i < C::lane_count; ++i) + { + group_elem lane = group_zero(g); + lane.lanes = 1; + lane.lane_bits = g.lane_bits; + lane.xor_group = g.xor_group; + uint64_t raw[4]{}; + read_lane(g, i, raw); + write_lane(lane, 0, raw); + out.lanes[i] = group_to_beta(lane); + } + return out; + } + else + { + uint64_t tmp[4]{}; + read_lane(g, 0, tmp); + if constexpr (has_integral_representation::value) + { + using integral = typename C::integral_type; + integral raw{}; + std::memcpy(&raw, tmp, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + return C::from_raw(raw); + } + else if constexpr (is_modint_tag::value) + { + using integral = typename C::integral_type; + integral raw{}; + std::memcpy(&raw, tmp, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + return C{raw}; + } + else if constexpr (is_bitstring_tag::value) + { + using integral = typename utils::make_from_integral_value::integral_type; + integral raw{}; + std::memcpy(&raw, tmp, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + return utils::make_from_integral_value{}(raw); + } + else if constexpr (utils::is_xor_wrapper_v) + { + using raw_type = typename C::value_type; + raw_type raw{}; + std::memcpy(&raw, tmp, sizeof(raw) < sizeof(tmp) ? sizeof(raw) : sizeof(tmp)); + return C{raw}; + } + else + { + C raw{}; + std::memcpy(&raw, tmp, sizeof(C) < sizeof(tmp) ? sizeof(C) : sizeof(tmp)); + return raw; + } + } +} + +template +group_elem group_value_cw(const typename PRG::block_type & c0L, + const typename PRG::block_type & c0R, + const typename PRG::block_type & c1L, + const typename PRG::block_type & c1R, + uint8_t t0, uint8_t t1, int ai, group_elem & Va, const group_elem & beta) +{ + (void)t0; + const group_elem v0L = group_from_node(c0L, Va); + const group_elem v0R = group_from_node(c0R, Va); + const group_elem v1L = group_from_node(c1L, Va); + const group_elem v1R = group_from_node(c1R, Va); + const group_elem & v0K = ai == 0 ? v0L : v0R; + const group_elem & v1K = ai == 0 ? v1L : v1R; + const group_elem & v0Lo = ai == 0 ? v0R : v0L; + const group_elem & v1Lo = ai == 0 ? v1R : v1L; + group_elem vcw = group_sgn(t1 != 0, + group_add(group_add(v1Lo, group_neg(v0Lo)), group_neg(Va))); + // Lose-left plants β. A planted recipe passes the plant in `beta` for both directions. + if (ai == 1) + vcw = group_add(vcw, group_sgn(t1 != 0, beta)); + Va = group_add(group_add(group_add(Va, group_neg(v1K)), v0K), + group_sgn(t1 != 0, vcw)); + return vcw; +} + +template +group_elem group_final_cw(const typename PRG::block_type & s0, + const typename PRG::block_type & s1, uint8_t t1, const group_elem & Va, + const group_elem & on_path) +{ + const group_elem c0 = group_from_node(s0, Va); + const group_elem c1 = group_from_node(s1, Va); + return group_sgn(t1 != 0, + group_add(group_add(group_add(c1, group_neg(c0)), group_neg(Va)), on_path)); +} + +} // namespace detail +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_CMP_GROUP_HPP__ diff --git a/include/dpf/constrained_cmp.hpp b/include/dpf/constrained_cmp.hpp new file mode 100644 index 0000000..6a78494 --- /dev/null +++ b/include/dpf/constrained_cmp.hpp @@ -0,0 +1,110 @@ +/// @file dpf/constrained_cmp.hpp +/// @brief Constrained integer comparison Π_CCMP (NDSS 2025 Alg. 1). +/// @details Given two positive integers that differ by exactly one, +/// `1{x0 < x1}` is computed with a single AND on two derived bits. +/// Local joint simulation opens the AND clearly; an MPC backend would +/// replace that open with the existing Beaver AND tape. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_CONSTRAINED_CMP_HPP__ +#define LIBDPF_INCLUDE_DPF_CONSTRAINED_CMP_HPP__ + +#include +#include + +#include "hedley/hedley.h" + +namespace dpf +{ +namespace detail +{ + +/// @brief Last two bits of `x`: high = bit 1, low = bit 0. +/// @param x the `x` +/// @return Last two bits of `x`: high = bit 1, low = bit 0 +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +constexpr uint8_t ccmp_lo_bit(std::uint64_t x) noexcept +{ + return static_cast(x & 1u); +} + +/// @brief Bit 1 of `x`. +/// @param x the integer +/// @return `(x >> 1) & 1` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +constexpr uint8_t ccmp_hi_bit(std::uint64_t x) noexcept +{ + return static_cast((x >> 1) & 1u); +} + +/// @brief Party `b`'s local share inputs for the AND: `z0 = h`, `z1 = h ⊕ l ⊕ b`. +/// @param x the integer whose low two bits are split +/// @param party party index, `0` or `1` +/// @param z0 first AND input, `h` +/// @param z1 second AND input, `h ⊕ l ⊕ party` +/// @param l bit 0 of `x` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +constexpr void ccmp_party_terms(std::uint64_t x, uint8_t party, + uint8_t & z0, uint8_t & z1, uint8_t & l) noexcept +{ + const uint8_t h = ccmp_hi_bit(x); + l = ccmp_lo_bit(x); + z0 = h; + z1 = static_cast(h ^ l ^ (party & 1u)); +} + +/// @brief Opened result of Π_CCMP when both inputs are known (local joint sim). +/// @details Precondition: `|x0 - x1| = 1`. +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @return Opened result of Π_CCMP when both inputs are known (local joint sim) +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +constexpr uint8_t local_ccmp(std::uint64_t x0, std::uint64_t x1) noexcept +{ + uint8_t z00 = 0, z01 = 0, l0 = 0; + uint8_t z10 = 0, z11 = 0, l1 = 0; + ccmp_party_terms(x0, 0, z00, z01, l0); + ccmp_party_terms(x1, 1, z10, z11, l1); + const uint8_t z0 = static_cast(z00 ^ z10); + const uint8_t z1 = static_cast(z01 ^ z11); + const uint8_t t = static_cast(z0 & z1); + // Party shares: y0 = t0, y1 = t1 ⊕ (l1 ∧ 1). Opened y = t ⊕ l1. + return static_cast(t ^ l1); +} + +/// @brief Same as `local_ccmp` for any unsigned or enum-convertible integer. +/// @tparam T0 integral type of the first operand +/// @tparam T1 integral type of the second operand +/// @param x0 the first integer +/// @param x1 the second integer +/// @return Same as `local_ccmp` for any unsigned or enum-convertible integer +template +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +constexpr uint8_t local_ccmp_int(T0 x0, T1 x1) noexcept +{ + static_assert(std::is_integral_v && std::is_integral_v, + "local_ccmp_int: integral operands"); + return local_ccmp(static_cast(x0), + static_cast(x1)); +} + +} // namespace detail + +/// @brief Constrained comparison: `1{x0 < x1}` when `|x0 − x1| = 1`. +using detail::local_ccmp; +using detail::local_ccmp_int; + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_CONSTRAINED_CMP_HPP__ diff --git a/include/dpf/dcf.hpp b/include/dpf/dcf.hpp index 9aae0b5..6907d9f 100644 --- a/include/dpf/dcf.hpp +++ b/include/dpf/dcf.hpp @@ -22,6 +22,7 @@ #include "dpf/bit.hpp" #include "dpf/xor_wrapper.hpp" #include "dpf/twiddle.hpp" +#include "dpf/cmp_group.hpp" namespace dpf { @@ -39,8 +40,8 @@ template inline constexpr bool no_ic_pack_v = (!is_ic_pack>::value && ...); -/// Comparison kind for the optional DCF channel on a key. -/// `lt`/`leq`/`gt`/`geq` are the comparison predicates. The later kinds are +/// @brief Comparison kind for the optional DCF channel on a key. +/// @details `lt`/`leq`/`gt`/`geq` are the comparison predicates. The later kinds are /// path paints: one constant on each sibling subtree of the secret point, /// evaluated by the same value-correction walk. enum class cmp_kind : uint8_t @@ -58,7 +59,9 @@ enum class cmp_kind : uint8_t paint = 10 // caller-supplied unit plant }; -/// True for the path-paint kinds. Comparisons stay `lt`/`leq`/`gt`/`geq`. +/// @brief True for the path-paint kinds. Comparisons stay `lt`/`leq`/`gt`/`geq`. +/// @param kind the comparison or paint kind +/// @return True for the path-paint kinds HEDLEY_NO_THROW inline constexpr bool is_paint_kind(cmp_kind kind) noexcept { @@ -77,7 +80,7 @@ inline constexpr bool is_paint_kind(cmp_kind kind) noexcept } } -/// Unit plant for `path_paint`. `prefix` is the in-lane matched prefix. +/// @brief Unit plant for `path_paint`. `prefix` is the in-lane matched prefix. using paint_callback = uint64_t (*)(std::size_t matched, uint64_t prefix, bool leaf, const void * ctx); @@ -119,6 +122,16 @@ uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false, return (static_cast(if_true) ^ static_cast(if_false)) & mask; } + else if constexpr (detail::has_integral_representation::value) + { + using raw_type = typename Beta::integral_type; + using unsigned_type = utils::make_unsigned_t; + const auto t = static_cast( + static_cast(if_true.integral_representation())); + const auto f = static_cast( + static_cast(if_false.integral_representation())); + return (t - f) & mask; + } else { return (static_cast(if_true) @@ -132,6 +145,13 @@ uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept { if constexpr (std::is_same_v) return (static_cast(beta) ? 1ULL : 0ULL) & mask; + else if constexpr (detail::has_integral_representation::value) + { + using raw_type = typename Beta::integral_type; + using unsigned_type = utils::make_unsigned_t; + return static_cast(static_cast( + beta.integral_representation())) & mask; + } else return static_cast(beta) & mask; } @@ -154,6 +174,8 @@ Beta u64_to_beta(uint64_t v) noexcept { if constexpr (std::is_same_v) return dpf::bit{static_cast(v & 1u)}; + else if constexpr (detail::has_integral_representation::value) + return Beta::from_raw(static_cast(v)); else return static_cast(v); } @@ -186,7 +208,10 @@ constexpr uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept return t1 ? neg_m(x, mask) : x; } -/// Convert a GGM node to a group element (low 64 bits, control bits cleared). +/// @brief Convert a GGM node to a group element (low 64 bits, control bits cleared). +/// @param n the `n` +/// @param mask the bit mask +/// @return the returned `uint64_t` HEDLEY_ALWAYS_INLINE uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept { @@ -194,11 +219,15 @@ uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept simde_mm_cvtsi128_si64(dpf::unset_lo_2bits(n))) & mask; } -/// Draw the group-width blind `r` used to split the `cmp_addend` share. -/// `sample` yields one interior block; only `popcount(mask)` live bits are +/// @brief Draw the group-width blind `r` used to split the `cmp_addend` share. +/// @details `sample` yields one interior block; only `popcount(mask)` live bits are /// kept, so the blind (and thus the addend share) never needs a full padded /// `uint64_t` on the wire. Dealer and Doerner–Shelat gen call this with the /// same block source so their keys stay byte-identical (matched tapes). +/// @tparam BlockSampler block sampler +/// @param mask the bit mask +/// @param sample the `sample` +/// @return the returned `uint64_t` template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -207,8 +236,19 @@ uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept return convert_node(dpf::unset_lo_2bits(sample()), mask); } -/// One level of value CW on GGM children. Updates running `Va`. -/// `ai` is the keep-path bit of the (effective) threshold. +/// @brief One level of value CW on GGM children. Updates running `Va`. +/// @details `ai` is the keep-path bit of the (effective) threshold. +/// @param c0L the `c0L` +/// @param c0R the `c0R` +/// @param c1L the `c1L` +/// @param c1R the `c1R` +/// @param t0 the `t0` +/// @param t1 the `t1` +/// @param ai the `ai` +/// @param Va the `Va` +/// @param beta the payload +/// @param mask the bit mask +/// @return One level of value CW on GGM children HEDLEY_NO_THROW inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R, simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai, @@ -239,8 +279,20 @@ inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R, return vcw; } -/// Same recurrence as `make_value_cw`, planting `plant` on the lose child +/// @brief Same recurrence as `make_value_cw`, planting `plant` on the lose child /// in both directions. `plant == 0` leaves the correction unchanged. +/// @param c0L the `c0L` +/// @param c0R the `c0R` +/// @param c1L the `c1L` +/// @param c1R the `c1R` +/// @param t0 the `t0` +/// @param t1 the `t1` +/// @param ai the `ai` +/// @param Va the `Va` +/// @param plant the unit plant +/// @param mask the bit mask +/// @return Same recurrence as `make_value_cw`, planting `plant` on the lose child in both +/// directions HEDLEY_NO_THROW inline uint64_t make_value_cw_planted(simde__m128i c0L, simde__m128i c0R, simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai, @@ -280,7 +332,11 @@ inline unsigned __int128 paint_lane_mask(std::size_t nbits) noexcept return (u128{1} << nbits) - 1; } -/// High `d` bits of an `nbits`-wide lane, in that lane's own positions. +/// @brief High `d` bits of an `nbits`-wide lane, in that lane's own positions. +/// @param alpha the secret input point +/// @param nbits the width in bits +/// @param d the `d` +/// @return High `d` bits of an `nbits`-wide lane, in that lane's own positions HEDLEY_NO_THROW inline unsigned __int128 paint_high_bits(unsigned __int128 alpha, std::size_t nbits, std::size_t d) noexcept @@ -306,9 +362,18 @@ inline unsigned __int128 paint_low_aligned(unsigned __int128 alpha, return alpha >> (nbits - d); } -/// Unit (β = 1) lose-subtree or leaf plant. The caller scales by δ. -/// `matched` is the number of leading bits already shared with α. A lose +/// @brief Unit (β = 1) lose-subtree or leaf plant. The caller scales by δ. +/// @details `matched` is the number of leading bits already shared with α. A lose /// subtree at that depth reconstructs to this value; `leaf` is the full match. +/// @param kind the comparison or paint kind +/// @param matched the number of leading bits shared with the secret point +/// @param alpha the secret input point +/// @param nbits the width in bits +/// @param length_bits the bits used to store the prefix length +/// @param leaf the leaf value +/// @param fn the `fn` +/// @param ctx the `ctx` +/// @return Unit (β = 1) lose-subtree or leaf plant inline uint64_t paint_unit(cmp_kind kind, std::size_t matched, unsigned __int128 alpha, std::size_t nbits, std::size_t length_bits, bool leaf, paint_callback fn, const void * ctx) @@ -357,8 +422,15 @@ inline uint64_t scale_plant(uint64_t unit, uint64_t scale, uint64_t mask) noexce return (unit * scale) & mask; } -/// Final leaf value CW. `on_path` is the payload reconstructed when the query +/// @brief Final leaf value CW. `on_path` is the payload reconstructed when the query /// stays on α's path through all levels (0 for strict lt/geq; β for leq/gt). +/// @param s0 the `s0` +/// @param s1 the `s1` +/// @param t1 the `t1` +/// @param Va the `Va` +/// @param mask the bit mask +/// @param on_path the value reconstructed on the secret path +/// @return Final leaf value CW HEDLEY_NO_THROW inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1, uint64_t Va, uint64_t mask, uint64_t on_path = 0) noexcept @@ -371,8 +443,8 @@ inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1, } // namespace dcf_impl -/// Comparison metadata on an incremental key (value CWs live on the key). -/// Payload δ = if_true − if_false is dealer-known and baked into `value_cw` / +/// @brief Comparison metadata on an incremental key (value CWs live on the key). +/// @details Payload δ = if_true − if_false is dealer-known and baked into `value_cw` / /// `cw_last` only — never stored clear on the key (traditional DPF hiding). /// The second output value (`if_false`) is held as a per-party additive share /// on the key (`cmp_addend`), not as a public constant. @@ -393,7 +465,7 @@ struct cmp_meta bool empty() const noexcept { return !active; } }; -/// Backward-compatible alias while call sites migrate. +/// @brief Backward-compatible alias while call sites migrate. using cmp_channel = cmp_meta; } // namespace detail @@ -591,8 +663,13 @@ inline auto break_bit_at(Beta t = Beta{1}, return paint_at_pack(std::move(t), std::move(f)); } -/// Low `LengthBits` hold the common-prefix length. Above them sits the +/// @brief Low `LengthBits` hold the common-prefix length. Above them sits the /// matched prefix packed into the low bits of the lane (`α >> (N − d)`). +/// @tparam LengthBits length bits +/// @tparam Beta payload type +/// @param t the `t` +/// @param f the `f` +/// @return Low `LengthBits` hold the common-prefix length template inline auto prefix_with_length(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) @@ -608,9 +685,11 @@ inline auto prefix_with_length_at(Beta t = Beta{1}, std::move(t), std::move(f)); } -/// Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1 +/// @brief Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1 /// value of that sibling subtree (`leaf` is the full match, `matched == N`). -/// The result is scaled by `if_true − if_false` like the canned recipes. +/// @details The result is scaled by `if_true − if_false` like the canned recipes. +/// @tparam Beta payload type +/// @tparam Fn fn template struct paint_fn_pack { @@ -667,8 +746,9 @@ inline auto path_paint_at(Fn fn, Beta t, std::move(t), std::move(f), std::move(fn)); } -/// Incremental comparison: the same predicate, correct at every prefix length. -/// Evaluate the full point with `cmp`, and a prefix with `cmp_prefix`. +/// @brief Incremental comparison: the same predicate, correct at every prefix length. +/// @details Evaluate the full point with `cmp`, and a prefix with `cmp_prefix`. +/// @tparam Spec comparison or interval specification template struct idcf_pack { diff --git a/include/dpf/doerner_shelat.hpp b/include/dpf/doerner_shelat.hpp index bdfd243..4878a20 100644 --- a/include/dpf/doerner_shelat.hpp +++ b/include/dpf/doerner_shelat.hpp @@ -25,11 +25,12 @@ #include "dpf/dpf_key.hpp" #include "dpf/random.hpp" #include "dpf/dcf.hpp" +#include "dpf/constrained_cmp.hpp" namespace dpf { -/// Tag: Doerner–Shelat / geneval takes additive shares of the point +/// @brief Tag: Doerner–Shelat / geneval takes additive shares of the point /// (`x0 + x1` in the input ring). Default calls take XOR shares. struct arith_input_t { @@ -37,9 +38,60 @@ struct arith_input_t inline constexpr arith_input_t arith_input{}; -/// Roots and the Beaver-pad stream for one Doerner–Shelat generation. -/// `root` is called twice, same as `make_dpf`: party 0 clears the low bit of +/// @brief Tag: payload β is additively shared (`y0 + y1`). Leaf CW is opened via +/// Π_CCMP on the on-path control bits (see `open_arith_leaf`). +/// @see `open_arith_leaf` +struct arith_output_t +{ +}; + +inline constexpr arith_output_t arith_output{}; + +/// @brief Additive (or XOR) shares of one concrete payload for dealerless leaf open. +/// @details Use as a placed value / `at<>` element when several outputs are shared. +/// @tparam T value type +template +struct arith_beta +{ + using payload_type = T; + T y0{}; + T y1{}; +}; + +template +struct is_arith_beta : std::false_type +{ +}; + +template +struct is_arith_beta> : std::true_type +{ +}; + +template +inline constexpr bool is_arith_beta_v = is_arith_beta>::value; + +namespace detail +{ +namespace incr +{ + +/// @brief `placed>::output_type` is `T` (see placement.hpp). +/// @tparam T value type +template +struct unwrap_placed_output> +{ + using type = T; +}; + +} // namespace incr +} // namespace detail + +/// @brief Roots and the Beaver-pad stream for one Doerner–Shelat generation. +/// @details `root` is called twice, same as `make_dpf`: party 0 clears the low bit of /// the first sample, party 1 sets the low bit of the second. +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol template struct ds_randomness { @@ -227,7 +279,7 @@ inline simde__m128i ds_deliver(uint8_t b_exp, simde__m128i base, simde__m128i M, return ds_xor(ds_xor(local, z.z0), z.z1); } -/// Per-level messages prepared before the CW protocol runs (blinds + pads). +/// @brief Per-level messages prepared before the CW protocol runs (blinds + pads). struct ds_level_blinds { ds_cw_pads cwp; @@ -240,7 +292,7 @@ struct ds_level_blinds uint8_t bit1; }; -/// Opened CW, advice, and AND products delivered by a `CwProtocol`. +/// @brief Opened CW, advice, and AND products delivered by a `CwProtocol`. struct ds_level_open { simde__m128i cw; @@ -250,8 +302,8 @@ struct ds_level_open uint64_t value_cw = 0; // public after open when cmp is active at this level }; -/// Running comparison-gen state shared across DS levels (Va residual). -/// When `track_coeff` is set (wildcard cmp payload), a parallel β = 1 +/// @brief Running comparison-gen state shared across DS levels (Va residual). +/// @details When `track_coeff` is set (wildcard cmp payload), a parallel β = 1 /// accumulator `Va1` is advanced alongside `Va` so the gen can stash /// `value_cw(1) − value_cw(β)` coefficients for a later `assign_cmp`. struct ds_cmp_gen_state @@ -274,8 +326,9 @@ struct ds_cmp_gen_state const void * paint_ctx = nullptr; }; -/// Local joint simulation: today's `ds_cw_outs` / `ds_open_advice` / `ds_and_open`. -/// An MPC backend would send `blinds` and return the same `ds_level_open` shape. +/// @brief Local joint simulation: today's `ds_cw_outs` / `ds_open_advice` / `ds_and_open`. +/// @details An MPC backend would send `blinds` and return the same `ds_level_open` shape. +/// @tparam PadRng pad stream for the Doerner–Shelat protocol template struct local_cw_protocol { @@ -310,7 +363,11 @@ struct local_cw_protocol return out; } - /// Open CW + advice only (AND pads stay in `blinds` for a later open). + /// @brief Open CW + advice only (AND pads stay in `blinds` for a later open). + /// @param b the `b` + /// @return the returned `std::pair` + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") HEDLEY_NO_THROW std::pair open_cw(const ds_level_blinds & b) noexcept { @@ -318,9 +375,18 @@ struct local_cw_protocol b.b0, b.b1), ds_open_advice(b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1)}; } + HEDLEY_PRAGMA(GCC diagnostic pop) - /// Open the public value CW for this level (local: clear convert+make_value_cw). - /// MPC backends open additive shares of the same word. + /// @brief Open the public value CW for this level (local: clear convert+make_value_cw). + /// @details MPC backends open additive shares of the same word. + /// @param b the `b` + /// @param adv0 the `adv0` + /// @param adv1 the `adv1` + /// @param ai the `ai` + /// @param Va the `Va` + /// @param beta the payload + /// @param mask the bit mask + /// @return the returned `uint64_t` HEDLEY_NO_THROW uint64_t open_value_cw(const ds_level_blinds & b, uint8_t adv0, uint8_t adv1, int ai, uint64_t & Va, uint64_t beta, uint64_t mask) noexcept @@ -329,7 +395,15 @@ struct local_cw_protocol adv1, ai, Va, beta, mask); } - /// Open a path-paint value CW. `plant` is the scaled lose-subtree constant. + /// @brief Open a path-paint value CW. `plant` is the scaled lose-subtree constant. + /// @param b the `b` + /// @param adv0 the `adv0` + /// @param adv1 the `adv1` + /// @param ai the `ai` + /// @param Va the `Va` + /// @param plant the unit plant + /// @param mask the bit mask + /// @return the returned `uint64_t` HEDLEY_NO_THROW uint64_t open_planted_cw(const ds_level_blinds & b, uint8_t adv0, uint8_t adv1, int ai, uint64_t & Va, uint64_t plant, uint64_t mask) noexcept @@ -345,9 +419,16 @@ struct local_cw_protocol return ds_and_open(p, M, b_recv); } - /// Open the final comparison leaf CW. Wraps `make_final_cw` so the + /// @brief Open the final comparison leaf CW. Wraps `make_final_cw` so the /// Doerner–Shelat gen does not call it directly on reconstructed seeds; /// an MPC backend would open additive shares of the same word. + /// @param s0 the `s0` + /// @param s1 the `s1` + /// @param t1 the `t1` + /// @param Va the `Va` + /// @param mask the bit mask + /// @param on_path the value reconstructed on the secret path + /// @return the returned `uint64_t` HEDLEY_NO_THROW uint64_t open_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1, uint64_t Va, uint64_t mask, uint64_t on_path) noexcept @@ -355,9 +436,13 @@ struct local_cw_protocol return dcf_impl::make_final_cw(s0, s1, t1, Va, mask, on_path); } - /// Draw the group-width `cmp_addend` blind. Local joint simulation reuses + /// @brief Draw the group-width `cmp_addend` blind. Local joint simulation reuses /// the shared root sampler so the blind matches the dealer's; an MPC /// backend would instead pull a group-width element from the pad stream. + /// @tparam BlockSampler block sampler + /// @param mask the bit mask + /// @param sample the `sample` + /// @return the returned `uint64_t` template HEDLEY_NO_THROW uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept @@ -366,14 +451,23 @@ struct local_cw_protocol std::forward(sample)); } - /// Majority of three bits (next carry of a full adder). + /// @brief Majority of three bits (next carry of a full adder). + /// @param a the `a` + /// @param b the `b` + /// @param c the `c` + /// @return Majority of three bits (next carry of a full adder) HEDLEY_NO_THROW static constexpr uint8_t majority(uint8_t a, uint8_t b, uint8_t c) noexcept { return static_cast((a & b) | (a & c) | (b & c)); } - /// One additive digit: sum bit `a XOR b XOR cin`, carry out = majority. + /// @brief One additive digit: sum bit `a XOR b XOR cin`, carry out = majority. + /// @param a the `a` + /// @param b the `b` + /// @param cin the `cin` + /// @param cout the `cout` + /// @return One additive digit: sum bit `a XOR b XOR cin`, carry out = majority HEDLEY_NO_THROW static constexpr uint8_t open_sum_bit(uint8_t a, uint8_t b, uint8_t cin, uint8_t & cout) noexcept @@ -382,9 +476,14 @@ struct local_cw_protocol return static_cast(a ^ b ^ cin); } - /// Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB + /// @brief Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB /// when the domain is signed — matching `make_dpf` on the sum. The call /// site never forms the sum; an MPC backend would open the same bits. + /// @tparam InputT input domain type + /// @param a0 the `a0` + /// @param a1 the `a1` + /// @return Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB when the domain + /// is signed — matching `make_dpf` on the sum template InputT open_arith_point(InputT a0, InputT a1) const { @@ -409,9 +508,13 @@ struct local_cw_protocol return out; } - /// Encode shares for the XOR-style CW walk. XOR mode flips party 0's MSB + /// @brief Encode shares for the XOR-style CW walk. XOR mode flips party 0's MSB /// (linear over XOR). Arithmetic mode opens the sum (carry + signed MSB) /// and returns `(alpha, 0)` so the walk matches `make_dpf(alpha)`. + /// @tparam InputT input domain type + /// @param x0 the `x0` + /// @param x1 the `x1` + /// @param arith the `arith` template void encode_walk_shares(InputT & x0, InputT & x1, bool arith) const { @@ -427,13 +530,84 @@ struct local_cw_protocol } } - /// Open a group of leaf correction words for one prefix group. In this + /// @brief Constrained comparison Π_CCMP: open `1{x0 < x1}` when `|x0−x1|=1`. + /// @param x0 the `x0` + /// @param x1 the `x1` + /// @return Constrained comparison Π_CCMP: open `1{x0 < x1}` when `|x0−x1|=1` + HEDLEY_NO_THROW + uint8_t open_ccmp(std::uint64_t x0, std::uint64_t x1) noexcept + { + (void)pads; // MPC backend would consume an AND pad here + return dpf::local_ccmp(x0, x1); + } + + /// @brief Open a public leaf CW for a shared payload. + /// @details Ring: `β = y0 + y1`; `g = CCMP(t0,t1)` selects `β − M` vs `M − β` + /// (matches `make_leaf` with `sign = t0`). Characteristic 2: `β = y0 ⊕ y1` + /// and CW = `β ⊕ M` (sign mux is a no-op under XOR). + /// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` + /// @tparam I output index + /// @tparam OutputsTuple outputs tuple + /// @tparam InteriorBlock interior block + /// @tparam OutputT output type + /// @param seed0 the `seed0` + /// @param seed1 the `seed1` + /// @param t0 the `t0` + /// @param t1 the `t1` + /// @param y0 the `y0` + /// @param y1 the `y1` + /// @param pos_base the `pos_base` +/// @param lane_x lane of the shared payload +/// @return the opened leaf correction word + template + auto open_arith_leaf(const InteriorBlock & seed0, const InteriorBlock & seed1, + uint8_t t0, uint8_t t1, OutputT y0, OutputT y1, std::size_t pos_base, + std::size_t lane_x) -> dpf::leaf_node_t + { + using output_type = OutputT; + using node_type = typename ExteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + using leaf_type = dpf::leaf_node_t; + HEDLEY_PRAGMA(GCC diagnostic pop) + + const auto M = dpf::make_leaf_mask( + seed0, seed1, pos_base); + output_type beta{}; + if constexpr (utils::has_characteristic_two_v) + { + (void)t0; + (void)t1; + beta = static_cast(y0 ^ y1); + const leaf_type naked = dpf::make_naked_leaf(lane_x, beta); + return dpf::subtract_leaf(naked, M); + } + else + { + const uint8_t g = open_ccmp(t0, t1); + beta = static_cast(y0 + y1); + const leaf_type naked = dpf::make_naked_leaf(lane_x, beta); + // CW = (−1)^{t1}(β − M): g=0 → β−M; g=1 → M−β. Matches make_leaf(sign=t0). + if (g & 1u) + return dpf::subtract_leaf(M, naked); + return dpf::subtract_leaf(naked, M); + } + } + + /// @brief Open a group of leaf correction words for one prefix group. In this /// local joint simulation both XOR shares of the point are present, so the /// point is reconstructed *inside* the protocol and handed to `leaf_fn` /// (which runs `make_leaves` for the group). The Doerner–Shelat gen never /// forms `x = x0 ^ x1` at its own call site; an MPC backend would instead /// run a per-group leaf CW exchange that never reveals `x`. After /// `encode_walk_shares`, arithmetic inputs are already `(alpha, 0)`. + /// @tparam InputT input domain type + /// @tparam LeafFn leaf fn + /// @param x0 the `x0` + /// @param x1 the `x1` + /// @param leaf_fn the `leaf_fn` template void open_leaf_group(InputT x0, InputT x1, LeafFn && leaf_fn) { @@ -441,7 +615,8 @@ struct local_cw_protocol } }; -/// Generation-side level state (seeds / home bits). Not an eval path memoizer. +/// @brief Generation-side level state (seeds / home bits). Not an eval path memoizer. +/// @tparam NodeT GGM node type template struct ds_gen_state { @@ -471,16 +646,34 @@ struct ds_gen_state const NodeT & seed1() const noexcept { return inbox[home[1]]; } }; -/// One interior level: expand, protocol open, advance both party seeds. -/// When `cmp` is non-null and active for `level`, also opens `value_cw` via +/// @brief One interior level: expand, protocol open, advance both party seeds. +/// @details When `cmp` is non-null and active for `level`, also opens `value_cw` via /// the protocol (no second PRG expand outside). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam CwProtocol correction-word protocol +/// @tparam NodeT GGM node type +/// @tparam InputT input domain type +/// @tparam MaskT mask type +/// @tparam AdviceT advice type +/// @param st the `st` +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param mask the bit mask +/// @param level the tree level +/// @param depth the tree depth +/// @param proto the `proto` +/// @param cw_out the `cw_out` +/// @param advice_out the `advice_out` +/// @param value_cw_out the `value_cw_out` +/// @param cmp the comparison specification template void ds_advance_level(ds_gen_state & st, InputT x0, InputT x1, - MaskT mask, std::size_t level, CwProtocol & proto, NodeT & cw_out, - AdviceT & advice_out, uint64_t * value_cw_out = nullptr, + MaskT mask, std::size_t level, std::size_t depth, CwProtocol & proto, + NodeT & cw_out, AdviceT & advice_out, uint64_t * value_cw_out = nullptr, ds_cmp_gen_state * cmp = nullptr) { + using tree = dpf::tree_traits; // Integral bridge so bit extraction works for `keyword` / `modint` / // signed / bitstring the same way dealer gen does via `mask & x`. // `msb_mask` is the unsigned bit pattern; a signed input must not be @@ -490,21 +683,28 @@ void ds_advance_level(ds_gen_state & st, InputT x0, InputT x1, const auto mi = to_mask(mask); const uint8_t bit0 = static_cast(!!(mi & to_int(x0))); const uint8_t bit1 = static_cast(!!(mi & to_int(x1))); + const bool is_last = tree::is_last_level(level, depth); NodeT s0 = st.seed0(); NodeT s1 = st.seed1(); - const uint8_t adv0 = static_cast( - dpf::get_lo_bit_and_clear_lo_2bits(s0)); - const uint8_t adv1 = static_cast( - dpf::get_lo_bit_and_clear_lo_2bits(s1)); - const auto c0 = InteriorPRG::eval01(s0); - const auto c1 = InteriorPRG::eval01(s1); + const uint8_t adv0 = static_cast(dpf::get_lo_bit(s0)); + const uint8_t adv1 = static_cast(dpf::get_lo_bit(s1)); + const auto c0 = tree::expand(s0, is_last); + const auto c1 = tree::expand(s1, is_last); auto blinds = proto.prepare_level(c0[0], c0[1], bit0, c1[0], c1[1], bit1); if (value_cw_out != nullptr && cmp != nullptr && cmp->active && cmp->trivial == cmp_trivial::none && level < cmp->nbits) { + // Convert uses expand_value (HT: always two-tweak); seed walk used expand. + const auto v0 = tree::expand_value(s0); + const auto v1 = tree::expand_value(s1); + auto vblinds = blinds; + vblinds.L0 = v0[0]; + vblinds.R0 = v0[1]; + vblinds.L1 = v1[0]; + vblinds.R1 = v1[1]; const int ai = static_cast( (cmp->thresh >> (cmp->nbits - 1 - level)) & 1); if (cmp->paint) @@ -514,34 +714,47 @@ void ds_advance_level(ds_gen_state & st, InputT x0, InputT x1, cmp->paint_cb, cmp->paint_ctx); const uint64_t plant = dcf_impl::scale_plant(unit, cmp->beta, cmp->mask); - *value_cw_out = proto.open_planted_cw(blinds, adv0, adv1, ai, + *value_cw_out = proto.open_planted_cw(vblinds, adv0, adv1, ai, cmp->Va, plant, cmp->mask); if (cmp->track_coeff) { const uint64_t plant1 = dcf_impl::scale_plant(unit, 1ULL, cmp->mask); - const uint64_t v1 = proto.open_planted_cw(blinds, adv0, adv1, + const uint64_t v1w = proto.open_planted_cw(vblinds, adv0, adv1, ai, cmp->Va1, plant1, cmp->mask); cmp->last_vcw_coeff = - (v1 + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask; + (v1w + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask; } } else { - *value_cw_out = proto.open_value_cw(blinds, adv0, adv1, ai, cmp->Va, + *value_cw_out = proto.open_value_cw(vblinds, adv0, adv1, ai, cmp->Va, cmp->beta, cmp->mask); if (cmp->track_coeff) { // Affine coefficient: same level with β = 1 on a parallel Va. - const uint64_t v1 = proto.open_value_cw(blinds, adv0, adv1, ai, + const uint64_t v1w = proto.open_value_cw(vblinds, adv0, adv1, ai, cmp->Va1, 1ULL, cmp->mask); cmp->last_vcw_coeff = - (v1 + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask; + (v1w + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask; } } } auto [cw, tpack] = proto.open_cw(blinds); + // Half-Tree mid levels store no advice; last level keeps BGI packing. + if constexpr (tree::is_half_tree) + { + if (!is_last) + tpack = 0; + } + else + { + // BGI: opened advice stands. + } + + // Dealer-equivalent CW for Half-Tree mid: off-path children XOR already + // matches H(s0)⊕H(s1)⊕ᾱΔ via the open. For last/BGI, open matches Gen. const uint8_t exp0 = st.home[0] == 0 ? bit0 : bit1; const uint8_t rec0 = st.home[0] == 0 ? bit1 : bit0; @@ -549,8 +762,29 @@ void ds_advance_level(ds_gen_state & st, InputT x0, InputT x1, const uint8_t rec1 = st.home[1] == 0 ? bit1 : bit0; NodeT M0, base0, M1, base1; - ds_next_terms(c0[0], c0[1], adv0, cw, tpack, M0, base0); - ds_next_terms(c1[0], c1[1], adv1, cw, tpack, M1, base1); + if constexpr (tree::is_half_tree) + { + if (!is_last) + { + // Mid: next = child[bit] ⊕ (t ? full_cw : 0). + const NodeT D0 = ds_xor(c0[0], c0[1]); + const NodeT D1 = ds_xor(c1[0], c1[1]); + M0 = D0; + base0 = (adv0 & 1u) ? ds_xor(c0[0], cw) : c0[0]; + M1 = D1; + base1 = (adv1 & 1u) ? ds_xor(c1[0], cw) : c1[0]; + } + else + { + ds_next_terms(c0[0], c0[1], adv0, cw, tpack, M0, base0); + ds_next_terms(c1[0], c1[1], adv1, cw, tpack, M1, base1); + } + } + else + { + ds_next_terms(c0[0], c0[1], adv0, cw, tpack, M0, base0); + ds_next_terms(c1[0], c1[1], adv1, cw, tpack, M1, base1); + } const NodeT nxt0 = ds_deliver(exp0, base0, M0, proto.open_and(blinds.and0, M0, rec0)); const NodeT nxt1 = @@ -598,9 +832,124 @@ template +auto make_dpf_doerner_shelat_impl(bool arith, bool arith_out, InputT x0, + InputT x1, RootSampler & root_sampler, CwProtocol & proto, OutputT y0, + OutputT y1 = OutputT{}) +{ + static_assert(!dpf::is_wildcard_v, + "Doerner–Shelat gen takes shares of a concrete point"); + static_assert(!dpf::is_secret_share_v, + "Doerner–Shelat: pass additive_share of xor_wrapper, or raw shares"); + static_assert(!dpf::is_wildcard_v, + "arith_output / classic DS leaf expects a concrete payload"); + static_assert(sizeof(typename InteriorPRG::block_type) == sizeof(simde__m128i), + "Doerner–Shelat gen uses the AES-block interior node"); + + using dpf_type = utils::dpf_type_t; + using node = typename dpf_type::interior_node; + using input_type = typename dpf_type::input_type; + using leaf_tuple = typename dpf_type::leaf_tuple; + using beaver_tuple = typename dpf_type::beaver_tuple; + using outputs_tuple = std::tuple; + constexpr auto depth = dpf_type::depth; + + proto.encode_walk_shares(x0, x1, arith); + + using tree = dpf::tree_traits; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + node roots[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(roots, [&]() -> node { + return static_cast(root_sampler()); + }); + const node root0 = roots[0]; + const node root1 = roots[1]; + + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + ds_gen_state st; + HEDLEY_PRAGMA(GCC diagnostic pop) + st.init(root0, root1); + + typename dpf_type::correction_words_array correction_words{}; + typename dpf_type::correction_advice_array correction_advice{}; + + auto mask = dpf_type::msb_mask; + for (std::size_t level = 0; level < depth; ++level, mask >>= 1) + { + ds_advance_level(st, x0, x1, mask, level, depth, proto, + correction_words[level], correction_advice[level]); + } + + const node parent0 = st.seed0(); + const node parent1 = st.seed1(); + const bool sign0 = dpf::get_lo_bit(parent0); + const uint8_t t0 = static_cast(sign0); + const uint8_t t1 = static_cast(dpf::get_lo_bit(parent1)); + + input_type x = utils::xor_input_shares(x0, x1); + leaf_tuple leaves0{}; + leaf_tuple leaves1{}; + beaver_tuple beavers0{}; + beaver_tuple beavers1{}; + if (arith_out) + { + constexpr auto to_int = utils::to_integral_type{}; + const std::size_t lane = static_cast(to_int(x)); + auto cw = proto.template open_arith_leaf( + dpf::unset_lo_2bits(parent0), dpf::unset_lo_2bits(parent1), t0, t1, + y0, y1, std::size_t{0}, lane); + std::get<0>(leaves0) = cw; + std::get<0>(leaves1) = cw; + } + else + { + auto built = dpf::make_leaves(x, + dpf::unset_lo_2bits(parent0), dpf::unset_lo_2bits(parent1), sign0, + std::size_t{0}, y0); + leaves0 = std::move(built.first.first); + beavers0 = std::move(built.first.second); + leaves1 = std::move(built.second.first); + beavers1 = std::move(built.second.second); + (void)y1; + } + + input_type off0{}; + input_type off1{}; + return dpf::make_party_key_pair( + dpf_type{root0, correction_words, correction_advice, + leaves0, beavers0, off0}, + dpf_type{root1, correction_words, correction_advice, + leaves1, beavers1, off1}); +} + +/// @brief Plaintext-β multi-output classic path (unchanged). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam CwProtocol correction-word protocol +/// @param arith the `arith` +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param root_sampler the `root_sampler` +/// @param proto the `proto` +/// @param y the `y` +/// @param ys the `ys` +/// @return Plaintext-β multi-output classic path (unchanged) +template 0)>> auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1, RootSampler & root_sampler, CwProtocol & proto, OutputT && y, OutputTs && ...ys) @@ -620,10 +969,21 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1, proto.encode_walk_shares(x0, x1, arith); - const node root0 = dpf::unset_lo_bit(static_cast(root_sampler())); - const node root1 = dpf::set_lo_bit(static_cast(root_sampler())); + using tree = dpf::tree_traits; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + node roots[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(roots, [&]() -> node { + return static_cast(root_sampler()); + }); + const node root0 = roots[0]; + const node root1 = roots[1]; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") ds_gen_state st; + HEDLEY_PRAGMA(GCC diagnostic pop) st.init(root0, root1); typename dpf_type::correction_words_array correction_words{}; @@ -632,7 +992,7 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1, auto mask = dpf_type::msb_mask; for (std::size_t level = 0; level < depth; ++level, mask >>= 1) { - ds_advance_level(st, x0, x1, mask, level, proto, + ds_advance_level(st, x0, x1, mask, level, depth, proto, correction_words[level], correction_advice[level]); } @@ -654,9 +1014,37 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1, built.second.first, built.second.second, off1}); } +/// @brief Single-output plaintext β (disambiguates from arith_out overload). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam CwProtocol correction-word protocol +/// @param arith the `arith` +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param root_sampler the `root_sampler` +/// @param proto the `proto` +/// @param y the `y` +/// @return Single-output plaintext β (disambiguates from arith_out overload) +template +auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1, + RootSampler & root_sampler, CwProtocol & proto, OutputT && y) +{ + return make_dpf_doerner_shelat_impl(arith, false, + std::move(x0), std::move(x1), root_sampler, proto, + std::forward(y), OutputT{}); +} + } // namespace detail -/// Local CW protocol (pads cancel; same keys as dealer when roots match). +/// @brief Local CW protocol (pads cancel; same keys as dealer when roots match). template using local_cw_protocol = detail::local_cw_protocol; diff --git a/include/dpf/dpf_key.hpp b/include/dpf/dpf_key.hpp index 8f4d7e1..23bbf21 100644 --- a/include/dpf/dpf_key.hpp +++ b/include/dpf/dpf_key.hpp @@ -1,6 +1,5 @@ /// @file dpf/dpf_key.hpp -/// @brief -/// @details +/// @brief The DPF key, its correction words, and interior traversal. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -19,6 +18,7 @@ #include #include "dpf/prg_aes.hpp" +#include "dpf/tree_traits.hpp" #include "dpf/wildcard.hpp" #include "dpf/twiddle.hpp" #include "dpf/leaf_node.hpp" @@ -26,6 +26,7 @@ #include "dpf/leaf_wrapper.hpp" #include "dpf/emplace.hpp" #include "dpf/placement.hpp" +#include "dpf/verifiable.hpp" #include "dpf/dcf.hpp" namespace dpf @@ -88,16 +89,25 @@ auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys) std::forward(ys)...) }; } +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template using root_sampler_t = std::add_pointer_t; +HEDLEY_PRAGMA(GCC diagnostic pop) namespace detail { -/// Classic single-level DPF key body (all outputs bare, at full input width, +/// @brief Classic single-level DPF key body (all outputs bare, at full input width, /// equal widths, no comparison channel). `Derived` is the public `dpf_key` /// specialization that inherits this body — threaded through only so that /// `emplace`/`emplace_back` construct the public key type. +/// @tparam Derived derived +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts template ; using interior_node = typename InteriorPRG::block_type; using exterior_prg = ExteriorPRG; @@ -156,9 +167,18 @@ HEDLEY_PRAGMA(GCC diagnostic pop) static constexpr std::size_t cmp_h = 0; static constexpr std::size_t cmp_checkpoints = 0; static constexpr std::size_t cmp_tail = 0; - /// Classic keys are single-level; the unified eval surface keeps routing + /// @brief Classic keys are single-level; the unified eval surface keeps routing /// them through the classic `eval_*` fast paths (see `is_multilevel_key`). + /// @see `is_multilevel_key` static constexpr bool is_multilevel = false; + static constexpr bool is_verifiable = false; + static constexpr bool is_extractable = false; + using correction_seeds_array = std::array; + const correction_seeds_array & correction_seeds() const + { + static const correction_seeds_array empty{}; + return empty; + } private: using meta_placed_tuple = std::tuple< @@ -178,7 +198,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop) static constexpr std::size_t outputs_per_leaf_of = std::size_t{1} << lg_outputs_per_leaf_of; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using correction_words_array = std::array; + HEDLEY_PRAGMA(GCC diagnostic pop) using correction_advice_array = std::array; template @@ -267,8 +290,9 @@ HEDLEY_PRAGMA(GCC diagnostic pop) HEDLEY_ALWAYS_INLINE auto correction_word(std::size_t level, bool direction) const { - return set_lo_bit(correction_word(level), - (correction_advice_[level] >> direction) & 1); + return tree::pack_cw(correction_word(level), + correction_advice_[level], direction, + tree::is_last_level(level, depth)); } template @@ -318,58 +342,46 @@ HEDLEY_PRAGMA(GCC diagnostic pop) HEDLEY_ALWAYS_INLINE HEDLEY_PURE static auto traverse_interior(const interior_node & node, - const interior_node & cw, bool dir) noexcept + const interior_node & cw, bool dir, bool is_last = false) noexcept { - return dpf::xor_if_lo_bit( - interior_prg::eval(unset_lo_2bits(node), dir), cw, node); + return tree::traverse(node, cw, dir, is_last); } - /// Expand both children of `node` with one pipelined `eval01`. - /// Equivalent to `traverse_interior(node, cw0, 0)` and - /// `traverse_interior(node, cw1, 1)`, but the two AES-128 blocks share - /// a round loop. Full-domain interval eval uses this at almost every - /// interior parent. + /// @brief Expand both children of `node` with one pipelined expand. + /// @details Equivalent to `traverse_interior(node, cw0, 0)` and + /// `traverse_interior(node, cw1, 1)`. Full-domain interval eval uses this + /// at almost every interior parent. + /// @param node the GGM node + /// @param cw0 correction word for the left child + /// @param cw1 correction word for the right child + /// @param is_last whether this is the last interior level + /// @return both children of `node` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE static auto traverse_interior01(const interior_node & node, - const interior_node & cw0, const interior_node & cw1) noexcept + const interior_node & cw0, const interior_node & cw1, + bool is_last = false) noexcept { -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") - auto kids = interior_prg::eval01(unset_lo_2bits(node)); - return std::array{ - dpf::xor_if_lo_bit(kids[0], cw0, node), - dpf::xor_if_lo_bit(kids[1], cw1, node) - }; -HEDLEY_PRAGMA(GCC diagnostic pop) + return tree::traverse01(node, cw0, cw1, is_last); } - /// Four independent `traverse_interior01` via `InteriorPRG::eval01_x4`. - /// `left[i]` / `right[i]` are the children of `parents[i]`. + /// @brief Four independent `traverse_interior01` via traits batched expand. + /// @details `left[i]` / `right[i]` are the children of `parents[i]`. + /// @param parents the `parents` + /// @param cw0 the `cw0` + /// @param cw1 the `cw1` + /// @param left the `left` + /// @param right the `right` + /// @param is_last the `is_last` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE static void traverse_interior01_x4(const interior_node * HEDLEY_RESTRICT parents, const interior_node & cw0, const interior_node & cw1, interior_node * HEDLEY_RESTRICT left, - interior_node * HEDLEY_RESTRICT right) noexcept + interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept { -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") - alignas(interior_node) interior_node seeds[4]; - DPF_UNROLL_LOOP - for (std::size_t i = 0; i < 4; ++i) - { - seeds[i] = unset_lo_2bits(parents[i]); - } - interior_prg::eval01_x4(seeds, left, right); - DPF_UNROLL_LOOP - for (std::size_t i = 0; i < 4; ++i) - { - left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]); - right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]); - } -HEDLEY_PRAGMA(GCC diagnostic pop) + tree::traverse01_x4(parents, cw0, cw1, left, right, is_last); } template ; +HEDLEY_PRAGMA(GCC diagnostic pop) // Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β. return dpf::subtract_leaf( dpf::get_if_lo_bit(correction_word, node), make_leaf_mask_inner(unset_lo_2bits(node))); -HEDLEY_PRAGMA(GCC diagnostic pop) } template @@ -414,9 +426,12 @@ HEDLEY_PRAGMA(GCC diagnostic pop) { return std::apply([&leaf..., &beaver...](auto & ...foo) { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return std::make_tuple( dpf::leaf_wrapper, exterior_node>(leaf, beaver)... ); + HEDLEY_PRAGMA(GCC diagnostic pop) }, tmp); }, beavers); }, leaves); @@ -439,16 +454,21 @@ namespace detail namespace incr { -/// Comparison-channel storage. Value CWs, `cw_last`, and the `cmp_addend` +/// @brief Comparison-channel storage. Value CWs, `cw_last`, and the `cmp_addend` /// share are held at the comparison group width (`ValueCwWord`), not a full /// padded `uint64_t` per level: a bit comparison carries 1 byte/level, a /// `uint16_t` payload 2 bytes/level, etc. Arithmetic still runs in `uint64_t` /// (masked); the narrow word is only the on-key / on-wire representation. -/// Extra per-level δ-coefficients kept only for wildcard comparison payloads +/// @details Extra per-level δ-coefficients kept only for wildcard comparison payloads /// (empty for concrete cmp keys, so their layout is unchanged). The value CWs /// / `cw_last` are affine in the payload δ, so after keygen with δ = 0 the /// concrete values are `base[i] + coeff[i]·δ`; `assign_cmp` patches them in /// place with no tree re-walk / re-PRG. +/// @tparam Depth depth +/// @tparam ValueCwWord value cw word +/// @tparam Wild whether the payload is a wildcard +/// @tparam TailLen tail len +/// @tparam Idcf idcf template struct cmp_wild_state { }; @@ -520,6 +540,10 @@ struct cmp_storage return static_cast(value_cw_[level]); } HEDLEY_NO_THROW + value_cw_word cw_last_word() const noexcept { return cw_last_; } + HEDLEY_NO_THROW + value_cw_word cmp_addend_word() const noexcept { return cmp_addend_; } + HEDLEY_NO_THROW uint64_t cw_last() const noexcept { return static_cast(cw_last_); } HEDLEY_NO_THROW const tail_array & tail_cw() const noexcept { return tail_; } @@ -553,9 +577,11 @@ struct cmp_storage return true; } - /// Patch the (public) value CWs / `cw_last` in place for a resolved δ and + /// @brief Patch the (public) value CWs / `cw_last` in place for a resolved δ and /// install this party's `cmp_addend` share. No-op on the CWs when there is /// no wildcard coefficient table (trivial domain-edge cmp). + /// @param delta the payload difference `if_true - if_false` + /// @param addend_share the `addend_share` void assign_cmp_delta(uint64_t delta, uint64_t addend_share) { static_assert(Wild, @@ -595,6 +621,117 @@ struct cmp_storage } } + /// @brief Overwrite the public final correction and this party's addend. Used when + /// the group element does not fit in the `uint64_t` the constructor takes. + /// @param last the past-the-end element of the range + /// @param addend the additive share of the off-point payload + /// @param last_coeff the `last_coeff` + void set_scalars(value_cw_word last, value_cw_word addend, + value_cw_word last_coeff) + { + cw_last_ = last; + cmp_addend_ = addend; + if constexpr (Wild) + wild_.cw_last_coeff = last_coeff; + else + (void)last_coeff; + } + + /// @brief `base + coeff · δ` in `delta`'s group, then install `addend`. + /// @param delta the payload difference `if_true - if_false` + /// @param addend the additive share of the off-point payload + void assign_group(const detail::group_elem & delta, value_cw_word addend) + { + static_assert(Wild, + "assign_cmp on a key whose comparison payload is not a wildcard"); + if constexpr (Wild) + { + auto mix = [&](value_cw_word base_w, value_cw_word coeff_w) { + const auto base = detail::group_from_word(base_w, delta); + const auto coeff = detail::group_from_word(coeff_w, delta); + return detail::group_to_word( + detail::group_add(base, detail::group_mul(coeff, delta))); + }; + for (std::size_t i = 0; i < Depth; ++i) + value_cw_[i] = mix(value_cw_[i], wild_.value_cw_coeff[i]); + if constexpr (Blocked) + { + for (std::size_t i = 0; i < TailLen; ++i) + tail_[i] = mix(tail_[i], wild_.tail_coeff[i]); + } + cw_last_ = mix(cw_last_, wild_.cw_last_coeff); + if constexpr (Idcf) + { + for (std::size_t i = 0; i < prefix_cw_len; ++i) + prefix_cw_[i] = mix(prefix_cw_[i], wild_.prefix_cw_coeff[i]); + } + cmp_addend_ = addend; + wild_.assigned = true; + } + } + + /// @brief Per-level δ coefficients for a wildcard comparison. Empty when the + /// payload is concrete. + /// @return the coefficient table + const value_cw_array & value_cw_coeff() const noexcept + { + if constexpr (Wild) + return wild_.value_cw_coeff; + else + { + static const value_cw_array empty{}; + return empty; + } + } + + /// @brief Coefficient of δ in `cw_last`. Zero when the payload is concrete. + /// @return the final coefficient word + HEDLEY_NO_THROW + value_cw_word cw_last_coeff_word() const noexcept + { + if constexpr (Wild) + return wild_.cw_last_coeff; + else + return value_cw_word{}; + } + + /// @brief Tail δ coefficients for a blocked wildcard comparison. + /// @return the tail coefficient table + const tail_array & tail_coeff() const noexcept + { + if constexpr (Wild) + return wild_.tail_coeff; + else + { + static const tail_array empty{}; + return empty; + } + } + + /// @brief Prefix δ coefficients for an iDCF wildcard comparison. + /// @return the prefix coefficient table + const prefix_cw_array & prefix_cw_coeff() const noexcept + { + if constexpr (Wild) + return wild_.prefix_cw_coeff; + else + { + static const prefix_cw_array empty{}; + return empty; + } + } + + /// @brief Mark whether a wildcard comparison payload has been assigned. + /// @param assigned true once `assign_cmp` has run + HEDLEY_NO_THROW + void set_assigned(bool assigned) noexcept + { + if constexpr (Wild) + wild_.assigned = assigned; + else + (void)assigned; + } + private: detail::cmp_meta cmp_{}; value_cw_array value_cw_{}; @@ -605,33 +742,46 @@ struct cmp_storage cmp_wild_state wild_{}; }; -/// Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of +/// @brief Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of /// `placed` slots; `CmpDepth > 0` activates the comparison channel. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam PlacedTuple placed tuple +/// @tparam CmpDepth cmp depth +/// @tparam CmpOutBits cmp out bits +/// @tparam CmpWild cmp wild +/// @tparam CmpBlock cmp block +/// @tparam CmpIdcf cmp idcf template + std::size_t CmpBlock = 0, bool CmpIdcf = false, + bool IsVerifiable = false, bool IsExtractable = false> struct incr_key_base { public: using interior_prg = InteriorPRG; using exterior_prg = ExteriorPRG; + using tree = dpf::tree_traits; using interior_node = typename InteriorPRG::block_type; using exterior_node = typename ExteriorPRG::block_type; using input_type = dpf::concrete_type_t; using placed_tuple = PlacedTuple; using node_type = exterior_node; static constexpr std::size_t cmp_depth = CmpDepth; - /// Comparison output group width in bits (0 when there is no cmp channel). + /// @brief Comparison output group width in bits (0 when there is no cmp channel). static constexpr std::size_t cmp_out_bits = CmpOutBits; - /// True when the comparison payload is an unassigned wildcard. + /// @brief True when the comparison payload is an unassigned wildcard. static constexpr bool cmp_is_wildcard = CmpWild; - /// 0 = per-level path-sum. `B >= 1` = blocked checkpoints of width `B`. + /// @brief 0 = per-level path-sum. `B >= 1` = blocked checkpoints of width `B`. static constexpr std::size_t cmp_block = CmpBlock; static constexpr bool cmp_idcf = CmpIdcf; + static constexpr bool is_verifiable = IsVerifiable; + static constexpr bool is_extractable = IsExtractable; static constexpr std::size_t max_output_level = detail::incr::max_tree_level_v; - /// Residual tail width. 2 only when dropping those levels does not cut an + /// @brief Residual tail width. 2 only when dropping those levels does not cut an /// output and the comparison itself is what sets the tree height. static constexpr std::size_t cmp_q = [] { if (CmpBlock == 0 || CmpDepth <= 2) @@ -646,9 +796,7 @@ struct incr_key_base (CmpBlock == 0 || cmp_h == 0) ? 0 : (cmp_h + CmpBlock - 1) / CmpBlock; static constexpr std::size_t cmp_tail = (CmpBlock == 0 || cmp_q == 0) ? 0 : (std::size_t{1} << cmp_q); - /// Multi-level / comparison keys route through the slot-aware eval path. - static constexpr bool is_multilevel = true; - /// Narrowest unsigned word that holds `cmp_out_bits` bits (1 byte for a + /// @brief Narrowest unsigned word that holds `cmp_out_bits` bits (1 byte for a /// bit / ≤8-bit payload, 2 for ≤16, 4 for ≤32, 8 for ≤64). Value CWs and /// the addend share are stored in this word. using value_cw_word = utils::integral_type_from_bitlength_t< @@ -667,11 +815,15 @@ struct incr_key_base static_assert(num_outputs > 0 || CmpDepth > 0, "incremental DPF needs at least one output or a comparison channel"); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") static_assert(detail::incr::all_prefixes_ok_v, "at is shorter than the packing lanes required by an output"); using correction_words_array = std::array; + HEDLEY_PRAGMA(GCC diagnostic pop) using correction_advice_array = std::array; + using correction_seeds_array = std::array; using value_cw_array = std::array; using tail_array = std::array; static constexpr std::size_t prefix_cw_len = CmpIdcf ? depth + 1 : 0; @@ -680,6 +832,26 @@ struct incr_key_base static constexpr meta_array meta = detail::incr::build_meta(); + /// @brief Multi-level / comparison keys route through the slot-aware eval path. + /// Classic-shaped packs (every slot at full input width, no cmp) keep the + /// classic `eval_*` fast path even when verifiable/extractable phantoms are + /// present. + static constexpr bool is_multilevel = [] { + if constexpr (CmpDepth > 0) + return true; + if constexpr (num_outputs == 0) + return true; + else + { + for (std::size_t i = 0; i < num_outputs; ++i) + { + if (meta[i].prefix != input_bits) + return true; + } + return false; + } + }(); + template struct output_type_at { @@ -714,7 +886,7 @@ struct incr_key_base public: using leaf_wrapper_tuple = decltype(wrapper_tuple_t( std::make_index_sequence{})); - /// Raw leaf shares (pre-wrapper), matching classic `leaf_tuple` for asio. + /// @brief Raw leaf shares (pre-wrapper), matching classic `leaf_tuple` for asio. using leaf_tuple = decltype(leaf_tuple_type( std::make_index_sequence{})); using offset_type = offset_wrapper; @@ -739,7 +911,7 @@ struct incr_key_base } }(); - /// First output (source order) whose prefix equals `deepest_prefix`. + /// @brief First output (source order) whose prefix equals `deepest_prefix`. static constexpr std::size_t deepest_output = [] { if constexpr (num_outputs == 0) return std::size_t{0}; @@ -754,7 +926,7 @@ struct incr_key_base } }(); - /// Classic-shaped packing traits for deepest-group interval/sequence APIs. + /// @brief Classic-shaped packing traits for deepest-group interval/sequence APIs. static constexpr std::size_t outputs_per_leaf = (num_outputs > 0) ? outputs_per_leaf_of : 1; static constexpr std::size_t lg_outputs_per_leaf = @@ -775,7 +947,8 @@ struct incr_key_base addend_tuple addends = {}, value_cw_array value_cw_coeff = {}, uint64_t cw_last_coeff_in = 0, tail_array tail_in = {}, tail_array tail_coeff_in = {}, prefix_cw_array prefix_in = {}, - prefix_cw_array prefix_coeff_in = {}) + prefix_cw_array prefix_coeff_in = {}, + correction_seeds_array correction_seeds = {}) : leaf_nodes{std::move(leaves)}, offset_x{offset_share}, cmp_store_{cmp, value_cws, @@ -788,8 +961,9 @@ struct incr_key_base root_{root}, correction_words_{correction_words}, correction_advice_{correction_advice}, + correction_seeds_{correction_seeds}, common_part_hash_{utils::get_common_part_hash(correction_words_, - correction_advice_, leaf_nodes, wildcard_mask)} + correction_advice_, leaf_nodes, wildcard_mask, correction_seeds_)} { } incr_key_base(const incr_key_base &) = default; @@ -806,17 +980,63 @@ struct incr_key_base { return correction_advice_; } + const correction_seeds_array & correction_seeds() const + { + return correction_seeds_; + } const value_cw_array & value_cw() const { return cmp_store_.value_cw(); } HEDLEY_NO_THROW uint64_t cw_last() const noexcept { return cmp_store_.cw_last(); } HEDLEY_NO_THROW + value_cw_word cw_last_word() const noexcept { return cmp_store_.cw_last_word(); } + HEDLEY_NO_THROW + value_cw_word cmp_addend_word() const noexcept + { + return cmp_store_.cmp_addend_word(); + } + void set_cmp_scalars(value_cw_word last, value_cw_word addend, + value_cw_word last_coeff) + { + cmp_store_.set_scalars(last, addend, last_coeff); + } + /// @brief Mark whether a wildcard comparison payload has been assigned. + /// @param assigned true once `assign_cmp` has run + HEDLEY_NO_THROW + void set_cmp_assigned(bool assigned) noexcept + { + cmp_store_.set_assigned(assigned); + } + const value_cw_array & value_cw_coeff() const noexcept + { + return cmp_store_.value_cw_coeff(); + } + HEDLEY_NO_THROW + value_cw_word cw_last_coeff_word() const noexcept + { + return cmp_store_.cw_last_coeff_word(); + } + const tail_array & tail_coeff() const noexcept + { + return cmp_store_.tail_coeff(); + } + const prefix_cw_array & prefix_cw_coeff() const noexcept + { + return cmp_store_.prefix_cw_coeff(); + } + void assign_cmp_group(const detail::group_elem & delta, value_cw_word addend) + { + cmp_store_.assign_group(delta, addend); + } + HEDLEY_NO_THROW const prefix_cw_array & prefix_cws() const noexcept { return cmp_store_.prefix_cws(); } uint64_t prefix_cw(std::size_t i) const { return cmp_store_.prefix_cw(i); } - /// Party-local share of the constant absorb (`if_false`, or + /// @brief Party-local share of the constant absorb (`if_false`, or /// `δ + if_false` when `eval_as_ge`). Reconstructs with the peer share. + /// @return Party-local share of the constant absorb (`if_false`, or `δ + if_false` when + /// `eval_as_ge`) HEDLEY_NO_THROW uint64_t cmp_addend() const noexcept { return cmp_store_.cmp_addend(); } HEDLEY_NO_THROW @@ -834,8 +1054,9 @@ struct incr_key_base } auto correction_word(std::size_t level, bool direction) const { - return set_lo_bit(correction_word(level), - (correction_advice_[level] >> direction) & 1); + return tree::pack_cw(correction_word(level), + correction_advice_[level], direction, + tree::is_last_level(level, depth)); } uint64_t value_cw(std::size_t level) const { return cmp_store_.value_cw(level); } const tail_array & tail_cw() const { return cmp_store_.tail_cw(); } @@ -879,25 +1100,19 @@ struct incr_key_base HEDLEY_ALWAYS_INLINE HEDLEY_PURE static auto traverse_interior(const interior_node & node, - const interior_node & cw, bool dir) noexcept + const interior_node & cw, bool dir, bool is_last = false) noexcept { - return dpf::xor_if_lo_bit( - interior_prg::eval(unset_lo_2bits(node), dir), cw, node); + return tree::traverse(node, cw, dir, is_last); } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE static auto traverse_interior01(const interior_node & node, - const interior_node & cw0, const interior_node & cw1) noexcept + const interior_node & cw0, const interior_node & cw1, + bool is_last = false) noexcept { -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") - auto kids = interior_prg::eval01(unset_lo_2bits(node)); - return std::array{ - dpf::xor_if_lo_bit(kids[0], cw0, node), - dpf::xor_if_lo_bit(kids[1], cw1, node)}; -HEDLEY_PRAGMA(GCC diagnostic pop) + return tree::traverse01(node, cw0, cw1, is_last); } HEDLEY_NO_THROW @@ -905,22 +1120,9 @@ HEDLEY_PRAGMA(GCC diagnostic pop) static void traverse_interior01_x4(const interior_node * HEDLEY_RESTRICT parents, const interior_node & cw0, const interior_node & cw1, interior_node * HEDLEY_RESTRICT left, - interior_node * HEDLEY_RESTRICT right) noexcept + interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept { -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") - alignas(interior_node) interior_node seeds[4]; - DPF_UNROLL_LOOP - for (std::size_t i = 0; i < 4; ++i) - seeds[i] = unset_lo_2bits(parents[i]); - interior_prg::eval01_x4(seeds, left, right); - DPF_UNROLL_LOOP - for (std::size_t i = 0; i < 4; ++i) - { - left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]); - right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]); - } -HEDLEY_PRAGMA(GCC diagnostic pop) + tree::traverse01_x4(parents, cw0, cw1, left, right, is_last); } template @@ -932,33 +1134,49 @@ HEDLEY_PRAGMA(GCC diagnostic pop) constexpr auto pos = meta[I].pos_base + meta[I].index_in_group * meta[I].block_len; constexpr auto count = meta[I].block_len; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using leaf_type = dpf::leaf_node_t; + HEDLEY_PRAGMA(GCC diagnostic pop) leaf_type mask{}; auto seed_ = utils::to_exterior_node(unset_lo_2bits(node)); - exterior_prg::eval(seed_, leaf_blocks(mask), - static_cast(count), - static_cast(pos)); - // Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β. + if constexpr (IsExtractable) + { + detail::vdpf::extractable_leaf_prg::eval(seed_, + leaf_blocks(mask), + static_cast(count), + static_cast(pos)); + } + else + { + exterior_prg::eval(seed_, leaf_blocks(mask), + static_cast(count), + static_cast(pos)); + } return dpf::subtract_leaf( dpf::get_if_lo_bit(std::get(leaf_nodes).get(), node), mask); } leaf_wrapper_tuple leaf_nodes; offset_type offset_x; - /// Public `if_false` addends for `eq` / `eq_at` slots. + /// @brief Public `if_false` addends for `eq` / `eq_at` slots. addend_tuple public_addends{}; HEDLEY_NO_THROW bool has_cmp() const noexcept { return cmp_store_.has_cmp(); } - /// True once a wildcard comparison payload has been assigned (always true + /// @brief True once a wildcard comparison payload has been assigned (always true /// for concrete cmp keys and for keys without a comparison channel). + /// @return True once a wildcard comparison payload has been assigned (always true for concrete + /// cmp keys and for keys without a comparison channel) HEDLEY_NO_THROW bool cmp_assigned() const noexcept { return cmp_store_.cmp_assigned(); } - /// Patch the value CWs / `cw_last` for a resolved payload δ and install + /// @brief Patch the value CWs / `cw_last` for a resolved payload δ and install /// this party's `cmp_addend` share. Only valid for wildcard cmp keys; see /// the free `dpf::assign_cmp`. No tree re-walk / re-PRG. + /// @param delta the payload difference `if_true - if_false` + /// @param addend_share the `addend_share` void assign_cmp_delta(uint64_t delta, uint64_t addend_share) { cmp_store_.assign_cmp_delta(delta, addend_share); @@ -971,6 +1189,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) interior_node root_; correction_words_array correction_words_; correction_advice_array correction_advice_; + correction_seeds_array correction_seeds_{}; digest_type common_part_hash_; }; // struct incr_key_base @@ -1013,7 +1232,13 @@ using dpf_key_base_t = std::conditional_t< OutputT, OutputTs...>::cmp_block, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, - OutputT, OutputTs...>::cmp_idcf>>; + OutputT, OutputTs...>::cmp_idcf, + dpf::detail::incr::normalize_pack< + utils::bitlength_of_v>, + OutputT, OutputTs...>::is_verifiable, + dpf::detail::incr::normalize_pack< + utils::bitlength_of_v>, + OutputT, OutputTs...>::is_extractable>>; } // namespace detail @@ -1038,44 +1263,122 @@ namespace detail namespace incr { -// Assemble the public dpf_key type for a (PlacedTuple, CmpDepth) pair by -// expanding the placed slots into the output pack and appending the phantom -// cmp tag when a comparison channel is present. -template -struct assemble_key +// Assemble the public dpf_key type for a (PlacedTuple, CmpDepth, flags) pack by +// expanding the placed slots into the output pack and appending phantom tags. +// +// `dpf_key` always takes an output type. A comparison-only key (empty placed +// pack, CmpDepth > 0) is `dpf_key<..., cmp_channel_tag<...>>`. The no-comparison +// form is a separate specialization so an empty pack is not named as +// `dpf_key` — `std::conditional_t` would require +// that type to be valid even when CmpDepth > 0. +template +struct plain_assembled_key; + +template +struct plain_assembled_key { using type = dpf::dpf_key>; }; -template -struct assemble_key<0, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, InteriorPRG, - ExteriorPRG, InputT, Ps...> + +template +struct plain_assembled_key { - using type = dpf::dpf_key; + using type = dpf::dpf_key; +}; + +template +struct assemble_key; + +template +struct assemble_key +{ + using type = typename plain_assembled_key<(CmpDepth > 0), + InteriorPRG, ExteriorPRG, InputT, CmpDepth, CmpOutBits, CmpWild, + CmpBlock, CmpIdcf, Ps...>::type; +}; + +template +struct assemble_key +{ + using with_cmp = std::conditional_t< + (CmpDepth > 0), + dpf::dpf_key, + dpf::verifiable>, + dpf::dpf_key>; + using type = with_cmp; +}; + +template +struct assemble_key +{ + using type = std::conditional_t< + (CmpDepth > 0), + dpf::dpf_key, + dpf::extractable>, + dpf::dpf_key>; +}; + +template +struct assemble_key +{ + using type = std::conditional_t< + (CmpDepth > 0), + dpf::dpf_key, + dpf::verifiable, dpf::extractable>, + dpf::dpf_key>; }; template + bool CmpWild = false, std::size_t CmpBlock = 0, bool CmpIdcf = false, + bool IsVerifiable = false, bool IsExtractable = false> struct incr_dpf_key_of; template + bool CmpWild, std::size_t CmpBlock, bool CmpIdcf, + bool IsVerifiable, bool IsExtractable> struct incr_dpf_key_of, - CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf> + CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable> { using type = typename assemble_key::type; + CmpIdcf, IsVerifiable, IsExtractable, InteriorPRG, ExteriorPRG, InputT, + Ps...>::type; }; template + bool CmpWild = false, std::size_t CmpBlock = 0, bool CmpIdcf = false, + bool IsVerifiable = false, bool IsExtractable = false> using incr_dpf_key_of_t = typename incr_dpf_key_of::type; + InputT, PlacedTuple, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, + IsVerifiable, IsExtractable>::type; } // namespace incr } // namespace detail @@ -1167,10 +1470,12 @@ auto make_dpf_impl(dpfargs args, root_sampler_t; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + interior_node root[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(root, root_sampler); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") @@ -1179,32 +1484,29 @@ HEDLEY_PRAGMA(GCC diagnostic pop) correction_advice_array correction_advice; interior_node parent[2] = { root[0], root[1] }; - bool advice[2]; for (std::size_t level = 0; level < depth; ++level, mask >>= 1) { - bool bit = !!(mask & x); + const bool bit = !!(mask & x); + const bool is_last = tree::is_last_level(level, depth); + const bool ctrl0 = static_cast(dpf::get_lo_bit(parent[0])); + const bool ctrl1 = static_cast(dpf::get_lo_bit(parent[1])); - advice[0] = dpf::get_lo_bit_and_clear_lo_2bits(parent[0]); - advice[1] = dpf::get_lo_bit_and_clear_lo_2bits(parent[1]); + const auto child0 = tree::expand(parent[0], is_last); + const auto child1 = tree::expand(parent[1], is_last); - auto child0 = InteriorPRG::eval01(parent[0]); - auto child1 = InteriorPRG::eval01(parent[1]); - interior_node child[2] = { - child0[0] ^ child1[0], - child0[1] ^ child1[1] - }; + interior_node cw{}; + psnip_uint8_t advice = 0; + tree::make_cw(cw, advice, child0, child1, parent[0], parent[1], bit, + is_last); - bool t[2] = { - static_cast(dpf::get_lo_bit(child[0]) ^ !bit), - static_cast(dpf::get_lo_bit(child[1]) ^ bit) - }; - auto cw = dpf::set_lo_bit(child[!bit], t[bit]); - parent[0] = dpf::xor_if(child0[bit], cw, advice[0]); - parent[1] = dpf::xor_if(child1[bit], cw, advice[1]); + parent[0] = tree::advance(parent[0], child0, cw, advice, bit, ctrl0, + is_last); + parent[1] = tree::advance(parent[1], child1, cw, advice, bit, ctrl1, + is_last); - correction_words[level] = child[!bit]; - correction_advice[level] = static_cast(t[1] << 1) | t[0]; + correction_words[level] = cw; + correction_advice[level] = advice; } bool sign0 = dpf::get_lo_bit(parent[0]); diff --git a/include/dpf/emplace.hpp b/include/dpf/emplace.hpp index d23620b..15461d1 100644 --- a/include/dpf/emplace.hpp +++ b/include/dpf/emplace.hpp @@ -34,6 +34,7 @@ namespace utils /// @brief Emplaces a `dpf::dpf_key` object into the specified, pre-allocated memory. /// @tparam DpfKey The concrete specialization of `dpf::dpf_key` to construct. +/// @tparam T value type /// @param storage Reference to the container where the `dpf::dpf_key` object will be emplaced. /// @param root The root node used by the `dpf::dpf_key`. /// @param correction_words Correction words array for the `dpf::dpf_key`. @@ -59,6 +60,14 @@ struct dpf_emplacer using input_type = typename DpfKey::input_type; /// @brief Generic version is intentionally left undefined. + /// @param storage the `storage` + /// @param root the root seed + /// @param correction_words the `correction_words` + /// @param correction_advice the advice bit on the correction word + /// @param leaves the leaf values + /// @param beavers the `beavers` + /// @param offset_share the `offset_share` + /// @return Generic version is intentionally left undefined static auto emplace(T & storage, const interior_node & root, const correction_words_array & correction_words, @@ -69,6 +78,7 @@ struct dpf_emplacer }; /// @brief Specialization for `std::unique_ptr`. +/// @tparam DpfKey DPF key type template struct dpf_emplacer> { diff --git a/include/dpf/eval_common.hpp b/include/dpf/eval_common.hpp index c029ccc..4e907de 100644 --- a/include/dpf/eval_common.hpp +++ b/include/dpf/eval_common.hpp @@ -1,6 +1,5 @@ /// @file dpf/eval_common.hpp -/// @brief -/// @details +/// @brief Shared evaluation types, party tags, and output cursors. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -23,11 +22,13 @@ namespace dpf { -/// Sentinel: `dpf_output` converts to a bare `OutputT` (no party tag). +/// @brief Sentinel: `dpf_output` converts to a bare `OutputT` (no party tag). inline constexpr std::size_t no_party = std::numeric_limits::max(); -/// Eval result type for a leaf output of `KeyT`: subtractive share when the +/// @brief Eval result type for a leaf output of `KeyT`: subtractive share when the /// key is party-tagged, otherwise the concrete output type. +/// @tparam KeyT key type +/// @tparam OutputT output type template > struct eval_leaf_result { @@ -41,8 +42,10 @@ struct eval_leaf_result template using eval_leaf_result_t = typename eval_leaf_result::type; -/// Eval result type for a comparison output of `KeyT`: additive share when +/// @brief Eval result type for a comparison output of `KeyT`: additive share when /// the key is party-tagged, otherwise `Beta`. +/// @tparam KeyT key type +/// @tparam Beta payload type template > struct eval_cmp_result { @@ -112,9 +115,14 @@ struct alignas(utils::max_align_v) dpf_output friend auto make_dpf_output(const Node & node, Input x); }; -/// Copy one packed leaf into a buffer whose element type may differ +/// @brief Copy one packed leaf into a buffer whose element type may differ /// from `LeafT` (bit arrays store `word_type`, not the exterior node). -/// Byte destination keeps the store free of strict-aliasing UB. +/// @details Byte destination keeps the store free of strict-aliasing UB. +/// @tparam LeafT leaf type +/// @tparam Buffer output buffer type +/// @param buf the output buffer +/// @param index the index +/// @param leaf the leaf value template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -144,8 +152,16 @@ auto make_dpf_output(const Node & node, Input x) offset_within_block, Node>(x)}; } -/// Wrap a raw leaf node into a party-tagged `dpf_output` when `KeyT` is a +/// @brief Wrap a raw leaf node into a party-tagged `dpf_output` when `KeyT` is a /// `party_key`, otherwise a bare `dpf_output`. +/// @tparam KeyT key type +/// @tparam Output output +/// @tparam Input input domain type +/// @tparam Node node +/// @param node the GGM node +/// @param x the `x` +/// @return Wrap a raw leaf node into a party-tagged `dpf_output` when `KeyT` is a `party_key`, +/// otherwise a bare `dpf_output` template auto make_eval_dpf_output(const Node & node, Input x) { @@ -155,8 +171,12 @@ auto make_eval_dpf_output(const Node & node, Input x) return make_dpf_output(node, x); } -/// Wrap a raw comparison `Beta` value as an additive share when `KeyT` is a +/// @brief Wrap a raw comparison `Beta` value as an additive share when `KeyT` is a /// `party_key`. +/// @tparam KeyT key type +/// @tparam Beta payload type +/// @param raw the underlying integer +/// @return Wrap a raw comparison `Beta` value as an additive share when `KeyT` is a `party_key` template HEDLEY_NO_THROW auto make_eval_cmp_result(Beta raw) noexcept diff --git a/include/dpf/eval_full.hpp b/include/dpf/eval_full.hpp index 52d208e..e5b785d 100644 --- a/include/dpf/eval_full.hpp +++ b/include/dpf/eval_full.hpp @@ -137,7 +137,13 @@ auto eval_full(const DpfKey & dpf, return std::make_pair(std::move(outbufs), std::move(iterable)); } -/// Evaluate the whole domain, allocating a basic full memoizer and a buffer. +/// @brief Evaluate the whole domain, allocating a basic full memoizer and a buffer. +/// @tparam I output index +/// @tparam Is is +/// @tparam DpfKey DPF key type +/// @tparam DpfKey DPF key type +/// @param dpf the DPF key +/// @return the evaluation result template ) -HEDLEY_PRAGMA(GCC diagnostic pop) { if (HEDLEY_LIKELY(opl == 2)) { @@ -126,6 +125,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) return; } } +HEDLEY_PRAGMA(GCC diagnostic pop) for (std::size_t p = 0; p < opl; ++p) { @@ -208,7 +208,10 @@ void eval_inner_product_exterior(const DpfKey & dpf, IntegralT from_node, using node_type = typename DpfKey::exterior_node; using outputs_tuple = typename DpfKey::concrete_outputs_tuple; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using range = ip_prg_range; + HEDLEY_PRAGMA(GCC diagnostic pop) constexpr std::size_t opl = DpfKey::outputs_per_leaf; std::size_t nodes_in_interval = static_cast(to_node - from_node); @@ -397,11 +400,18 @@ auto eval_inner_product_impl(const DpfKey & dpf, InputT from, InputT to, } // namespace internal -/// Expand the interior tree for `[from, to]`. A wrapping interval is left +/// @brief Expand the interior tree for `[from, to]`. A wrapping interval is left /// cold: the memoizer holds one half, and walking the first half of the later /// inner product would clobber a cached second half. Safe to call before the /// weight vector exists; a subsequent inner-product on the same memoizer /// skips the interior AES when the interval did not wrap. +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the `to` +/// @param memoizer the memoizer built for this key template @@ -425,11 +435,24 @@ void eval_prepare_full(const DpfKey & dpf, IntervalMemoizer && memoizer) memoizer); } -/// `sum_x DPF_I(x) * w[x]` (additive) or `xor_x DPF_I(x) & w[x]` (XOR). -/// `w[j]` is the weight for the `j`-th output in the interval, matching +/// @brief `sum_x DPF_I(x) * w[x]` (additive) or `xor_x DPF_I(x) & w[x]` (XOR). +/// @details `w[j]` is the weight for the `j`-th output in the interval, matching /// `eval_interval`'s destination layout. Multiple `Is` take a tuple of /// weight ranges and return a tuple of accumulators; a single `I` takes /// one range and returns one accumulator. +/// @tparam I output index +/// @tparam Is is +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type +/// @tparam Weights weights +/// @tparam IntervalMemoizer interval memoizer type +/// @tparam DpfKey DPF key type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the `to` +/// @param weights the weights +/// @param memoizer the memoizer built for this key +/// @return `sum_x DPF_I(x) * w[x]` (additive) or `xor_x DPF_I(x) & w[x]` (XOR) template (dpf.leaf_nodes).get(); +HEDLEY_PRAGMA(GCC diagnostic pop) auto *nodes = memoizer[dpf_type::depth]; DPF_UNROLL_LOOP for (std::size_t j = 0, k = start; j < nodes_in_interval; ++j, ++k) @@ -151,7 +156,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") sizeof(output_type) * dpf_type::outputs_per_leaf); } } -HEDLEY_PRAGMA(GCC diagnostic pop) } template ; + HEDLEY_PRAGMA(GCC diagnostic pop) std::size_t nodes_in_interval = static_cast(to_node - from_node); auto *nodes = memoizer[DpfKey::depth]; @@ -312,7 +333,10 @@ void eval_interval_exterior_all(const DpfKey & dpf, IntegralT from_node, { using node_type = typename DpfKey::exterior_node; using outputs_tuple = typename DpfKey::concrete_outputs_tuple; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using range = leaf_prg_range; + HEDLEY_PRAGMA(GCC diagnostic pop) if constexpr (range::is_contiguous) { eval_interval_exterior_fused(dpf, from_node, to_node, outbufs, @@ -397,10 +421,26 @@ auto eval_interval(const DpfKey & dpf, InputT from, InputT to, } // namespace internal -/// Write outputs `I, Is...` for `[from, to]` into `outbufs`. -/// @param outbufs Named buffer, or a tuple of buffers when several outputs +/// @name Closed-interval evaluation +/// @tparam I output index +/// @tparam Is the remaining output indices +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @{ + +/// @brief Write outputs `I, Is...` for `[from, to]` into `outbufs`. +/// @tparam OutputBuffers tuple of output buffers +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @param outbufs named buffer, or a tuple of buffers when several outputs /// are selected. Must outlive the returned iterable. -/// @param memoizer Workspace sized for at least this interval. +/// @param memoizer workspace sized for at least this interval +/// @return an iterable over the written outputs template (dpf, dpf.offset_x(from), dpf.offset_x(to), outbufs, memoizer, std::make_index_sequence<1+sizeof...(Is)>()); } -/// Evaluate `[from, to]` into `outbufs`, allocating a basic interval memoizer. +/// @brief Evaluate `[from, to]` into `outbufs`, allocating a basic interval memoizer. +/// @tparam OutputBuffers tuple of output buffers +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @param outbufs the named output buffers +/// @return an iterable over the written outputs template (from, to)); } -/// Evaluate `[from, to]` with a caller-supplied memoizer. +/// @brief Evaluate `[from, to]` with a caller-supplied memoizer. +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @param memoizer the memoizer built for this key /// @return `std::pair` of a new buffer (or tuple of buffers) and an iterable /// into that buffer. template (from, to)); } +/// @} + } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__ diff --git a/include/dpf/eval_point.hpp b/include/dpf/eval_point.hpp index 70c5cda..db7e62c 100644 --- a/include/dpf/eval_point.hpp +++ b/include/dpf/eval_point.hpp @@ -5,6 +5,7 @@ /// `eval_point` returns a tuple of shares. /// Pass a `basic_path_memoizer` lvalue to resume a previous path. /// An unassigned wildcard output throws `std::runtime_error`. +/// `eval_point(key, x, dpf::prove(π))` folds a VDPF proof token. /// @snippet evaluation/eval_point.cpp eval-point /// @author Ryan Henry /// @author Christopher Jiang @@ -25,6 +26,7 @@ #include "dpf/eval_common.hpp" #include "dpf/eval_target.hpp" #include "dpf/path_memoizer.hpp" +#include "dpf/verifiable.hpp" namespace dpf { @@ -35,7 +37,8 @@ namespace internal template -inline auto eval_point_interior(const DpfKey & dpf, InputT && x, PathMemoizer && path) +inline auto eval_point_interior(const DpfKey & dpf, InputT && x, PathMemoizer && path, + proof_token * pi = nullptr) { using dpf_type = DpfKey; @@ -47,7 +50,21 @@ inline auto eval_point_interior(const DpfKey & dpf, InputT && x, PathMemoizer && { bool bit = !!(mask & x); auto cw = dpf.correction_word(level_index-1, bit); - path[level_index] = dpf_type::traverse_interior(path[level_index-1], cw, bit); + const bool is_last = dpf_type::tree::is_last_level(level_index - 1, + dpf.depth); + path[level_index] = dpf_type::traverse_interior(path[level_index-1], + cw, bit, is_last); + if constexpr (dpf_type::is_verifiable) + { + if (pi != nullptr) + { + const auto x_bits = static_cast( + utils::to_integral_type>{}(x) + >> (utils::bitlength_of_v> - level_index)); + detail::vdpf::fold_node(*pi, level_index - 1, x_bits, + path[level_index], dpf.correction_seeds()[level_index - 1]); + } + } } detail::path_note_filled_to(path, dpf.depth); } @@ -68,19 +85,17 @@ template HEDLEY_ALWAYS_INLINE -auto eval_point(const DpfKey & dpf, InputT && x, PathMemoizer && path) +auto eval_point(const DpfKey & dpf, InputT && x, PathMemoizer && path, + proof_token * pi = nullptr) { utils::flip_msb_if_signed_integral(x); - internal::eval_point_interior(dpf, x, path); + internal::eval_point_interior(dpf, x, path, pi); return internal::eval_point_exterior(dpf, path); } } // namespace internal /// Evaluate output `I` at `x`. -/// @param path Mutable path memoizer. The default is a fresh -/// nonmemoizing workspace for this call. -/// @return Handle whose `operator*` is the party's share. template (dpf, tx, path), tx); } +/// Evaluate and fold a VDPF proof token for the walked path. +template , + std::enable_if_t, bool> = true> +HEDLEY_ALWAYS_INLINE +auto eval_point(const DpfKey & dpf, InputT && x, prove_ref pr, + PathMemoizer && path = PathMemoizer{}) +{ + static_assert(DpfKey::is_verifiable, + "eval_point(..., prove(π)): key must carry dpf::verifiable"); + assert_not_wildcard_output(dpf); + using output_type = typename DpfKey::concrete_output_type; + + detail::vdpf::init_proof(pr.token, dpf); + auto tx = dpf.offset_x(x); + return make_eval_dpf_output( + internal::eval_point(dpf, tx, path, &pr.token), tx); +} + /// Evaluate several outputs at `x`. -/// @return Tuple of shares, already dereferenced. template (dpf, x, path)...); } +/// Fold every point in `[from, to]` into `pi` (caller must `init_proof` first, +/// or pass a fresh token via `prove_interval` below). +template +void prove_fold_interval(const KeyT & key, InputT from, InputT to, + proof_token & pi) +{ + static_assert(KeyT::is_verifiable, + "prove_fold_interval: key must carry dpf::verifiable"); + using input_type = typename KeyT::input_type; + auto cur = static_cast(from); + const auto last = static_cast(to); + for (;;) + { + nonmemoizing_path_memoizer path{}; + auto tx = key.offset_x(cur); + utils::flip_msb_if_signed_integral(tx); + internal::eval_point_interior(key, tx, path, &pi); + if (cur == last) + break; + ++cur; + } +} + +/// Initialise `pr.token` and fold `[from, to]`. +template +void prove_interval(const KeyT & key, InputT from, InputT to, prove_ref pr) +{ + detail::vdpf::init_proof(pr.token, key); + prove_fold_interval(key, from, to, pr.token); +} + } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_EVAL_POINT_HPP__ diff --git a/include/dpf/eval_sequence.hpp b/include/dpf/eval_sequence.hpp index e5ff8d5..d7943bc 100644 --- a/include/dpf/eval_sequence.hpp +++ b/include/dpf/eval_sequence.hpp @@ -148,8 +148,18 @@ inline auto eval_sequence(const DpfKey & dpf, ForwardIterator begin, ForwardIter } } -/// Evaluate the sorted range `[begin, end)`, allocating a buffer. +/// @brief Evaluate the sorted range `[begin, end)`, allocating a buffer. +/// @tparam I output index +/// @tparam Is is +/// @tparam DpfKey DPF key type +/// @tparam ForwardIterator forward iterator type +/// @tparam ReturnType return type +/// @tparam DpfKey DPF key type +/// @tparam ReturnType return type /// @param return_type `return_entire_node_tag_{}` or `return_output_only_tag_{}`. +/// @param dpf the DPF key +/// @param begin the iterator to the first query +/// @param end the iterator past the last query /// @return Pair of buffer (or tuple of buffers) and an iterable in list order. template ; - using unique_ptr = typename allocator::unique_ptr; HEDLEY_PRAGMA(GCC diagnostic pop) + using unique_ptr = typename allocator::unique_ptr; allocator alloc = allocator{}; if (HEDLEY_UNLIKELY(!std::is_sorted(begin, end))) @@ -219,6 +229,8 @@ HEDLEY_PRAGMA(GCC diagnostic pop) dpf.correction_word(level_index-1, 0), dpf.correction_word(level_index-1, 1) }; + const bool is_last = dpf_type::tree::is_last_level(level_index - 1, + dpf_type::depth); // `lower` and `upper` are always adjacent elements of `splits` with `lower` < `upper` // [lower, upper) = "block" for (auto upper = std::begin(splits), lower = upper++; upper != std::end(splits); lower = upper++) @@ -228,16 +240,16 @@ HEDLEY_PRAGMA(GCC diagnostic pop) [&flip](auto a, auto b){ return static_cast(a&b) ^ flip; }); if (it == *lower) // right only since first element in "block" requires right traversal { - memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[1], 1); + memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[1], 1, is_last); } else if (it == *upper) // left only since no element in "block" requires right traversal { - memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[0], 0); + memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[0], 0, is_last); } else // both ways since some (non-lower) element within "block" requires right traversal { auto cur_node = memo[!curhalf*nodes_in_sequence + j++]; - auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1]); + auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1], is_last); memo[curhalf*nodes_in_sequence + i++] = kids[0]; memo[curhalf*nodes_in_sequence + i++] = kids[1]; splits.insert(upper, it); @@ -259,6 +271,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto cw = dpf.template leaf(); +HEDLEY_PRAGMA(GCC diagnostic pop) auto buf = memo.get(); constexpr auto clz = utils::countl_zero_symmetric_difference{}; @@ -278,7 +291,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") } prev = curr++; } -HEDLEY_PRAGMA(GCC diagnostic pop) return subsequence_iterable(std::begin(outbuf), begin, end); } @@ -324,6 +336,8 @@ inline auto eval_sequence_interior(const DpfKey & dpf, const sequence_recipe & r dpf.correction_word(level_index-1, 0), dpf.correction_word(level_index-1, 1) }; + const bool is_last = dpf_type::tree::is_last_level(level_index - 1, + dpf_type::depth); auto prevbuf = memoizer[level_index-1]; auto currbuf = memoizer[level_index]; @@ -334,12 +348,12 @@ inline auto eval_sequence_interior(const DpfKey & dpf, const sequence_recipe & r if (memoizer.traverse_first(recipe_index) == true) { bool dir = memoizer.get_direction(0); - currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir); + currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir, is_last); } if (memoizer.traverse_second(recipe_index) == true) { bool dir = memoizer.get_direction(1); - currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir); + currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir, is_last); } } } @@ -362,6 +376,7 @@ inline auto eval_sequence_exterior_entire_node(const DpfKey & dpf, const sequenc HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto buf = memoizer[dpf.depth]; +HEDLEY_PRAGMA(GCC diagnostic pop) DPF_UNROLL_LOOP for (std::size_t j = 0; j < nodes_in_interval; ++j) { @@ -375,7 +390,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::memcpy(&outbuf[j*dpf_type::outputs_per_leaf], &leaf, sizeof(output_type)*dpf_type::outputs_per_leaf); } } -HEDLEY_PRAGMA(GCC diagnostic pop) } template (); +HEDLEY_PRAGMA(GCC diagnostic pop) using node_type = typename DpfKey::exterior_node; using leaf_node_type = std::tuple_element_t; auto buf = memoizer[dpf.depth]; @@ -417,7 +432,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") else outbuf[i] = v; } -HEDLEY_PRAGMA(GCC diagnostic pop) } template #include +#include "hedley/hedley.h" + namespace dpf { -/// Sentinel: deduce point-slot prefix from the key (`meta[I].prefix`). +/// @brief Sentinel: deduce point-slot prefix from the key (`meta[I].prefix`). inline constexpr std::size_t prefix_deduce = std::numeric_limits::max(); -/// Point-output channel: slot `I`, optional prefix check `N`. +/// @brief Point-output channel: slot `I`, optional prefix check `N`. +/// @tparam I output index +/// @tparam N prefix length, or `prefix_deduce` template struct out_t { @@ -29,13 +33,14 @@ struct out_t template inline constexpr out_t out{}; -/// Comparison (DCF) channel. +/// @brief Comparison (DCF) channel. struct cmp_t { }; inline constexpr cmp_t cmp{}; -/// Prefix of an `idcf` comparison. `L` is the number of leading bits. +/// @brief Prefix of an `idcf` comparison. `L` is the number of leading bits. +/// @tparam L number of leading bits template struct cmp_prefix_t { @@ -74,7 +79,7 @@ template inline constexpr bool is_cmp_prefix_target_v = is_cmp_prefix_target>::value; -/// True for channel tags that must not bind as the key in classic eval_*. +/// @brief True for channel tags that must not bind as the key in classic eval_*. template inline constexpr bool is_eval_channel_tag_v = is_out_v || is_cmp_target_v || is_cmp_prefix_target_v; @@ -83,12 +88,15 @@ template struct looks_like_dpf_key : std::false_type { }; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template struct looks_like_dpf_key> : std::true_type { }; +HEDLEY_PRAGMA(GCC diagnostic pop) template inline constexpr bool looks_like_dpf_key_v = looks_like_dpf_key>::value; @@ -107,12 +115,13 @@ template inline constexpr bool is_incremental_dpf_key_v = is_incremental_dpf_key>::value; -/// True only for keys that must use the slot-aware (multi-level / comparison) +/// @brief True only for keys that must use the slot-aware (multi-level / comparison) /// eval path. Every key now carries a `slot_meta` table (so /// `is_incremental_dpf_key_v` is true for all keys), but classic single-level /// equal-width keys keep using the classic `eval_*` fast paths; they set /// `is_multilevel == false`. Multi-level (`at`) and comparison keys set it /// to true. +/// @tparam T value type template struct is_multilevel_key : std::false_type { diff --git a/include/dpf/eval_unified.hpp b/include/dpf/eval_unified.hpp index 4193275..b2f246e 100644 --- a/include/dpf/eval_unified.hpp +++ b/include/dpf/eval_unified.hpp @@ -410,7 +410,10 @@ auto eval_out_inner_product_impl(const KeyT & dpf, LaneT from, LaneT to, const bool wraps = utils::interval_wraps(from_i, to_i, N); const auto segs = utils::split_leaf_nodes(from_node, to_node, to_level, wraps); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") ml_ip_accum acc{}; + HEDLEY_PRAGMA(GCC diagnostic pop) std::size_t start = 0; for (std::size_t s = 0; s < segs.n; ++s) { @@ -552,7 +555,10 @@ void eval_out_sequence_breadth_first_impl(const KeyT & dpf, if (begin == end) return; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using allocator = aligned_allocator; + HEDLEY_PRAGMA(GCC diagnostic pop) allocator alloc{}; const std::size_t nseq = static_cast(std::distance(begin, end)); auto memo = alloc.allocate_unique_ptr(nseq * 2); @@ -570,6 +576,8 @@ void eval_out_sequence_breadth_first_impl(const KeyT & dpf, const node_type cw[2] = { dpf.correction_word(level_index - 1, 0), dpf.correction_word(level_index - 1, 1)}; + const bool is_last = key_type::tree::is_last_level(level_index - 1, + key_type::depth); const std::size_t cur = static_cast(curhalf) * nseq; const std::size_t prv = static_cast(!curhalf) * nseq; for (auto upper = std::begin(splits), lower = upper++; @@ -580,17 +588,17 @@ void eval_out_sequence_breadth_first_impl(const KeyT & dpf, if (it == *lower) { memo[cur + i++] = key_type::traverse_interior( - memo[prv + j++], cw[1], 1); + memo[prv + j++], cw[1], 1, is_last); } else if (it == *upper) { memo[cur + i++] = key_type::traverse_interior( - memo[prv + j++], cw[0], 0); + memo[prv + j++], cw[0], 0, is_last); } else { auto kids = key_type::traverse_interior01(memo[prv + j++], - cw[0], cw[1]); + cw[0], cw[1], is_last); memo[cur + i++] = kids[0]; memo[cur + i++] = kids[1]; splits.insert(upper, it); @@ -649,7 +657,17 @@ auto eval_sequence_breadth_first(out_t, const KeyT & key, return buf; } -/// Build a sequence recipe stopped at slot `I`'s tree level (prefix domain). +/// @brief Build a sequence recipe stopped at slot `I`'s tree level (prefix domain). +/// @tparam I output index +/// @tparam N width in bits +/// @tparam KeyT key type +/// @tparam ForwardIterator forward iterator type +/// @tparam KeyT key type +/// @param N the `N` +/// @param key the `key` +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @return the constructed object template , bool> = true> auto make_sequence_recipe(out_t, const KeyT & key, ForwardIterator begin, diff --git a/include/dpf/fp61.hpp b/include/dpf/fp61.hpp new file mode 100644 index 0000000..05ac0de --- /dev/null +++ b/include/dpf/fp61.hpp @@ -0,0 +1,241 @@ +/// @file dpf/fp61.hpp +/// @brief Prime field \(\mathbb{F}_{2^{61}-1}\) additive output type. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_FP61_HPP__ +#define LIBDPF_INCLUDE_DPF_FP61_HPP__ + +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" + +#include "dpf/utils.hpp" +#include "dpf/leaf_arithmetic.hpp" + +namespace dpf +{ + +/// @brief Modulus \(p = 2^{61}-1\). `p` itself reduces to 0. +inline constexpr std::uint64_t fp61_mod = (std::uint64_t{1} << 61) - 1; + +/// @brief Additive element of \(\mathbb{F}_{2^{61}-1}\). +class fp61 +{ + public: + /// @brief Underlying unsigned word. Values are stored already reduced. + using integral_type = std::uint64_t; + static constexpr std::size_t num_bits = 61; + static constexpr bool dpf_modint = true; + static constexpr bool dpf_fp61 = true; + + /// @brief Reduce `v` into the field. + /// @param v the integer to reduce. Defaults to 0 + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr fp61(integral_type v = 0) noexcept + : val{reduce(v)} + { } + + /// @brief Copy constructor. + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr fp61(const fp61 &) noexcept = default; + /// @brief Move constructor. + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr fp61(fp61 &&) noexcept = default; + /// @brief Copy assignment. + /// @return `*this` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr fp61 & operator=(const fp61 &) noexcept = default; + /// @brief Move assignment. + /// @return `*this` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr fp61 & operator=(fp61 &&) noexcept = default; + + /// @brief The reduced representative in `[0, p)`. + /// @return the stored field element + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + constexpr integral_type raw() const noexcept { return val; } + + /// @brief Same value as `raw()`. + /// @return the stored field element + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + explicit constexpr operator integral_type() const noexcept { return val; } + + /// @brief Mersenne reduction of a 64-bit word. + /// @param x the integer to reduce + /// @return `x` modulo `2^61-1`, with `p` itself represented as 0 + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static constexpr integral_type reduce(integral_type x) noexcept + { + x = (x & fp61_mod) + (x >> 61); + if (x >= fp61_mod) + x -= fp61_mod; + return x; + } + + /// @brief Field addition. + /// @param a left addend + /// @param b right addend + /// @return `a + b` in the field + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr fp61 operator+(fp61 a, fp61 b) noexcept + { + return fp61{a.val + b.val}; + } + + /// @brief Field subtraction. + /// @param a minuend + /// @param b subtrahend + /// @return `a - b` in the field + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr fp61 operator-(fp61 a, fp61 b) noexcept + { + return fp61{a.val + fp61_mod - b.val}; + } + + /// @brief Field negation. + /// @param a the element to negate + /// @return `-a`, with `-0 = 0` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr fp61 operator-(fp61 a) noexcept + { + return fp61{a.val == 0 ? 0 : fp61_mod - a.val}; + } + + /// @brief Field multiplication. + /// @param a left factor + /// @param b right factor + /// @return `a * b` in the field + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr fp61 operator*(fp61 a, fp61 b) noexcept + { + using u128 = unsigned __int128; + const u128 p = static_cast(a.val) * static_cast(b.val); + const auto lo = static_cast(p) & fp61_mod; + const auto mid = static_cast(p >> 61) & fp61_mod; + const auto hi = static_cast(p >> 122); + return fp61{lo + mid + hi}; + } + + /// @brief Field equality. + /// @param a left element + /// @param b right element + /// @return `true` when the reduced values match + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr bool operator==(fp61 a, fp61 b) noexcept + { + return a.val == b.val; + } + + /// @brief Field inequality. + /// @param a left element + /// @param b right element + /// @return `true` when the reduced values differ + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + friend constexpr bool operator!=(fp61 a, fp61 b) noexcept + { + return a.val != b.val; + } + + /// @brief Write the reduced representative in decimal. + /// @param os the output stream + /// @param a the element to write + /// @return `os` + friend std::ostream & operator<<(std::ostream & os, fp61 a) + { + return os << a.val; + } + + private: + integral_type val; +}; + +namespace utils +{ + +template <> +struct bitlength_of + : std::integral_constant +{ }; + +template <> +struct has_characteristic_two : std::false_type +{ }; + +} // namespace utils + +namespace leaf_arithmetic +{ + +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") +template <> +struct add_t +{ + auto operator()(const simde__m128i & a, const simde__m128i & b) const + { + return add_t{}(a, b); + } +}; + +template <> +struct subtract_t +{ + auto operator()(const simde__m128i & a, const simde__m128i & b) const + { + return subtract_t{}(a, b); + } +}; + +template <> +struct add_t +{ + auto operator()(const simde__m256i & a, const simde__m256i & b) const + { + return add_t{}(a, b); + } +}; + +template <> +struct subtract_t +{ + auto operator()(const simde__m256i & a, const simde__m256i & b) const + { + return subtract_t{}(a, b); + } +}; +HEDLEY_PRAGMA(GCC diagnostic pop) + +} // namespace leaf_arithmetic + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_FP61_HPP__ diff --git a/include/dpf/geneval.hpp b/include/dpf/geneval.hpp index 865dc0a..1e0791d 100644 --- a/include/dpf/geneval.hpp +++ b/include/dpf/geneval.hpp @@ -50,16 +50,21 @@ namespace dpf { -/// Shares and the correction words opened along the query trie. -/// `correction_words[i]` / `correction_advice[i]` match a reusable key at +/// @brief Shares and the correction words opened along the query trie. +/// @details `correction_words[i]` / `correction_advice[i]` match a reusable key at /// the same target for every `i < live_levels`. `leaf_live` means the /// target's leaf was in the trie, so `leaf` is that key's leaf word. +/// @tparam Output output +/// @tparam Leaf leaf template struct geneval_result { std::vector party0; std::vector party1; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::vector> correction_words; + HEDLEY_PRAGMA(GCC diagnostic pop) std::vector correction_advice; std::size_t live_levels = 0; bool leaf_live = false; @@ -88,8 +93,11 @@ T geneval_flipped(T x) return x; } -/// Leaf-node id of an already MSB-flipped input. The id is the high +/// @brief Leaf-node id of an already MSB-flipped input. The id is the high /// `depth` bits; the low `lg(outputs_per_leaf)` bits select the lane. +/// @tparam Dpf dpf +/// @param x the `x` +/// @return Leaf-node id of an already MSB-flipped input template uint64_t geneval_leaf_id(typename Dpf::input_type x) { @@ -134,9 +142,9 @@ template -auto geneval_run(bool arith, InputT x0, InputT x1, +auto geneval_run(bool arith, bool arith_out, InputT x0, InputT x1, const std::vector & queries, RootSampler & root_sampler, - PadRng & pads, OutputT y) + PadRng & pads, OutputT y0, OutputT y1 = OutputT{}) { static_assert(std::is_integral_v, "geneval input shares are an integral domain"); @@ -147,7 +155,11 @@ auto geneval_run(bool arith, InputT x0, InputT x1, using dpf_type = utils::dpf_type_t; using node = typename dpf_type::interior_node; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using leaf_node = leaf_node_t; + HEDLEY_PRAGMA(GCC diagnostic pop) + using outputs_tuple = std::tuple; constexpr std::size_t depth = dpf_type::depth; if (queries.empty()) @@ -183,8 +195,16 @@ auto geneval_run(bool arith, InputT x0, InputT x1, constexpr auto to_int = utils::to_integral_type{}; - const node root0 = dpf::unset_lo_bit(static_cast(root_sampler())); - const node root1 = dpf::set_lo_bit(static_cast(root_sampler())); + using tree = dpf::tree_traits; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + node roots[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(roots, [&]() -> node { + return static_cast(root_sampler()); + }); + const node root0 = roots[0]; + const node root1 = roots[1]; struct slot { @@ -195,7 +215,10 @@ auto geneval_run(bool arith, InputT x0, InputT x1, std::vector frontier; frontier.push_back(slot{0, root0, root1}); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") geneval_result result; + HEDLEY_PRAGMA(GCC diagnostic pop) std::memset(&result.leaf, 0, sizeof(result.leaf)); result.correction_words.reserve(depth); result.correction_advice.reserve(depth); @@ -207,6 +230,7 @@ auto geneval_run(bool arith, InputT x0, InputT x1, const uint8_t bit0 = static_cast(!!(to_int(mask) & to_int(x0c))); const uint8_t bit1 = static_cast(!!(to_int(mask) & to_int(x1c))); const uint64_t parent_id = geneval_prefix(secret_leaf, depth, level); + const bool is_last = tree::is_last_level(level, depth); node L0 = simde_mm_setzero_si128(); node R0 = simde_mm_setzero_si128(); @@ -225,8 +249,8 @@ auto geneval_run(bool arith, InputT x0, InputT x1, { if (n.id == parent_id) level_live = true; - const auto c0 = InteriorPRG::eval01(dpf::unset_lo_2bits(n.s0)); - const auto c1 = InteriorPRG::eval01(dpf::unset_lo_2bits(n.s1)); + const auto c0 = tree::expand(n.s0, is_last); + const auto c1 = tree::expand(n.s1, is_last); L0 = ds_xor(L0, c0[0]); R0 = ds_xor(R0, c0[1]); L1 = ds_xor(L1, c1[0]); @@ -242,21 +266,42 @@ auto geneval_run(bool arith, InputT x0, InputT x1, auto opened = proto.open_cw(blinds); cw = opened.first; advice = opened.second; + if constexpr (tree::is_half_tree) + { + if (!is_last) + advice = 0; + } ++result.live_levels; } else { still_live = false; cw = pads.block(); - const uint8_t t0 = static_cast(pads.bit() & 1u); - const uint8_t t1 = static_cast(pads.bit() & 1u); - advice = static_cast((t1 << 1) | t0); + if constexpr (tree::is_half_tree) + { + if (!is_last) + { + advice = 0; + } + else + { + const uint8_t t0 = static_cast(pads.bit() & 1u); + const uint8_t t1 = static_cast(pads.bit() & 1u); + advice = static_cast((t1 << 1) | t0); + } + } + else + { + const uint8_t t0 = static_cast(pads.bit() & 1u); + const uint8_t t1 = static_cast(pads.bit() & 1u); + advice = static_cast((t1 << 1) | t0); + } } result.correction_words.push_back(cw); result.correction_advice.push_back(advice); - const node cw0 = dpf::set_lo_bit(cw, advice & 1u); - const node cw1 = dpf::set_lo_bit(cw, (advice >> 1) & 1u); + const node cw0 = tree::pack_cw(cw, advice, false, is_last); + const node cw1 = tree::pack_cw(cw, advice, true, is_last); const std::size_t child_bits = level + 1; std::vector next; next.reserve(exps.size() * 2); @@ -294,11 +339,23 @@ auto geneval_run(bool arith, InputT x0, InputT x1, } if (on == nullptr) throw std::logic_error("geneval: secret leaf missing from trie"); - const bool sign0 = dpf::get_lo_bit(on->s0); - auto built = dpf::make_leaves(alpha, - dpf::unset_lo_2bits(on->s0), dpf::unset_lo_2bits(on->s1), sign0, - std::size_t{0}, y); - result.leaf = std::get<0>(built.first.first); + if (arith_out) + { + const uint8_t t0 = static_cast(dpf::get_lo_bit(on->s0)); + const uint8_t t1 = static_cast(dpf::get_lo_bit(on->s1)); + const std::size_t lane = static_cast(to_int(alpha)); + result.leaf = proto.template open_arith_leaf( + dpf::unset_lo_2bits(on->s0), dpf::unset_lo_2bits(on->s1), t0, t1, + y0, y1, std::size_t{0}, lane); + } + else + { + const bool sign0 = dpf::get_lo_bit(on->s0); + auto built = dpf::make_leaves(alpha, + dpf::unset_lo_2bits(on->s0), dpf::unset_lo_2bits(on->s1), sign0, + std::size_t{0}, y0); + result.leaf = std::get<0>(built.first.first); + } } result.party0.reserve(flipped.size()); @@ -326,6 +383,20 @@ auto geneval_run(bool arith, InputT x0, InputT x1, return result; } +template +auto geneval_run(bool arith, InputT x0, InputT x1, + const std::vector & queries, RootSampler & root_sampler, + PadRng & pads, OutputT y) +{ + return geneval_run(arith, false, x0, x1, queries, + root_sampler, pads, y, OutputT{}); +} + template & queries, RootSampler & root_sampler, PadRng & pads, OutputT y) { - return geneval_run(false, x0, x1, queries, - root_sampler, pads, y); + return geneval_run(false, false, x0, x1, queries, + root_sampler, pads, y, OutputT{}); } template @@ -400,7 +471,26 @@ std::vector geneval_inclusive(InputT from, InputT to) } // namespace detail -/// Geneval at one public point. The secret point is `x0 XOR x1`. +/// @name Point geneval +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param query the query point +/// @param rng the Doerner–Shelat randomness tapes +/// @{ + +/// @brief The secret point is `x0 XOR x1`. +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param query the query point +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the payload +/// @return the opened shares and correction words template rng, OutputT y) { - return detail::geneval_run(false, x0, x1, - std::vector{query}, rng.root, rng.pad, y); + return detail::geneval_run(false, false, x0, x1, + std::vector{query}, rng.root, rng.pad, y, OutputT{}); } -/// Geneval at one public point. The secret point is `x0 + x1`. +/// @brief The secret point is `x0 + x1`. +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param query the query point +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the payload +/// @return the opened shares and correction words template rng, OutputT y) { - return detail::geneval_run(true, x0, x1, - std::vector{query}, rng.root, rng.pad, y); + return detail::geneval_run(true, false, x0, x1, + std::vector{query}, rng.root, rng.pad, y, OutputT{}); } -/// Geneval on the inclusive interval `[from, to]`. +/// @brief XOR-index shares, additively shared payload `y0 + y1 = β`. +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param query the query point +/// @param rng the Doerner–Shelat randomness tapes +/// @param y0 party 0's share of the payload +/// @param y1 party 1's share of the payload +/// @return the opened shares and correction words +template +HEDLEY_WARN_UNUSED_RESULT +auto geneval_point(arith_output_t, InputT x0, InputT x1, InputT query, + ds_randomness rng, OutputT y0, OutputT y1) +{ + return detail::geneval_run(false, true, x0, x1, + std::vector{query}, rng.root, rng.pad, y0, y1); +} + +/// @brief Additive index and additive payload shares. +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param query the query point +/// @param rng the Doerner–Shelat randomness tapes +/// @param y0 party 0's share of the payload +/// @param y1 party 1's share of the payload +/// @return the opened shares and correction words +template +HEDLEY_WARN_UNUSED_RESULT +auto geneval_point(arith_input_t, arith_output_t, InputT x0, InputT x1, + InputT query, ds_randomness rng, OutputT y0, OutputT y1) +{ + return detail::geneval_run(true, true, x0, x1, + std::vector{query}, rng.root, rng.pad, y0, y1); +} + +/// @} + +/// @brief Geneval on the inclusive interval `[from, to]`. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param from the inclusive start of the range +/// @param to the `to` +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the `y` +/// @return Geneval on the inclusive interval `[from, to]` template (), rng.root, rng.pad, y); } -/// Geneval on a public sequence, in the order given. +/// @brief Geneval on a public sequence, in the order given. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam ForwardIterator forward iterator type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the `y` +/// @return Geneval on a public sequence, in the order given template party0; std::vector party1; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::vector> correction_words; + HEDLEY_PRAGMA(GCC diagnostic pop) std::vector correction_advice; std::vector value_cw; std::vector tail_cw; @@ -540,10 +723,30 @@ struct geneval_cmp_result std::size_t live_levels = 0; }; -/// Doerner–Shelat comparison geneval. `x0 XOR x1` is the secret point, in the -/// same share convention as `geneval_point`. `spec` is an `lt` / `leq` / `gt` -/// / `geq` pack. Each endpoint is returned in order as the two parties' -/// `eval_point(cmp, ...)` shares. An empty range opens nothing. +/// @name Comparison geneval +/// @tparam InputT input domain type +/// @tparam ForwardIterator forward iterator type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param rng the Doerner–Shelat randomness tapes +/// @return the opened comparison shares +/// @{ + +/// @brief `x0 XOR x1` is the secret point, in the same share convention as +/// `geneval_point`. `spec` is an `lt` / `leq` / `gt` / `geq` pack. Each +/// endpoint is returned in order as the two parties' `eval_point(cmp, ...)` +/// shares. An empty range opens nothing. +/// @tparam Spec comparison or interval specification +/// @param x0 party 0's share of the secret point +/// @param x1 party 1's share of the secret point +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param rng the Doerner–Shelat randomness tapes +/// @param spec the comparison specification template inline constexpr bool args_have_cmp_v = (is_cmp_spec_v> || ...); +template +inline constexpr bool args_have_verifiable_v = + (is_verifiable_tag_v> || ...); + +template +inline constexpr bool args_have_extractable_v = + (is_extractable_tag_v> || ...); + template inline constexpr bool args_have_eq_v = (is_eq_spec_v> || ...); @@ -91,15 +99,16 @@ constexpr std::size_t forced_cmp_out_bits_sum() noexcept return std::max(m, forced_cmp_out_bits_sum()); } -/// Comparison output group width (bits) forced by any `lt`/`leq`/`gt`/`geq` +/// @brief Comparison output group width (bits) forced by any `lt`/`leq`/`gt`/`geq` /// (or `_at`) spec in the pack — 0 when there is no comparison channel. template inline constexpr std::size_t forced_cmp_out_bits_v = forced_cmp_out_bits_sum(); -/// True when the (single) comparison spec in the pack carries a wildcard +/// @brief True when the (single) comparison spec in the pack carries a wildcard /// payload (`lt(dpf::wildcard, ...)` etc.) — the δ is assigned after /// keygen via `dpf::assign_cmp`. +/// @tparam A alignment of the rebound allocator template >> struct arg_is_wild_cmp : std::false_type {}; template @@ -144,13 +153,18 @@ inline uint64_t paint_fn_adapter(std::size_t matched, uint64_t prefix, bool leaf return (*static_cast(ctx))(matched, prefix, leaf); } -/// Runtime description of one comparison channel peeled from `make_dpf` args. +/// @brief Runtime description of one comparison channel peeled from `make_dpf` args. struct dcf_runtime_spec { std::size_t prefix = 0; // 0 => full input bitlength uint64_t beta = 0; // if_true - if_false uint64_t false_value = 0; uint64_t mask = ~0ULL; + /// @brief Payload group wider than the masked `uint64_t` ring, or a product + /// (`dpf::vec`) / XOR group. `beta_g` is δ and `false_g` is `if_false`. + bool custom = false; + detail::group_elem beta_g{}; + detail::group_elem false_g{}; cmp_kind kind = cmp_kind::lt; bool is_wildcard = false; // payload assigned after keygen (δ unknown now) std::size_t length_bits = 0; @@ -185,6 +199,10 @@ auto flatten_one(Arg && arg) { return std::tuple<>{}; } + else if constexpr (is_verifiable_tag_v || is_extractable_tag_v) + { + return std::tuple<>{}; + } else if constexpr (is_idpf_v) { return expand_idpf(std::forward(arg)); @@ -232,14 +250,35 @@ void collect_cmp_one(dcf_runtime_spec & spec, bool & found, Arg && arg) if constexpr (spec_has_paint_fn::value) spec.paint = arg.fn; using Beta = typename A::beta_type; + using Concrete = dpf::concrete_type_t; constexpr auto bits = [] { - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) return std::size_t{1}; else - return utils::bitlength_of_v; + return utils::bitlength_of_v; }(); spec.mask = detail::dcf_impl::default_mask_for_bits(bits); - if constexpr (dpf::is_wildcard_v) + if constexpr (detail::cmp_group_info::custom) + { + spec.custom = true; + const auto layout = detail::group_layout(); + if constexpr (dpf::is_wildcard_v) + { + spec.is_wildcard = true; + spec.beta_g = detail::group_zero(layout); + spec.false_g = detail::group_zero(layout); + } + else + { + spec.beta_g = detail::group_sub( + detail::group_from_beta(arg.if_true), + detail::group_from_beta(arg.if_false)); + spec.false_g = detail::group_from_beta(arg.if_false); + } + spec.beta = 0; + spec.false_value = 0; + } + else if constexpr (dpf::is_wildcard_v) { // Payload is unknown at keygen: keep δ = if_false = 0 (open the // value CWs for the trivial payload) and record that this key's @@ -318,6 +357,83 @@ void zip_install(OutL & out_leaves, OutB & out_beavers, (assign_tuple_element(std::get(out_beavers), std::get(in_beavers)), ...); } +template +auto concrete_placed_value(Placed & p) +{ + using stored = typename Placed::stored_type; + if constexpr (dpf::is_arith_beta_v) + { + using T = typename Placed::output_type; + if constexpr (utils::has_characteristic_two_v) + return static_cast(p.value.y0 ^ p.value.y1); + else + return static_cast(p.value.y0 + p.value.y1); + } + else + { + return p.value; + } +} + +template +constexpr bool placed_has_arith_beta_impl(std::index_sequence) +{ + return (dpf::is_arith_beta_v< + typename std::tuple_element_t::stored_type> + || ...); +} + +template +constexpr bool placed_has_arith_beta() +{ + constexpr auto n = std::tuple_size_v; + if constexpr (n == 0) + return false; + else + return placed_has_arith_beta_impl( + std::make_index_sequence{}); +} + +template +using group_outputs_t = std::tuple< + typename std::tuple_element_t::output_type...>; + +template +void overwrite_arith_slot_one(CwProtocol & proto, const SeedT & s0, + const SeedT & s1, uint8_t t0, uint8_t t1, std::size_t pos_base, + std::size_t lane, PlacedTuple & placed, Leaves0T & leaves0, + Leaves1T & leaves1, std::index_sequence) +{ + using P = std::tuple_element_t; + if constexpr (dpf::is_arith_beta_v) + { + using outs = group_outputs_t; + auto & slot = std::get(placed); + auto cw = proto.template open_arith_leaf( + s0, s1, t0, t1, slot.value.y0, slot.value.y1, pos_base, lane); + std::get(leaves0) = cw; + std::get(leaves1) = cw; + } +} + +template +void overwrite_arith_slots(CwProtocol & proto, const SeedT & s0, + const SeedT & s1, uint8_t t0, uint8_t t1, std::size_t pos_base, + std::size_t lane, PlacedTuple & placed, Leaves0T & leaves0, + Leaves1T & leaves1, std::index_sequence, + std::index_sequence) +{ + (overwrite_arith_slot_one(proto, s0, s1, t0, t1, + pos_base, lane, placed, leaves0, leaves1, + std::index_sequence{}), + ...); +} + template auto call_make_leaves(std::size_t pos_base, InputT lane_x, const SeedT & s0, @@ -325,37 +441,43 @@ auto call_make_leaves(std::size_t pos_base, InputT lane_x, const SeedT & s0, std::index_sequence) { return dpf::make_leaves(lane_x, s0, s1, sign, pos_base, - std::get(placed).value...); + concrete_placed_value(std::get(placed))...); } template auto empty_leaves(std::index_sequence) { using node = typename ExteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return std::make_tuple(dpf::leaf_node_t::output_type>{}...); + HEDLEY_PRAGMA(GCC diagnostic pop) } template auto empty_beavers(std::index_sequence) { using node = typename ExteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return std::make_tuple(dpf::beaver< dpf::is_wildcard_v< typename std::tuple_element_t::output_type>, node, concrete_type_t< typename std::tuple_element_t::output_type>>{}...); + HEDLEY_PRAGMA(GCC diagnostic pop) } template + typename Beavers1T, typename CwProtocol = void> void gen_group(InputT x, const typename InteriorPRG::block_type & s0, - const typename InteriorPRG::block_type & s1, bool sign0, PlacedTuple & placed, - Leaves0T & leaves0, Beavers0T & beavers0, Leaves1T & leaves1, - Beavers1T & beavers1) + const typename InteriorPRG::block_type & s1, uint8_t t0, uint8_t t1, + PlacedTuple & placed, Leaves0T & leaves0, Beavers0T & beavers0, + Leaves1T & leaves1, Beavers1T & beavers1, CwProtocol * proto = nullptr) { constexpr std::size_t n = std::tuple_size_v; constexpr std::size_t bitlen = utils::bitlength_of_v; @@ -373,6 +495,7 @@ void gen_group(InputT x, const typename InteriorPRG::block_type & s0, } } InputT lane_x = lane_input(x, prefix, bitlen); + const bool sign0 = static_cast(t0 & 1u); auto built = call_make_leaves(pos_base, lane_x, s0, s1, sign0, placed, idxs{}); @@ -381,6 +504,25 @@ void gen_group(InputT x, const typename InteriorPRG::block_type & s0, idxs{}, std::make_index_sequence{}); zip_install(leaves1, beavers1, built.second.first, built.second.second, idxs{}, std::make_index_sequence{}); + + if constexpr (!std::is_void_v) + { + if (proto != nullptr) + { + constexpr auto to_int = utils::to_integral_type{}; + const std::size_t lane = static_cast(to_int(lane_x)); + overwrite_arith_slots( + *proto, s0, s1, t0, t1, pos_base, lane, placed, leaves0, + leaves1, idxs{}, std::make_index_sequence{}); + } + } + else + { + (void)t1; + (void)proto; + static_assert(!placed_has_arith_beta_impl(idxs{}), + "arith_beta payloads require Doerner–Shelat gen (CwProtocol)"); + } } template @@ -394,13 +536,13 @@ template void gen_all_groups(InputT x, const typename InteriorPRG::block_type & s0, - const typename InteriorPRG::block_type & s1, bool sign0, PlacedTuple & placed, - Leaves0T & leaves0, Beavers0T & beavers0, Leaves1T & leaves1, - Beavers1T & beavers1, + const typename InteriorPRG::block_type & s1, uint8_t t0, uint8_t t1, + PlacedTuple & placed, Leaves0T & leaves0, Beavers0T & beavers0, + Leaves1T & leaves1, Beavers1T & beavers1, std::index_sequence) { (gen_group( - x, s0, s1, sign0, placed, leaves0, beavers0, leaves1, beavers1), + x, s0, s1, t0, t1, placed, leaves0, beavers0, leaves1, beavers1), ...); } @@ -411,16 +553,20 @@ auto wrap_leaves(LeavesT & leaves, BeaversT & beavers, std::index_sequence) { using node = typename ExteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return std::make_tuple( dpf::leaf_wrapper< typename std::tuple_element_t::output_type, node>(std::get(leaves), std::get(beavers))...); + HEDLEY_PRAGMA(GCC diagnostic pop) } template + std::size_t CmpBlock = 0, bool CmpIdcf = false, + bool IsVerifiable = false, bool IsExtractable = false> auto make_incremental_impl(InputT x, PlacedTuple placed, root_sampler_t root_sampler, const dcf_runtime_spec * cmp_spec = nullptr); @@ -434,8 +580,11 @@ namespace incr { -/// Map kind → flags. Tree keep-path stays on α; leq/gt plant δ on that path +/// @brief Map kind → flags. Tree keep-path stays on α; leq/gt plant δ on that path /// via `include_eq`. Domain-edge α yields trivial always-true/false. +/// @param ch the `ch` +/// @param thresh the `thresh` +/// @param nbits the width in bits inline void adjust_cmp_threshold(detail::cmp_meta & ch, unsigned __int128 & thresh, std::size_t nbits) { @@ -474,10 +623,15 @@ inline void adjust_cmp_threshold(detail::cmp_meta & ch, ch.trivial = cmp_trivial::none; } -/// Split the constant absorb into party shares. `target` is `if_false` for +/// @brief Split the constant absorb into party shares. `target` is `if_false` for /// lt/leq, or `δ + if_false` when the path-sum is inverted (geq/gt), or the /// full if_true / if_false for trivial domain edges. Clears nothing — caller /// must not put δ on the key. +/// @param target the opened payload target +/// @param mask the bit mask +/// @param r the `r` +/// @param add0 the `add0` +/// @param add1 the `add1` HEDLEY_NO_THROW inline void split_cmp_addend(uint64_t target, uint64_t mask, uint64_t r, uint64_t & add0, uint64_t & add1) noexcept @@ -489,7 +643,12 @@ inline void split_cmp_addend(uint64_t target, uint64_t mask, uint64_t r, add1 = (target + neg_m(r, mask)) & mask; } -/// Typed overload: write party-0 / party-1 additive shares of the absorb. +/// @brief Typed overload: write party-0 / party-1 additive shares of the absorb. +/// @param target the opened payload target +/// @param mask the bit mask +/// @param r the `r` +/// @param add0 the `add0` +/// @param add1 the `add1` HEDLEY_NO_THROW inline void split_cmp_addend(uint64_t target, uint64_t mask, uint64_t r, additive_share & add0, @@ -509,7 +668,8 @@ auto extract_addends(const PlacedTuple & placed, std::index_sequence) template + bool CmpWild, std::size_t CmpBlock, bool CmpIdcf, + bool IsVerifiable, bool IsExtractable> auto make_incremental_impl(InputT x, PlacedTuple placed, root_sampler_t root_sampler, const dcf_runtime_spec * cmp_spec) @@ -518,7 +678,7 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, "idcf uses the per-level path, not blocked checkpoints"); using key_type = incr_dpf_key_of_t; + CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable>; using interior_node = typename key_type::interior_node; using input_type = typename key_type::input_type; constexpr auto depth = key_type::depth; @@ -526,19 +686,37 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, using MetaHolder = meta_holder; using namespace detail::dcf_impl; + if constexpr (IsExtractable) + { + static_assert(n > 0, "extractable requires at least one output"); + [](std::index_sequence) { + static_assert((extractable_codomain_ok_v< + typename key_type::template concrete_output_type> && ...), + "extractable: each output must be fp61 or >= 128 bits"); + }(std::make_index_sequence{}); + } + utils::flip_msb_if_signed_integral(x); - const interior_node root[2] = {dpf::unset_lo_bit(root_sampler()), - dpf::set_lo_bit(root_sampler())}; + using tree = dpf::tree_traits; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + interior_node root[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(root, root_sampler); typename key_type::correction_words_array correction_words{}; typename key_type::correction_advice_array correction_advice{}; + typename key_type::correction_seeds_array correction_seeds{}; interior_node parent[2] = {root[0], root[1]}; auto mask = key_type::msb_mask; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::array snap0{}; std::array snap1{}; + HEDLEY_PRAGMA(GCC diagnostic pop) std::array snap_sign{}; std::array need_snap{}; for (std::size_t i = 0; i <= depth; ++i) @@ -554,6 +732,7 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, } detail::cmp_meta cmp{}; + // ... rest continues from existing body — patched via smaller edits below typename key_type::value_cw_array value_cws{}; typename key_type::tail_array tail{}; typename key_type::tail_array tail_coeff{}; @@ -591,8 +770,29 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, if (CmpBlock > 0 && is_paint_kind(cmp.kind)) throw std::invalid_argument( "path recipes use the per-level comparison channel"); + if (cmp_spec->custom && (CmpBlock > 0 || is_paint_kind(cmp.kind))) + throw std::invalid_argument( + "this comparison payload uses the per-level channel"); } + const bool custom_cmp = cmp_spec != nullptr && cmp_spec->custom; + detail::group_elem g_delta = custom_cmp + ? cmp_spec->beta_g : detail::group_elem{}; + detail::group_elem g_false = custom_cmp + ? cmp_spec->false_g : detail::group_elem{}; + detail::group_elem g_va = detail::group_zero(g_delta); + detail::group_elem g_va1 = detail::group_zero(g_delta); + detail::group_elem g_one = detail::group_one(g_delta); + detail::group_elem g_on = detail::group_zero(g_delta); + detail::group_elem g_on_unit = detail::group_zero(g_delta); + if (custom_cmp && cmp.include_eq && !is_paint_kind(cmp.kind)) + { + g_on = g_delta; + g_on_unit = g_one; + } + typename key_type::value_cw_word g_last{}; + typename key_type::value_cw_word g_last_coeff{}; + const paint_callback paint_cb = (cmp_spec != nullptr && cmp_spec->paint) ? &paint_fn_adapter : nullptr; @@ -630,37 +830,72 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, } } }; + auto snap_prefix_group = [&](std::size_t at) { + if constexpr (CmpIdcf) + { + if (!cmp.incremental || cmp.trivial != cmp_trivial::none) + return; + const auto word = detail::group_final_cw(parent[0], + parent[1], static_cast(dpf::get_lo_bit(parent[1])), + g_va, g_on); + prefix_cw[at] = detail::group_to_word(word); + if constexpr (CmpWild) + { + const auto w1 = detail::group_final_cw(parent[0], + parent[1], static_cast(dpf::get_lo_bit(parent[1])), + g_va1, g_on_unit); + prefix_coeff[at] = detail::group_to_word( + detail::group_sub(w1, word)); + } + } + }; if constexpr (CmpIdcf) { - if (cmp.active) + if (cmp.active && !custom_cmp) snap_prefix(0); + if (cmp.active && custom_cmp) + snap_prefix_group(0); } for (std::size_t level = 0; level < depth; ++level, mask >>= 1) { bool bit = !!(mask & x); - bool advice[2]; - advice[0] = dpf::get_lo_bit_and_clear_lo_2bits(parent[0]); - advice[1] = dpf::get_lo_bit_and_clear_lo_2bits(parent[1]); + const bool is_last = tree::is_last_level(level, depth); + const bool advice0 = static_cast(dpf::get_lo_bit(parent[0])); + const bool advice1 = static_cast(dpf::get_lo_bit(parent[1])); - auto child0 = InteriorPRG::eval01(parent[0]); - auto child1 = InteriorPRG::eval01(parent[1]); - interior_node child[2] = {child0[0] ^ child1[0], child0[1] ^ child1[1]}; + auto child0 = tree::expand(parent[0], is_last); + auto child1 = tree::expand(parent[1], is_last); + // Value Convert stretch (HT mid: two-tweak, independent of seed expand). + const auto val0 = tree::expand_value(parent[0]); + const auto val1 = tree::expand_value(parent[1]); - bool t[2] = {static_cast(dpf::get_lo_bit(child[0]) ^ !bit), - static_cast(dpf::get_lo_bit(child[1]) ^ bit)}; - auto cw = dpf::set_lo_bit(child[!bit], t[bit]); - parent[0] = dpf::xor_if(child0[bit], cw, advice[0]); - parent[1] = dpf::xor_if(child1[bit], cw, advice[1]); + interior_node cw{}; + psnip_uint8_t tpack = 0; + tree::make_cw(cw, tpack, child0, child1, parent[0], parent[1], bit, + is_last); - correction_words[level] = child[!bit]; - correction_advice[level] = - static_cast(t[1] << 1) | t[0]; + parent[0] = tree::advance(parent[0], child0, cw, tpack, bit, advice0, + is_last); + parent[1] = tree::advance(parent[1], child1, cw, tpack, bit, advice1, + is_last); + + correction_words[level] = cw; + correction_advice[level] = tpack; + if constexpr (IsVerifiable) + { + // Prefix bits of α through this level (same labelling as eval fold). + const auto prefix = static_cast( + utils::to_integral_type{}(x) + >> (utils::bitlength_of_v - (level + 1))); + correction_seeds[level] = detail::vdpf::make_cs(level, prefix, + parent[0], parent[1]); + } if constexpr (CmpBlock == 0) { if (cmp.active && cmp.trivial == cmp_trivial::none - && level < cmp_nbits) + && level < cmp_nbits && !custom_cmp) { const int ai = static_cast( (thresh >> (cmp_nbits - 1 - level)) & 1); @@ -670,18 +905,18 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, const uint64_t unit = detail::dcf_impl::paint_unit(cmp.kind, level, thresh, cmp_nbits, paint_length_bits, false, paint_cb, paint_ctx); - base = detail::dcf_impl::make_value_cw_planted(child0[0], - child0[1], child1[0], child1[1], - static_cast(advice[0]), - static_cast(advice[1]), ai, Va, + base = detail::dcf_impl::make_value_cw_planted(val0[0], + val0[1], val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, Va, detail::dcf_impl::scale_plant(unit, delta, cmp.mask), cmp.mask); if constexpr (CmpWild) { const uint64_t v1 = detail::dcf_impl::make_value_cw_planted( - child0[0], child0[1], child1[0], child1[1], - static_cast(advice[0]), - static_cast(advice[1]), ai, Va1, + val0[0], val0[1], val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, Va1, detail::dcf_impl::scale_plant(unit, 1ULL, cmp.mask), cmp.mask); value_cw_coeff[level] = @@ -692,18 +927,18 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, } else { - base = make_value_cw(child0[0], child0[1], - child1[0], child1[1], - static_cast(advice[0]), - static_cast(advice[1]), ai, Va, delta, cmp.mask); + base = make_value_cw(val0[0], val0[1], + val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, Va, delta, cmp.mask); if constexpr (CmpWild) { // Same recurrence with β = 1 on a parallel accumulator; the // value CW is affine in β so `coeff = value_cw(1) − base`. - const uint64_t v1 = make_value_cw(child0[0], child0[1], - child1[0], child1[1], - static_cast(advice[0]), - static_cast(advice[1]), ai, Va1, 1ULL, cmp.mask); + const uint64_t v1 = make_value_cw(val0[0], val0[1], + val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, Va1, 1ULL, cmp.mask); value_cw_coeff[level] = static_cast( (v1 + detail::dcf_impl::neg_m(base, cmp.mask)) @@ -714,6 +949,29 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, static_cast(base); snap_prefix(level + 1); } + else if (cmp.active && cmp.trivial == cmp_trivial::none + && level < cmp_nbits && custom_cmp) + { + const int ai = static_cast( + (thresh >> (cmp_nbits - 1 - level)) & 1); + const auto base = detail::group_value_cw(val0[0], + val0[1], val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, g_va, g_delta); + value_cws[level] = + detail::group_to_word(base); + if constexpr (CmpWild) + { + const auto v1 = detail::group_value_cw(val0[0], + val0[1], val1[0], val1[1], + static_cast(advice0), + static_cast(advice1), ai, g_va1, g_one); + value_cw_coeff[level] = + detail::group_to_word( + detail::group_sub(v1, base)); + } + snap_prefix_group(level + 1); + } } else if (cmp.active && cmp.trivial == cmp_trivial::none) { @@ -765,7 +1023,7 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, if constexpr (CmpBlock == 0) { if (cmp.active && cmp.trivial == cmp_trivial::none - && level + 1 == cmp_nbits) + && level + 1 == cmp_nbits && !custom_cmp) { cw_last = make_final_cw(parent[0], parent[1], static_cast(dpf::get_lo_bit(parent[1])), Va, cmp.mask, @@ -779,12 +1037,31 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, (l1 + detail::dcf_impl::neg_m(cw_last, cmp.mask)) & cmp.mask; } } + else if (cmp.active && cmp.trivial == cmp_trivial::none + && level + 1 == cmp_nbits && custom_cmp) + { + g_last = detail::group_to_word( + detail::group_final_cw(parent[0], parent[1], + static_cast(dpf::get_lo_bit(parent[1])), g_va, g_on)); + if constexpr (CmpWild) + { + const auto l1 = detail::group_final_cw(parent[0], + parent[1], static_cast(dpf::get_lo_bit(parent[1])), + g_va1, g_on_unit); + g_last_coeff = detail::group_to_word( + detail::group_sub(l1, + detail::group_from_word(g_last, g_delta))); + } + } } } + // `key_type::num_outputs` is a static constexpr, so the loop bound is a + // constant expression. A local `n` is not usable here: capturing it is not + // a constant, and the loop would lower to a goto. constexpr std::size_t ngroups = [] { std::size_t m = 0; - for (std::size_t i = 0; i < n; ++i) + for (std::size_t i = 0; i < key_type::num_outputs; ++i) m = std::max(m, key_type::meta[i].group_id + 1); return m; }(); @@ -801,19 +1078,27 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, constexpr auto order = build_group_order(key_type::meta, n); + using leaf_prg = std::conditional_t, ExteriorPRG>; + for_each_index(std::make_index_sequence{}, [&](auto oi) { constexpr std::size_t G = order[decltype(oi)::value]; constexpr std::size_t lvl = [] { - for (std::size_t i = 0; i < n; ++i) + for (std::size_t i = 0; i < key_type::num_outputs; ++i) { if (key_type::meta[i].group_id == G) return key_type::meta[i].tree_level; } return std::size_t{0}; }(); - gen_group( + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + gen_group( x, dpf::unset_lo_2bits(snap0[lvl]), dpf::unset_lo_2bits(snap1[lvl]), - snap_sign[lvl], placed, leaves0, beavers0, leaves1, beavers1); + static_cast(snap_sign[lvl]), + static_cast(dpf::get_lo_bit(snap1[lvl])), placed, leaves0, + beavers0, leaves1, beavers1); + HEDLEY_PRAGMA(GCC diagnostic pop) }); auto wrap0 = wrap_leaves( @@ -822,7 +1107,9 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, leaves1, beavers1, std::make_index_sequence{}); uint64_t cmp_add0 = 0, cmp_add1 = 0; - if (cmp.active) + typename key_type::value_cw_word g_add0{}; + typename key_type::value_cw_word g_add1{}; + if (cmp.active && !custom_cmp) { uint64_t target = false_value; if (cmp.trivial == cmp_trivial::always_true) @@ -838,21 +1125,39 @@ auto make_incremental_impl(InputT x, PlacedTuple placed, [&]() -> interior_node { return root_sampler(); }); split_cmp_addend(target, cmp.mask, r, cmp_add0, cmp_add1); } + else if (cmp.active && custom_cmp) + { + detail::group_elem target = g_false; + if (cmp.trivial == cmp_trivial::always_true || cmp.eval_as_ge) + target = detail::group_add(g_delta, g_false); + const auto blind = detail::group_from_node(root_sampler(), g_delta); + g_add0 = detail::group_to_word(blind); + g_add1 = detail::group_to_word( + detail::group_sub(target, blind)); + } input_type off0{}, off1{}; auto adds = extract_addends(placed, std::make_index_sequence{}); - return dpf::make_party_key_pair( - key_type{root[0], correction_words, correction_advice, std::move(wrap0), - off0, cmp, value_cws, cw_last, cmp_add0, adds, value_cw_coeff, - cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff}, - key_type{root[1], correction_words, correction_advice, std::move(wrap1), - off1, cmp, value_cws, cw_last, cmp_add1, adds, value_cw_coeff, - cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff}); + key_type key0{root[0], correction_words, correction_advice, std::move(wrap0), + off0, cmp, value_cws, cw_last, cmp_add0, adds, value_cw_coeff, + cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff, + correction_seeds}; + key_type key1{root[1], correction_words, correction_advice, std::move(wrap1), + off1, cmp, value_cws, cw_last, cmp_add1, adds, value_cw_coeff, + cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff, + correction_seeds}; + if (custom_cmp) + { + key0.set_cmp_scalars(g_last, g_add0, g_last_coeff); + key1.set_cmp_scalars(g_last, g_add1, g_last_coeff); + } + return dpf::make_party_key_pair(std::move(key0), std::move(key1)); } template auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, @@ -868,7 +1173,7 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, using key_type = incr_dpf_key_of_t; + CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable>; using interior_node = typename key_type::interior_node; using input_type = typename key_type::input_type; constexpr auto depth = key_type::depth; @@ -878,19 +1183,32 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, proto.encode_walk_shares(x0, x1, arith); - const interior_node root0 = - dpf::unset_lo_bit(static_cast(root_sampler())); - const interior_node root1 = - dpf::set_lo_bit(static_cast(root_sampler())); + using tree = dpf::tree_traits; +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + interior_node roots[2]; +HEDLEY_PRAGMA(GCC diagnostic pop) + tree::root_init(roots, [&]() -> interior_node { + return static_cast(root_sampler()); + }); + const interior_node root0 = roots[0]; + const interior_node root1 = roots[1]; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") ds_gen_state st; + HEDLEY_PRAGMA(GCC diagnostic pop) st.init(root0, root1); typename key_type::correction_words_array correction_words{}; typename key_type::correction_advice_array correction_advice{}; + typename key_type::correction_seeds_array correction_seeds{}; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::array snap0{}; std::array snap1{}; + HEDLEY_PRAGMA(GCC diagnostic pop) std::array snap_sign{}; std::array need_snap{}; for (std::size_t i = 0; i <= depth; ++i) @@ -943,6 +1261,9 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, if (CmpBlock > 0 && is_paint_kind(cmp.kind)) throw std::invalid_argument( "path recipes use the per-level comparison channel"); + if (cmp_spec->custom && (CmpBlock > 0 || is_paint_kind(cmp.kind))) + throw std::invalid_argument( + "this comparison payload uses the per-level channel"); cmp_st.active = cmp.active; cmp_st.nbits = cmp_nbits; cmp_st.mask = cmp.mask; @@ -965,6 +1286,24 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, } } + const bool custom_cmp = cmp_spec != nullptr && cmp_spec->custom; + detail::group_elem g_delta = custom_cmp + ? cmp_spec->beta_g : detail::group_elem{}; + detail::group_elem g_false = custom_cmp + ? cmp_spec->false_g : detail::group_elem{}; + detail::group_elem g_va = detail::group_zero(g_delta); + detail::group_elem g_va1 = detail::group_zero(g_delta); + detail::group_elem g_one = detail::group_one(g_delta); + detail::group_elem g_on = detail::group_zero(g_delta); + detail::group_elem g_on_unit = detail::group_zero(g_delta); + if (custom_cmp && cmp.include_eq && !is_paint_kind(cmp.kind)) + { + g_on = g_delta; + g_on_unit = g_one; + } + typename key_type::value_cw_word g_last{}; + typename key_type::value_cw_word g_last_coeff{}; + typename key_type::prefix_cw_array prefix_cw{}; typename key_type::prefix_cw_array prefix_coeff{}; const uint64_t on_path = [&]() -> uint64_t { @@ -988,9 +1327,12 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, { if (!cmp.incremental || cmp.trivial != cmp_trivial::none) return; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") const uint64_t word = proto.open_final_cw(st.seed0(), st.seed1(), static_cast(dpf::get_lo_bit(st.seed1())), cmp_st.Va, cmp.mask, on_path); + HEDLEY_PRAGMA(GCC diagnostic pop) prefix_cw[at] = static_cast(word); if constexpr (CmpWild) { @@ -1002,10 +1344,34 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, } } }; + auto snap_prefix_group = [&](std::size_t at) { + if constexpr (CmpIdcf) + { + if (!cmp.incremental || cmp.trivial != cmp_trivial::none) + return; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + const auto word = detail::group_final_cw(st.seed0(), + st.seed1(), static_cast(dpf::get_lo_bit(st.seed1())), + g_va, g_on); + HEDLEY_PRAGMA(GCC diagnostic pop) + prefix_cw[at] = detail::group_to_word(word); + if constexpr (CmpWild) + { + const auto w1 = detail::group_final_cw(st.seed0(), + st.seed1(), static_cast(dpf::get_lo_bit(st.seed1())), + g_va1, g_on_unit); + prefix_coeff[at] = detail::group_to_word( + detail::group_sub(w1, word)); + } + } + }; if constexpr (CmpIdcf) { - if (cmp.active) + if (cmp.active && !custom_cmp) snap_prefix(0); + if (cmp.active && custom_cmp) + snap_prefix_group(0); } auto mask = key_type::msb_mask; @@ -1014,11 +1380,45 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, uint64_t vcw = 0; if constexpr (CmpBlock == 0) { - ds_advance_level(st, x0, x1, mask, level, proto, + if (custom_cmp && cmp_st.active && cmp_st.trivial == cmp_trivial::none + && level < cmp_st.nbits) + { + using tree = dpf::tree_traits; + auto s0 = st.seed0(); + auto s1 = st.seed1(); + const auto a0 = static_cast(dpf::get_lo_bit(s0)); + const auto a1 = static_cast(dpf::get_lo_bit(s1)); + auto v0 = tree::expand_value(s0); + auto v1 = tree::expand_value(s1); + const int ai = static_cast( + (cmp_st.thresh >> (cmp_st.nbits - 1 - level)) & 1); + const auto base = detail::group_value_cw(v0[0], + v0[1], v1[0], v1[1], a0, a1, ai, g_va, g_delta); + value_cws[level] = + detail::group_to_word(base); + if constexpr (CmpWild) + { + const auto v1w = detail::group_value_cw(v0[0], + v0[1], v1[0], v1[1], a0, a1, ai, g_va1, g_one); + value_cw_coeff[level] = + detail::group_to_word( + detail::group_sub(v1w, base)); + } + } + ds_advance_level(st, x0, x1, mask, level, depth, proto, correction_words[level], correction_advice[level], - cmp_st.active ? &vcw : nullptr, - cmp_st.active ? &cmp_st : nullptr); - if (cmp_st.active && cmp_st.trivial == cmp_trivial::none + (!custom_cmp && cmp_st.active) ? &vcw : nullptr, + (!custom_cmp && cmp_st.active) ? &cmp_st : nullptr); + if constexpr (IsVerifiable) + { + const auto prefix = static_cast( + utils::to_integral_type{}( + utils::xor_input_shares(x0, x1)) + >> (utils::bitlength_of_v - (level + 1))); + correction_seeds[level] = detail::vdpf::make_cs(level, prefix, + st.seed0(), st.seed1()); + } + if (!custom_cmp && cmp_st.active && cmp_st.trivial == cmp_trivial::none && level < cmp_st.nbits) { value_cws[level] = @@ -1031,11 +1431,25 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, } snap_prefix(level + 1); } + else if (custom_cmp && cmp_st.active + && cmp_st.trivial == cmp_trivial::none && level < cmp_st.nbits) + { + snap_prefix_group(level + 1); + } } else { - ds_advance_level(st, x0, x1, mask, level, proto, + ds_advance_level(st, x0, x1, mask, level, depth, proto, correction_words[level], correction_advice[level]); + if constexpr (IsVerifiable) + { + const auto prefix = static_cast( + utils::to_integral_type{}( + utils::xor_input_shares(x0, x1)) + >> (utils::bitlength_of_v - (level + 1))); + correction_seeds[level] = detail::vdpf::make_cs(level, prefix, + st.seed0(), st.seed1()); + } if (cmp_st.active && cmp_st.trivial == cmp_trivial::none) { using sched = detail::blocked::schedule; @@ -1090,7 +1504,7 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, if constexpr (CmpBlock == 0) { if (cmp_st.active && cmp_st.trivial == cmp_trivial::none - && level + 1 == cmp_st.nbits) + && level + 1 == cmp_st.nbits && !custom_cmp) { cw_last = proto.open_final_cw(st.seed0(), st.seed1(), static_cast(dpf::get_lo_bit(st.seed1())), cmp_st.Va, @@ -1104,12 +1518,27 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, (l1 + neg_m(cw_last, cmp_st.mask)) & cmp_st.mask; } } + else if (cmp_st.active && cmp_st.trivial == cmp_trivial::none + && level + 1 == cmp_st.nbits && custom_cmp) + { + g_last = detail::group_to_word( + detail::group_final_cw(st.seed0(), st.seed1(), + static_cast(dpf::get_lo_bit(st.seed1())), g_va, g_on)); + if constexpr (CmpWild) + { + const auto l1 = detail::group_final_cw(st.seed0(), + st.seed1(), static_cast(dpf::get_lo_bit(st.seed1())), + g_va1, g_on_unit); + g_last_coeff = detail::group_to_word( + detail::group_sub(l1, detail::group_from_word(g_last, g_delta))); + } + } } } constexpr std::size_t ngroups = [] { std::size_t m = 0; - for (std::size_t i = 0; i < n; ++i) + for (std::size_t i = 0; i < key_type::num_outputs; ++i) m = std::max(m, key_type::meta[i].group_id + 1); return m; }(); @@ -1129,21 +1558,28 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, // Leaf phase: hand both point shares to the protocol. In the local joint // simulation `open_leaf_group` reconstructs `x` internally and runs // `make_leaves` per group via this closure; the gen body never forms `x`. + using leaf_prg = std::conditional_t, ExteriorPRG>; proto.open_leaf_group(x0, x1, [&](input_type x) { for_each_index(std::make_index_sequence{}, [&](auto oi) { constexpr std::size_t G = order[decltype(oi)::value]; constexpr std::size_t lvl = [] { - for (std::size_t i = 0; i < n; ++i) + for (std::size_t i = 0; i < key_type::num_outputs; ++i) { if (key_type::meta[i].group_id == G) return key_type::meta[i].tree_level; } return std::size_t{0}; }(); - gen_group(x, dpf::unset_lo_2bits(snap0[lvl]), - dpf::unset_lo_2bits(snap1[lvl]), snap_sign[lvl], placed, - leaves0, beavers0, leaves1, beavers1); + dpf::unset_lo_2bits(snap1[lvl]), + static_cast(snap_sign[lvl]), + static_cast(dpf::get_lo_bit(snap1[lvl])), placed, + leaves0, beavers0, leaves1, beavers1, &proto); + HEDLEY_PRAGMA(GCC diagnostic pop) }); }); @@ -1153,7 +1589,9 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, leaves1, beavers1, std::make_index_sequence{}); uint64_t cmp_add0 = 0, cmp_add1 = 0; - if (cmp.active) + typename key_type::value_cw_word g_add0{}; + typename key_type::value_cw_word g_add1{}; + if (cmp.active && !custom_cmp) { uint64_t target = false_value; if (cmp.trivial == cmp_trivial::always_true) @@ -1174,16 +1612,34 @@ auto make_incremental_ds_impl(bool arith, InputT x0, InputT x1, }); split_cmp_addend(target, cmp.mask, r, cmp_add0, cmp_add1); } + else if (cmp.active && custom_cmp) + { + detail::group_elem target = g_false; + if (cmp.trivial == cmp_trivial::always_true || cmp.eval_as_ge) + target = detail::group_add(g_delta, g_false); + const auto blind = detail::group_from_node( + static_cast(root_sampler()), g_delta); + g_add0 = detail::group_to_word(blind); + g_add1 = detail::group_to_word( + detail::group_sub(target, blind)); + } input_type off0{}, off1{}; auto adds = extract_addends(placed, std::make_index_sequence{}); - return dpf::make_party_key_pair( - key_type{root0, correction_words, correction_advice, std::move(wrap0), - off0, cmp, value_cws, cw_last, cmp_add0, adds, value_cw_coeff, - cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff}, - key_type{root1, correction_words, correction_advice, std::move(wrap1), - off1, cmp, value_cws, cw_last, cmp_add1, adds, value_cw_coeff, - cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff}); + key_type key0{root0, correction_words, correction_advice, std::move(wrap0), + off0, cmp, value_cws, cw_last, cmp_add0, adds, value_cw_coeff, + cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff, + correction_seeds}; + key_type key1{root1, correction_words, correction_advice, std::move(wrap1), + off1, cmp, value_cws, cw_last, cmp_add1, adds, value_cw_coeff, + cw_last_coeff, tail, tail_coeff, prefix_cw, prefix_coeff, + correction_seeds}; + if (custom_cmp) + { + key0.set_cmp_scalars(g_last, g_add0, g_last_coeff); + key1.set_cmp_scalars(g_last, g_add1, g_last_coeff); + } + return dpf::make_party_key_pair(std::move(key0), std::move(key1)); } @@ -1215,6 +1671,8 @@ auto make_dpf(InputT && x, OutputTs && ...ys) constexpr bool CW = forced_cmp_wild_v; constexpr std::size_t BK = forced_cmp_block_v; constexpr bool ID = forced_cmp_idcf_v; + constexpr bool V = args_have_verifiable_v; + constexpr bool E = args_have_extractable_v; dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto placed = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, @@ -1227,11 +1685,13 @@ auto make_dpf(InputT && x, OutputTs && ...ys) if (!has_cmp) throw std::invalid_argument("make_dpf: no outputs"); return detail::incr::make_incremental_impl(x, std::move(placed), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), dpf::uniform_sample, cs); } else if constexpr (!args_have_cmp_v && !args_have_eq_v - && detail::incr::is_classic_placed()) + && !args_have_verifiable_v && !args_have_extractable_v + && detail::incr::is_classic_placed() + && !detail::incr::placed_has_arith_beta()) { auto vals = detail::incr::classic_values(placed, std::make_index_sequence>{}); @@ -1245,7 +1705,7 @@ auto make_dpf(InputT && x, OutputTs && ...ys) else { return detail::incr::make_incremental_impl(x, std::move(placed), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), dpf::uniform_sample, cs); } } @@ -1271,6 +1731,8 @@ auto make_dpf(InputT && x, OutputTs && ...ys) constexpr bool CW = forced_cmp_wild_v; constexpr std::size_t BK = forced_cmp_block_v; constexpr bool ID = forced_cmp_idcf_v; + constexpr bool V = args_have_verifiable_v; + constexpr bool E = args_have_extractable_v; dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto placed = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, @@ -1284,10 +1746,12 @@ auto make_dpf(InputT && x, OutputTs && ...ys) if (!has_cmp) throw std::invalid_argument("make_dpf: no outputs"); return detail::incr::make_incremental_impl(x, std::move(placed), seed, cs); + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), seed, cs); } else if constexpr (!args_have_cmp_v && !args_have_eq_v - && detail::incr::is_classic_placed()) + && !args_have_verifiable_v && !args_have_extractable_v + && detail::incr::is_classic_placed() + && !detail::incr::placed_has_arith_beta()) { auto vals = detail::incr::classic_values(placed, std::make_index_sequence>{}); @@ -1302,7 +1766,7 @@ auto make_dpf(InputT && x, OutputTs && ...ys) else { return detail::incr::make_incremental_impl(x, std::move(placed), seed, cs); + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), seed, cs); } } @@ -1326,6 +1790,8 @@ auto make_dpf(InputT && x, root_sampler_t root_sampler, constexpr bool CW = forced_cmp_wild_v; constexpr std::size_t BK = forced_cmp_block_v; constexpr bool ID = forced_cmp_idcf_v; + constexpr bool V = args_have_verifiable_v; + constexpr bool E = args_have_extractable_v; dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto placed = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, @@ -1339,11 +1805,13 @@ auto make_dpf(InputT && x, root_sampler_t root_sampler, if (!has_cmp) throw std::invalid_argument("make_dpf: no outputs"); return detail::incr::make_incremental_impl(x, std::move(placed), root_sampler, + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), root_sampler, cs); } else if constexpr (!args_have_cmp_v && !args_have_eq_v - && detail::incr::is_classic_placed()) + && !args_have_verifiable_v && !args_have_extractable_v + && detail::incr::is_classic_placed() + && !detail::incr::placed_has_arith_beta()) { auto vals = detail::incr::classic_values(placed, std::make_index_sequence>{}); @@ -1358,7 +1826,7 @@ auto make_dpf(InputT && x, root_sampler_t root_sampler, else { return detail::incr::make_incremental_impl(x, std::move(placed), root_sampler, + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(x, std::move(placed), root_sampler, cs); } } @@ -1367,10 +1835,21 @@ auto make_dpf(InputT && x, root_sampler_t root_sampler, // make_dpf_doerner_shelat(x0, x1, ...): classic or incremental // --------------------------------------------------------------------------- -/// Doerner–Shelat keygen with caller-supplied roots and pad stream. -/// The point is `x0 XOR x1` (before the signed-MSB flip `make_dpf` applies). +/// @brief Doerner–Shelat keygen with caller-supplied roots and pad stream. +/// @details The point is `x0 XOR x1` (before the signed-MSB flip `make_dpf` applies). /// `rng.pad` is the Beaver randomness; it cancels and must not draw from /// `uniform_fill` when beaver coins are being matched to `make_dpf`. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param ys the `ys` +/// @return Doerner–Shelat keygen with caller-supplied roots and pad stream template (ys)...); } -/// Doerner–Shelat with additive shares: the point is `x0 + x1` in the input +/// @brief Doerner–Shelat with additive shares: the point is `x0 + x1` in the input /// ring (unsigned wrap; signed MSB flipped after the carry chain). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param ys the `ys` +/// @return Doerner–Shelat with additive shares: the point is `x0 + x1` in the input ring (unsigned +/// wrap; signed MSB flipped after the carry chain) template (ys)...); } +/// @brief XOR-index shares, additively shared payload `y0 + y1 = β` (single concrete). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputT output type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param y0 the `y0` +/// @param y1 the `y1` +/// @return XOR-index shares, additively shared payload `y0 + y1 = β` (single concrete) +template && !is_at_v + && !is_wildcard_v && no_ic_pack_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_output_t, InputT x0, InputT x1, + ds_randomness rng, OutputT y0, OutputT y1) +{ + local_cw_protocol proto{rng.pad}; + return detail::make_dpf_doerner_shelat_impl( + false, true, std::move(x0), std::move(x1), rng.root, proto, + std::move(y0), std::move(y1)); +} + +/// @brief Additive index and additive payload shares (single concrete). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputT output type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param y0 the `y0` +/// @param y1 the `y1` +/// @return Additive index and additive payload shares (single concrete) +template && !is_at_v + && !is_wildcard_v && no_ic_pack_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_input_t, arith_output_t, InputT x0, InputT x1, + ds_randomness rng, OutputT y0, OutputT y1) +{ + local_cw_protocol proto{rng.pad}; + return detail::make_dpf_doerner_shelat_impl( + true, true, std::move(x0), std::move(x1), rng.root, proto, + std::move(y0), std::move(y1)); +} + +/// @brief Shared payloads via `arith_beta` / packs (XOR index). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the `y` +/// @param ys the `ys` +/// @return Shared payloads via `arith_beta` / packs (XOR index) +template || is_at_v || (sizeof...(OutputTs) > 0)) + && no_ic_pack_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_output_t, InputT x0, InputT x1, + ds_randomness rng, OutputT && y, OutputTs && ...ys) +{ + return make_dpf_doerner_shelat( + false, std::move(x0), std::move(x1), std::move(rng), + std::forward(y), std::forward(ys)...); +} + +/// @brief Shared payloads via `arith_beta` / packs (additive index). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param rng the Doerner–Shelat randomness tapes +/// @param y the `y` +/// @param ys the `ys` +/// @return Shared payloads via `arith_beta` / packs (additive index) +template || is_at_v || (sizeof...(OutputTs) > 0)) + && no_ic_pack_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_input_t, arith_output_t, InputT x0, InputT x1, + ds_randomness rng, OutputT && y, OutputTs && ...ys) +{ + return make_dpf_doerner_shelat( + true, std::move(x0), std::move(x1), std::move(rng), + std::forward(y), std::forward(ys)...); +} + template ; constexpr std::size_t BK = forced_cmp_block_v; constexpr bool ID = forced_cmp_idcf_v; + constexpr bool V = args_have_verifiable_v; + constexpr bool E = args_have_extractable_v; dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto placed = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, @@ -1439,11 +2064,13 @@ auto make_dpf_doerner_shelat(bool arith, InputT x0, InputT x1, throw std::invalid_argument("make_dpf_doerner_shelat: no outputs"); local_cw_protocol proto{rng.pad}; return detail::incr::make_incremental_ds_impl(arith, std::move(x0), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(arith, std::move(x0), std::move(x1), rng.root, proto, std::move(placed), cs); } else if constexpr (!args_have_cmp_v && !args_have_eq_v - && detail::incr::is_classic_placed()) + && !args_have_verifiable_v && !args_have_extractable_v + && detail::incr::is_classic_placed() + && !detail::incr::placed_has_arith_beta()) { auto vals = detail::incr::classic_values(placed, std::make_index_sequence>{}); @@ -1460,14 +2087,29 @@ auto make_dpf_doerner_shelat(bool arith, InputT x0, InputT x1, { local_cw_protocol proto{rng.pad}; return detail::incr::make_incremental_ds_impl(arith, std::move(x0), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(arith, std::move(x0), std::move(x1), rng.root, proto, std::move(placed), cs); } } -/// Doerner–Shelat with an injectable `CwProtocol` (local or MPC backend). -/// Signature is `make_dpf_doerner_shelat(x0, x1, root_sampler, proto, y...)` +/// @brief Doerner–Shelat with an injectable `CwProtocol` (local or MPC backend). +/// @details Signature is `make_dpf_doerner_shelat(x0, x1, root_sampler, proto, y...)` /// so it does not collide with the `ds_randomness` or bare-output overloads. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam CwProtocol correction-word protocol +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param root_sampler the `root_sampler` +/// @param proto the `proto` +/// @param y the `y` +/// @param ys the `ys` +/// @return Doerner–Shelat with an injectable `CwProtocol` (local or MPC backend) template ; constexpr std::size_t BK = forced_cmp_block_v; constexpr bool ID = forced_cmp_idcf_v; + constexpr bool V = args_have_verifiable_v; + constexpr bool E = args_have_extractable_v; dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto placed = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, @@ -1542,12 +2186,15 @@ auto make_dpf_doerner_shelat(bool arith, InputT x0, InputT x1, if (!has_cmp) throw std::invalid_argument("make_dpf_doerner_shelat: no outputs"); return detail::incr::make_incremental_ds_impl(arith, std::move(x0), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(arith, std::move(x0), std::move(x1), root_sampler, proto, std::move(placed), cs); } else if constexpr (!args_have_cmp_v + && !args_have_verifiable_v + && !args_have_extractable_v && !args_have_eq_v - && detail::incr::is_classic_placed()) + && detail::incr::is_classic_placed() + && !detail::incr::placed_has_arith_beta()) { auto vals = detail::incr::classic_values(placed, std::make_index_sequence>{}); @@ -1562,12 +2209,23 @@ auto make_dpf_doerner_shelat(bool arith, InputT x0, InputT x1, else { return detail::incr::make_incremental_ds_impl(arith, std::move(x0), + input_type, placed_tuple, CD, CB, CW, BK, ID, V, E>(arith, std::move(x0), std::move(x1), root_sampler, proto, std::move(placed), cs); } } -/// Doerner–Shelat keygen. Roots and pads come from `uniform_sample`. +/// @brief Doerner–Shelat keygen. Roots and pads come from `uniform_sample`. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputTs output ts +/// @tparam OutputTs output ts +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param y the `y` +/// @param ys the `ys` +/// @return Doerner–Shelat keygen template rng{ dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) return make_dpf_doerner_shelat( std::move(x0), std::move(x1), rng, std::forward(y), std::forward(ys)...); @@ -1604,14 +2265,89 @@ auto make_dpf_doerner_shelat(arith_input_t, InputT x0, InputT x1, OutputT && y, OutputTs && ...ys) { using block = typename InteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") ds_randomness rng{ dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) return make_dpf_doerner_shelat( arith_input, std::move(x0), std::move(x1), rng, std::forward(y), std::forward(ys)...); } -/// Doerner–Shelat from party-tagged additive XOR shares of the point. +/// @brief XOR-index, additive payload shares (urandom roots/pads). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputT output type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param y0 the `y0` +/// @param y1 the `y1` +/// @return XOR-index, additive payload shares (urandom roots/pads) +template && !is_at_v + && !is_wildcard_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_output_t, InputT x0, InputT x1, OutputT y0, + OutputT y1) +{ + using block = typename InteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + ds_randomness rng{ + dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + return make_dpf_doerner_shelat( + arith_output, std::move(x0), std::move(x1), rng, std::move(y0), + std::move(y1)); +} + +/// @brief Additive index and additive payload (urandom roots/pads). +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam OutputT output type +/// @tparam OutputT output type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param y0 the `y0` +/// @param y1 the `y1` +/// @return Additive index and additive payload (urandom roots/pads) +template && !is_at_v + && !is_wildcard_v>> +HEDLEY_WARN_UNUSED_RESULT +auto make_dpf_doerner_shelat(arith_input_t, arith_output_t, InputT x0, InputT x1, + OutputT y0, OutputT y1) +{ + using block = typename InteriorPRG::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + ds_randomness rng{ + dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + return make_dpf_doerner_shelat( + arith_input, arith_output, std::move(x0), std::move(x1), rng, + std::move(y0), std::move(y1)); +} + +/// @brief Doerner–Shelat from party-tagged additive XOR shares of the point. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam U rebound value type +/// @tparam Args args +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param args the arguments forwarded to the constructor +/// @return Doerner–Shelat from party-tagged additive XOR shares of the point template {}, ...)` (or any /// `leq`/`gt`/`geq`), which opened the value CWs / `cw_last` for δ = 0 and @@ -1662,6 +2403,12 @@ inline uint64_t cmp_assign_target(const detail::cmp_meta & ch, uint64_t delta, /// identical) value CWs in place — `value_cw[i] += coeff[i]·δ`, /// `cw_last += coeff_last·δ` — and splits the constant absorb into fresh /// additive `cmp_addend` shares. No tree re-walk and no re-PRG. +/// @tparam KeyT key type +/// @tparam Beta payload type +/// @param key0 the `key0` +/// @param key1 the `key1` +/// @param if_true the payload on a true comparison +/// @param if_false the payload on a false comparison template void assign_cmp(KeyT & key0, KeyT & key1, const Beta & if_true, const Beta & if_false = Beta{}) @@ -1671,6 +2418,26 @@ void assign_cmp(KeyT & key0, KeyT & key1, const Beta & if_true, if (!key0.has_cmp() || !key1.has_cmp()) throw std::invalid_argument("assign_cmp: key has no comparison channel"); const auto & ch = key0.cmp(); + using Concrete = dpf::concrete_type_t; + if constexpr (detail::cmp_group_info::custom) + { + const auto layout = detail::group_layout(); + const auto delta = detail::group_sub( + detail::group_from_beta(if_true), detail::group_from_beta(if_false)); + const auto false_value = detail::group_from_beta(if_false); + detail::group_elem target = false_value; + if (ch.trivial == cmp_trivial::always_true || ch.eval_as_ge) + target = detail::group_add(delta, false_value); + using prg = typename KeyT::interior_prg; + const auto blind = detail::group_from_node( + dpf::uniform_sample(), layout); + const auto add0 = detail::group_to_word(blind); + const auto add1 = detail::group_to_word( + detail::group_sub(target, blind)); + key0.assign_cmp_group(delta, add0); + key1.assign_cmp_group(delta, add1); + return; + } const uint64_t mask = ch.mask; const uint64_t delta = detail::dcf_impl::beta_delta_u64(if_true, if_false, mask); @@ -1690,8 +2457,14 @@ void assign_cmp(KeyT & key0, KeyT & key1, const Beta & if_true, key1.assign_cmp_delta(delta, add1); } -/// Party-tagged overload: `make_dpf` returns distinct `party_key<0>` / +/// @brief Party-tagged overload: `make_dpf` returns distinct `party_key<0>` / /// `party_key<1>` types, so the same-type pair overload cannot bind both. +/// @tparam Key key type +/// @tparam Beta payload type +/// @param key0 the `key0` +/// @param key1 the `key1` +/// @param if_true the payload on a true comparison +/// @param if_false the payload on a false comparison template void assign_cmp(party_key<0, Key> & key0, party_key<1, Key> & key1, const Beta & if_true, const Beta & if_false = Beta{}) @@ -1699,10 +2472,15 @@ void assign_cmp(party_key<0, Key> & key0, party_key<1, Key> & key1, assign_cmp(key0.key(), key1.key(), if_true, if_false); } -/// Party-local variant: patch one key with a *public* (already-opened) δ and a +/// @brief Party-local variant: patch one key with a *public* (already-opened) δ and a /// caller-supplied `cmp_addend` share. Both parties must call this with the /// same δ (so the public value CWs stay identical) and additive shares of the /// absorb target that reconstruct to `cmp_assign_target(cmp(), δ, if_false)`. +/// @tparam KeyT key type +/// @param key the `key` +/// @param delta the payload difference `if_true - if_false` +/// @param addend_share the `addend_share` +/// @throws std::invalid_argument if `key has no comparison channel` template void assign_cmp_local(KeyT & key, uint64_t delta, uint64_t addend_share) { @@ -1714,8 +2492,15 @@ void assign_cmp_local(KeyT & key, uint64_t delta, uint64_t addend_share) key.assign_cmp_delta(delta, addend_share); } -/// Share-typed overload: subtractive shares are converted with the party +/// @brief Share-typed overload: subtractive shares are converted with the party /// coefficient before the existing additive leaf absorb math. +/// @tparam KeyT key type +/// @tparam T value type +/// @tparam Party party index, `0` or `1` +/// @tparam Scheme scheme +/// @param key the `key` +/// @param delta the payload difference `if_true - if_false` +/// @param addend_share the `addend_share` template void assign_cmp_local(KeyT & key, uint64_t delta, const secret_share & addend_share) @@ -1751,8 +2536,17 @@ namespace detail namespace incr { -/// Evaluate output slot `I` of an incremental key at the programmed point. -/// `N` must match the prefix of that slot (`at` or full input width). +/// @brief Evaluate output slot `I` of an incremental key at the programmed point. +/// @details `N` must match the prefix of that slot (`at` or full input width). +/// @tparam N width in bits +/// @tparam I output index +/// @tparam KeyT key type +/// @tparam QueryT query type +/// @tparam PathMemoizer path memoizer type +/// @param dpf the DPF key +/// @param x the `x` +/// @param path the root-to-leaf path +/// @return the evaluation result template > auto eval_out_point_impl( @@ -1804,7 +2598,15 @@ auto eval_out_point_impl( } // namespace incr } // namespace detail -/// Evaluate the first deepest-prefix output (plan default for `eval_point`). +/// @brief Evaluate the first deepest-prefix output (plan default for `eval_point`). +/// @tparam KeyT key type +/// @tparam QueryT query type +/// @tparam PathMemoizer path memoizer type +/// @tparam KeyT key type +/// @param dpf the DPF key +/// @param x the `x` +/// @param path the root-to-leaf path +/// @return the evaluation result template , std::enable_if_t, bool> = true> @@ -1830,7 +2632,11 @@ namespace incr } // namespace incr } // namespace detail -/// Per-slot buffer: `num_leaf_nodes * outputs_per_leaf_of`. +/// @brief Per-slot buffer: `num_leaf_nodes * outputs_per_leaf_of`. +/// @tparam I output index +/// @tparam KeyT key type +/// @param num_leaf_nodes the `num_leaf_nodes` +/// @return Per-slot buffer: `num_leaf_nodes * outputs_per_leaf_of` template auto make_output_buffer_for(const KeyT &, std::size_t num_leaf_nodes) { @@ -1845,7 +2651,15 @@ namespace detail namespace incr { -/// Buffer sized for lane-domain interval `[from, to]` of output `I` at prefix `N`. +/// @brief Buffer sized for lane-domain interval `[from, to]` of output `I` at prefix `N`. +/// @tparam N width in bits +/// @tparam I output index +/// @tparam KeyT key type +/// @tparam LaneT lane type +/// @param key the `key` +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Buffer sized for lane-domain interval `[from, to]` of output `I` at prefix `N` template auto make_output_buffer_for_out_interval_impl(const KeyT & key, LaneT from, LaneT to) { @@ -1905,13 +2719,15 @@ void eval_out_interval_interior(const DpfKey & dpf, IntegralT from_node, const node_type cw[2] = { dpf.correction_word(level_index - 1, 0), dpf.correction_word(level_index - 1, 1)}; + const bool is_last = dpf_type::tree::is_last_level(level_index - 1, + dpf_type::depth); auto *prev = memoizer[level_index - 1]; auto *curr = memoizer[level_index]; if (from_offset == true) { - curr[i++] = dpf_type::traverse_interior(prev[j++], cw[1], 1); + curr[i++] = dpf_type::traverse_interior(prev[j++], cw[1], 1, is_last); } const std::size_t both_end = nodes_at_level - to_offset; while (i + 8 <= both_end) @@ -1922,7 +2738,8 @@ void eval_out_interval_interior(const DpfKey & dpf, IntegralT from_node, DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) parents[t] = prev[j + t]; - dpf_type::traverse_interior01_x4(parents, cw[0], cw[1], left, right); + dpf_type::traverse_interior01_x4(parents, cw[0], cw[1], left, right, + is_last); DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { @@ -1936,13 +2753,14 @@ void eval_out_interval_interior(const DpfKey & dpf, IntegralT from_node, for (; i < both_end;) { auto cur_node = prev[j++]; - auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1]); + auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1], + is_last); curr[i++] = kids[0]; curr[i++] = kids[1]; } if (to_offset == true) { - curr[i] = dpf_type::traverse_interior(prev[j], cw[0], 0); + curr[i] = dpf_type::traverse_interior(prev[j], cw[0], 0, is_last); } } } @@ -1985,8 +2803,20 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace internal -/// Evaluate output `I` over an interval in the N-bit lane subdomain. -/// `from`/`to` are lane values in `[0, 2^N)` (not the full input domain). +/// @brief Evaluate output `I` over an interval in the N-bit lane subdomain. +/// @details `from`/`to` are lane values in `[0, 2^N)` (not the full input domain). +/// @tparam N width in bits +/// @tparam I output index +/// @tparam KeyT key type +/// @tparam LaneT lane type +/// @tparam OutputBuffer output buffer type +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param from the inclusive start of the range +/// @param to the `to` +/// @param outbuf the `outbuf` +/// @param memoizer the memoizer built for this key +/// @return the evaluation result template auto eval_out_interval_impl( @@ -2054,7 +2884,10 @@ auto eval_out_interval_impl( const integral_type to_node = utils::leaf_node_ceil_exclusive(to_i, lg); const bool wraps = utils::interval_wraps(from_i, to_i, N); const auto segs = utils::split_leaf_nodes(from_node, to_node, L, wraps); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto memo = basic_interval_memoizer_at(segs.total); + HEDLEY_PRAGMA(GCC diagnostic pop) return eval_out_interval_impl(dpf, from, to, outbuf, memo); } @@ -2068,7 +2901,16 @@ auto eval_out_interval_impl( return std::make_pair(std::move(buf), std::move(it)); } -/// Full N-bit lane-domain eval of output `I`. +/// @brief Full N-bit lane-domain eval of output `I`. +/// @tparam N width in bits +/// @tparam I output index +/// @tparam KeyT key type +/// @tparam OutputBuffer output buffer type +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param outbuf the `outbuf` +/// @param memoizer the memoizer built for this key +/// @return Full N-bit lane-domain eval of output `I` template auto eval_out_full_impl( @@ -2098,7 +2940,11 @@ auto eval_out_full_impl( } // namespace incr } // namespace detail -/// Deepest-group full-domain eval (first cut of incremental `eval_full`). +/// @brief Deepest-group full-domain eval (first cut of incremental `eval_full`). +/// @tparam KeyT key type +/// @tparam KeyT key type +/// @param dpf the DPF key +/// @return Deepest-group full-domain eval (first cut of incremental `eval_full`) template , bool> = true> auto eval_full( @@ -2114,7 +2960,19 @@ namespace detail namespace incr { -/// Sequence eval over lane points for output `I` at prefix `N`. +/// @brief Sequence eval over lane points for output `I` at prefix `N`. +/// @tparam N width in bits +/// @tparam I output index +/// @tparam KeyT key type +/// @tparam ForwardIterator forward iterator type +/// @tparam OutputBuffer output buffer type +/// @tparam PathMemoizer path memoizer type +/// @param dpf the DPF key +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param outbuf the `outbuf` +/// @param path the root-to-leaf path +/// @return Sequence eval over lane points for output `I` at prefix `N` template > @@ -2154,7 +3012,16 @@ auto eval_out_sequence_impl( } // namespace incr } // namespace detail -/// Deepest-group sequence eval (first cut of incremental `eval_sequence`). +/// @brief Deepest-group sequence eval (first cut of incremental `eval_sequence`). +/// @tparam KeyT key type +/// @tparam ForwardIterator forward iterator type +/// @tparam OutputBuffer output buffer type +/// @tparam KeyT key type +/// @param dpf the DPF key +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param outbuf the `outbuf` +/// @return Deepest-group sequence eval (first cut of incremental `eval_sequence`) template , bool> = true> auto eval_sequence( @@ -2215,7 +3082,7 @@ uint64_t eval_cmp_path_sum(const KeyT & dpf, typename KeyT::input_type tx, const bool xi = !!(bit_mask & tx); const auto & parent = path[i]; const uint8_t t = static_cast(dpf::get_lo_bit(parent)); - auto kids = KeyT::interior_prg::eval01(dpf::unset_lo_2bits(parent)); + auto kids = KeyT::tree::expand_value(parent); const uint64_t v = convert_node(kids[xi ? 1 : 0], mask); const uint64_t contrib = (v + (t ? dpf.value_cw(i) : 0ULL)) & mask; @@ -2234,8 +3101,11 @@ uint64_t eval_cmp_path_sum(const KeyT & dpf, typename KeyT::input_type tx, return (V + add) & mask; } -/// Full-tree interval memoizer stopped at `StopLevel` (retains every level; +/// @brief Full-tree interval memoizer stopped at `StopLevel` (retains every level; /// unlike `basic_interval_memoizer_at` which ping-pongs two buffers). +/// @tparam DpfKey DPF key type +/// @tparam StopLevel stop level +/// @tparam interior_node interior node template ::interior_node>> @@ -2335,7 +3205,16 @@ struct cmp_full_interval_memo } }; -/// Expand interval interior nodes for the comparison prefix (stop = nbits). +/// @brief Expand interval interior nodes for the comparison prefix (stop = nbits). +/// @tparam KeyT key type +/// @tparam IntegralT integral type +/// @tparam IntervalMemoizer interval memoizer type +/// @param dpf the DPF key +/// @param from_node the `from_node` +/// @param to_node the `to_node` +/// @param nbits the width in bits +/// @param memoizer the memoizer built for this key +/// @param tree_levels the `tree_levels` template void eval_cmp_interval_impl_interior(const KeyT & dpf, IntegralT from_node, IntegralT to_node, std::size_t nbits, IntervalMemoizer & memoizer, @@ -2363,22 +3242,25 @@ void eval_cmp_interval_impl_interior(const KeyT & dpf, IntegralT from_node, const node_type cw[2] = { dpf.correction_word(level_index - 1, 0), dpf.correction_word(level_index - 1, 1)}; + const bool is_last = KeyT::tree::is_last_level(level_index - 1, + KeyT::depth); auto *prev = memoizer[level_index - 1]; auto *curr = memoizer[level_index]; if (from_offset == true) - curr[i++] = KeyT::traverse_interior(prev[j++], cw[1], 1); + curr[i++] = KeyT::traverse_interior(prev[j++], cw[1], 1, is_last); const std::size_t both_end = nodes_at_level - to_offset; for (; i < both_end;) { auto cur_node = prev[j++]; - auto kids = KeyT::traverse_interior01(cur_node, cw[0], cw[1]); + auto kids = KeyT::traverse_interior01(cur_node, cw[0], cw[1], + is_last); curr[i++] = kids[0]; curr[i++] = kids[1]; } if (to_offset == true) - curr[i] = KeyT::traverse_interior(prev[j], cw[0], 0); + curr[i] = KeyT::traverse_interior(prev[j], cw[0], 0, is_last); } } @@ -2413,7 +3295,7 @@ uint64_t eval_cmp_from_interval_memo(const KeyT & dpf, const bool xi = !!(lane & (typename KeyT::integral_type{1} << (nbits - 1 - i))); const uint8_t t = static_cast(dpf::get_lo_bit(parent)); - auto kids = KeyT::interior_prg::eval01(dpf::unset_lo_2bits(parent)); + auto kids = KeyT::tree::expand_value(parent); const uint64_t v = convert_node(kids[xi ? 1 : 0], mask); const uint64_t contrib = (v + (t ? dpf.value_cw(i) : 0ULL)) & mask; @@ -2440,6 +3322,50 @@ namespace detail namespace incr { +template +auto eval_group_path_sum(const KeyT & dpf, typename KeyT::input_type tx, + PathMemoizer & path, bool as_prefix = false, std::size_t prefix_len = 0) +{ + using prg = typename unwrap_party_key_t::interior_prg; + using concrete = dpf::concrete_type_t; + const auto layout = detail::group_layout(); + const auto & ch = dpf.cmp(); + const auto add = detail::group_from_word(dpf.cmp_addend_word(), layout); + const std::size_t full_bits = static_cast(ch.nbits); + const std::size_t nbits = as_prefix ? prefix_len : full_bits; + if ((ch.trivial == cmp_trivial::always_true + || ch.trivial == cmp_trivial::always_false) + && (!as_prefix || prefix_len == full_bits)) + return detail::group_to_beta(add); + + dpf::detail::ensure_level(dpf, tx, path, nbits); + const int party = dpf::get_lo_bit(dpf.root()) ? 1 : 0; + auto V = detail::group_zero(layout); + const auto zero = detail::group_zero(layout); + auto bit_mask = KeyT::msb_mask; + for (std::size_t i = 0; i < nbits; ++i, bit_mask >>= 1) + { + const bool xi = !!(bit_mask & tx); + const auto & parent = path[i]; + const uint8_t t = static_cast(dpf::get_lo_bit(parent)); + auto kids = KeyT::tree::expand_value(parent); + const auto v = detail::group_from_node(kids[xi ? 1 : 0], layout); + const auto cw = detail::group_from_word(dpf.value_cw()[i], layout); + const auto contrib = detail::group_add(v, t ? cw : zero); + V = detail::group_add(V, party ? detail::group_neg(contrib) : contrib); + } + const auto & leaf = path[nbits]; + const uint8_t t = static_cast(dpf::get_lo_bit(leaf)); + const auto c = detail::group_from_node(leaf, layout); + const auto last = detail::group_from_word( + as_prefix ? dpf.prefix_cws()[nbits] : dpf.cw_last_word(), layout); + const auto contrib = detail::group_add(c, t ? last : zero); + V = detail::group_add(V, party ? detail::group_neg(contrib) : contrib); + if (ch.eval_as_ge) + V = detail::group_neg(V); + return detail::group_to_beta(detail::group_add(V, add)); +} + template > auto eval_cmp_point_impl(const KeyT & dpf, QueryT && x, @@ -2452,10 +3378,18 @@ auto eval_cmp_point_impl(const KeyT & dpf, QueryT && x, "cmp eval: wildcard comparison payload not assigned (call assign_cmp)"); auto tx = dpf.offset_x(std::forward(x)); utils::flip_msb_if_signed_integral(tx); - const uint64_t raw = - detail::incr::eval_cmp_path_sum(dpf, tx, path); - return make_eval_cmp_result( - detail::dcf_impl::u64_to_beta(raw)); + if constexpr (detail::cmp_group_info>::custom) + { + return make_eval_cmp_result( + detail::incr::eval_group_path_sum(dpf, tx, path)); + } + else + { + const uint64_t raw = + detail::incr::eval_cmp_path_sum(dpf, tx, path); + return make_eval_cmp_result( + detail::dcf_impl::u64_to_beta(raw)); + } } template (x)); utils::flip_msb_if_signed_integral(tx); - const uint64_t raw = - detail::incr::eval_cmp_path_sum(dpf, tx, path, true, L); - return make_eval_cmp_result( - detail::dcf_impl::u64_to_beta(raw)); + if constexpr (detail::cmp_group_info>::custom) + { + return make_eval_cmp_result( + detail::incr::eval_group_path_sum(dpf, tx, path, true, L)); + } + else + { + const uint64_t raw = + detail::incr::eval_cmp_path_sum(dpf, tx, path, true, L); + return make_eval_cmp_result( + detail::dcf_impl::u64_to_beta(raw)); + } } // --------------------------------------------------------------------------- @@ -2560,6 +3502,18 @@ void eval_cmp_interval_impl(const KeyT & dpf, LaneT from, LaneT to, if (!dpf.cmp_assigned()) throw std::invalid_argument( "cmp interval eval: wildcard payload not assigned (call assign_cmp)"); + if constexpr (detail::cmp_group_info>::custom) + { + std::size_t i = 0; + dpf::basic_path_memoizer path; + for (LaneT q = from; ; ++q, ++i) + { + outbuf[i] = eval_cmp_point_impl(dpf, q, path); + if (q == to) + break; + } + return; + } constexpr auto to_int = utils::to_integral_type{}; utils::flip_msb_if_signed_integral(from); utils::flip_msb_if_signed_integral(to); @@ -2577,7 +3531,10 @@ void eval_cmp_interval_impl(const KeyT & dpf, LaneT from, LaneT to, // the same assign/advance API as basic_interval_memoizer_at, but allocate // per-level storage. Stop stays the logical comparison width so prefix // indexes match `nbits`, even when a blocked key's seed spine is shorter. + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") detail::incr::cmp_full_interval_memo memo{count}; + HEDLEY_PRAGMA(GCC diagnostic pop) const std::size_t levels = unwrap_party_key_t::cmp_block > 0 ? unwrap_party_key_t::cmp_h : nbits; detail::incr::eval_cmp_interval_impl_interior(dpf, a, cmp_exclusive_end(b), @@ -2589,8 +3546,11 @@ void eval_cmp_interval_impl(const KeyT & dpf, LaneT from, LaneT to, const uint64_t raw = [&] { if constexpr (unwrap_party_key_t::cmp_block > 0) { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return detail::blocked::eval_share_memo(dpf, q, a, cmp_exclusive_end(b), memo); + HEDLEY_PRAGMA(GCC diagnostic pop) } else { @@ -2613,6 +3573,19 @@ void eval_cmp_interval_impl(const KeyT & dpf, LaneT from, LaneT to, if (!dpf.cmp_assigned()) throw std::invalid_argument( "cmp interval eval: wildcard payload not assigned (call assign_cmp)"); + if constexpr (detail::cmp_group_info>::custom) + { + (void)memo; + std::size_t i = 0; + dpf::basic_path_memoizer path; + for (LaneT q = from; ; ++q, ++i) + { + outbuf[i] = eval_cmp_point_impl(dpf, q, path); + if (q == to) + break; + } + return; + } constexpr auto to_int = utils::to_integral_type{}; utils::flip_msb_if_signed_integral(from); utils::flip_msb_if_signed_integral(to); @@ -2636,8 +3609,11 @@ void eval_cmp_interval_impl(const KeyT & dpf, LaneT from, LaneT to, const uint64_t raw = [&] { if constexpr (unwrap_party_key_t::cmp_block > 0) { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return detail::blocked::eval_share_memo(dpf, q, a, cmp_exclusive_end(b), memo); + HEDLEY_PRAGMA(GCC diagnostic pop) } else { diff --git a/include/dpf/interval.hpp b/include/dpf/interval.hpp index 6d848d8..740183e 100644 --- a/include/dpf/interval.hpp +++ b/include/dpf/interval.hpp @@ -45,7 +45,7 @@ struct ic_pack Beta if_false{}; }; -/// Spec tag and factory. `dpf::ic(p, q, beta)` builds a pack; +/// @brief Spec tag and factory. `dpf::ic(p, q, beta)` builds a pack; /// `eval_point(dpf::ic, key, x)` evaluates it. struct ic_fn { @@ -68,6 +68,12 @@ inline constexpr ic_fn ic{}; template struct is_ic_key : std::false_type {}; +/// @brief One party's interval key: the inner comparison, the public bounds, and the +/// secret correction shares. +/// @tparam Party party index, `0` or `1` +/// @tparam Key key type +/// @tparam Input input domain type +/// @tparam Beta payload type template struct ic_key { @@ -76,24 +82,27 @@ struct ic_key using input_type = Input; using key_type = party_key; using beta_type = Beta; + using share_type = std::conditional_t< + detail::cmp_group_info>::custom, + detail::group_elem, uint64_t>; key_type key; uint64_t lo = 0; uint64_t hi = 0; uint64_t input_mask = 0; uint64_t group_mask = 0; - /// Share of `δ`. Public `c_x ∈ {-1,0,1}` scales it locally. - uint64_t delta_share = 0; - /// Share of `δ · c_r + if_false`. - uint64_t cr_share = 0; - /// Wildcard only: shares of `1` and of `c_r`, scaled by `δ` in `assign_cmp`. - uint64_t delta_coeff = 0; - uint64_t cr_coeff = 0; + /// @brief Share of `δ`. Public `c_x ∈ {-1,0,1}` scales it locally. + share_type delta_share{}; + /// @brief Share of `δ · c_r + if_false`. + share_type cr_share{}; + /// @brief Wildcard only: shares of `1` and of `c_r`, scaled by `δ` in `assign_cmp`. + share_type delta_coeff{}; + share_type cr_coeff{}; bool assigned = !wildcard; ic_key(key_type k, uint64_t lo_in, uint64_t hi_in, uint64_t nmask, - uint64_t gmask, uint64_t dshare, uint64_t cshare, uint64_t dcoeff, - uint64_t ccoeff) + uint64_t gmask, share_type dshare, share_type cshare, share_type dcoeff, + share_type ccoeff) noexcept(std::is_nothrow_move_constructible_v) : key(std::move(k)) , lo(lo_in) , hi(hi_in) @@ -167,20 +176,40 @@ constexpr uint64_t mul_mask(uint64_t a, uint64_t b, uint64_t mask) noexcept return static_cast(static_cast(a) * b) & mask; } -/// Dealer correction in Fig. 3, as an element of the payload group. +/// @brief Public integer in Fig. 3, before it is embedded in the payload group. +/// @param r the `r` +/// @param p the `p` +/// @param q the `q` +/// @param nmask the mask of the live input bits +/// @return Public integer in Fig. 3, before it is embedded in the payload group +HEDLEY_CONST +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +constexpr int correction_s(uint64_t r, uint64_t p, uint64_t q, + uint64_t nmask) noexcept +{ + const uint64_t aq = (q + r) & nmask; + const uint64_t ap = (p + r) & nmask; + const uint64_t q0 = (q + 1ULL) & nmask; + const uint64_t aq0 = (q0 + r) & nmask; + return (ap > aq ? 1 : 0) - (ap > p ? 1 : 0) + + (aq0 > q0 ? 1 : 0) + (aq == nmask ? 1 : 0); +} + +/// @brief Dealer correction in Fig. 3, as an element of the payload group. +/// @param r the `r` +/// @param p the `p` +/// @param q the `q` +/// @param nmask the mask of the live input bits +/// @param gmask the mask of the live payload bits +/// @return Dealer correction in Fig. 3, as an element of the payload group HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t correction(uint64_t r, uint64_t p, uint64_t q, uint64_t nmask, uint64_t gmask) noexcept { - const uint64_t aq = (q + r) & nmask; - const uint64_t ap = (p + r) & nmask; - const uint64_t q0 = (q + 1ULL) & nmask; - const uint64_t aq0 = (q0 + r) & nmask; - const int s = (ap > aq ? 1 : 0) - (ap > p ? 1 : 0) - + (aq0 > q0 ? 1 : 0) + (aq == nmask ? 1 : 0); - return embed_small(s, gmask); + return embed_small(correction_s(r, p, q, nmask), gmask); } HEDLEY_CONST @@ -251,8 +280,10 @@ void check_bounds(const ic_pack & spec) } template +HEDLEY_CONST HEDLEY_NO_THROW -uint64_t group_mask_of() noexcept +HEDLEY_ALWAYS_INLINE +constexpr uint64_t group_mask_of() noexcept { using B = concrete_type_t; if constexpr (std::is_same_v) @@ -261,11 +292,11 @@ uint64_t group_mask_of() noexcept return dcf_impl::default_mask_for_bits(utils::bitlength_of_v); } -template +template ic_key make_side(party_key key, uint64_t lo, uint64_t hi, uint64_t nmask, uint64_t gmask, - uint64_t delta_share, uint64_t cr_share, - uint64_t delta_coeff, uint64_t cr_coeff) + Share delta_share, Share cr_share, Share delta_coeff, Share cr_coeff) { return ic_key(std::move(key), lo, hi, nmask, gmask, delta_share, cr_share, delta_coeff, cr_coeff); @@ -281,6 +312,46 @@ auto finish(uint64_t r_bits, const ic_pack & spec, Pair && inner) const uint64_t nmask = input_mask_of(); const uint64_t gmask = group_mask_of(); constexpr bool wild = is_wildcard_v; + if constexpr (detail::cmp_group_info::custom) + { + using prg = typename raw_key::interior_prg; + const auto layout = detail::group_layout(); + auto delta = detail::group_zero(layout); + auto fval = detail::group_zero(layout); + if constexpr (!wild) + { + delta = detail::group_sub(detail::group_from_beta(spec.if_true), + detail::group_from_beta(spec.if_false)); + fval = detail::group_from_beta(spec.if_false); + } + const auto cr = detail::group_scalar( + correction_s(r_bits, spec.lo, spec.hi, nmask), layout); + auto splitg = [&](const detail::group_elem & target, + detail::group_elem & a, detail::group_elem & b) { + const auto blind = detail::group_from_node( + dpf::uniform_sample(), layout); + a = blind; + b = detail::group_sub(target, blind); + }; + detail::group_elem d0{}, d1{}, c0{}, c1{}, dc0{}, dc1{}, cc0{}, cc1{}; + if constexpr (wild) + { + splitg(detail::group_one(layout), dc0, dc1); + splitg(cr, cc0, cc1); + } + else + { + splitg(delta, d0, d1); + splitg(detail::group_add(detail::group_mul(delta, cr), fval), c0, c1); + } + auto k0 = make_side<0, raw_key, in_type, out_beta>(std::move(inner.first), + spec.lo, spec.hi, nmask, gmask, d0, c0, dc0, cc0); + auto k1 = make_side<1, raw_key, in_type, out_beta>(std::move(inner.second), + spec.lo, spec.hi, nmask, gmask, d1, c1, dc1, cc1); + return std::make_pair(std::move(k0), std::move(k1)); + } + else + { uint64_t delta = 0; uint64_t fval = 0; if constexpr (!wild) @@ -310,6 +381,7 @@ auto finish(uint64_t r_bits, const ic_pack & spec, Pair && inner) auto k1 = make_side<1, raw_key, in_type, out_beta>(std::move(inner.second), spec.lo, spec.hi, nmask, gmask, d1, c1, dc1, cc1); return std::make_pair(std::move(k0), std::move(k1)); + } } template @@ -318,6 +390,15 @@ auto inner_lt(const ic_pack & spec) using B = std::decay_t; if constexpr (is_wildcard_v) return lt(spec.if_true, spec.if_false); + else if constexpr (detail::cmp_group_info::custom) + { + const auto layout = detail::group_layout>(); + const auto delta = detail::group_sub( + detail::group_from_beta(spec.if_true), + detail::group_from_beta(spec.if_false)); + return lt(detail::group_to_beta(delta), + detail::group_to_beta(detail::group_zero(layout))); + } else { const uint64_t gmask = group_mask_of(); @@ -342,9 +423,35 @@ auto eval_one(const IcKey & k, Query && x, Memo & memo) throw std::invalid_argument( "ic eval: wildcard payload not assigned (call assign_cmp)"); using in_type = typename IcKey::input_type; + using beta = typename IcKey::beta_type; const uint64_t xu = bits_of(in_type(std::forward(x))); const uint64_t xp = shift_p(xu, k.lo, k.input_mask); const uint64_t xq = shift_q0(xu, k.hi, k.input_mask); + if constexpr (detail::cmp_group_info::custom) + { + auto opened = [](const auto & v) { + if constexpr (is_secret_share_v>) + return detail::group_from_beta(v.raw()); + else + return detail::group_from_beta(v); + }; + const auto a = opened(eval_point(dpf::cmp, k.key, + input_from_bits(xp), memo)); + const auto b = opened(eval_point(dpf::cmp, k.key, + input_from_bits(xq), memo)); + const int cx = public_cx(xu, k.lo, k.hi, k.input_mask); + auto scaled = detail::group_zero(a); + if (cx == 1) + scaled = k.delta_share; + else if (cx == -1) + scaled = detail::group_neg(k.delta_share); + const auto y = detail::group_add(detail::group_add( + detail::group_add(detail::group_neg(a), b), k.cr_share), scaled); + return make_eval_cmp_result( + detail::group_to_beta(y)); + } + else + { const uint64_t a = opened_u64( eval_point(dpf::cmp, k.key, input_from_bits(xp), memo), k.group_mask); @@ -361,12 +468,20 @@ auto eval_one(const IcKey & k, Query && x, Memo & memo) + scaled) & k.group_mask; return make_eval_cmp_result( dcf_impl::u64_to_beta(y)); + } } } // namespace ic_impl } // namespace detail -/// Dealer key for public bounds `spec` and secret mask `r`. +/// @brief Dealer key for public bounds `spec` and secret mask `r`. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam Beta payload type +/// @param r the secret input mask +/// @param spec the public bounds and payloads +/// @return Dealer key for public bounds `spec` and secret mask `r` template & spec) return detail::ic_impl::finish(r_bits, spec, std::move(inner)); } -/// Doerner–Shelat key. `r0 XOR r1` is the secret mask. +/// @name Doerner–Shelat interval keys +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam InputT input domain type +/// @tparam Beta payload type +/// @param r0 party 0's share of the mask +/// @param r1 party 1's share of the mask +/// @param spec the public bounds and payloads +/// @{ + +/// @brief XOR shares. `r0 XOR r1` is the secret mask. +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @param r0 party 0's share of the mask +/// @param r1 party 1's share of the mask +/// @param rng the Doerner–Shelat randomness tapes +/// @param spec the public bounds and payloads +/// @return the two party keys template (r_bits, spec, std::move(inner)); } -/// Doerner–Shelat IC key. `r0 + r1` is the secret mask; γ = (r0 + r1) − 1. +/// @brief Additive shares. `r0 + r1` is the secret mask; γ = (r0 + r1) − 1. +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @param r0 party 0's share of the mask +/// @param r1 party 1's share of the mask +/// @param rng the Doerner–Shelat randomness tapes +/// @param spec the public bounds and payloads +/// @return the two party keys template & spec) std::move(r0), std::move(r1), rng, spec); } +/// @brief Additive shares, sampled from the library entropy source. +/// @return the two party keys template void assign_cmp(ic_key<0, Key, Input, Beta> & k0, ic_key<1, Key, Input, Beta> & k1, const Payload & if_true, @@ -474,6 +626,31 @@ void assign_cmp(ic_key<0, Key, Input, Beta> & k0, { static_assert(Key::cmp_is_wildcard, "assign_cmp: interval payload is not a wildcard"); + if constexpr (detail::cmp_group_info>::custom) + { + using prg = typename Key::interior_prg; + const auto layout = detail::group_layout>(); + const auto delta = detail::group_sub( + detail::group_from_beta(if_true), detail::group_from_beta(if_false)); + const auto fval = detail::group_from_beta(if_false); + assign_cmp(k0.key, k1.key, + detail::group_to_beta(delta), + detail::group_to_beta(detail::group_zero(layout))); + k0.delta_share = detail::group_mul(k0.delta_coeff, delta); + k1.delta_share = detail::group_mul(k1.delta_coeff, delta); + k0.cr_share = detail::group_mul(k0.cr_coeff, delta); + k1.cr_share = detail::group_mul(k1.cr_coeff, delta); + const auto blind = detail::group_from_node( + dpf::uniform_sample(), layout); + const auto f1 = detail::group_sub(fval, blind); + k0.cr_share = detail::group_add(k0.cr_share, blind); + k1.cr_share = detail::group_add(k1.cr_share, f1); + k0.assigned = true; + k1.assigned = true; + return; + } + else + { const uint64_t mask = k0.group_mask; const uint64_t delta = detail::dcf_impl::beta_delta_u64(if_true, if_false, mask); @@ -492,9 +669,17 @@ void assign_cmp(ic_key<0, Key, Input, Beta> & k0, k1.cr_share = (k1.cr_share + f1) & mask; k0.assigned = true; k1.assigned = true; + } } -/// Point evaluation. `memo` is a path memoizer for the inner comparison key. +/// @brief Point evaluation. `memo` is a path memoizer for the inner comparison key. +/// @tparam IcKey interval-containment key type +/// @tparam Query query point type +/// @tparam Memo path memoizer type +/// @param key the key to evaluate +/// @param x the `x` +/// @param memo the memoizer reused across queries +/// @return Point evaluation template , typename = std::enable_if_t>> @@ -504,7 +689,24 @@ auto eval_point(ic_fn, const IcKey & key, Query && x, Memo && memo = Memo{}) return detail::ic_impl::eval_one(key, std::forward(x), memo); } -/// Inclusive interval `[from, to]` on the input domain. +/// @name Interval evaluation +/// @tparam IcKey interval-containment key type +/// @tparam Lane input-domain lane type +/// @tparam Buffer output buffer type +/// @param key the interval key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @param buf the output buffer +/// @throws std::invalid_argument if `to < from` +/// @{ + +/// @brief Inclusive interval `[from, to]` on the input domain. +/// @tparam Memo path memoizer type +/// @param key the interval key +/// @param from the inclusive start of the range +/// @param to the inclusive end of the range +/// @param buf the output buffer +/// @param memo the memoizer reused across queries template >> void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to, @@ -528,6 +730,7 @@ void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to, } } +/// @brief Inclusive interval `[from, to]`, with a fresh path memoizer. template >> void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to, Buffer && buf) @@ -536,7 +739,25 @@ void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to, Buffer && buf) eval_interval(ic, key, from, to, std::forward(buf), memo); } -/// Evaluate the points in `[begin, end)`. +/// @} + +/// @name Sequence evaluation +/// @tparam IcKey interval-containment key type +/// @tparam Iter iterator type +/// @tparam Buffer output buffer type +/// @param key the interval key +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param buf the output buffer +/// @{ + +/// @brief Evaluate the points in `[begin, end)`. +/// @tparam Memo path memoizer type +/// @param key the interval key +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param buf the output buffer +/// @param memo the memoizer reused across queries template >> void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end, @@ -547,6 +768,7 @@ void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end, buf[i] = detail::ic_impl::eval_one(key, *it, memo); } +/// @brief Evaluate `[begin, end)`, with a fresh path memoizer. template >> void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end, Buffer && buf) @@ -555,7 +777,12 @@ void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end, Buffer && buf eval_sequence(ic, key, begin, end, std::forward(buf), memo); } -/// Buffer of `n` interval shares. +/// @} + +/// @brief Buffer of `n` interval shares. +/// @tparam IcKey interval-containment key type +/// @param n the `n` +/// @return Buffer of `n` interval shares template >> HEDLEY_WARN_UNUSED_RESULT auto make_output_buffer(ic_fn, const IcKey &, std::size_t n) @@ -565,7 +792,14 @@ auto make_output_buffer(ic_fn, const IcKey &, std::size_t n) return output_buffer(n); } -/// Buffer large enough for the inclusive interval `[from, to]`. +/// @brief Buffer large enough for the inclusive interval `[from, to]`. +/// @tparam IcKey interval-containment key type +/// @tparam Lane input-domain lane type +/// @param key the `key` +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Buffer large enough for the inclusive interval `[from, to]` +/// @throws std::invalid_argument if `to < from` template >> HEDLEY_WARN_UNUSED_RESULT @@ -580,7 +814,22 @@ auto make_output_buffer(ic_fn, const IcKey & key, Lane from, Lane to) return make_output_buffer(ic, key, static_cast(n)); } -/// Doerner–Shelat geneval. `r0 XOR r1` is the secret mask. Each query is +/// @name Interval geneval +/// @tparam InputT input domain type +/// @tparam Iter iterator type +/// @tparam RootSampler sampler for the Doerner–Shelat root seed +/// @tparam PadRng pad stream for the Doerner–Shelat protocol +/// @tparam Beta payload type +/// @param r0 party 0's share of the mask +/// @param r1 party 1's share of the mask +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @param rng the Doerner–Shelat randomness tapes +/// @param spec the public bounds and payloads +/// @return the opened party shares +/// @{ + +/// @brief XOR mask. `r0 XOR r1` is the secret mask. Each query is /// returned already combined into the interval share. template @@ -625,7 +874,7 @@ geneval_cmp_result geneval_ic(InputT r0, InputT r1, Iter begin, Iter end, return out; } -/// Additive-share geneval_ic. `r0 + r1` is the secret mask. +/// @brief Additive mask. `r0 + r1` is the secret mask. template HEDLEY_WARN_UNUSED_RESULT @@ -669,6 +918,8 @@ geneval_cmp_result geneval_ic(arith_input_t, InputT r0, InputT r1, Iter begin, return out; } +/// @} + } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_INTERVAL_HPP__ diff --git a/include/dpf/interval_memoizer.hpp b/include/dpf/interval_memoizer.hpp index 061af06..be8589a 100644 --- a/include/dpf/interval_memoizer.hpp +++ b/include/dpf/interval_memoizer.hpp @@ -36,14 +36,16 @@ namespace dpf { -/// Ping-pong pivot math underflows at 0 leaves. Keep a one-node slab so the +/// @brief Ping-pong pivot math underflows at 0 leaves. Keep a one-node slab so the /// root still has a place to land; callers never walk a 0-leaf interval. +/// @param output_len the `output_len` +/// @return Ping-pong pivot math underflows at 0 leaves inline std::size_t interval_memoizer_slots(std::size_t output_len) { return output_len == 0 ? std::size_t{1} : output_len; } -/// Interval memoizers key on the underlying DPF key type (same rule as path +/// @brief Interval memoizers key on the underlying DPF key type (same rule as path /// memoizers): a memoizer built from `party_key<0, Key>` also accepts /// `party_key<1, Key>` and bare `Key`. template @@ -161,18 +163,20 @@ struct interval_memoizer_base std::optional to_; }; -/// Two-level workspace for one interval. This is what +/// @brief Two-level workspace for one interval. This is what /// `eval_interval(key, from, to)` allocates when you omit the memoizer. +/// @tparam DpfKey DPF key type +/// @tparam interior_node interior node template ::interior_node>> struct basic_interval_memoizer final : public interval_memoizer_base { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") private: -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using parent = interval_memoizer_base; -HEDLEY_PRAGMA(GCC diagnostic pop) + HEDLEY_PRAGMA(GCC diagnostic pop) public: using unique_ptr = typename Allocator::unique_ptr; using return_type = typename interval_memoizer_key_t::interior_node *; @@ -241,17 +245,19 @@ HEDLEY_PRAGMA(GCC diagnostic pop) unique_ptr buf; }; -/// Every level of the interval. `retains_all_levels` is true. +/// @brief Every level of the interval. `retains_all_levels` is true. +/// @tparam DpfKey DPF key type +/// @tparam interior_node interior node template ::interior_node>> struct full_tree_interval_memoizer final : public interval_memoizer_base { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") private: -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using parent = interval_memoizer_base; -HEDLEY_PRAGMA(GCC diagnostic pop) + HEDLEY_PRAGMA(GCC diagnostic pop) public: using node_type = typename interval_memoizer_key_t::interior_node; using unique_ptr = typename Allocator::unique_ptr; @@ -333,7 +339,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } }; -/// Interval memoizer whose leaf depth is `StopLevel` (incremental `eval_interval`). +/// @brief Interval memoizer whose leaf depth is `StopLevel` (incremental `eval_interval`). +/// @tparam DpfKey DPF key type +/// @tparam StopLevel stop level +/// @tparam interior_node interior node template ::interior_node>> @@ -441,10 +450,13 @@ auto make_interval_memoizer(InputT from, InputT to) } // namespace detail -/// Two-level workspace sized for the closed interval `[from, to]`. +/// @brief Two-level workspace sized for the closed interval `[from, to]`. +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type /// @param from Inclusive start, in the key's input domain. /// @param to Inclusive end. `to` is at least `from` in that domain. /// @snippet evaluation/memoizers.cpp interval-memoizer +/// @return Two-level workspace sized for the closed interval `[from, to]` template inline auto make_basic_interval_memoizer(InputT from, InputT to) @@ -463,7 +475,9 @@ inline auto make_basic_interval_memoizer(const DpfKey &, InputT from, InputT to) return make_basic_interval_memoizer(from, to); } -/// `make_basic_interval_memoizer` sized for the whole input domain. +/// @brief `make_basic_interval_memoizer` sized for the whole input domain. +/// @tparam DpfKey DPF key type +/// @return `make_basic_interval_memoizer` sized for the whole input domain template inline auto make_basic_full_memoizer() { @@ -480,7 +494,12 @@ inline auto make_basic_full_memoizer(const DpfKey &) return make_basic_full_memoizer(); } -/// Full-tree workspace sized for the closed interval `[from, to]`. +/// @brief Full-tree workspace sized for the closed interval `[from, to]`. +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Full-tree workspace sized for the closed interval `[from, to]` template inline auto make_full_tree_interval_memoizer(InputT from, InputT to) @@ -499,7 +518,9 @@ inline auto make_full_tree_interval_memoizer(const DpfKey &, InputT from, InputT return make_full_tree_interval_memoizer(from, to); } -/// `make_full_tree_interval_memoizer` sized for the whole input domain. +/// @brief `make_full_tree_interval_memoizer` sized for the whole input domain. +/// @tparam DpfKey DPF key type +/// @return `make_full_tree_interval_memoizer` sized for the whole input domain template inline auto make_full_tree_full_memoizer() { @@ -522,10 +543,17 @@ inline auto make_basic_interval_memoizer_at(std::size_t leaf_nodes) return basic_interval_memoizer_at(leaf_nodes); } -/// Stop-level interval memoizer for output slot `I` of a multi-level key. -/// Sizes the ping-pong buffer for the lane-domain interval `[from, to]` +/// @brief Stop-level interval memoizer for output slot `I` of a multi-level key. +/// @details Sizes the ping-pong buffer for the lane-domain interval `[from, to]` /// expanded to `meta[I].tree_level` (the leaf level of slot `I`). This is the /// default memoizer for a multi-level `eval_interval(out, ...)`. +/// @tparam DpfKey DPF key type +/// @tparam I output index +/// @tparam InputT input domain type +/// @tparam is_multilevel is multilevel +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Stop-level interval memoizer for output slot `I` of a multi-level key template = true> @@ -546,7 +574,10 @@ inline auto make_basic_interval_memoizer(InputT from, InputT to) const bool wraps = utils::interval_wraps(from_i, to_i, utils::bitlength_of_v); const auto segs = utils::split_leaf_nodes(from_node, to_node, stop, wraps); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") return basic_interval_memoizer_at(segs.total); + HEDLEY_PRAGMA(GCC diagnostic pop) } template ` keys, +/// comparison channels (including payloads wider than 64 bits), +/// wildcard coefficients, and verifiable correction seeds round-trip. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -12,79 +16,262 @@ #include #include +#include +#include +#include #include #include -#include -#include #include #include +#if !defined(NLOHMANN_JSON_VERSION_MAJOR) #include "json/include/nlohmann/json.hpp" +#endif + #include "portable-snippets/exact-int/exact-int.h" #include "dpf/dpf_key.hpp" +#include "dpf/secret_share.hpp" -namespace nlohmann +namespace dpf +{ +namespace json +{ +namespace codec { -template -struct adl_serializer> +template +struct is_std_array : std::false_type {}; +template +struct is_std_array> : std::true_type {}; + +template +struct is_std_tuple : std::false_type {}; +template +struct is_std_tuple> : std::true_type {}; + +inline std::uint64_t as_u64(const nlohmann::json & j) { - static void from_json(const nlohmann::json & j, dpf::beaver & beaver) // NOLINT(runtime/references) + if (j.is_number_unsigned()) + return j.get(); + if (j.is_number_integer()) + return static_cast(j.get()); + throw std::invalid_argument("dpf::json: expected an integer"); +} + +inline std::string hex_encode(const void * data, std::size_t n) +{ + static constexpr char digits[] = "0123456789abcdef"; + const auto * bytes = static_cast(data); + std::string out(n * 2, '\0'); + for (std::size_t i = 0; i < n; ++i) { - j.get_to(beaver.output_blind); - j.get_to(beaver.vector_blind); - j.get_to(beaver.blinded_vector); + out[2 * i] = digits[bytes[i] >> 4]; + out[2 * i + 1] = digits[bytes[i] & 0x0f]; } + return out; +} - static void to_json(nlohmann::json & j, const dpf::beaver & beaver) // NOLINT(runtime/references) +inline int hex_nybble(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +template +nlohmann::json dump(const T & value); + +template +T load(const nlohmann::json & j); + +template +nlohmann::json dump_tuple(const Tuple & value, std::index_sequence) +{ + return nlohmann::json::array({dump(std::get(value))...}); +} + +template +Tuple load_tuple(const nlohmann::json & j, std::index_sequence) +{ + return Tuple{load>(j.at(Is))...}; +} + +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") +template +nlohmann::json dump(const T & value) +{ + using U = std::remove_cv_t; + if constexpr (std::is_same_v) { - j = nlohmann::json{ - {"output_blind", beaver.output_blind}, - {"vector_blind", beaver.vector_blind}, - {"blinded_vector", beaver.blinded_vector} + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + std::uint64_t lane[2]; + std::memcpy(lane, &value, sizeof(lane)); + HEDLEY_PRAGMA(GCC diagnostic pop) + nlohmann::json out = nlohmann::json::array(); + out.push_back(lane[0]); + out.push_back(lane[1]); + return out; + } + else if constexpr (std::is_same_v) + { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + std::uint64_t lane[4]; + std::memcpy(lane, &value, sizeof(lane)); + HEDLEY_PRAGMA(GCC diagnostic pop) + nlohmann::json out = nlohmann::json::array(); + for (std::uint64_t limb : lane) + out.push_back(limb); + return out; + } + else if constexpr (std::is_same_v) + { + nlohmann::json out = nlohmann::json::array(); + out.push_back(static_cast(value)); + out.push_back(static_cast(value >> 64)); + return out; + } + else if constexpr (std::is_same_v) + { + nlohmann::json out = nlohmann::json::array(); + out.push_back(value.lower()); + out.push_back(value.upper()); + return out; + } + else if constexpr (std::is_same_v) + { + nlohmann::json out = nlohmann::json::array(); + out.push_back(value.lower().lower()); + out.push_back(value.lower().upper()); + out.push_back(value.upper().lower()); + out.push_back(value.upper().upper()); + return out; + } + else if constexpr (std::is_same_v) + { + nlohmann::json out = nlohmann::json{ + {"nbits", value.nbits}, + {"mask", value.mask}, + {"kind", static_cast(value.kind)}, + {"trivial", static_cast(value.trivial)}, + {"eval_as_ge", value.eval_as_ge}, + {"include_eq", value.include_eq}, + {"active", value.active} }; + if (value.incremental) + out["incremental"] = true; + if (value.block_width != 0) + { + out["block_width"] = value.block_width; + out["tail_bits"] = value.tail_bits; + } + return out; } -}; + else if constexpr (is_std_array::value) + { + nlohmann::json out = nlohmann::json::array(); + for (const auto & elem : value) + out.push_back(dump(elem)); + return out; + } + else if constexpr (is_std_tuple::value) + { + return dump_tuple(value, std::make_index_sequence>{}); + } + else if constexpr (dpf::is_wildcard_v) + { + return nlohmann::json(nullptr); + } + else if constexpr (std::is_enum_v) + { + return dump(static_cast>(value)); + } + else if constexpr (std::is_same_v) + { + return nlohmann::json(value); + } + else if constexpr (std::is_integral_v && sizeof(U) <= sizeof(std::uint64_t)) + { + if constexpr (std::is_signed_v) + return nlohmann::json(static_cast(value)); + else + return nlohmann::json(static_cast(value)); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + return nlohmann::json(value); + } + else if constexpr (std::is_trivially_copyable_v) + { + nlohmann::json out = nlohmann::json::object(); + out["$bytes"] = hex_encode(&value, sizeof(U)); + return out; + } + else + { + static_assert(sizeof(U) == 0, "dpf::json: no conversion for this type"); + return nlohmann::json(nullptr); + } +} -template <> -struct adl_serializer +template +T load(const nlohmann::json & j) { - static void from_json(const nlohmann::json & j, simde__m128i & a) // NOLINT(runtime/references) + using U = std::remove_cv_t; + if constexpr (std::is_same_v) { - std::array A; - j.get_to(A); - a = simde_mm_set_epi64x(A[1], A[0]); + std::uint64_t lane[2] = {as_u64(j.at(0)), as_u64(j.at(1))}; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + simde__m128i out; + std::memcpy(&out, lane, sizeof(out)); + HEDLEY_PRAGMA(GCC diagnostic pop) + return out; } - - static void to_json(nlohmann::json & j, const simde__m128i & a) // NOLINT(runtime/references) + else if constexpr (std::is_same_v) { - j = nlohmann::json{a[0], a[1]}; + std::uint64_t lane[4] = { + as_u64(j.at(0)), as_u64(j.at(1)), as_u64(j.at(2)), as_u64(j.at(3)) + }; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + simde__m256i out; + std::memcpy(&out, lane, sizeof(out)); + HEDLEY_PRAGMA(GCC diagnostic pop) + return out; } -}; - -template <> -struct adl_serializer -{ - static void from_json(const nlohmann::json & j, simde__m256i & a) // NOLINT(runtime/references) + else if constexpr (std::is_same_v) { - std::array A; - j.get_to(A); - a = simde_mm256_set_epi64x(A[3], A[2], A[1], A[0]); + if (j.is_number()) + return static_cast(as_u64(j)); + const simde_uint128 lo = as_u64(j.at(0)); + const simde_uint128 hi = as_u64(j.at(1)); + return lo | (hi << 64); } - - static void to_json(nlohmann::json & j, const simde__m256i & a) // NOLINT(runtime/references) + else if constexpr (std::is_same_v) { - j = nlohmann::json{a[0], a[1], a[2], a[3]}; + if (j.is_number()) + return uint128_t{as_u64(j)}; + return uint128_t{as_u64(j.at(1)), as_u64(j.at(0))}; } -}; - -template <> -struct adl_serializer -{ - static void from_json(const nlohmann::json & j, dpf::detail::cmp_meta & c) // NOLINT(runtime/references) + else if constexpr (std::is_same_v) { + if (j.is_number()) + return uint256_t{as_u64(j)}; + const uint128_t lo{as_u64(j.at(1)), as_u64(j.at(0))}; + const uint128_t hi{as_u64(j.at(3)), as_u64(j.at(2))}; + return uint256_t{hi, lo}; + } + else if constexpr (std::is_same_v) + { + dpf::detail::cmp_meta c; j.at("nbits").get_to(c.nbits); j.at("mask").get_to(c.mask); c.kind = static_cast(j.at("kind").get()); @@ -96,162 +283,89 @@ struct adl_serializer c.incremental = j.value("incremental", false); c.block_width = j.value("block_width", 0); c.tail_bits = j.value("tail_bits", 0); + return c; } - - static void to_json(nlohmann::json & j, const dpf::detail::cmp_meta & c) // NOLINT(runtime/references) + else if constexpr (is_std_array::value) { - j = nlohmann::json{ - {"nbits", c.nbits}, - {"mask", c.mask}, - {"kind", static_cast(c.kind)}, - {"trivial", static_cast(c.trivial)}, - {"eval_as_ge", c.eval_as_ge}, - {"include_eq", c.include_eq}, - {"active", c.active} - }; - if (c.incremental) - j["incremental"] = true; - if (c.block_width != 0) + U out{}; + if (j.size() != out.size()) + throw std::invalid_argument("dpf::json: array length mismatch"); + for (std::size_t i = 0; i < out.size(); ++i) + out[i] = load(j.at(i)); + return out; + } + else if constexpr (is_std_tuple::value) + { + if (j.size() != std::tuple_size_v) + throw std::invalid_argument("dpf::json: tuple length mismatch"); + return load_tuple(j, std::make_index_sequence>{}); + } + else if constexpr (dpf::is_wildcard_v) + { + return U{}; + } + else if constexpr (std::is_enum_v) + { + return static_cast(load>(j)); + } + else if constexpr (std::is_same_v) + { + if (j.is_boolean()) + return j.get(); + return as_u64(j) != 0; + } + else if constexpr (std::is_integral_v && sizeof(U) <= sizeof(std::uint64_t)) + { + if constexpr (std::is_signed_v) + return static_cast(static_cast(as_u64(j))); + else + return static_cast(as_u64(j)); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + return j.get(); + } + else if constexpr (std::is_trivially_copyable_v) + { + if (!j.is_object() || !j.contains("$bytes")) + throw std::invalid_argument("dpf::json: expected a byte blob"); + const auto hex = j.at("$bytes").get(); + if (hex.size() != sizeof(U) * 2) + throw std::invalid_argument("dpf::json: byte blob has the wrong size"); + U out{}; + auto * bytes = reinterpret_cast(&out); + for (std::size_t i = 0; i < sizeof(U); ++i) { - j["block_width"] = c.block_width; - j["tail_bits"] = c.tail_bits; + const int hi = hex_nybble(hex[2 * i]); + const int lo = hex_nybble(hex[2 * i + 1]); + if (hi < 0 || lo < 0) + throw std::invalid_argument("dpf::json: bad hex"); + bytes[i] = static_cast((hi << 4) | lo); } + return out; } -}; - -// Classic single-level key (no `at<>` / no comparison channel). -template -struct adl_serializer, - std::enable_if_t::is_multilevel>> -{ - using dpf_type = dpf::dpf_key; - using interior_node = typename dpf_type::interior_node; - using leaf_tuple = typename dpf_type::leaf_tuple; - using beaver_tuple = typename dpf_type::beaver_tuple; - - static dpf_type from_json(const nlohmann::json & j) + else { - interior_node root; - j.at("root").get_to(root); - std::array correction_words; - j.at("correction_words").get_to(correction_words); - std::array correction_advice; - j.at("correction_advice").get_to(correction_advice); - leaf_tuple leaves; - j.at("leaves").get_to(leaves); - std::string wildcard_mask_str; - j.at("wildcards").get_to(wildcard_mask_str); - beaver_tuple beavers; - j.at("beavers").get_to(beavers); - - return dpf_type{ - root, - correction_words, - correction_advice, - leaves, - std::bitset>(wildcard_mask_str), - beavers - }; + static_assert(sizeof(U) == 0, "dpf::json: no conversion for this type"); + return U{}; } +} +HEDLEY_PRAGMA(GCC diagnostic pop) - static void to_json(nlohmann::json & j, const dpf_type & dpf) // NOLINT(runtime/references) - { - j = nlohmann::json{ - {"root", dpf.root()}, - {"correction_words", dpf.correction_words()}, - {"correction_advice", dpf.correction_advice()}, - {"leaves", dpf.mutable_leaf_tuple()}, - {"wildcards", dpf.mutable_wildcard_mask()}, - {"beavers", dpf.mutable_beaver_tuple()} - }; - } -}; - -// Multi-level / comparison key (`at<>` and/or a `cmp` channel). Round-trips -// the public tree (root, CWs, advice) and the comparison channel (cmp meta, -// value CWs, `cw_last`, and this party's `cmp_addend` share). Leaf outputs are -// not yet serialized here, so this path currently supports comparison-only -// keys (`num_outputs == 0`, e.g. `make_dpf(x, dpf::lt(...))`). -template -struct adl_serializer, - std::enable_if_t::is_multilevel>> +template +std::uint64_t low64(const Word & word) { - using dpf_type = dpf::dpf_key; - using interior_node = typename dpf_type::interior_node; - using input_type = typename dpf_type::input_type; + if constexpr (std::is_same_v) + return static_cast(word.lower().lower()); + else if constexpr (std::is_same_v) + return word.lower(); + else if constexpr (sizeof(Word) <= sizeof(std::uint64_t)) + return static_cast(word); + else + return static_cast(word); +} - static dpf_type from_json(const nlohmann::json & j) - { - static_assert(dpf_type::num_outputs == 0, - "dpf::json round-trip currently supports comparison-only " - "multi-level keys (no leaf outputs)"); - interior_node root; - j.at("root").get_to(root); - typename dpf_type::correction_words_array correction_words; - j.at("correction_words").get_to(correction_words); - typename dpf_type::correction_advice_array correction_advice; - j.at("correction_advice").get_to(correction_advice); - - dpf::detail::cmp_meta cmp; - j.at("cmp").get_to(cmp); - typename dpf_type::value_cw_array value_cws; - j.at("value_cw").get_to(value_cws); - uint64_t cw_last = j.at("cw_last").template get(); - uint64_t cmp_addend = j.at("cmp_addend").template get(); - typename dpf_type::tail_array tail{}; - if constexpr (dpf_type::cmp_block > 0) - j.at("tail_cw").get_to(tail); - typename dpf_type::prefix_cw_array prefix{}; - if constexpr (dpf_type::cmp_idcf) - j.at("prefix_cw").get_to(prefix); - - typename dpf_type::leaf_wrapper_tuple leaves{}; - input_type offset_share{}; - typename dpf_type::addend_tuple addends{}; - - return dpf_type{root, correction_words, correction_advice, - std::move(leaves), offset_share, cmp, value_cws, cw_last, - cmp_addend, addends, {}, 0, tail, {}, prefix}; - } - - static void to_json(nlohmann::json & j, const dpf_type & dpf) // NOLINT(runtime/references) - { - static_assert(dpf_type::num_outputs == 0, - "dpf::json round-trip currently supports comparison-only " - "multi-level keys (no leaf outputs)"); - j = nlohmann::json{ - {"root", dpf.root()}, - {"correction_words", dpf.correction_words()}, - {"correction_advice", dpf.correction_advice()}, - {"cmp", dpf.cmp()}, - {"value_cw", dpf.value_cw()}, - {"cw_last", static_cast(dpf.cw_last())}, - {"cmp_addend", static_cast(dpf.cmp_addend())} - }; - if constexpr (dpf_type::cmp_block > 0) - j["tail_cw"] = dpf.tail_cw(); - if constexpr (dpf_type::cmp_idcf) - j["prefix_cw"] = dpf.prefix_cws(); - } -}; - -} // namespace nlohmann - -namespace dpf -{ - -namespace json -{ +} // namespace codec template static std::string to_json(const DpfKey & dpf) @@ -268,7 +382,429 @@ static auto from_json(const std::string & json_string) } } // namespace json - } // namespace dpf +NLOHMANN_JSON_NAMESPACE_BEGIN + +template +struct adl_serializer, void> +{ + static void from_json(const nlohmann::json & j, dpf::beaver & beaver) // NOLINT(runtime/references) + { + using beaver_type = dpf::beaver; + beaver.output_blind = dpf::json::codec::load(j.at("output_blind")); + beaver.vector_blind = dpf::json::codec::load(j.at("vector_blind")); + beaver.blinded_vector = dpf::json::codec::load(j.at("blinded_vector")); + } + + static void to_json(nlohmann::json & j, const dpf::beaver & beaver) // NOLINT(runtime/references) + { + j = nlohmann::json{ + {"output_blind", dpf::json::codec::dump(beaver.output_blind)}, + {"vector_blind", dpf::json::codec::dump(beaver.vector_blind)}, + {"blinded_vector", dpf::json::codec::dump(beaver.blinded_vector)} + }; + } +}; + +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") +template <> +struct adl_serializer +{ + static simde__m128i from_json(const nlohmann::json & j) + { + return dpf::json::codec::load(j); + } + + static void to_json(nlohmann::json & j, const simde__m128i & a) // NOLINT(runtime/references) + { + j = dpf::json::codec::dump(a); + } +}; + +template <> +struct adl_serializer +{ + static simde__m256i from_json(const nlohmann::json & j) + { + return dpf::json::codec::load(j); + } + + static void to_json(nlohmann::json & j, const simde__m256i & a) // NOLINT(runtime/references) + { + j = dpf::json::codec::dump(a); + } +}; +HEDLEY_PRAGMA(GCC diagnostic pop) + +template <> +struct adl_serializer +{ + static simde_uint128 from_json(const nlohmann::json & j) + { + return dpf::json::codec::load(j); + } + + static void to_json(nlohmann::json & j, const simde_uint128 & a) // NOLINT(runtime/references) + { + j = dpf::json::codec::dump(a); + } +}; + +template <> +struct adl_serializer +{ + static uint128_t from_json(const nlohmann::json & j) + { + return dpf::json::codec::load(j); + } + + static void to_json(nlohmann::json & j, const uint128_t & a) // NOLINT(runtime/references) + { + j = dpf::json::codec::dump(a); + } +}; + +template <> +struct adl_serializer +{ + static uint256_t from_json(const nlohmann::json & j) + { + return dpf::json::codec::load(j); + } + + static void to_json(nlohmann::json & j, const uint256_t & a) // NOLINT(runtime/references) + { + j = dpf::json::codec::dump(a); + } +}; + +template +struct adl_serializer, void> +{ + using dpf_type = dpf::dpf_key; + using input_type = typename dpf_type::input_type; + using leaf_tuple = typename dpf_type::leaf_tuple; + using leaf_wrapper_tuple = typename dpf_type::leaf_wrapper_tuple; + static constexpr bool classic = + dpf::detail::incr::is_classic_pack_v; + + template + static nlohmann::json dump_leaf(const std::tuple_element_t & wrapper) + { + nlohmann::json entry = nlohmann::json::object(); + entry["leaf"] = dpf::json::codec::dump(wrapper.raw_leaf()); + if constexpr (dpf::is_wildcard_v>) + { + const auto & beaver = wrapper.beaver(); + entry["beaver"] = nlohmann::json{ + {"output_blind", dpf::json::codec::dump(beaver.output_blind)}, + {"vector_blind", dpf::json::codec::dump(beaver.vector_blind)}, + {"blinded_vector", dpf::json::codec::dump(beaver.blinded_vector)} + }; + entry["output_share"] = dpf::json::codec::dump(wrapper.output_share()); + entry["state"] = wrapper.state(); + } + return entry; + } + + template + static nlohmann::json dump_leaves(const leaf_wrapper_tuple & leaves, + std::index_sequence) + { + return nlohmann::json::array({dump_leaf(std::get(leaves))...}); + } + + template + static auto load_beaver(const nlohmann::json & entry) + { + using beaver_type = std::tuple_element_t; + if constexpr (dpf::is_wildcard_v>) + { + beaver_type beaver{}; + const auto & stored = entry.at("beaver"); + beaver.output_blind = dpf::json::codec::load( + stored.at("output_blind")); + beaver.vector_blind = dpf::json::codec::load( + stored.at("vector_blind")); + beaver.blinded_vector = dpf::json::codec::load( + stored.at("blinded_vector")); + return beaver; + } + else + { + return beaver_type{}; + } + } + + template + static leaf_tuple load_leaf_tuple(const nlohmann::json & leaves, + std::index_sequence) + { + return leaf_tuple{ + dpf::json::codec::load>( + leaves.at(Is).at("leaf"))... + }; + } + + template + static auto load_beaver_tuple(const nlohmann::json & leaves, + std::index_sequence) + { + return typename dpf_type::beaver_tuple{load_beaver(leaves.at(Is))...}; + } + + template + static void restore_leaf(Wrapper & wrapper, const nlohmann::json & entry) + { + if constexpr (dpf::is_wildcard_v>) + { + if (!entry.contains("state")) + return; + using output_type = typename Wrapper::output_type; + output_type share{}; + if (entry.contains("output_share")) + share = dpf::json::codec::load(entry.at("output_share")); + wrapper.restore_state(std::move(share), + entry.at("state").template get()); + } + else + { + (void)wrapper; + (void)entry; + } + } + + template + static void restore_leaves(dpf_type & key, const nlohmann::json & leaves, + std::index_sequence) + { + (restore_leaf(std::get(key.leaf_nodes), leaves.at(Is)), ...); + } + + template + static auto load_wrapper(const nlohmann::json & entry) + { + using wrapper = std::tuple_element_t; + auto leaf = dpf::json::codec::load(entry.at("leaf")); + if constexpr (dpf::is_wildcard_v>) + { + using beaver_type = typename wrapper::beaver_type; + beaver_type beaver{}; + const auto & stored = entry.at("beaver"); + beaver.output_blind = dpf::json::codec::load( + stored.at("output_blind")); + beaver.vector_blind = dpf::json::codec::load( + stored.at("vector_blind")); + beaver.blinded_vector = dpf::json::codec::load( + stored.at("blinded_vector")); + wrapper out{std::move(leaf), std::move(beaver)}; + restore_leaf(out, entry); + return out; + } + else + { + return wrapper{std::move(leaf)}; + } + } + + template + static leaf_wrapper_tuple load_wrappers(const nlohmann::json & leaves, + std::index_sequence) + { + return leaf_wrapper_tuple{load_wrapper(leaves.at(Is))...}; + } + + static void restore_offset(dpf_type & key, const nlohmann::json & j) + { + if constexpr (dpf::is_wildcard_v) + { + if (j.contains("offset_state")) + { + key.offset_x.restore( + dpf::json::codec::load(j.at("offset")), + j.at("offset_state").template get()); + } + } + else + { + (void)key; + (void)j; + } + } + + static dpf_type from_json(const nlohmann::json & j) + { + const auto root = dpf::json::codec::load(j.at("root")); + const auto correction_words = + dpf::json::codec::load(j.at("correction_words")); + const auto correction_advice = + dpf::json::codec::load(j.at("correction_advice")); + input_type offset{}; + if (j.contains("offset")) + offset = dpf::json::codec::load(j.at("offset")); + + if constexpr (classic) + { + constexpr auto idx = std::make_index_sequence{}; + const auto & leaves_json = j.at("leaves"); + dpf_type key{root, correction_words, correction_advice, + load_leaf_tuple(leaves_json, idx), + load_beaver_tuple(leaves_json, idx), + offset}; + restore_leaves(key, leaves_json, idx); + restore_offset(key, j); + return key; + } + else + { + auto leaves = [&]() { + if constexpr (dpf_type::num_outputs == 0) + return leaf_wrapper_tuple{}; + else + return load_wrappers(j.at("leaves"), + std::make_index_sequence{}); + }(); + + dpf::detail::cmp_meta cmp{}; + if (j.contains("cmp")) + cmp = dpf::json::codec::load(j.at("cmp")); + + typename dpf_type::value_cw_array value_cws{}; + if (j.contains("value_cw")) + value_cws = dpf::json::codec::load(j.at("value_cw")); + + using word = typename dpf_type::value_cw_word; + word cw_last{}; + if (j.contains("cw_last")) + cw_last = dpf::json::codec::load(j.at("cw_last")); + word cmp_addend{}; + if (j.contains("cmp_addend")) + cmp_addend = dpf::json::codec::load(j.at("cmp_addend")); + + typename dpf_type::addend_tuple addends{}; + if constexpr (dpf_type::num_outputs > 0) + { + if (j.contains("addends")) + addends = dpf::json::codec::load(j.at("addends")); + } + + typename dpf_type::value_cw_array value_cw_coeff{}; + if (j.contains("value_cw_coeff")) + value_cw_coeff = dpf::json::codec::load( + j.at("value_cw_coeff")); + word cw_last_coeff{}; + if (j.contains("cw_last_coeff")) + cw_last_coeff = dpf::json::codec::load(j.at("cw_last_coeff")); + + typename dpf_type::tail_array tail{}; + typename dpf_type::tail_array tail_coeff{}; + if constexpr (dpf_type::cmp_block > 0) + { + if (j.contains("tail_cw")) + tail = dpf::json::codec::load(j.at("tail_cw")); + if (j.contains("tail_coeff")) + tail_coeff = dpf::json::codec::load(j.at("tail_coeff")); + } + + typename dpf_type::prefix_cw_array prefix{}; + typename dpf_type::prefix_cw_array prefix_coeff{}; + if constexpr (dpf_type::cmp_idcf) + { + if (j.contains("prefix_cw")) + prefix = dpf::json::codec::load(j.at("prefix_cw")); + if (j.contains("prefix_coeff")) + prefix_coeff = dpf::json::codec::load( + j.at("prefix_coeff")); + } + + typename dpf_type::correction_seeds_array seeds{}; + if constexpr (dpf_type::is_verifiable) + { + if (j.contains("correction_seeds")) + seeds = dpf::json::codec::load( + j.at("correction_seeds")); + } + + dpf_type key{root, correction_words, correction_advice, + std::move(leaves), offset, cmp, value_cws, + dpf::json::codec::low64(cw_last), dpf::json::codec::low64(cmp_addend), + std::move(addends), value_cw_coeff, dpf::json::codec::low64(cw_last_coeff), + tail, tail_coeff, prefix, prefix_coeff, seeds}; + key.set_cmp_scalars(cw_last, cmp_addend, cw_last_coeff); + if (j.contains("cmp_assigned")) + key.set_cmp_assigned(j.at("cmp_assigned").template get()); + restore_offset(key, j); + return key; + } + } + + static void to_json(nlohmann::json & j, const dpf_type & dpf) // NOLINT(runtime/references) + { + j = nlohmann::json::object(); + j["root"] = dpf::json::codec::dump(dpf.root()); + j["correction_words"] = dpf::json::codec::dump(dpf.correction_words()); + j["correction_advice"] = dpf::json::codec::dump(dpf.correction_advice()); + if constexpr (dpf_type::num_outputs > 0) + { + j["leaves"] = dump_leaves(dpf.leaf_nodes, + std::make_index_sequence{}); + } + j["offset"] = dpf::json::codec::dump(dpf.offset_x.raw()); + if constexpr (dpf::is_wildcard_v) + j["offset_state"] = dpf.offset_x.state(); + + if constexpr (!classic) + { + if constexpr (dpf_type::cmp_depth > 0) + { + j["cmp"] = dpf::json::codec::dump(dpf.cmp()); + j["value_cw"] = dpf::json::codec::dump(dpf.value_cw()); + j["cw_last"] = dpf::json::codec::dump(dpf.cw_last_word()); + j["cmp_addend"] = dpf::json::codec::dump(dpf.cmp_addend_word()); + if constexpr (dpf_type::cmp_block > 0) + j["tail_cw"] = dpf::json::codec::dump(dpf.tail_cw()); + if constexpr (dpf_type::cmp_idcf) + j["prefix_cw"] = dpf::json::codec::dump(dpf.prefix_cws()); + if constexpr (dpf_type::cmp_is_wildcard) + { + j["value_cw_coeff"] = dpf::json::codec::dump(dpf.value_cw_coeff()); + j["cw_last_coeff"] = dpf::json::codec::dump(dpf.cw_last_coeff_word()); + if constexpr (dpf_type::cmp_block > 0) + j["tail_coeff"] = dpf::json::codec::dump(dpf.tail_coeff()); + if constexpr (dpf_type::cmp_idcf) + j["prefix_coeff"] = dpf::json::codec::dump(dpf.prefix_cw_coeff()); + j["cmp_assigned"] = dpf.cmp_assigned(); + } + } + if constexpr (dpf_type::num_outputs > 0) + j["addends"] = dpf::json::codec::dump(dpf.public_addends); + if constexpr (dpf_type::is_verifiable) + j["correction_seeds"] = dpf::json::codec::dump(dpf.correction_seeds()); + } + } +}; + +template +struct adl_serializer, void> +{ + static dpf::party_key from_json(const nlohmann::json & j) + { + return dpf::party_key(j.template get()); + } + + static void to_json(nlohmann::json & j, const dpf::party_key & key) // NOLINT(runtime/references) + { + j = key.key(); + } +}; + +NLOHMANN_JSON_NAMESPACE_END + #endif // LIBDPF_INCLUDE_DPF_JSON_HPP__ diff --git a/include/dpf/keyword.hpp b/include/dpf/keyword.hpp index a45db43..7da834b 100644 --- a/include/dpf/keyword.hpp +++ b/include/dpf/keyword.hpp @@ -214,6 +214,7 @@ class basic_fixed_length_string : public dpf::modint(st /// @} /// @brief assign the `basic_fixed_length_string` + /// @return `*this` /// @{ /// @brief value assignment @@ -283,8 +284,10 @@ class basic_fixed_length_string : public dpf::modint(st /// @brief converts a string of length at-most `max_length` over /// `alphabet` into an integer - /// @throws `std::length_error` if `str` exceeds `max_length` - /// @throws `std::domain_error` if `str` contains a char not in `alphabet` + /// @param str the source string + /// @return the returned `integral_type` + /// @throws std::length_error if `str` exceeds `max_length` + /// @throws std::domain_error if `str` contains a char not in `alphabet` HEDLEY_ALWAYS_INLINE static constexpr integral_type encode_(string_view str) { @@ -309,6 +312,8 @@ class basic_fixed_length_string : public dpf::modint(st /// @brief Index of `c` in `alphabet`, or `npos` when `c` is absent. /// Byte alphabets use a 256-entry table; wider character types scan. + /// @param c the `c` + /// @return Index of `c` in `alphabet`, or `npos` when `c` is absent static constexpr std::size_t digit_of_(CharT c) { constexpr auto missing = string_view::npos; @@ -340,6 +345,9 @@ class basic_fixed_length_string : public dpf::modint(st /// @{ /// @brief Writes the decoded string, not the packed integer. + /// @param os the character output stream + /// @param k the `k` + /// @return the stream friend std::basic_ostream & operator<<(std::basic_ostream & os, const basic_fixed_length_string & k) @@ -348,6 +356,9 @@ class basic_fixed_length_string : public dpf::modint(st } /// @brief Reads a whitespace-delimited token and encodes it. + /// @param is the character input stream + /// @param k the `k` + /// @return the stream friend std::basic_istream & operator>>(std::basic_istream & is, basic_fixed_length_string & k) @@ -386,6 +397,13 @@ using keyword = basic_fixed_length_string; /// @details Uses a `static_cast` to convert `str` to recreate the string /// representation of a `basic_fixed_length_string` /// @complexity `O(MaxLen)` where `MaxLen` is the maximum string length +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alphabet alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Allocator allocator type +/// @param str the source string +/// @return the returned `std::basic_string` template ::bits> { }; /// @brief specializes `dpf::msb_of` for `dpf::basic_fixed_length_string` +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alpha alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Alloc allocator type template ` +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alpha alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Alloc allocator type template const` +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alpha alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Alloc allocator type template > {}; /// @details specializes `std::numeric_limits` for -/// `dpf::basic_fixed_length_string volatile` +/// @brief `dpf::basic_fixed_length_string volatile` +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alpha alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Alloc allocator type template > {}; /// @details specializes `std::numeric_limits` for -/// `dpf::basic_fixed_length_string const volatile` +/// @brief `dpf::basic_fixed_length_string const volatile` +/// @tparam MaxLen maximum string length +/// @tparam CharT character type +/// @tparam Alpha alphabet the string is drawn from +/// @tparam Traits character traits +/// @tparam Alloc allocator type template constexpr keyword2_error keyword2_status() noexcept { diff --git a/include/dpf/leaf_arithmetic.hpp b/include/dpf/leaf_arithmetic.hpp index f90a381..361a5d1 100644 --- a/include/dpf/leaf_arithmetic.hpp +++ b/include/dpf/leaf_arithmetic.hpp @@ -1,6 +1,5 @@ /// @file dpf/leaf_arithmetic.hpp -/// @brief -/// @details +/// @brief Addition, subtraction, and multiplication of packed leaves. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -361,6 +360,7 @@ template struct add_t struct add_t, void> final : public detail::bitstring_xor_t {}; /// @brief Bitwise XOR, not IEEE addition. Float addition does not form an /// exact secret-sharing group; XOR of the representation does. +/// @tparam NodeT GGM node type template struct add_t final : public std::bit_xor<> {}; template struct add_t final : public std::bit_xor<> {}; template <> struct add_t final : public std::bit_xor<> {}; @@ -372,6 +372,7 @@ template struct add_t, NodeT> final /// @brief Integer outputs whose width matches a SIMD lane but whose type is /// not one of the explicitly specialized aliases (`char`, `long long`, /// `char16_t`, and so on). +/// @tparam OutputT output type template struct add_t && sizeof(OutputT) <= 8>> @@ -405,7 +406,6 @@ struct add_t, simde__m256i> return add_t::integral_type, simde__m256i>{}(a, b); } }; - HEDLEY_PRAGMA(GCC diagnostic pop) namespace detail @@ -646,6 +646,7 @@ template <> struct subtract_t final template struct subtract_t> final : public detail::sub_array_t {}; template struct subtract_t, void> final : public detail::bitstring_xor_t {}; /// @brief Bitwise XOR, not IEEE subtraction. +/// @tparam NodeT GGM node type template struct subtract_t final : public std::bit_xor<> {}; template struct subtract_t final : public std::bit_xor<> {}; template struct subtract_t final : public std::bit_xor<> {}; @@ -687,7 +688,6 @@ struct subtract_t, simde__m256i> return subtract_t::integral_type, simde__m256i>{}(a, b); } }; - HEDLEY_PRAGMA(GCC diagnostic pop) namespace detail @@ -806,7 +806,6 @@ struct mul4x64_t static_cast(a[3]*b)}; } }; -HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace detail @@ -1279,8 +1278,8 @@ struct multiply_t final return dpf::lane_arith::mul_epi4(a, b); } }; - HEDLEY_PRAGMA(GCC diagnostic pop) + } // namespace leaf_arithmetic } // namespace dpf diff --git a/include/dpf/leaf_node.hpp b/include/dpf/leaf_node.hpp index 474029d..15960c3 100644 --- a/include/dpf/leaf_node.hpp +++ b/include/dpf/leaf_node.hpp @@ -1,6 +1,5 @@ /// @file dpf/leaf_node.hpp -/// @brief -/// @details +/// @brief The packed leaf image of one output group. /// @author Ryan Henry /// @author Christopher Jiang /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) @@ -136,10 +135,13 @@ struct const_max_size static constexpr std::size_t value = Only; }; -/// PRG position span covering output indices `Is...` of `OutputsTuple`. -/// `is_contiguous` is true when the selected outputs occupy a hole-free +/// @brief PRG position span covering output indices `Is...` of `OutputsTuple`. +/// @details `is_contiguous` is true when the selected outputs occupy a hole-free /// range, so one `ExteriorPRG::eval(..., count, pos_min)` produces every /// leaf mask. +/// @tparam NodeT GGM node type +/// @tparam OutputsTuple outputs tuple +/// @tparam Is is template @@ -268,8 +270,12 @@ auto make_naked_leaf(InputT x, OutputT y) noexcept return Y; } -/// Address of the first `NodeT` block inside a leaf. -/// A one-block leaf *is* a `NodeT`; a longer leaf is `std::array`. +/// @brief Address of the first `NodeT` block inside a leaf. +/// @details A one-block leaf *is* a `NodeT`; a longer leaf is `std::array`. +/// @tparam NodeT GGM node type +/// @tparam LeafT leaf type +/// @param leaf the leaf value +/// @return Address of the first `NodeT` block inside a leaf template HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -314,6 +320,7 @@ auto make_leaf_mask(const InteriorBlock & seed0, const InteriorBlock & seed1, HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using output_type = concrete_type_t>; +HEDLEY_PRAGMA(GCC diagnostic pop) auto mask0 = make_leaf_mask_inner( seed0, pos_base); @@ -321,7 +328,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") seed1, pos_base); return dpf::subtract_leaf(mask1, mask0); -HEDLEY_PRAGMA(GCC diagnostic pop) } template ( make_naked_leaf(x, Y), @@ -349,7 +356,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") make_leaf_mask( seed0, seed1, pos_base), make_naked_leaf(x, Y)); -HEDLEY_PRAGMA(GCC diagnostic pop) } template /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -167,7 +166,11 @@ struct leaf_wrapper, NodeT> return blinded_output_share; } - /// Accept a party-tagged share; convert to additive before Beaver math. + /// @brief Accept a party-tagged share; convert to additive before Beaver math. + /// @tparam Party party index, `0` or `1` + /// @tparam Scheme scheme + /// @param output_share the `output_share` + /// @return the returned `const output_type` template const output_type compute_and_get_blinded_output_share( const secret_share & output_share) @@ -212,6 +215,32 @@ struct leaf_wrapper, NodeT> HEDLEY_NO_THROW const beaver_type & beaver() const noexcept { return beaver_; } + /// @brief Output share captured during Beaver blinding, if any. + /// @return the output share + HEDLEY_ALWAYS_INLINE + HEDLEY_NO_THROW + const output_type & output_share() const noexcept { return output_share_; } + + /// @brief `leaf_status` as a byte, for serialization. + /// @return the status byte + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + HEDLEY_NO_THROW + constexpr std::uint8_t state() const noexcept + { + return static_cast(leaf_state_); + } + + /// @brief Restore a serialized blinding share and status byte. + /// @param share the output share + /// @param state the status byte + HEDLEY_ALWAYS_INLINE + void restore_state(output_type share, std::uint8_t state) noexcept + { + output_share_ = std::move(share); + leaf_state_ = static_cast(state); + } + private: enum class leaf_status : psnip_uint8_t { ready = 0, waiting = 1, computing = 2, blinded = 3, notset = 4 }; diff --git a/include/dpf/modint.hpp b/include/dpf/modint.hpp index 35154c8..1b1ac6f 100644 --- a/include/dpf/modint.hpp +++ b/include/dpf/modint.hpp @@ -13,6 +13,7 @@ #define LIBDPF_INCLUDE_DPF_MODINT_HPP__ #include +#include #include #include #include @@ -32,6 +33,7 @@ namespace dpf { /// @brief represents an unsigned integer modulo `2^Nbits` for small values of `Nbits` +/// @tparam Nbits width in bits template class modint { @@ -40,6 +42,7 @@ class modint using integral_type = dpf::utils::nonvoid_integral_type_from_bitlength_t; static constexpr std::size_t num_bits = Nbits; + static constexpr bool dpf_modint = true; /// @brief construct the `modint` /// @{ @@ -78,6 +81,7 @@ class modint /// @} /// @brief assign the `modint` + /// @return `*this` /// @{ /// @brief value assignment @@ -110,10 +114,11 @@ class modint ~modint() = default; /// @brief addition operator + /// @param rhs the other addend + /// @return the sum /// @{ /// @details Performs addition with an `integral_type`. - /// @param rhs the other addend HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -123,7 +128,6 @@ class modint } /// @details Performs addition with another `modint`. - /// @param rhs the other addend HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -135,10 +139,11 @@ class modint /// @} /// @brief addition-assignment operator + /// @param rhs the other addend + /// @return `*this` /// @{ /// @details Adds an `integral_type` to this `modint`. - /// @param rhs the other addend HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator+=(integral_type rhs) noexcept @@ -148,7 +153,6 @@ class modint } /// @details Adds another `modint` to this one. - /// @param rhs the other addend HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator+=(modint rhs) noexcept @@ -164,6 +168,7 @@ class modint /// @brief pre-increment operator /// @details Increments this `modint` and returns a reference to the /// result. + /// @return `*this` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator++() noexcept @@ -174,6 +179,7 @@ class modint /// @brief post-increment operator /// @details Creates a copy of this `modint`, and then increments this /// `modint` and returns the copy from before the increment. + /// @return `*this` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint operator++(int) noexcept @@ -189,6 +195,7 @@ class modint /// @details Returns the additive inverse modulo `2^Nbits` (two's /// complement on the underlying word). Required by /// `grotto::for_each_offset`, which computes `-offset`. + /// @return unary negation HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -198,10 +205,11 @@ class modint } /// @brief subtraction operator + /// @param rhs the subtrahend + /// @return the difference /// @{ /// @details Performs subtraction by an `integral_type`. - /// @param rhs the subtrahend HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -211,7 +219,6 @@ class modint } /// @details Performs subtraction by another `modint`. - /// @param rhs the subtrahend HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -223,10 +230,11 @@ class modint /// @} /// @brief subtraction-assignment operator + /// @param rhs the subtrahend + /// @return `*this` /// @{ /// @details Subtracts an `integral_type` from this `modint`. - /// @param rhs the subtrahend HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator-=(integral_type rhs) noexcept @@ -236,7 +244,6 @@ class modint } /// @details Subtracts another `modint` from this one. - /// @param rhs the subtrahend HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator-=(modint rhs) noexcept @@ -252,6 +259,7 @@ class modint /// @brief pre-decrement operator /// @details Decrements this `modint` and returns a reference to the /// result. + /// @return `*this` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator--() noexcept @@ -262,6 +270,7 @@ class modint /// @brief post-decrement operator /// @details Creates a copy of this `modint`, and then decrements this /// `modint` and returns the copy from before the decrement. + /// @return `*this` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint operator--(int) noexcept @@ -278,6 +287,7 @@ class modint /// one by `shift_amount` bits to the left. The value of `a<>b` /// is therefore a `modint` equal to the integer part of `a/2^b`. /// @param shift_amount the number of bits to shift by + /// @return bitwise-right-shift operator HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -338,6 +350,8 @@ class modint } /// @brief Integer division of the reduced values. + /// @param rhs the right-hand operand + /// @return Integer division of the reduced values HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -364,10 +378,11 @@ class modint } /// @brief multiplication operator + /// @param rhs the other multiplicand + /// @return the product /// @{ /// @brief Multiplies this `modint` with an `integral_type`. - /// @param rhs the other multiplicand HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -377,7 +392,6 @@ class modint } /// @brief Multiplies another `modint` with this one. - /// @param rhs the other multiplicand HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -389,10 +403,11 @@ class modint /// @} /// @brief multiplication-assignment operator + /// @param rhs the other multiplicand + /// @return `*this` /// @{ /// @details Multiplies an `integral_type` into this `modint`. - /// @param rhs the other multiplicand HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator*=(integral_type rhs) noexcept @@ -402,7 +417,6 @@ class modint } /// @details Multiplies another `modint` into this one. - /// @param rhs the other multiplicand HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr modint & operator*=(modint rhs) noexcept @@ -532,6 +546,7 @@ class modint } /// @brief convert this `modint` to the equivalent `integeral_type` + /// @return the returned `operator` HEDLEY_PURE HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -572,7 +587,44 @@ class modint operator<<(std::basic_ostream & os, const modint & i) { - return os << i.reduced_value(); + const auto raw = i.reduced_value(); + using word = std::remove_cv_t>; + if constexpr (std::is_same_v + || std::is_same_v) + { + if (raw == 0) + return os << CharT('0'); + CharT buf[40]; + int n = 0; + auto v = raw; + while (v != 0) + { + buf[n++] = static_cast('0' + static_cast(v % 10)); + v /= 10; + } + while (n > 0) + os << buf[--n]; + return os; + } + else if constexpr (std::is_integral_v) + return os << raw; + else + { + unsigned char bytes[sizeof(word)]; + std::memcpy(bytes, &raw, sizeof(word)); + os << "0x"; + bool started = false; + constexpr char hex[] = "0123456789abcdef"; + for (int b = static_cast(sizeof(word)) - 1; b >= 0; --b) + { + if (!started && bytes[static_cast(b)] == 0 && b != 0) + continue; + started = true; + const auto byte = bytes[static_cast(b)]; + os << hex[byte >> 4] << hex[byte & 0x0f]; + } + return os; + } } template ` with an `modint::integral_type`. +/// @tparam Nbits width in bits /// @param lhs the `integral_type` multiplicand /// @param rhs the `modint` multiplicand +/// @return Multiplies a `modint` with an `modint::integral_type` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -628,9 +682,13 @@ constexpr modint operator*(typename modint::integral_type lhs, } /// @brief Compare two `modint`s as if they were regular integers +/// @tparam Nbits width in bits +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand /// @{ /// @brief less-than operator +/// @return `true` when `lhs < rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -642,6 +700,7 @@ constexpr bool operator<(modint lhs, modint rhs) noexcept } /// @brief less-than-or-equal-to operator +/// @return `true` when `lhs <= rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -653,6 +712,7 @@ constexpr bool operator<=(modint lhs, modint rhs) noexcept } /// @brief greater-than operator +/// @return `true` when `lhs > rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -664,6 +724,7 @@ constexpr bool operator>(modint lhs, modint rhs) noexcept } /// @brief greater-than-or-equal-to operator +/// @return `true` when `lhs >= rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -675,6 +736,7 @@ constexpr bool operator>=(modint lhs, modint rhs) noexcept } /// @brief equality operator +/// @return `true` when `lhs == rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -686,6 +748,7 @@ constexpr bool operator==(modint lhs, modint rhs) noexcept } /// @brief inequality operator +/// @return `true` when `lhs != rhs` template HEDLEY_CONST HEDLEY_ALWAYS_INLINE @@ -1358,6 +1421,7 @@ namespace std /// @{ /// @details specializes `std::numeric_limits` for `dpf::modint` +/// @tparam Nbits width in bits template class numeric_limits> { @@ -1409,18 +1473,21 @@ class numeric_limits> }; /// @details specializes `std::numeric_limits` for `dpf::modint const` +/// @tparam Nbits width in bits template class numeric_limits const> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `dpf::modint volatile` +/// @brief `dpf::modint volatile` +/// @tparam Nbits width in bits template class numeric_limits volatile> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `dpf::modint const volatile` +/// @brief `dpf::modint const volatile` +/// @tparam Nbits width in bits template class numeric_limits const volatile> : public numeric_limits> {}; diff --git a/include/dpf/multipoint.hpp b/include/dpf/multipoint.hpp new file mode 100644 index 0000000..888d41e --- /dev/null +++ b/include/dpf/multipoint.hpp @@ -0,0 +1,560 @@ +/// @file dpf/multipoint.hpp +/// @brief Cuckoo-packed multi-point DPF and verifiable multi-point DPF. +/// @details Packs t distinct points into m ≈ O(t) buckets (de Castro– +/// Polychroniadou, EUROCRYPT 2022, §4). Each bucket is an ordinary +/// point key on a smaller domain — `dpf::verifiable` selects VDPF +/// buckets. Evaluation probes κ = 3 buckets and sums the shares. +/// A batched proof is one 2λ token. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__ +#define LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" +#include "simde/simde/x86/avx2.h" + +#include "dpf/eval_point.hpp" +#include "dpf/incremental.hpp" +#include "dpf/prg_aes.hpp" +#include "dpf/random.hpp" +#include "dpf/secret_share.hpp" +#include "dpf/verifiable.hpp" + +namespace dpf +{ + +/// @brief Knobs for cuckoo packing. `lambda` is the Remark 1 failure target. +struct multipoint_params +{ + std::uint32_t lambda = 40; + std::uint32_t max_evictions = 4096; + int retries = 8; +}; + +template +struct is_multipoint_key : std::false_type +{ +}; + +template +struct multipoint_key +{ + static constexpr std::size_t party = Party; + static constexpr bool is_multipoint = true; + static constexpr bool is_verifiable = BucketKey::is_verifiable; + static constexpr std::size_t kappa = 3; + + using input_type = InputT; + using output_type = OutputT; + using bucket_key = BucketKey; + using bucket_input = typename BucketKey::input_type; + using share_type = subtractive_share; + + simde__m128i sigma{}; + std::uint32_t bucket_count = 0; + std::uint64_t bucket_domain = 0; + std::vector> buckets{}; +}; + +template +struct is_multipoint_key> + : std::true_type +{ +}; + +template +inline constexpr bool is_multipoint_key_v = + is_multipoint_key>::value; + +namespace detail +{ +namespace mpf +{ + +struct prp_walk_error : std::runtime_error +{ + prp_walk_error() + : std::runtime_error("multipoint PRP cycle walk exceeded its bound") + { + } +}; + +struct located +{ + std::uint32_t bucket = 0; + std::uint64_t index = 0; +}; + +using wide = unsigned __int128; + +inline wide domain_size(std::size_t bits) +{ + return wide{1} << bits; +} + +/// @brief 4-round Feistel on the next power-of-two square, then cycle-walk +/// into `[0, domain)`. AES-MMO is the round function. +/// @param seed the PRP seed +/// @param x the input, in `[0, domain)` +/// @param domain the domain size +/// @return the permuted value in `[0, domain)` +/// @throws std::invalid_argument if `x` is outside the domain +/// @throws prp_walk_error if the cycle walk exceeds its bound +inline wide permute(simde__m128i seed, wide x, wide domain) +{ + if (domain <= 1) + return 0; + if (x >= domain) + throw std::invalid_argument("multipoint PRP input is outside the domain"); + + int bits = 0; + for (wide v = domain - 1; v > 0; v >>= 1) + ++bits; + const int half = (bits + 1) / 2; + const wide mask = (half >= 128) + ? ~wide{0} + : (wide{1} << half) - 1; + + wide val = x; + for (int guard = 0; guard < 128; ++guard) + { + unsigned __int128 left = (val >> half) & mask; + unsigned __int128 right = val & mask; + for (int round = 0; round < 4; ++round) + { + alignas(16) std::uint64_t lanes[2] = { + static_cast(right), + static_cast(right >> 64)}; + auto msg = simde_mm_load_si128( + reinterpret_cast(lanes)); + msg = simde_mm_xor_si128(msg, seed); + msg = simde_mm_xor_si128(msg, + simde_mm_set_epi32(0, 0, 0, round + 1)); + const auto out = prg::aes128::eval(msg, + static_cast(round + 1)); + simde_mm_store_si128(reinterpret_cast(lanes), out); + wide f = lanes[0] | (wide{lanes[1]} << 64); + f &= mask; + left ^= f; + const wide tmp = left; + left = right; + right = tmp; + } + val = (left << half) | right; + if (val < domain) + return val; + } + throw prp_walk_error{}; +} + +inline located locate(simde__m128i sigma, wide x, int hash, + wide n, wide bucket_domain) +{ + constexpr int kappa = 3; + const wide y = permute(sigma, + x + n * static_cast(hash), n * kappa); + located out; + out.bucket = static_cast(y / bucket_domain); + out.index = static_cast(y % bucket_domain); + return out; +} + +inline std::uint32_t bucket_count_for(std::uint32_t t, std::uint32_t lambda) +{ + const double log2t = (t <= 1) ? 0.0 : std::log2(static_cast(t)); + const double e = (static_cast(lambda) + 130.0 + log2t) / 123.5; + auto m = static_cast(std::ceil(e * static_cast(t))); + if (m < t + 1) + m = t + 1; + // Remark 1's simplification wants t ≥ 30. Below that, keep a 2t table. + if (t < 30 && m < t * 2) + m = t * 2; + return m; +} + +inline std::uint32_t rng_seed(simde__m128i sigma) +{ + const auto block = prg::aes128::eval(sigma, 0xC000u); + alignas(16) std::uint32_t words[4]; + simde_mm_store_si128(reinterpret_cast(words), block); + return words[0] ^ (words[1] * 0x9E3779B9u) ^ words[2] ^ words[3]; +} + +struct slot +{ + int item = -1; + int hash = -1; +}; + +template +bool insert_cuckoo(simde__m128i sigma, const std::vector & alphas, + std::uint32_t m, wide n, wide bucket_domain, + std::uint32_t max_evictions, std::vector & table) +{ + table.assign(m, slot{}); + std::mt19937 rng(rng_seed(sigma)); + std::uniform_int_distribution pick(0, 2); + const int t = static_cast(alphas.size()); + for (int omega = 0; omega < t; ++omega) + { + int cur = omega; + int hash = pick(rng); + std::uint32_t evictions = 0; + for (;;) + { + const auto loc = locate(sigma, + static_cast(alphas[static_cast(cur)]), + hash, n, bucket_domain); + if (loc.bucket >= m) + return false; + if (table[loc.bucket].item < 0) + { + table[loc.bucket] = slot{cur, hash}; + break; + } + const int evicted = table[loc.bucket].item; + table[loc.bucket] = slot{cur, hash}; + cur = evicted; + hash = pick(rng); + if (++evictions > max_evictions) + return false; + } + } + return true; +} + +template +auto make_bucket(BucketInput index, const OutputT & beta) +{ + if constexpr (Verifiable) + { + return dpf::make_dpf(index, beta, + dpf::verifiable{}); + } + else + { + return dpf::make_dpf(index, beta); + } +} + +template +struct bucket_bare +{ + using type = typename decltype(make_bucket(std::declval(), + std::declval()).first)::key_type; +}; + +template +auto make_impl(std::vector alphas, std::vector betas, + multipoint_params params) +{ + using bare = typename bucket_bare::type; + using key0 = multipoint_key<0, InputT, OutputT, bare>; + using key1 = multipoint_key<1, InputT, OutputT, bare>; + + static_assert(std::is_unsigned_v && !std::is_same_v, + "make_multipoint: input domain must be an unsigned integer"); + static_assert(utils::bitlength_of_v <= 32, + "make_multipoint: input domain wider than 32 bits is not supported"); + static_assert(std::is_unsigned_v + && !std::is_same_v, + "make_multipoint: BucketInput must be an unsigned integer"); + + if (alphas.size() != betas.size()) + throw std::invalid_argument("make_multipoint: point and payload counts differ"); + if (alphas.empty()) + throw std::invalid_argument("make_multipoint: no points"); + if (alphas.size() > static_cast(std::numeric_limits::max())) + throw std::invalid_argument("make_multipoint: too many points"); + + { + auto sorted = alphas; + std::sort(sorted.begin(), sorted.end()); + if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end()) + throw std::invalid_argument("make_multipoint: duplicate points"); + } + + const auto t = static_cast(alphas.size()); + const auto m = bucket_count_for(t, params.lambda); + constexpr std::size_t input_bits = utils::bitlength_of_v; + const wide n = domain_size(input_bits); + constexpr int kappa = 3; + const wide b = (n * kappa + m - 1) / m; + constexpr std::size_t bucket_bits = utils::bitlength_of_v; + const wide bucket_cap = domain_size(bucket_bits); + if (b > bucket_cap) + { + throw std::invalid_argument( + "make_multipoint: bucket domain does not fit in BucketInput"); + } + + const int attempts = params.retries < 1 ? 1 : params.retries; + for (int attempt = 0; attempt < attempts; ++attempt) + { + try + { + const simde__m128i sigma = dpf::uniform_sample(); + std::vector table; + if (!insert_cuckoo(sigma, alphas, m, n, b, params.max_evictions, table)) + continue; + + key0 left; + key1 right; + left.sigma = sigma; + right.sigma = sigma; + left.bucket_count = m; + right.bucket_count = m; + left.bucket_domain = static_cast(b); + right.bucket_domain = static_cast(b); + left.buckets.reserve(m); + right.buckets.reserve(m); + + for (std::uint32_t i = 0; i < m; ++i) + { + BucketInput gamma{}; + OutputT beta{}; + if (table[i].item >= 0) + { + const auto & alpha = alphas[static_cast(table[i].item)]; + const auto loc = locate(sigma, + static_cast(alpha), table[i].hash, n, b); + if (loc.bucket != i) + throw prp_walk_error{}; + gamma = static_cast(loc.index); + beta = betas[static_cast(table[i].item)]; + } + auto made = make_bucket( + gamma, beta); + left.buckets.push_back(std::move(made.first)); + right.buckets.push_back(std::move(made.second)); + } + return std::make_pair(std::move(left), std::move(right)); + } + catch (const prp_walk_error &) + { + continue; + } + } + throw std::runtime_error("make_multipoint: cuckoo hashing failed"); +} + +inline void absorb_proof(proof_token & acc, const proof_token & inner) +{ + acc = detail::vdpf::xor_proof(acc, inner); + acc[0] = detail::vdpf::mmo(acc[0], 1); +} + +template +typename Key::share_type eval_at(const Key & key, typename Key::input_type x, + proof_token * acc) +{ + using input_type = typename Key::input_type; + using bucket_input = typename Key::bucket_input; + constexpr std::size_t input_bits = utils::bitlength_of_v; + const wide n = domain_size(input_bits); + const wide b = key.bucket_domain; + typename Key::share_type sum = + Key::share_type::from_raw(typename Key::output_type{}); + + for (int hash = 0; hash < static_cast(Key::kappa); ++hash) + { + const auto loc = locate(key.sigma, static_cast(x), hash, n, b); + if (loc.bucket >= key.bucket_count) + throw std::runtime_error("multipoint eval: bucket out of range"); + const auto gamma = static_cast(loc.index); + const auto & bucket = key.buckets[loc.bucket]; + if constexpr (Key::is_verifiable) + { + if (acc != nullptr) + { + proof_token inner{}; + sum += *dpf::eval_point(bucket, gamma, dpf::prove(inner)); + absorb_proof(*acc, inner); + continue; + } + } + sum += *dpf::eval_point(bucket, gamma); + } + return sum; +} + +} // namespace mpf +} // namespace detail + +/// @brief Cuckoo-pack distinct points into ordinary point-key buckets. +/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128` +/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG` +/// @tparam BucketInput unsigned type of a bucket index. Defaults to `uint32_t` +/// @tparam AlphaRange range of distinct domain points +/// @tparam BetaRange range of payloads, one per point +/// @param alphas the secret points +/// @param betas the payloads +/// @param params packing knobs. `lambda` is the Remark 1 failure target +/// @return the two party keys +/// @throws std::invalid_argument if the lists differ in length, are empty, +/// contain a duplicate, or a bucket index does not fit `BucketInput` +/// @throws std::runtime_error if cuckoo hashing does not succeed +template +HEDLEY_WARN_UNUSED_RESULT +auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas, + multipoint_params params = {}) +{ + using input_type = std::decay_t; + using output_type = std::decay_t; + return detail::mpf::make_impl( + std::vector(std::begin(alphas), std::end(alphas)), + std::vector(std::begin(betas), std::end(betas)), + params); +} + +/// @brief Same packing as `make_multipoint`, with a verifiable bucket key. +/// @see `make_multipoint` +/// @param alphas the secret points +/// @param betas the payloads +/// @param params packing knobs +/// @return the two verifiable party keys +/// @throws std::invalid_argument if the lists differ in length, are empty, +/// contain a duplicate, or a bucket index does not fit `BucketInput` +/// @throws std::runtime_error if cuckoo hashing does not succeed +template +HEDLEY_WARN_UNUSED_RESULT +auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas, + verifiable, multipoint_params params = {}) +{ + using input_type = std::decay_t; + using output_type = std::decay_t; + return detail::mpf::make_impl( + std::vector(std::begin(alphas), std::end(alphas)), + std::vector(std::begin(betas), std::end(betas)), + params); +} + +/// @brief Sum the three bucket shares at `x`. +/// @tparam Key a `multipoint_key` +/// @param key the party key +/// @param x the query point +/// @return the party's share of the payload, or of zero off the packed points +/// @throws std::runtime_error if a located bucket is outside the key +template , int> = 0> +auto eval_multipoint(const Key & key, typename Key::input_type x) +{ + return detail::mpf::eval_at(key, x, nullptr); +} + +/// @brief Evaluate `x` and fold that query into `pr`. +/// @tparam Key a verifiable `multipoint_key` +/// @param key the party key +/// @param x the query point +/// @param pr proof token replaced with this query's folded proof +/// @return the party's share of the payload +/// @throws std::runtime_error if a located bucket is outside the key +template , int> = 0> +auto eval_multipoint(const Key & key, typename Key::input_type x, prove_ref pr) +{ + static_assert(Key::is_verifiable, + "eval_multipoint(..., prove(π)): key must be a verifiable multipoint key"); + pr.token = detail::vdpf::zero_proof(); + return detail::mpf::eval_at(key, x, &pr.token); +} + +/// @brief Evaluate each point of `xs`, writing one share per point. +/// @tparam Key a `multipoint_key` +/// @tparam Range range of query points +/// @tparam OutIt output iterator of shares +/// @param key the party key +/// @param xs the query points +/// @param out where each share is written +/// @throws std::runtime_error if a located bucket is outside the key +template , int> = 0> +void eval_multipoint(const Key & key, const Range & xs, OutIt out) +{ + for (const auto & x : xs) + *out++ = eval_multipoint(key, static_cast(x)); +} + +/// @brief Evaluate `xs` and fold every query into one proof. +/// @tparam Key a verifiable `multipoint_key` +/// @tparam Range range of query points +/// @tparam OutIt output iterator of shares +/// @param key the party key +/// @param xs the query points +/// @param out where each share is written +/// @param pr proof token replaced with the folded proof of `xs` +/// @throws std::runtime_error if a located bucket is outside the key +template , int> = 0> +void eval_multipoint(const Key & key, const Range & xs, OutIt out, prove_ref pr) +{ + static_assert(Key::is_verifiable, + "eval_multipoint(..., prove(π)): key must be a verifiable multipoint key"); + pr.token = detail::vdpf::zero_proof(); + for (const auto & x : xs) + { + *out++ = detail::mpf::eval_at(key, + static_cast(x), &pr.token); + } +} + +/// @brief Fold a canonical evaluation of every bucket into one proof. +/// @tparam Key a verifiable `multipoint_key` +/// @param key the party key +/// @param pr proof token replaced with the audit proof +template , int> = 0> +void audit_multipoint(const Key & key, prove_ref pr) +{ + static_assert(Key::is_verifiable, + "audit_multipoint: key must be a verifiable multipoint key"); + pr.token = detail::vdpf::zero_proof(); + for (const auto & bucket : key.buckets) + { + proof_token inner{}; + (void)*dpf::eval_point(bucket, typename Key::bucket_input{}, + dpf::prove(inner)); + detail::mpf::absorb_proof(pr.token, inner); + } +} + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__ diff --git a/include/dpf/nyble.hpp b/include/dpf/nyble.hpp index 315ce00..b293012 100644 --- a/include/dpf/nyble.hpp +++ b/include/dpf/nyble.hpp @@ -4,6 +4,7 @@ /// packs one lane every four bits, low nibble first. Leaf addition /// is not XOR and is not `add_epi8`: a carry must not cross into /// the neighbouring nibble. See `packed_lane_arithmetic.hpp`. +/// @see packed_lane_arithmetic.hpp #ifndef LIBDPF_INCLUDE_DPF_NYBLE_HPP__ #define LIBDPF_INCLUDE_DPF_NYBLE_HPP__ @@ -47,6 +48,9 @@ static constexpr dpf::nyble to_nyble(unsigned long long value) noexcept } /// @brief parse one hex digit as a nibble +/// @tparam CharT character type +/// @param value the value to convert or store +/// @return the returned `dpf::nyble` /// @throws std::domain_error if `value` is not `0-9`, `a-f`, or `A-F` template static constexpr dpf::nyble to_nyble(CharT value) @@ -97,6 +101,9 @@ operator>>(std::basic_istream & is, dpf::nyble & value) } /// @brief addition in Z/16Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return addition in Z/16Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -107,6 +114,9 @@ constexpr dpf::nyble operator+(dpf::nyble lhs, dpf::nyble rhs) noexcept } /// @brief subtraction in Z/16Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return subtraction in Z/16Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -117,6 +127,8 @@ constexpr dpf::nyble operator-(dpf::nyble lhs, dpf::nyble rhs) noexcept } /// @brief additive inverse in Z/16Z +/// @param value the value to convert or store +/// @return additive inverse in Z/16Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -126,6 +138,9 @@ constexpr dpf::nyble operator-(dpf::nyble value) noexcept } /// @brief multiplication in Z/16Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return multiplication in Z/16Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE diff --git a/include/dpf/offset_wrapper.hpp b/include/dpf/offset_wrapper.hpp index 25f847d..1f2bf4a 100644 --- a/include/dpf/offset_wrapper.hpp +++ b/include/dpf/offset_wrapper.hpp @@ -1,6 +1,5 @@ /// @file dpf/offset_wrapper.hpp -/// @brief -/// @details +/// @brief An output value shifted by a public offset. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -9,6 +8,9 @@ #ifndef LIBDPF_INCLUDE_DPF_OFFSET_WRAPPER_HPP__ #define LIBDPF_INCLUDE_DPF_OFFSET_WRAPPER_HPP__ +#include +#include + #include "hedley/hedley.h" namespace dpf @@ -43,6 +45,13 @@ struct offset_wrapper final HEDLEY_NO_THROW static constexpr bool is_wildcard() noexcept { return false; } + /// @brief Stored share. Concrete wrappers ignore it at evaluation. + /// @return the stored share + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + HEDLEY_NO_THROW + constexpr const input_type & raw() const noexcept { return offset_; } + private: input_type offset_; // waste an `input_type` to make `sizeof` match up }; @@ -108,6 +117,34 @@ struct offset_wrapper> HEDLEY_PURE HEDLEY_NO_THROW static constexpr bool is_wildcard() noexcept { return true; } + + /// @brief Party share, including before the offset is marked ready. + /// @return the party share + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + HEDLEY_NO_THROW + constexpr const input_type & raw() const noexcept { return offset_; } + + /// @brief `offset_status` as a byte, for serialization. + /// @return the status byte + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + HEDLEY_NO_THROW + constexpr std::uint8_t state() const noexcept + { + return static_cast(offset_state_); + } + + /// @brief Restore a serialized share and status byte. + /// @param offset the party share + /// @param state the status byte + HEDLEY_ALWAYS_INLINE + void restore(input_type offset, std::uint8_t state) noexcept + { + offset_ = std::move(offset); + offset_state_ = static_cast(state); + } + private: enum class offset_status : psnip_uint8_t { ready = 0, waiting = 1, computing = 2, notset = 3 }; diff --git a/include/dpf/output_buffer.hpp b/include/dpf/output_buffer.hpp index 764ba3c..57a5f40 100644 --- a/include/dpf/output_buffer.hpp +++ b/include/dpf/output_buffer.hpp @@ -41,8 +41,10 @@ namespace dpf { -/// Buffer element type for leaf eval of `KeyT`: party-tagged subtractive +/// @brief Buffer element type for leaf eval of `KeyT`: party-tagged subtractive /// share when `KeyT` is a `party_key`, otherwise the concrete output. +/// @tparam KeyT key type +/// @tparam OutputT output type template > struct leaf_buffer_elem { @@ -56,7 +58,9 @@ struct leaf_buffer_elem template using leaf_buffer_elem_t = typename leaf_buffer_elem::type; -/// Buffer element type for comparison eval of `KeyT`. +/// @brief Buffer element type for comparison eval of `KeyT`. +/// @tparam KeyT key type +/// @tparam Beta payload type template > struct cmp_buffer_elem { @@ -70,9 +74,11 @@ struct cmp_buffer_elem template using cmp_buffer_elem_t = typename cmp_buffer_elem::type; -/// `std::vector(n)` value-initializes every slot. Interval / full eval +/// @brief `std::vector(n)` value-initializes every slot. Interval / full eval /// overwrites the whole buffer, so skip default-construction for trivial /// `T`. Non-trivial outputs still run their default constructor. +/// @tparam T value type +/// @tparam Alignment allocation alignment template class output_buffer_allocator : public aligned_allocator @@ -139,8 +145,10 @@ constexpr bool operator!=(const output_buffer_allocator & lhs, return !(lhs == rhs); } -/// Move-only vector of `T`. Copy construction and copy assignment are +/// @brief Move-only vector of `T`. Copy construction and copy assignment are /// deleted. `at`, `operator[]`, `data`, iterators, and `size` are public. +/// @tparam T value type +/// @tparam Alignment allocation alignment template class output_buffer final @@ -252,7 +260,7 @@ LIBDPF_PACKED_SHARE_BUFFER(dpf::nyble, 0); LIBDPF_PACKED_SHARE_BUFFER(dpf::nyble, 1); #undef LIBDPF_PACKED_SHARE_BUFFER -/// Packed bit share buffers reuse the bit-array image; iterators yield shares. +/// @brief Packed bit share buffers reuse the bit-array image; iterators yield shares. #define LIBDPF_BIT_SHARE_BUFFER(PARTY) \ template <> \ class output_buffer> \ @@ -272,8 +280,14 @@ LIBDPF_BIT_SHARE_BUFFER(0); LIBDPF_BIT_SHARE_BUFFER(1); #undef LIBDPF_BIT_SHARE_BUFFER -/// Buffer sized for the closed interval `[from, to]` of output `I`. -/// On a `party_key`, elements are subtractive shares of that output. +/// @brief Buffer sized for the closed interval `[from, to]` of output `I`. +/// @details On a `party_key`, elements are subtractive shares of that output. +/// @tparam DpfKey DPF key type +/// @tparam I output index +/// @tparam InputT input domain type +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Buffer sized for the closed interval `[from, to]` of output `I` template @@ -318,7 +332,10 @@ inline auto make_output_buffer_for_interval(const DpfKey &, InputT from, InputT return make_output_buffer_for_interval(from, to); } -/// Buffer sized for every input of output `I`. +/// @brief Buffer sized for every input of output `I`. +/// @tparam DpfKey DPF key type +/// @tparam I output index +/// @return Buffer sized for every input of output `I` template auto make_output_buffer_for_full() diff --git a/include/dpf/packed_array.hpp b/include/dpf/packed_array.hpp index 52ac4b2..b5b76e8 100644 --- a/include/dpf/packed_array.hpp +++ b/include/dpf/packed_array.hpp @@ -312,8 +312,10 @@ class dynamic_packed_array unique_ptr data_{}; }; -/// Packed lane storage whose iterators yield `subtractive_share`. -/// The bytes are the leaf image (`store_leaf_bytes`); each lane is one share. +/// @brief Packed lane storage whose iterators yield `subtractive_share`. +/// @details The bytes are the leaf image (`store_leaf_bytes`); each lane is one share. +/// @tparam LaneT lane type +/// @tparam Party party index, `0` or `1` template class packed_share_output : public dynamic_packed_array { diff --git a/include/dpf/packed_lane.hpp b/include/dpf/packed_lane.hpp index 7e765d7..738691b 100644 --- a/include/dpf/packed_lane.hpp +++ b/include/dpf/packed_lane.hpp @@ -41,6 +41,11 @@ LaneT extract_lane(const LeafT & leaf, std::size_t lane) noexcept /// @brief zero `out` is the caller's job; this writes one lane and leaves /// every other lane untouched. +/// @tparam LaneT lane type +/// @tparam LeafT leaf type +/// @param leaf the leaf value +/// @param lane the lane index or lane value +/// @param value the value to convert or store template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW diff --git a/include/dpf/packed_lane_arithmetic.hpp b/include/dpf/packed_lane_arithmetic.hpp index 63e9611..9423b94 100644 --- a/include/dpf/packed_lane_arithmetic.hpp +++ b/include/dpf/packed_lane_arithmetic.hpp @@ -131,8 +131,11 @@ inline simde__m256i shuffle_nibbles(simde__m256i table, simde__m256i a) noexcept simde_mm256_slli_epi16(simde_mm256_and_si256(hi, m), 4)); } -/// Low nibble of every byte, product mod 16. Even and odd bytes are split +/// @brief Low nibble of every byte, product mod 16. Even and odd bytes are split /// so a product in one byte cannot land in the next. +/// @param a the `a` +/// @param b the `b` +/// @return Low nibble of every byte, product mod 16 HEDLEY_NO_THROW inline simde__m128i mul_low_nibbles(simde__m128i a, simde__m128i b) noexcept { diff --git a/include/dpf/parallel_bit_iterable.hpp b/include/dpf/parallel_bit_iterable.hpp index 9d3db09..91d7904 100644 --- a/include/dpf/parallel_bit_iterable.hpp +++ b/include/dpf/parallel_bit_iterable.hpp @@ -1,7 +1,6 @@ /// @file dpf/parallel_bit_iterable.hpp /// @author Christopher Jiang -/// @brief -/// @details +/// @brief SIMD iteration over packed advice or correction bits. /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others]{@ref authors} /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. diff --git a/include/dpf/parallel_bit_iterable_helpers.hpp b/include/dpf/parallel_bit_iterable_helpers.hpp index 4653486..71f83da 100644 --- a/include/dpf/parallel_bit_iterable_helpers.hpp +++ b/include/dpf/parallel_bit_iterable_helpers.hpp @@ -1,7 +1,6 @@ /// @file dpf/parallel_bit_iterable_helpers.hpp /// @author Christopher Jiang -/// @brief -/// @details +/// @brief Loads and masks used by the parallel bit iterators. /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. @@ -28,8 +27,13 @@ namespace dpf namespace { -/// Unaligned 256-bit load of `words_per_vec` words starting at `offset`. -/// Words past `nwords` are zero so a short batch does not read off the end. +/// @brief Unaligned 256-bit load of `words_per_vec` words starting at `offset`. +/// @details Words past `nwords` are zero so a short batch does not read off the end. +/// @tparam Word word +/// @param words the `words` +/// @param nwords the number of words +/// @param offset the public offset +/// @return Unaligned 256-bit load of `words_per_vec` words starting at `offset` template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -56,6 +60,7 @@ template struct parallel_bit_iterable_helper; /// @brief for batch_size in 1..4 +/// @tparam ChildT CRTP derived type template struct parallel_bit_iterable_helper<2, ChildT> { @@ -88,6 +93,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) }; // struct parallel_bit_iterable_helper<2> /// @brief for batch_size in 5..8 +/// @tparam ChildT CRTP derived type template struct parallel_bit_iterable_helper<3, ChildT> { @@ -135,6 +141,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) }; // struct parallel_bit_iterable_helper<3> /// @brief for batch_size in 9..16 +/// @tparam ChildT CRTP derived type template struct parallel_bit_iterable_helper<4, ChildT> { @@ -203,6 +210,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop) }; // struct parallel_bit_iterable_helper<4> /// @brief for batch_size in 17..32 +/// @tparam ChildT CRTP derived type template struct parallel_bit_iterable_helper<5, ChildT> { diff --git a/include/dpf/path_memoizer.hpp b/include/dpf/path_memoizer.hpp index 5a4c7e0..fcd3adf 100644 --- a/include/dpf/path_memoizer.hpp +++ b/include/dpf/path_memoizer.hpp @@ -34,7 +34,7 @@ namespace dpf { -/// Path memoizers key on the underlying DPF key type. `party_key` wrappers +/// @brief Path memoizers key on the underlying DPF key type. `party_key` wrappers /// share the same tree layout, so a memoizer built for party 0 also accepts /// party 1 (and bare keys). template @@ -62,10 +62,11 @@ struct path_memoizer_base virtual return_type end() const noexcept = 0; }; -/// One interior node per level. `assign_x` returns the first level that the +/// @brief One interior node per level. `assign_x` returns the first level that the /// next walk must recompute. `filled_to` is the deepest level already /// written for the current input. Callers pass this object to `eval_point`; /// they do not call `assign_x` themselves. +/// @tparam DpfKey DPF key type template struct alignas(alignof(typename path_memoizer_key_t::interior_node)) basic_path_memoizer final @@ -143,7 +144,8 @@ HEDLEY_PRAGMA(GCC diagnostic pop) return std::addressof(arr_[depth+1]); } - /// Inclusive high-water: `arr_[0..filled_to_]` are valid for the current x. + /// @brief Inclusive high-water: `arr_[0..filled_to_]` are valid for the current x. + /// @return Inclusive high-water: `arr_[0..filled_to_]` are valid for the current x HEDLEY_NO_THROW std::size_t filled_to() const noexcept { return filled_to_; } @@ -166,7 +168,8 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") HEDLEY_PRAGMA(GCC diagnostic pop) }; -/// A single interior node. Every `assign_x` restarts at the root. +/// @brief A single interior node. Every `assign_x` restarts at the root. +/// @tparam DpfKey DPF key type template struct nonmemoizing_path_memoizer final : public path_memoizer_base> @@ -267,7 +270,13 @@ void path_note_filled_to(PathMemoizer & path, std::size_t level) path.note_filled(level); } -/// Walk interior nodes so `path[0..to_level]` is valid for `x`. +/// @brief Walk interior nodes so `path[0..to_level]` is valid for `x`. +/// @tparam DpfKey DPF key type +/// @tparam PathMemoizer path memoizer type +/// @param dpf the DPF key +/// @param x the `x` +/// @param path the root-to-leaf path +/// @param to_level the `to_level` template void ensure_level(const DpfKey & dpf, typename DpfKey::input_type x, PathMemoizer & path, std::size_t to_level) @@ -279,17 +288,21 @@ void ensure_level(const DpfKey & dpf, typename DpfKey::input_type x, { bool bit = !!(mask & x); auto cw = dpf.correction_word(level_index - 1, bit); + const bool is_last = DpfKey::tree::is_last_level(level_index - 1, + dpf.depth); path[level_index] = - DpfKey::traverse_interior(path[level_index - 1], cw, bit); + DpfKey::traverse_interior(path[level_index - 1], cw, bit, is_last); } path_note_filled_to(path, to_level); } } // namespace detail -/// Path workspace for `DpfKey`. A `party_key` argument is unwrapped, and the +/// @brief Path workspace for `DpfKey`. A `party_key` argument is unwrapped, and the /// result accepts both parties. /// @snippet evaluation/memoizers.cpp path-memoizer +/// @tparam DpfKey DPF key type +/// @return Path workspace for `DpfKey` template auto make_basic_path_memoizer() { @@ -302,7 +315,9 @@ auto make_basic_path_memoizer(const DpfKey &) return make_basic_path_memoizer(); } -/// Single-node path workspace. Suitable for one query. +/// @brief Single-node path workspace. Suitable for one query. +/// @tparam DpfKey DPF key type +/// @return Single-node path workspace template auto make_nonmemoizing_path_memoizer() { diff --git a/include/dpf/placement.hpp b/include/dpf/placement.hpp index 0150703..689af87 100644 --- a/include/dpf/placement.hpp +++ b/include/dpf/placement.hpp @@ -58,26 +58,31 @@ template struct is_at> : std::true_type {}; template inline constexpr bool is_at_v = is_at::value; -/// Phantom pack element for a key's comparison (DCF) channel. Not a leaf: it +/// @brief Phantom pack element for a key's comparison (DCF) channel. Not a leaf: it /// only records the cmp prefix depth in the key's type. `Depth` is the number /// of tree levels the comparison walks (0 is reserved for "no cmp"). -/// `OutBits` is the comparison output group width (bits of the β payload), +/// @details `OutBits` is the comparison output group width (bits of the β payload), /// so the value CWs / addend can be stored at group width instead of a full /// padded `uint64_t` per level. +/// @tparam Depth depth +/// @tparam OutBits out bits +/// @tparam Wild whether the payload is a wildcard +/// @tparam BlockWidth checkpoint spacing, in levels +/// @tparam Incremental whether a final correction is stored at every depth template struct cmp_channel_tag { static constexpr std::size_t depth = Depth; static constexpr std::size_t out_bits = OutBits; - /// True when the comparison payload (β) is a wildcard to be assigned + /// @brief True when the comparison payload (β) is a wildcard to be assigned /// after keygen. Concrete (non-wildcard) cmp keys keep `Wild == false` /// so their layout / type name is unchanged. static constexpr bool wild = Wild; - /// 0 keeps the per-level path-sum. `B >= 1` selects blocked checkpoints + /// @brief 0 keeps the per-level path-sum. `B >= 1` selects blocked checkpoints /// of target width `B`. static constexpr std::size_t block_width = BlockWidth; - /// Save a final correction at every depth (`idcf`). + /// @brief Save a final correction at every depth (`idcf`). static constexpr bool incremental = Incremental; }; @@ -90,6 +95,38 @@ template inline constexpr bool is_cmp_channel_tag_v = is_cmp_channel_tag>::value; +/// Phantom pack element: key carries per-level VDPF correction seeds. +struct verifiable +{ + static constexpr bool is_verifiable_tag = true; +}; + +/// Phantom pack element: ROM leaf stretch + extractability checks. +struct extractable +{ + static constexpr bool is_extractable_tag = true; +}; + +template +struct is_verifiable_tag : std::false_type +{ }; +template <> +struct is_verifiable_tag : std::true_type +{ }; +template +inline constexpr bool is_verifiable_tag_v = + is_verifiable_tag>::value; + +template +struct is_extractable_tag : std::false_type +{ }; +template <> +struct is_extractable_tag : std::true_type +{ }; +template +inline constexpr bool is_extractable_tag_v = + is_extractable_tag>::value; + namespace detail { namespace incr @@ -99,13 +136,25 @@ namespace incr // A concrete placed output: an output type `OutputT` planted at prefix `N`. // --------------------------------------------------------------------------- +/// @brief Default: placed payload type is the stored type. Specialized for +/// `arith_beta` in `doerner_shelat.hpp` so the key leaf type is `T`. +/// @tparam T value type +template +struct unwrap_placed_output +{ + using type = T; +}; + template struct placed { static constexpr std::size_t prefix = N; - using output_type = OutputT; + /// @brief Stored argument type (may be `arith_beta`). + using stored_type = OutputT; + /// @brief Key / leaf payload type (`T` when stored is `arith_beta`). + using output_type = typename unwrap_placed_output::type; OutputT value; - OutputT addend{}; // public if_false for eq(...); party 0 absorbs at eval + output_type addend{}; // public if_false for eq(...); party 0 absorbs at eval }; template struct is_placed : std::false_type {}; @@ -114,8 +163,10 @@ struct is_placed> : std::true_type {}; template inline constexpr bool is_placed_v = is_placed>::value; -/// Heavy-hitters incremental point function: one payload per prefix length. -/// `levels[i]` is the bit length of slot `i`. +/// @brief Heavy-hitters incremental point function: one payload per prefix length. +/// @details `levels[i]` is the bit length of slot `i`. +/// @tparam LevelSeq level seq +/// @tparam Betas betas template struct idpf_pack; @@ -416,6 +467,8 @@ struct normalize_one static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; + static constexpr bool is_verifiable = false; + static constexpr bool is_extractable = false; }; template struct normalize_one> @@ -426,6 +479,8 @@ struct normalize_one> static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; + static constexpr bool is_verifiable = false; + static constexpr bool is_extractable = false; }; template @@ -438,6 +493,32 @@ struct normalize_one +struct normalize_one +{ + using placed_tuple = std::tuple<>; + static constexpr std::size_t cmp_depth = 0; + static constexpr std::size_t cmp_out_bits = 0; + static constexpr bool cmp_wild = false; + static constexpr std::size_t cmp_block = 0; + static constexpr bool cmp_idcf = false; + static constexpr bool is_verifiable = true; + static constexpr bool is_extractable = false; +}; +template +struct normalize_one +{ + using placed_tuple = std::tuple<>; + static constexpr std::size_t cmp_depth = 0; + static constexpr std::size_t cmp_out_bits = 0; + static constexpr bool cmp_wild = false; + static constexpr std::size_t cmp_block = 0; + static constexpr bool cmp_idcf = false; + static constexpr bool is_verifiable = false; + static constexpr bool is_extractable = true; }; template @@ -448,18 +529,18 @@ struct normalize_pack std::declval::placed_tuple>()...)); static constexpr std::size_t cmp_depth = (std::size_t{0} + ... + normalize_one::cmp_depth); - // At most one comparison channel per key, so the sum is that channel's - // output width (0 when there is no cmp channel). static constexpr std::size_t cmp_out_bits = (std::size_t{0} + ... + normalize_one::cmp_out_bits); - // At most one comparison channel per key, so the OR is that channel's - // wildcard flag (false when there is no cmp channel). static constexpr bool cmp_wild = (false || ... || normalize_one::cmp_wild); static constexpr std::size_t cmp_block = (std::size_t{0} + ... + normalize_one::cmp_block); static constexpr bool cmp_idcf = (false || ... || normalize_one::cmp_idcf); + static constexpr bool is_verifiable = + (false || ... || normalize_one::is_verifiable); + static constexpr bool is_extractable = + (false || ... || normalize_one::is_extractable); }; template @@ -471,19 +552,26 @@ struct normalize_pack static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; + static constexpr bool is_verifiable = false; + static constexpr bool is_extractable = false; }; -/// True iff the pack is "classic-shaped": every element is a bare output (no -/// `placed<>` from `at<>` and no `cmp_channel_tag<>`). +/// @brief True iff the pack is "classic-shaped": every element is a bare output (no +/// `placed<>` from `at<>`, no `cmp_channel_tag<>`, and no verifiable/extractable). template inline constexpr bool is_classic_pack_v = - !((is_placed_v || is_cmp_channel_tag_v) || ...); + !((is_placed_v || is_cmp_channel_tag_v + || is_verifiable_tag_v || is_extractable_tag_v) || ...); } // namespace incr } // namespace detail -/// Sparse heavy-hitters IDPF. Slot `i` is the point function on prefix +/// @brief Sparse heavy-hitters IDPF. Slot `i` is the point function on prefix /// `Levels[i]`, evaluated with `out`. +/// @tparam Levels levels +/// @tparam Betas betas +/// @param betas the `betas` +/// @return Sparse heavy-hitters IDPF template auto idpf_at(Betas ...betas) { @@ -500,7 +588,10 @@ auto idpf_from_seq(std::index_sequence, Betas ...betas) return idpf_at<(I + 1)...>(std::move(betas)...); } -/// Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)`. +/// @brief Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)`. +/// @tparam Betas betas +/// @param betas the `betas` +/// @return Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)` template auto idpf(Betas ...betas) { diff --git a/include/dpf/prg.hpp b/include/dpf/prg.hpp index a8209d4..cf401e8 100644 --- a/include/dpf/prg.hpp +++ b/include/dpf/prg.hpp @@ -1,6 +1,5 @@ /// @file dpf/prg.hpp -/// @brief -/// @details +/// @brief PRG aliases, the share expander, and the call counter. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -17,6 +16,7 @@ #include #include "dpf/prg_aes.hpp" +#include "dpf/prg_aes_ccr.hpp" #include "dpf/prg_chacha.hpp" #include "dpf/prg_dummy.hpp" #include "dpf/prg_lowmc.hpp" @@ -31,7 +31,12 @@ namespace prg namespace detail { -/// Fill `T` from consecutive PRG blocks starting at `pos` (low bytes first). +/// @brief Fill `T` from consecutive PRG blocks starting at `pos` (low bytes first). +/// @tparam PRG pseudorandom generator +/// @tparam T value type +/// @param seed the PRG seed +/// @param pos the 0-based index +/// @return the returned `T` template HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -85,6 +90,13 @@ auto lowmc128::expand(block_type seed, psnip_uint32_t pos) noexcept return detail::expand_as_share(seed, pos); } +template +HEDLEY_NO_THROW +auto aes128_ccr::expand(block_type seed, psnip_uint32_t pos) noexcept +{ + return detail::expand_as_share(seed, pos); +} + template template HEDLEY_NO_THROW diff --git a/include/dpf/prg_aes.hpp b/include/dpf/prg_aes.hpp index 4533987..cc43e6e 100644 --- a/include/dpf/prg_aes.hpp +++ b/include/dpf/prg_aes.hpp @@ -1,6 +1,5 @@ /// @file dpf/prg_aes.hpp -/// @brief -/// @details +/// @brief Fixed-key AES Matyas–Meyer–Oseas PRG. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -100,10 +99,15 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") HEDLEY_PRAGMA(GCC diagnostic pop) } - /// Round-major multi-block MMO. Positions use the same lane as + /// @brief Round-major multi-block MMO. Positions use the same lane as /// `eval` / `eval01` (`set_epi64x(0, pos)`). The first AddRoundKey /// includes `rd_key[0]` so this matches the one-block `eval` for any /// key, not only the all-zero key this PRG currently installs. + /// @param seed the PRG seed + /// @param output the destination. Unused when `count` is 0 + /// @param count the number of blocks + /// @param pos the 0-based index + /// @throws std::invalid_argument if `prg lane index is out of range` HEDLEY_ALWAYS_INLINE static void eval(block_type seed, block_type * HEDLEY_RESTRICT output, psnip_uint32_t count, psnip_uint32_t pos = 0) @@ -164,8 +168,11 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } } - /// Four independent `eval01` calls as one 8-block round-major AES. - /// `left[i] == eval(seeds[i], 0)`, `right[i] == eval(seeds[i], 1)`. + /// @brief Four independent `eval01` calls as one 8-block round-major AES. + /// @details `left[i] == eval(seeds[i], 0)`, `right[i] == eval(seeds[i], 1)`. + /// @param seeds the root seeds + /// @param left the `left` + /// @param right the `right` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_NON_NULL(1, 2, 3) @@ -198,7 +205,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } } - /// Four independent `eval(seed, pos)` as one 4-block round-major AES. + /// @brief Four independent `eval(seed, pos)` as one 4-block round-major AES. + /// @param seeds the root seeds + /// @param output the destination. Unused when `count` is 0 + /// @param pos the 0-based index HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_NON_NULL(1, 2) @@ -229,7 +239,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } } - /// Eight independent `eval(seed, pos)` as one 8-block round-major AES. + /// @brief Eight independent `eval(seed, pos)` as one 8-block round-major AES. + /// @param seeds the root seeds + /// @param output the destination. Unused when `count` is 0 + /// @param pos the 0-based index HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_NON_NULL(1, 2) @@ -260,7 +273,13 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } } - /// Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @brief Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @tparam T value type + /// @tparam Party party index, `0` or `1` + /// @param seed the PRG seed + /// @param pos the 0-based index + /// @return Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`) + /// @see `prg.hpp` template HEDLEY_NO_THROW static auto expand(block_type seed, psnip_uint32_t pos = 0) noexcept; @@ -268,8 +287,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop) private: static const AesKey key; - /// `blk[i]` is already `seed[i] XOR rd_key[0] XOR pos_i`. Runs AES + /// @brief `blk[i]` is already `seed[i] XOR rd_key[0] XOR pos_i`. Runs AES /// rounds 1..last and the MMO feed-forward `XOR seed[i]`. + /// @param blk the `blk` + /// @param seed the PRG seed HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_NON_NULL(1, 2) diff --git a/include/dpf/prg_aes_ccr.hpp b/include/dpf/prg_aes_ccr.hpp new file mode 100644 index 0000000..2663095 --- /dev/null +++ b/include/dpf/prg_aes_ccr.hpp @@ -0,0 +1,254 @@ +/// @file dpf/prg_aes_ccr.hpp +/// @brief Circular correlation-robust (CCR) hash from fixed-key AES. +/// @details Implements the GKWY / Half-Tree CCR construction +/// `H(x) = π(σ(x)) ⊕ σ(x)` where `π` is the library's fixed-key AES +/// (same schedule as `prg::aes128`) and `σ` is the bitstring linear +/// orthomorphism `σ(xL∥xR) = (xL⊕xR)∥xL`. +/// +/// `eval01(s)` returns the Half-Tree children `{H(s), H(s)⊕s}`. That +/// expand is **not** a drop-in for BGI `make_dpf`; use it only with +/// Half-Tree `tree_traits` (see `dpf/tree_traits.hpp`). +/// @see dpf/tree_traits.hpp +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_PRG_AES_CCR_HPP__ +#define LIBDPF_INCLUDE_DPF_PRG_AES_CCR_HPP__ + +#include +#include +#include +#include + +#include "hedley/hedley.h" +#include "simde/simde/x86/avx2.h" +#include "portable-snippets/exact-int/exact-int.h" + +#include "dpf/prg_aes.hpp" +#include "dpf/twiddle.hpp" +#include "dpf/utils.hpp" + +namespace dpf +{ +namespace prg +{ + +namespace ccr_detail +{ + +/// @brief Linear orthomorphism on 128-bit strings: `σ(xL∥xR) = (xL⊕xR)∥xL`. +/// @param x the `x` +/// @return Linear orthomorphism on 128-bit strings: `σ(xL∥xR) = (xL⊕xR)∥xL` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +simde__m128i sigma(simde__m128i x) noexcept +{ + const std::uint64_t lo = static_cast( + simde_mm_cvtsi128_si64(x)); + const std::uint64_t hi = static_cast( + simde_mm_extract_epi64(x, 1)); + return simde_mm_set_epi64x(static_cast(lo), + static_cast(lo ^ hi)); +} + +/// @brief Inverse: if `σ(x)=(a,b)=(xL⊕xR, xL)` then `xL=b`, `xR=a⊕b`. +/// @param y the `y` +/// @return Inverse: if `σ(x)=(a,b)=(xL⊕xR, xL)` then `xL=b`, `xR=a⊕b` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +simde__m128i sigma_inv(simde__m128i y) noexcept +{ + const std::uint64_t a = static_cast( + simde_mm_cvtsi128_si64(y)); + const std::uint64_t b = static_cast( + simde_mm_extract_epi64(y, 1)); + return simde_mm_set_epi64x(static_cast(a ^ b), + static_cast(b)); +} + +/// @brief `σ'(x) = σ(x) ⊕ x`. +/// @param x the `x` +/// @return `σ'(x) = σ(x) ⊕ x` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +simde__m128i sigma_prime(simde__m128i x) noexcept +{ + return simde_mm_xor_si128(sigma(x), x); +} + +} // namespace ccr_detail + +/// @brief CCR hash + Half-Tree expand over fixed-key AES-128. +/// @details Selecting this as an *interior* PRG opts into Half-Tree via `half_tree_tag`. +struct aes128_ccr final +{ + using block_type = simde__m128i; + using half_tree_tag = void; + using underlying_aes = aes128; + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static void require_block_aligned(const void * p) noexcept + { + underlying_aes::require_block_aligned(p); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static block_type sigma(block_type x) noexcept + { + return ccr_detail::sigma(x); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static block_type sigma_inv(block_type y) noexcept + { + return ccr_detail::sigma_inv(y); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static block_type sigma_prime(block_type x) noexcept + { + return ccr_detail::sigma_prime(x); + } + + /// @brief `H(x) = π(σ(x)) ⊕ σ(x)` with `π` the fixed-key AES permutation + /// (implemented as the library MMO at position 0). + /// @param x the `x` + /// @return `H(x) = π(σ(x)) ⊕ σ(x)` with `π` the fixed-key AES permutation (implemented as the + /// library MMO at position 0) + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static block_type hash(block_type x) noexcept + { + return underlying_aes::eval(sigma(x), 0); + } + + /// @brief Alias for `hash`. + /// @param x the `x` + /// @return Alias for `hash` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static block_type H(block_type x) noexcept + { + return hash(x); + } + + /// @brief Position-tweaked CCR hash: `H(x; pos) = AES_MMO(σ(x), pos)`. + /// @param seed the PRG seed + /// @param pos the 0-based index + /// @return Position-tweaked CCR hash: `H(x; pos) = AES_MMO(σ(x), pos)` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static block_type eval(block_type seed, psnip_uint32_t pos) noexcept + { + return underlying_aes::eval(sigma(seed), pos); + } + + /// @brief Half-Tree children: left = `H(s)`, right = `H(s) ⊕ s`. + /// @param seed the PRG seed + /// @return Half-Tree children: left = `H(s)`, right = `H(s) ⊕ s` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static auto eval01(block_type seed) noexcept + { + const block_type h = hash(seed); +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + return std::array{h, simde_mm_xor_si128(h, seed)}; +HEDLEY_PRAGMA(GCC diagnostic pop) + } + + /// @brief Last-level two-tweak stretch: `{H(s|0), H(s|1)}` (LSB forced). + /// @param seed the PRG seed + /// @return Last-level two-tweak stretch: `{H(s|0), H(s|1)}` (LSB forced) + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static auto eval01_twotweak(block_type seed) noexcept + { + const block_type base = dpf::unset_lo_bit(seed); +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + return std::array{ + hash(base), + hash(dpf::set_lo_bit(base)) + }; +HEDLEY_PRAGMA(GCC diagnostic pop) + } + + /// @brief `count` CCR blocks starting at lane `pos`. + /// @details `output` is unused when `count` is 0. Each block is + /// `AES_MMO(σ(seed), pos + i)`. + /// @param seed the PRG seed + /// @param output the destination. Unused when `count` is 0 + /// @param count the number of blocks + /// @param pos the first lane index + /// @throws std::invalid_argument if `pos + count` wraps `uint32_t` + HEDLEY_ALWAYS_INLINE + static void eval(block_type seed, block_type * HEDLEY_RESTRICT output, + psnip_uint32_t count, psnip_uint32_t pos = 0) + { + underlying_aes::eval(sigma(seed), output, count, pos); + } + + /// @brief CCR hash of four seeds. + /// @param inputs four seeds + /// @param output four hashes, `H(inputs[i])` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 2) + static void hash_x4(const block_type * HEDLEY_RESTRICT inputs, + block_type * HEDLEY_RESTRICT output) noexcept + { + require_block_aligned(inputs); + require_block_aligned(output); + alignas(block_type) block_type sx[4]; + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + sx[i] = sigma(inputs[i]); + underlying_aes::eval_x4(sx, output, 0); + } + + /// @brief Half-Tree children of four seeds. + /// @param seeds four seeds + /// @param left `H(seeds[i])` + /// @param right `H(seeds[i]) ⊕ seeds[i]` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 2, 3) + static void eval01_x4(const block_type * HEDLEY_RESTRICT seeds, + block_type * HEDLEY_RESTRICT left, + block_type * HEDLEY_RESTRICT right) noexcept + { + require_block_aligned(seeds); + require_block_aligned(left); + require_block_aligned(right); + hash_x4(seeds, left); + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + right[i] = simde_mm_xor_si128(left[i], seeds[i]); + } + + template + HEDLEY_NO_THROW + static auto expand(block_type seed, psnip_uint32_t pos = 0) noexcept; +}; + +} // namespace prg +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_PRG_AES_CCR_HPP__ diff --git a/include/dpf/prg_chacha.hpp b/include/dpf/prg_chacha.hpp index f3a38c9..cf21d40 100644 --- a/include/dpf/prg_chacha.hpp +++ b/include/dpf/prg_chacha.hpp @@ -37,7 +37,7 @@ namespace chacha_detail inline constexpr std::uint32_t zero_nonce[3] = {0, 0, 0}; -/// ASCII `"dpf-chacha-prg"` plus two zero bytes. Public second half of the key. +/// @brief ASCII `"dpf-chacha-prg"` plus two zero bytes. Public second half of the key. inline constexpr std::uint8_t domain[16] = { 'd', 'p', 'f', '-', 'c', 'h', 'a', 'c', 'h', 'a', '-', 'p', 'r', 'g', 0, 0 @@ -77,7 +77,10 @@ simde__m128i load_block(const std::uint8_t * p) noexcept return out; } -/// 128-bit seed in the low half, `domain` in the high half, both little-endian. +/// @brief 128-bit seed in the low half, `domain` in the high half, both little-endian. +/// @param seed the PRG seed +/// @param key the `key` +HEDLEY_NON_NULL(2) HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE void seed_key(simde__m128i seed, std::uint32_t key[8]) noexcept @@ -92,8 +95,9 @@ void seed_key(simde__m128i seed, std::uint32_t key[8]) noexcept } template -HEDLEY_ALWAYS_INLINE +HEDLEY_CONST HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE constexpr std::uint32_t rotl(std::uint32_t x) noexcept { static_assert(N > 0 && N < 32, "ChaCha rotation is between 1 and 31"); @@ -112,8 +116,9 @@ void quarter(std::uint32_t & a, std::uint32_t & b, } template -HEDLEY_ALWAYS_INLINE +HEDLEY_CONST HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE simde__m128i rotl_epi32(simde__m128i v) noexcept { static_assert(N > 0 && N < 32, "ChaCha rotation is between 1 and 31"); @@ -121,6 +126,7 @@ simde__m128i rotl_epi32(simde__m128i v) noexcept simde_mm_srli_epi32(v, 32 - N)); } +HEDLEY_NON_NULL(1) HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE void quarter(simde__m128i x[], int a, int b, int c, int d) noexcept @@ -135,6 +141,7 @@ void quarter(simde__m128i x[], int a, int b, int c, int d) noexcept x[b] = rotl_epi32<7>(simde_mm_xor_si128(x[b], x[c])); } +HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE std::uint32_t epi32_lane(simde__m128i v, int lane) noexcept @@ -150,8 +157,21 @@ std::uint32_t epi32_lane(simde__m128i v, int lane) noexcept return static_cast(simde_mm_cvtsi128_si32(v)); } -/// One ChaCha block. `key` is 8 little-endian words. `nonce` is 3 words. +/// @name ChaCha blocks +/// @tparam Rounds ChaCha round count. Must be positive and even +/// @param key the ChaCha key words +/// @param counter the ChaCha block counter +/// @param out the output buffer +/// @{ + +/// @brief One ChaCha block. +/// @details `key` is 8 little-endian words. `nonce` is 3 words. +/// @param key the ChaCha key words +/// @param counter the ChaCha block counter +/// @param nonce the ChaCha nonce +/// @param out the output buffer template +HEDLEY_NON_NULL(1, 3, 4) HEDLEY_NO_THROW void block(const std::uint32_t key[8], std::uint32_t counter, const std::uint32_t nonce[3], std::uint8_t out[64]) noexcept @@ -187,9 +207,10 @@ HEDLEY_PRAGMA(GCC unroll 16) } } -/// Four independent ChaCha blocks. Lane `i` uses `key[i]` and `counter[i]`. -/// Nonce is zero. Each `out[i]` receives 64 bytes. +/// @brief Four independent ChaCha blocks. +/// @details Lane `i` uses `key[i]` and `counter[i]`. Nonce is zero. Each `out[i]` receives 64 bytes. template +HEDLEY_NON_NULL(1, 2, 3) HEDLEY_NO_THROW void block4(const std::uint32_t key[][8], const std::uint32_t counter[4], std::uint8_t out[][64]) noexcept @@ -248,9 +269,12 @@ HEDLEY_PRAGMA(GCC unroll 16) } } +/// @} + } // namespace chacha_detail -/// ChaCha stream PRG with `Rounds` rounds (20 is RFC 8439). +/// @brief ChaCha stream PRG with `Rounds` rounds (20 is RFC 8439). +/// @tparam Rounds ChaCha round count. Must be positive and even template struct chacha final { @@ -272,7 +296,9 @@ struct chacha final return chacha_detail::load_block(buf + 16 * (pos & 3u)); } - /// Positions 0 and 1, one ChaCha block (the first 32 keystream bytes). + /// @brief Positions 0 and 1, one ChaCha block (the first 32 keystream bytes). + /// @param seed the PRG seed + /// @return Positions 0 and 1, one ChaCha block (the first 32 keystream bytes) HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE static auto eval01(block_type seed) noexcept @@ -290,6 +316,12 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") HEDLEY_PRAGMA(GCC diagnostic pop) } + /// @brief `count` blocks starting at lane `pos`. `output` is unused when `count` is 0. + /// @param seed the PRG seed + /// @param output the destination. Unused when `count` is 0 + /// @param count the number of blocks + /// @param pos the 0-based index + /// @throws std::invalid_argument if `pos + count` wraps `uint32_t`. HEDLEY_ALWAYS_INLINE static void eval(block_type seed, block_type * HEDLEY_RESTRICT output, psnip_uint32_t count, psnip_uint32_t pos = 0) @@ -436,19 +468,25 @@ HEDLEY_PRAGMA(GCC diagnostic pop) eval_x4(seeds + 4, output + 4, pos); } - /// Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @brief Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @tparam T value type + /// @tparam Party party index, `0` or `1` + /// @param seed the PRG seed + /// @param pos the 0-based index + /// @return Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`) + /// @see `prg.hpp` template HEDLEY_NO_THROW static auto expand(block_type seed, psnip_uint32_t pos = 0) noexcept; }; // struct chacha -/// RFC 8439 ChaCha20. +/// @brief RFC 8439 ChaCha20. using chacha20 = chacha<20>; -/// ChaCha12. Same keying as `chacha20`, 12 rounds. +/// @brief ChaCha12. Same keying as `chacha20`, 12 rounds. using chacha12 = chacha<12>; -/// ChaCha8. Same keying as `chacha20`, 8 rounds. +/// @brief ChaCha8. Same keying as `chacha20`, 8 rounds. using chacha8 = chacha<8>; } // namespace prg diff --git a/include/dpf/prg_dummy.hpp b/include/dpf/prg_dummy.hpp index 8e4c6d4..5237594 100644 --- a/include/dpf/prg_dummy.hpp +++ b/include/dpf/prg_dummy.hpp @@ -88,7 +88,13 @@ HEDLEY_PRAGMA(GCC diagnostic pop) std::copy_n(seeds, 8, output); } - /// Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @brief Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @tparam T value type + /// @tparam Party party index, `0` or `1` + /// @param seed the PRG seed + /// @param pos the 0-based index + /// @return Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`) + /// @see `prg.hpp` template HEDLEY_NO_THROW static auto expand(block_type seed, psnip_uint32_t pos = 0) noexcept; diff --git a/include/dpf/prg_lowmc.hpp b/include/dpf/prg_lowmc.hpp index 1ab39dd..9b67ad6 100644 --- a/include/dpf/prg_lowmc.hpp +++ b/include/dpf/prg_lowmc.hpp @@ -26,8 +26,8 @@ namespace dpf namespace prg { -/// LowMCv3, 128-bit block and key, 10 S-boxes, 32 rounds, all-zero key. -/// `eval(seed, pos)` is `E(seed ⊕ pos) ⊕ seed`, with `pos` in the low lane. +/// @brief LowMCv3, 128-bit block and key, 10 S-boxes, 32 rounds, all-zero key. +/// @details `eval(seed, pos)` is `E(seed ⊕ pos) ⊕ seed`, with `pos` in the low lane. struct lowmc128 final { using block_type = simde__m128i; @@ -104,7 +104,13 @@ HEDLEY_PRAGMA(GCC diagnostic pop) } } - /// Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @brief Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`). + /// @tparam T value type + /// @tparam Party party index, `0` or `1` + /// @param seed the PRG seed + /// @param pos the 0-based index + /// @return Raw-bit subtractive share of `T` for party `Party` (see `prg.hpp`) + /// @see `prg.hpp` template HEDLEY_NO_THROW static auto expand(block_type seed, psnip_uint32_t pos = 0) noexcept; diff --git a/include/dpf/random.hpp b/include/dpf/random.hpp index a6ad5ce..28bf447 100644 --- a/include/dpf/random.hpp +++ b/include/dpf/random.hpp @@ -1,6 +1,5 @@ /// @file dpf/random.hpp -/// @brief -/// @details +/// @brief Entropy source and uniform sampling. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -32,7 +31,7 @@ namespace dpf namespace detail { -/// When set, `uniform_fill` copies from this hook and does not read the +/// @brief When set, `uniform_fill` copies from this hook and does not read the /// system RNG. Used to feed the same beaver coins to dealer `make_dpf` and /// Doerner–Shelat gen. Null in normal use. inline thread_local void (*uniform_bytes_hook)(void *, std::size_t) = nullptr; @@ -50,8 +49,10 @@ bool fill_from_hook(T & buf) noexcept return true; } -/// `bool` and `enum : bool` (including `dpf::bit`) have only two valid +/// @brief `bool` and `enum : bool` (including `dpf::bit`) have only two valid /// representations. Filling them with a raw entropy byte is undefined. +/// @tparam T value type +/// @return `bool` and `enum : bool` (including `dpf::bit`) have only two valid representations template HEDLEY_NO_THROW constexpr bool is_boolean_representation() noexcept @@ -73,8 +74,8 @@ constexpr bool is_boolean_representation() noexcept #if !defined(LIBDPF_USE_ARC4RANDOM) -/// One unbuffered, exclusively locked read of the entropy device. -/// Buffering would copy unread bytes into a `fork()` child, so parent and +/// @brief One unbuffered, exclusively locked read of the entropy device. +/// @details Buffering would copy unread bytes into a `fork()` child, so parent and /// child would repeat the same key material. The lock keeps concurrent /// `fread` calls off the shared `FILE`. struct entropy_source diff --git a/include/dpf/rotated_iterable.hpp b/include/dpf/rotated_iterable.hpp deleted file mode 100644 index d0f81d0..0000000 --- a/include/dpf/rotated_iterable.hpp +++ /dev/null @@ -1,245 +0,0 @@ -/// @file dpf/rotated_iterable.hpp -/// @brief Retired container rotation view. The live type is `rotation_iterable`. -/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) -/// @license Released under a GNU General Public v2.0 (GPLv2) license. - -// /// @file dpf/rotated_iterable.hpp -// /// @author Ryan Henry -// /// @brief defines `dpf::rotated_iterable` and associated helpers -// /// @details -// /// @copyright Copyright (c) 2019-2023 Ryan Henry and others -// /// @license Released under a GNU General Public v2.0 (GPLv2) license; -// /// see [LICENSE.md](@ref GPLv2) for details. - -// #ifndef LIBDPF_INCLUDE_DPF_ROTATED_VIEW_HPP__ -// #define LIBDPF_INCLUDE_DPF_ROTATED_VIEW_HPP__ - -// namespace dpf -// { - -// template -// struct rotated_iterable_iterator; // forward declaration - -// template -// struct rotated_iterable_const_iterator; // forward declaration - -// template -// struct rotated_iterable -// { -// using container_type = ContainerT; - -// using value_type = typename container_type::value_type; -// using size_type = typename container_type::size_type; -// using difference_type = typename container_type::difference_type; -// using reference = typename container_type::reference; -// using const_reference = typename container_type::const_reference; -// using pointer = typename container_type::pointer; -// using const_pointer = typename container_type::const_pointer; - -// using iterator = rotated_iterable_iterator; -// using const_iterator = rotated_iterable_const_iterator; -// using wrapped_iterator = typename ContainerT::iterator; - -// rotated_iterable(const ContainerT & container, difference_type distance) -// : container_{container}, -// distance_{distance >= 0 ? distance % container_.size() : (distance % container_.size()) + container_.size()}, -// wrap_to{std::begin(container)}, -// wrap_after{std::next(std::end(container), -1)}, -// end_after{std::next(wrap_to, distance-1)} -// { -// distance_ %= container_.size(); -// if (distance_ < 0) -// { -// distance_ += container_.size(); -// } -// } - -// HEDLEY_ALWAYS_INLINE -// reference operator[](size_type index) -// { -// index += distance_; -// if (index > container_.size()) -// { -// index -= container_.size(); -// } -// return container_[index]; -// } - -// HEDLEY_ALWAYS_INLINE -// const_reference operator[](size_type index) const -// { -// index += distance_; -// if (index > container_.size()) -// { -// index -= container_.size(); -// } -// return container_[index]; -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// iterator begin() noexcept -// { -// return iterator{*this, std::next(end_after, 1)}; -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// const_iterator begin() const noexcept -// { -// return const_iterator{*this, std::next(end_after, 1)}; -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// const_iterator cbegin() const noexcept -// { -// return begin(); -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// iterator end() noexcept -// { -// return iterator{*this, std::next(wrap_after, 1)}; -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// const_iterator end() const noexcept -// { -// return const_iterator{*this, std::next(wrap_after, 1)}; -// } - -// HEDLEY_NO_THROW -// HEDLEY_ALWAYS_INLINE -// const_iterator cend() const noexcept -// { -// return end(); -// } - -// auto distance() const -// { -// return distance_; -// } - -// private: -// container_type & container_; -// difference_type distance_; - -// wrapped_iterator wrap_to; -// wrapped_iterator wrap_after; -// wrapped_iterator end_after; -// }; // rotated_iterable - -// template -// struct rotated_iterator -// { -// using wrapped_iterable_type = rotated_iterable; -// using wrapped_iterator = typename wrapped_iterable_type::iterator; -// using size_type = typename wrapped_iterable_type::size_type; -// using reference = typename wrapped_iterable_type::reference; - -// rotated_iterable & v; -// wrapped_iterator it; -// rotated_iterator & operator++() -// { -// if (it == v.wrap_after) -// { -// it = v.wrap_to; -// } -// else if (it == v.end_after) -// { -// it = std::next(v.wrap_after, 1); -// } -// else -// { -// ++it; -// } - -// return *this; -// } -// rotated_iterator & operator--() -// { -// --it; -// if (it == v.wrap_after) -// { -// it = v.end_after; -// } -// else if (it == v.end_after) -// { -// it = std::next(v.wrap_to, -1); -// } - -// return *this; -// } -// reference operator*() const { return *it; } -// bool operator!=(const rotated_iterator other) const -// { return &v != &other.v || it != other.it; } -// }; - -// template -// struct rotated_const_iterator -// { -// using wrapped_iterable_type = rotated_iterable; -// using wrapped_iterator = typename wrapped_iterable_type::iterator; -// using size_type = typename wrapped_iterable_type::size_type; -// using const_reference = typename wrapped_iterable_type::const_reference; - -// const rotated_iterable & v; -// wrapped_iterator it; -// rotated_const_iterator & operator++() -// { -// if (it == v.wrap_after) -// { -// it = v.wrap_to; -// } -// else if (it == v.end_after) -// { -// it = std::next(v.wrap_after, 1); -// } -// else -// { -// ++it; -// } - -// return *this; -// } -// rotated_const_iterator & operator--() -// { -// --it; -// if (it == v.wrap_after) -// { -// it = v.end_after; -// } -// else if (it == v.end_after) -// { -// it = std::next(v.wrap_to, -1); -// } - -// return *this; -// } -// const_reference operator*() const { return *it; } -// bool operator!=(const rotated_const_iterator other) const -// { return &v != &other.v || it != other.it; } -// }; - -// template -// auto rotated_by(const ContainerT & container, -// typename ContainerT::size_type rotate_by) -// { -// return rotated_iterable{container, rotate_by}; -// } - -// template -// auto for_each_rotated_by(const ContainerT & container, -// typename ContainerT::size_type rotate_by, UnaryFunction && f) -// { -// for (auto i = rotate_by; i < container.size(); ++i) f(container[i]); -// for (auto i = 0; i < rotate_by; ++i) f(container[i]); -// } - -// } // namespace dpf - -// #endif // LIBDPF_INCLUDE_DPF_ROTATED_VIEW_HPP__ diff --git a/include/dpf/secret_share.hpp b/include/dpf/secret_share.hpp index 4d51b9d..02acd3a 100644 --- a/include/dpf/secret_share.hpp +++ b/include/dpf/secret_share.hpp @@ -26,7 +26,7 @@ namespace dpf { -/// Sharing scheme tag. +/// @brief Sharing scheme tag. enum class sharing : unsigned char { additive = 0, @@ -94,8 +94,14 @@ using share_value_type_t = typename share_value_type>::type; namespace detail { -/// Party coefficient of the secret for this scheme: additive always +1; +/// @brief Party coefficient of the secret for this scheme: additive always +1; /// subtractive is +1 for party 0 and −1 for party 1. +/// @tparam Scheme scheme +/// @tparam Party party index, `0` or `1` +/// @tparam T value type +/// @param v the `v` +/// @return Party coefficient of the secret for this scheme: additive always +1; subtractive is +1 +/// for party 0 and −1 for party 1 template HEDLEY_ALWAYS_INLINE HEDLEY_PURE @@ -141,7 +147,9 @@ struct secret_share secret_share & operator=(secret_share &&) noexcept = default; ~secret_share() = default; - /// Bit-preserving construction. Does not apply a party coefficient. + /// @brief Bit-preserving construction. Does not apply a party coefficient. + /// @param v the `v` + /// @return Bit-preserving construction HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_CONST @@ -162,7 +170,8 @@ struct secret_share HEDLEY_PURE constexpr T & raw() noexcept { return value; } - /// Secret-preserving conversion to an additive share of the same party. + /// @brief Secret-preserving conversion to an additive share of the same party. + /// @return Secret-preserving conversion to an additive share of the same party HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE @@ -175,7 +184,8 @@ struct secret_share detail::party_coeff_times(value)); } - /// Secret-preserving conversion to a subtractive share of the same party. + /// @brief Secret-preserving conversion to a subtractive share of the same party. + /// @return Secret-preserving conversion to a subtractive share of the same party HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE @@ -188,7 +198,10 @@ struct secret_share detail::party_coeff_times(value)); } - /// Bit-preserving retag (no secret-preserving sign fix). + /// @brief Bit-preserving retag (no secret-preserving sign fix). + /// @tparam NewScheme new scheme + /// @tparam NewParty new party + /// @return Bit-preserving retag (no secret-preserving sign fix) template HEDLEY_ALWAYS_INLINE HEDLEY_PURE @@ -239,7 +252,11 @@ struct secret_share return *this; } - /// Absorb a public plaintext on party 0 only. + /// @brief Absorb a public plaintext on party 0 only. + /// @tparam Plain plain + /// @tparam T value type + /// @param c the `c` + /// @return `*this` template && std::is_convertible_v, int> = 0> @@ -525,7 +542,8 @@ struct party_key : Key HEDLEY_ALWAYS_INLINE const Key & key() const noexcept { return static_cast(*this); } - /// Party-tagged additive share of the comparison absorb addend. + /// @brief Party-tagged additive share of the comparison absorb addend. + /// @return Party-tagged additive share of the comparison absorb addend HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE auto cmp_addend() const noexcept @@ -555,9 +573,10 @@ struct party_of> template inline constexpr std::size_t party_of_v = party_of>::value; -/// Strip a `party_key` wrapper; bare keys are unchanged. Memoizers and other +/// @brief Strip a `party_key` wrapper; bare keys are unchanged. Memoizers and other /// tree-layout helpers key on the underlying DPF key type so a memoizer built /// for party 0 also accepts party 1. +/// @tparam T value type template struct unwrap_party_key { diff --git a/include/dpf/sequence_memoizer.hpp b/include/dpf/sequence_memoizer.hpp index 54b1b0c..434a121 100644 --- a/include/dpf/sequence_memoizer.hpp +++ b/include/dpf/sequence_memoizer.hpp @@ -285,8 +285,10 @@ struct pointer_facade } // namespace detail -/// One level. The buffer is traversed in the opposite direction on +/// @brief One level. The buffer is traversed in the opposite direction on /// alternate levels. +/// @tparam DpfKey DPF key type +/// @tparam Allocator allocator type template > struct inplace_reversing_sequence_memoizer final @@ -381,18 +383,20 @@ HEDLEY_PRAGMA(GCC diagnostic pop) unique_ptr buf; }; -/// Two levels, so a level can be built while the previous level is still +/// @brief Two levels, so a level can be built while the previous level is still /// intact. Default workspace for `eval_sequence` on a recipe. +/// @tparam DpfKey DPF key type +/// @tparam Allocator allocator type template > struct double_space_sequence_memoizer final : public sequence_recipe_memoizer_base { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") private: -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using parent = sequence_recipe_memoizer_base; -HEDLEY_PRAGMA(GCC diagnostic pop) + HEDLEY_PRAGMA(GCC diagnostic pop) public: using unique_ptr = typename Allocator::unique_ptr; using return_type = typename DpfKey::interior_node *; @@ -435,17 +439,19 @@ HEDLEY_PRAGMA(GCC diagnostic pop) unique_ptr buf; }; -/// Every level of the recipe's traversal. +/// @brief Every level of the recipe's traversal. +/// @tparam DpfKey DPF key type +/// @tparam Allocator allocator type template > struct full_tree_sequence_memoizer final : public sequence_recipe_memoizer_base { + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") private: -HEDLEY_PRAGMA(GCC diagnostic push) -HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using parent = sequence_recipe_memoizer_base; -HEDLEY_PRAGMA(GCC diagnostic pop) + HEDLEY_PRAGMA(GCC diagnostic pop) public: using unique_ptr = typename Allocator::unique_ptr; using return_type = typename DpfKey::interior_node *; @@ -502,10 +508,12 @@ auto make_sequence_memoizer(const sequence_recipe & recipe) HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") -/// One-level sequence workspace bound to `recipe`. +/// @brief One-level sequence workspace bound to `recipe`. +/// @tparam DpfKey DPF key type /// @param recipe The object later passed to `eval_sequence`. The memoizer /// holds a reference to it. /// @snippet evaluation/memoizers.cpp sequence-memoizer +/// @return One-level sequence workspace bound to `recipe` template inline auto make_inplace_reversing_sequence_memoizer(const sequence_recipe & recipe) { @@ -519,8 +527,11 @@ inline auto make_inplace_reversing_sequence_memoizer(const DpfKey &, const seque return make_inplace_reversing_sequence_memoizer(recipe); } -/// Two-level sequence workspace bound to `recipe`. +/// @brief Two-level sequence workspace bound to `recipe`. /// @snippet evaluation/eval_sequence.cpp eval-sequence-recipe +/// @tparam DpfKey DPF key type +/// @param recipe the sequence recipe the memoizer was built from +/// @return Two-level sequence workspace bound to `recipe` template inline auto make_double_space_sequence_memoizer(const sequence_recipe & recipe) { @@ -534,20 +545,23 @@ inline auto make_double_space_sequence_memoizer(const DpfKey &, const sequence_r return make_double_space_sequence_memoizer(recipe); } -/// Full-tree sequence workspace bound to `recipe`. +/// @brief Full-tree sequence workspace bound to `recipe`. +/// @tparam DpfKey DPF key type +/// @param recipe the sequence recipe the memoizer was built from +/// @return Full-tree sequence workspace bound to `recipe` template inline auto make_full_tree_sequence_memoizer(const sequence_recipe & recipe) { using key_t = unwrap_party_key_t; return detail::make_sequence_memoizer>(recipe); } +HEDLEY_PRAGMA(GCC diagnostic pop) template inline auto make_full_tree_sequence_memoizer(const DpfKey &, const sequence_recipe & recipe) { return make_full_tree_sequence_memoizer(recipe); } -HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace dpf diff --git a/include/dpf/sequence_recipe.hpp b/include/dpf/sequence_recipe.hpp index 6efa515..2dd6e9f 100644 --- a/include/dpf/sequence_recipe.hpp +++ b/include/dpf/sequence_recipe.hpp @@ -26,7 +26,7 @@ namespace dpf { -/// Steps, leaf count, and per-level endpoints for one sorted point list. +/// @brief Steps, leaf count, and per-level endpoints for one sorted point list. struct sequence_recipe { public: @@ -52,7 +52,8 @@ struct sequence_recipe HEDLEY_PURE HEDLEY_NO_THROW constexpr const std::vector & level_endpoints() const noexcept { return level_endpoints_; } - /// `level_endpoints().size() - 1`. Not `constexpr`: `std::vector::size` is not a constant expression in C++17. + /// @brief `level_endpoints().size() - 1`. Not `constexpr`: `std::vector::size` is not a constant expression in C++17. + /// @return `level_endpoints().size() - 1` HEDLEY_PURE HEDLEY_NO_THROW std::size_t depth() const noexcept { return level_endpoints_.size()-1; } @@ -139,9 +140,13 @@ auto make_sequence_recipe(ForwardIterator begin, ForwardIterator end) } // namespace detail -/// Compile `[begin, end)` into a recipe for `DpfKey`'s input type. +/// @brief Compile `[begin, end)` into a recipe for `DpfKey`'s input type. /// @tparam DpfKey Key type, or a `party_key` of that key. Only the input /// type and depth are used. +/// @tparam ForwardIterator forward iterator type +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @return Compile `[begin, end)` into a recipe for `DpfKey`'s input type /// @throws std::runtime_error if the range is not sorted nondecreasing. template @@ -157,8 +162,17 @@ auto make_sequence_recipe(const DpfKey &, ForwardIterator begin, ForwardIterator return make_sequence_recipe(begin, end); } -/// Build a sequence recipe that stops at `StopLevel` with packing `LgOpl` +/// @brief Build a sequence recipe that stops at `StopLevel` with packing `LgOpl` /// (multi-level / `out` slots). Lane points are in the slot's prefix domain. +/// @tparam StopLevel stop level +/// @tparam LgOpl lg opl +/// @tparam InputT input domain type +/// @tparam ForwardIterator forward iterator type +/// @param msb_mask the `msb_mask` +/// @param begin the iterator to the first query +/// @param end the iterator past the last query +/// @return the constructed object +/// @throws std::runtime_error if `list must be sorted` template auto make_sequence_recipe_at(InputT msb_mask, ForwardIterator begin, diff --git a/include/dpf/sequence_utils.hpp b/include/dpf/sequence_utils.hpp index d853f0c..bf35ce9 100644 --- a/include/dpf/sequence_utils.hpp +++ b/include/dpf/sequence_utils.hpp @@ -9,15 +9,15 @@ namespace dpf { -/// Tag base for `eval_sequence` storage layout. +/// @brief Tag base for `eval_sequence` storage layout. struct return_type_tag_{}; -/// Store whole leaves. Default for `eval_sequence`. The iterable still +/// @brief Store whole leaves. Default for `eval_sequence`. The iterable still /// yields one share per listed point. struct return_entire_node_tag_ final : public return_type_tag_ {}; // static constexpr auto return_entire_node_tag = return_entire_node_tag_{}; -/// Store one share per listed point. +/// @brief Store one share per listed point. struct return_output_only_tag_ final : public return_type_tag_ {}; // static constexpr auto return_output_only_tag = return_output_only_tag_{}; diff --git a/include/dpf/setbit_index_iterable.hpp b/include/dpf/setbit_index_iterable.hpp index 696fb16..4f0af2b 100644 --- a/include/dpf/setbit_index_iterable.hpp +++ b/include/dpf/setbit_index_iterable.hpp @@ -1,6 +1,5 @@ /// @file dpf/setbit_index_iterable.hpp -/// @brief -/// @details +/// @brief Iterates the positions of set bits in a bit array. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; diff --git a/include/dpf/subsequence_iterable.hpp b/include/dpf/subsequence_iterable.hpp index 48ac7d5..ead354e 100644 --- a/include/dpf/subsequence_iterable.hpp +++ b/include/dpf/subsequence_iterable.hpp @@ -1,7 +1,7 @@ /// @file dpf/subsequence_iterable.hpp /// @author Ryan Henry /// @brief defines `dpf::subsequence_iterable` and associated helpers -/// @details +/// @details Yields a listed subset of another iterable without copying it. /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. diff --git a/include/dpf/tree_traits.hpp b/include/dpf/tree_traits.hpp new file mode 100644 index 0000000..0ef49cf --- /dev/null +++ b/include/dpf/tree_traits.hpp @@ -0,0 +1,385 @@ +/// @file dpf/tree_traits.hpp +/// @brief BGI vs Half-Tree walk policy, selected by the interior PRG. +/// @details Default traits match today's Boyle–Gilboa–Ishai tree. A PRG that +/// defines `half_tree_tag` (e.g. `prg::aes128_ccr`) opts into the +/// Guo et al. Half-Tree mid-level expand / CW / advance, with a +/// two-tweak last level that keeps BGI-style advice packing. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_TREE_TRAITS_HPP__ +#define LIBDPF_INCLUDE_DPF_TREE_TRAITS_HPP__ + +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" +#include "simde/simde/x86/avx2.h" +#include "portable-snippets/exact-int/exact-int.h" + +#include "dpf/twiddle.hpp" +#include "dpf/utils.hpp" + +namespace dpf +{ + +/// @brief Walk policy for interior DPF levels. Specialized when `PRG::half_tree_tag` +/// exists. +/// @tparam PRG pseudorandom generator +template +struct tree_traits +{ + using prg = PRG; + using node = typename PRG::block_type; + + static constexpr bool is_half_tree = false; + static constexpr bool stores_mid_advice = true; + static constexpr bool last_level_differs = false; + + template + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1) + static void root_init(node out[2], Sampler && sample) + { + out[0] = dpf::unset_lo_bit(static_cast(sample())); + out[1] = dpf::set_lo_bit(static_cast(sample())); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static bool is_last_level(std::size_t level, std::size_t depth) noexcept + { + (void)level; + (void)depth; + return false; + } + + /// @brief BGI expand: clear lo-2bits, then `PRG::eval01`. + /// @param s the `s` + /// @return BGI expand: clear lo-2bits, then `PRG::eval01` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto expand(node s, bool /*is_last*/ = false) noexcept + { + return PRG::eval01(dpf::unset_lo_2bits(s)); + } + +/// @brief Convert / value-CW stretch. BGI: identical to `expand`. +/// @param s the node to stretch +/// @return the same value as `expand` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto expand_value(node s) noexcept + { + return expand(s, false); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 2, 3) + static void expand_x4(const node * HEDLEY_RESTRICT seeds, + node * HEDLEY_RESTRICT left, node * HEDLEY_RESTRICT right, + bool /*is_last*/ = false) noexcept + { + alignas(node) node cleared[4]; + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + cleared[i] = dpf::unset_lo_2bits(seeds[i]); + PRG::eval01_x4(cleared, left, right); + } + + /// @brief Pack CW for eval: embed advice bit `dir` into the lo-bit. + /// @param cw the `cw` + /// @param advice the advice bit + /// @param dir the `dir` + /// @return the returned `node` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static node pack_cw(node cw, psnip_uint8_t advice, bool dir, + bool /*is_last*/ = false) noexcept + { + return dpf::set_lo_bit(cw, (advice >> dir) & 1); + } + + /// @brief Off-path child XOR + packed advice `t0|t1` (BGI). + /// @param cw_out the `cw_out` + /// @param advice_out the `advice_out` + /// @param kids0 the `kids0` + /// @param kids1 the `kids1` + /// @param bit the bit value or bit index + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static void make_cw(node & cw_out, psnip_uint8_t & advice_out, + const std::array & kids0, const std::array & kids1, + const node & /*s0*/, const node & /*s1*/, bool bit, + bool /*is_last*/ = false) noexcept + { + const node child[2] = { + simde_mm_xor_si128(kids0[0], kids1[0]), + simde_mm_xor_si128(kids0[1], kids1[1]) + }; + const bool t0 = static_cast(dpf::get_lo_bit(child[0]) ^ !bit); + const bool t1 = static_cast(dpf::get_lo_bit(child[1]) ^ bit); + cw_out = child[!bit]; + advice_out = static_cast((t1 << 1) | t0); + } + + /// @brief `xor_if(child[dir], pack_cw(...), parent_control)`. + /// @param parent the parent node + /// @param kids the `kids` + /// @param cw the `cw` + /// @param advice the advice bit + /// @param dir the `dir` + /// @param parent_control the `parent_control` + /// @return `xor_if(child[dir], pack_cw(...), parent_control)` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static node advance(node parent, const std::array & kids, + node cw, psnip_uint8_t advice, bool dir, + bool parent_control, bool /*is_last*/ = false) noexcept + { + const node packed = pack_cw(cw, advice, dir); + return dpf::xor_if(kids[dir ? 1u : 0u], packed, parent_control); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static node traverse(node parent, node cw_packed, bool dir, + bool /*is_last*/ = false) noexcept + { + auto kids = expand(parent, false); + return dpf::xor_if_lo_bit(kids[dir ? 1u : 0u], cw_packed, parent); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto traverse01(node parent, node cw0, node cw1, + bool is_last = false) noexcept + { +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + auto kids = expand(parent, is_last); + return std::array{ + dpf::xor_if_lo_bit(kids[0], cw0, parent), + dpf::xor_if_lo_bit(kids[1], cw1, parent) + }; +HEDLEY_PRAGMA(GCC diagnostic pop) + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 4, 5) + static void traverse01_x4(const node * HEDLEY_RESTRICT parents, + node cw0, node cw1, node * HEDLEY_RESTRICT left, + node * HEDLEY_RESTRICT right, bool is_last = false) noexcept + { +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + expand_x4(parents, left, right, is_last); +HEDLEY_PRAGMA(GCC diagnostic pop) + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + { + left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]); + right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]); + } + } +}; + +/// @brief Half-Tree specialization (PRG advertises `half_tree_tag`). +/// @tparam PRG pseudorandom generator +template +struct tree_traits> +{ + using prg = PRG; + using node = typename PRG::block_type; + + static constexpr bool is_half_tree = true; + static constexpr bool stores_mid_advice = false; + static constexpr bool last_level_differs = true; + + template + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1) + static void root_init(node out[2], Sampler && sample) + { + // Shares of a fixed Δ with lsb(Δ)=1. + const node s = dpf::unset_lo_bit(static_cast(sample())); + const node delta = dpf::set_lo_bit(static_cast(sample())); + out[0] = s; + out[1] = simde_mm_xor_si128(s, delta); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static bool is_last_level(std::size_t level, std::size_t depth) noexcept + { + return depth != 0 && level + 1 == depth; + } + + /// @brief Mid: `{H(s), H(s)⊕s}` (keep control bit). Last: two-tweak stretch. + /// @param s the `s` + /// @param is_last the `is_last` + /// @return Mid: `{H(s), H(s)⊕s}` (keep control bit) + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto expand(node s, bool is_last = false) noexcept + { + if (is_last) + { + // `{H(s|0), H(s|1)}` — LSB forced, matching Guo et al. / myl7. + const node base = dpf::unset_lo_bit(s); +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + return std::array{ + PRG::hash(base), + PRG::hash(dpf::set_lo_bit(base)) + }; +HEDLEY_PRAGMA(GCC diagnostic pop) + } + return PRG::eval01(s); + } + +/// @brief Convert / value-CW stretch: always two-tweak `{H(s|0), H(s|1)}`. +/// @details Seed walk mid levels keep using half-style `expand`. +/// @param s the node to stretch +/// @return the pair `{H(s|0), H(s|1)}` + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto expand_value(node s) noexcept + { + return expand(s, true); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 2, 3) + static void expand_x4(const node * HEDLEY_RESTRICT seeds, + node * HEDLEY_RESTRICT left, node * HEDLEY_RESTRICT right, + bool is_last = false) noexcept + { + if (is_last) + { + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + { + auto kids = expand(seeds[i], true); + left[i] = kids[0]; + right[i] = kids[1]; + } + return; + } + PRG::eval01_x4(seeds, left, right); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_CONST + static node pack_cw(node cw, psnip_uint8_t advice, bool dir, + bool is_last = false) noexcept + { + if (!is_last) + return cw; // mid: full CW, no advice packing + return dpf::set_lo_bit(cw, (advice >> dir) & 1); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static void make_cw(node & cw_out, psnip_uint8_t & advice_out, + const std::array & kids0, const std::array & kids1, + const node & s0, const node & s1, bool bit, bool is_last = false) noexcept + { + if (is_last) + { + // BGI-style advice on the leaf step only. + const node child[2] = { + simde_mm_xor_si128(kids0[0], kids1[0]), + simde_mm_xor_si128(kids0[1], kids1[1]) + }; + const bool t0 = static_cast(dpf::get_lo_bit(child[0]) ^ !bit); + const bool t1 = static_cast(dpf::get_lo_bit(child[1]) ^ bit); + cw_out = child[!bit]; + advice_out = static_cast((t1 << 1) | t0); + (void)s0; + (void)s1; + return; + } + // CW = H(s0)⊕H(s1)⊕ᾱ·Δ = off-path children XOR (Half-Tree identity). + const node child[2] = { + simde_mm_xor_si128(kids0[0], kids1[0]), + simde_mm_xor_si128(kids0[1], kids1[1]) + }; + cw_out = child[!bit]; + advice_out = 0; + (void)s0; + (void)s1; + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_PURE + static node advance(node parent, const std::array & kids, + node cw, psnip_uint8_t advice, bool dir, bool parent_control, + bool is_last = false) noexcept + { + // Mid and last: select child[dir], XOR packed CW if parent control set. + // Mid Half-Tree: child[1]=H⊕s so this is `h ⊕ (dir?s:0) ⊕ (t?cw:0)`. + const node packed = pack_cw(cw, advice, dir, is_last); + return dpf::xor_if(kids[dir ? 1u : 0u], packed, parent_control); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static node traverse(node parent, node cw_packed, bool dir, + bool is_last = false) noexcept + { + auto kids = expand(parent, is_last); + return dpf::xor_if_lo_bit(kids[dir ? 1u : 0u], cw_packed, parent); + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + static auto traverse01(node parent, node cw0, node cw1, + bool is_last = false) noexcept + { +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + auto kids = expand(parent, is_last); + return std::array{ + dpf::xor_if_lo_bit(kids[0], cw0, parent), + dpf::xor_if_lo_bit(kids[1], cw1, parent) + }; +HEDLEY_PRAGMA(GCC diagnostic pop) + } + + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + HEDLEY_NON_NULL(1, 4, 5) + static void traverse01_x4(const node * HEDLEY_RESTRICT parents, + node cw0, node cw1, node * HEDLEY_RESTRICT left, + node * HEDLEY_RESTRICT right, bool is_last = false) noexcept + { +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + expand_x4(parents, left, right, is_last); +HEDLEY_PRAGMA(GCC diagnostic pop) + DPF_UNROLL_LOOP + for (std::size_t i = 0; i < 4; ++i) + { + left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]); + right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]); + } + } +}; + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_TREE_TRAITS_HPP__ diff --git a/include/dpf/twiddle.hpp b/include/dpf/twiddle.hpp index 29908ae..2b9b7aa 100644 --- a/include/dpf/twiddle.hpp +++ b/include/dpf/twiddle.hpp @@ -1,6 +1,5 @@ /// @file dpf/twiddle.hpp -/// @brief -/// @details +/// @brief Low-bit extract, sibling nodes, and small bit masks. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -79,9 +78,9 @@ auto get_if_lo_bit(std::array a, simde__m128i b) noexcept std::transform(std::begin(a), std::end(a), std::begin(a), [mask](simde__m128i & a){return simde_mm_and_si128(a, mask);}); return a; } - HEDLEY_PRAGMA(GCC diagnostic pop) + // if low bit of c is set, then return xor of a and b, else return a HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE diff --git a/include/dpf/twobit.hpp b/include/dpf/twobit.hpp index dec5828..9a29a17 100644 --- a/include/dpf/twobit.hpp +++ b/include/dpf/twobit.hpp @@ -4,6 +4,7 @@ /// 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. See `packed_lane_arithmetic.hpp`. +/// @see packed_lane_arithmetic.hpp #ifndef LIBDPF_INCLUDE_DPF_TWOBIT_HPP__ #define LIBDPF_INCLUDE_DPF_TWOBIT_HPP__ @@ -50,7 +51,10 @@ static constexpr dpf::twobit to_twobit(unsigned long long value) noexcept } /// @brief parse one character as a 2-bit digit +/// @tparam CharT character type /// @param zero character for 0 (default `'0'`) +/// @param value the value to convert or store +/// @return the returned `dpf::twobit` /// @throws std::domain_error if `value` is not one of the four digits template HEDLEY_ALWAYS_INLINE @@ -92,6 +96,9 @@ operator>>(std::basic_istream & is, dpf::twobit & value) } /// @brief addition in Z/4Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return addition in Z/4Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -102,6 +109,9 @@ constexpr dpf::twobit operator+(dpf::twobit lhs, dpf::twobit rhs) noexcept } /// @brief subtraction in Z/4Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return subtraction in Z/4Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -112,6 +122,8 @@ constexpr dpf::twobit operator-(dpf::twobit lhs, dpf::twobit rhs) noexcept } /// @brief additive inverse in Z/4Z +/// @param value the value to convert or store +/// @return additive inverse in Z/4Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -121,6 +133,9 @@ constexpr dpf::twobit operator-(dpf::twobit value) noexcept } /// @brief multiplication in Z/4Z +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return multiplication in Z/4Z HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE diff --git a/include/dpf/utils.hpp b/include/dpf/utils.hpp index e8ebbf4..1d69679 100644 --- a/include/dpf/utils.hpp +++ b/include/dpf/utils.hpp @@ -1,6 +1,6 @@ /// @file dpf/utils.hpp /// @brief miscellaneous helper functions, structs, preprocessor directives -/// @details +/// @details Type traits, bit lengths, and small tuple helpers shared by the headers. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -95,13 +95,13 @@ class numeric_limits : public numeric_limits {}; /// @details specializes `std::numeric_limits` for -/// `uint128_t volatile` +/// @brief `uint128_t volatile` template<> class numeric_limits : public numeric_limits {}; /// @details specializes `std::numeric_limits` for -/// `uint128_t const volatile` +/// @brief `uint128_t const volatile` template<> class numeric_limits : public numeric_limits {}; @@ -162,13 +162,13 @@ class numeric_limits : public numeric_limits {}; /// @details specializes `std::numeric_limits` for -/// `uint256_t volatile` +/// @brief `uint256_t volatile` template<> class numeric_limits : public numeric_limits {}; /// @details specializes `std::numeric_limits` for -/// `uint256_t const volatile` +/// @brief `uint256_t const volatile` template<> class numeric_limits : public numeric_limits {}; @@ -184,6 +184,11 @@ namespace utils { /// @brief Ugly hack to implement `constexpr`-frien`dly conditional `throw` +/// @tparam Exception exception +/// @param b the `b` +/// @param what the diagnostic message +/// @return Ugly hack to implement `constexpr`-frien`dly conditional `throw` +/// @throws Exception template HEDLEY_ALWAYS_INLINE static constexpr auto constexpr_maybe_throw(bool b, std::string_view what) -> void @@ -213,6 +218,11 @@ template static constexpr bool is_quotient_integer_v = is_quotient_integer::value; /// @brief Integer overflow-proof ceiling of division +/// @tparam T value type +/// @tparam T value type +/// @param numerator the `numerator` +/// @param denominator the `denominator` +/// @return Integer overflow-proof ceiling of division template , bool> = false> HEDLEY_CONST @@ -225,6 +235,11 @@ static constexpr T quotient_ceiling(T numerator, T denominator) noexcept } /// @brief Integer overflow-proof floor of division +/// @tparam T value type +/// @tparam T value type +/// @param numerator the `numerator` +/// @param denominator the `denominator` +/// @return Integer overflow-proof floor of division template , bool> = false> HEDLEY_CONST @@ -242,11 +257,12 @@ struct is_signed_integral template static constexpr bool is_signed_integral_v = is_signed_integral::value; -/// Whether DPF keygen/eval flip the input MSB (two's-complement domains). -/// Distinct from `is_signed_integral`: wrappers such as signed `fixedpoint` +/// @brief Whether DPF keygen/eval flip the input MSB (two's-complement domains). +/// @details Distinct from `is_signed_integral`: wrappers such as signed `fixedpoint` /// are not `std::is_integral`, and treating them as such would break /// `make_unsigned`. Sequence recipe construction and breadth-first eval /// must use this trait, not `is_signed_integral_v`. +/// @tparam T value type template struct uses_signed_msb : std::bool_constant< @@ -274,6 +290,9 @@ template using make_unsigned_t = typename make_unsigned::type; /// @brief Make an `std::bitset` from a variadic list of `bool`s +/// @tparam Bools bools +/// @param bs the `bs` +/// @return Make an `std::bitset` from a variadic list of `bool`s template auto make_bitset(Bools ...bs) { @@ -356,6 +375,7 @@ struct bitlength_of template <> struct bitlength_of : public std::integral_constant { }; +HEDLEY_PRAGMA(GCC diagnostic pop) // template <> // struct bitlength_of @@ -364,7 +384,6 @@ struct bitlength_of template struct bitlength_of> : public std::integral_constant * N> { }; -HEDLEY_PRAGMA(GCC diagnostic pop) template @@ -385,6 +404,9 @@ template ::value; /// @brief the primitive integral type used to represent non integral types +/// @tparam Nbits width in bits +/// @tparam MinBits min bits +/// @tparam MaxBits max bits template @@ -413,6 +435,9 @@ template ::type; /// @brief the primitive integral type used to represent non integral types +/// @tparam Nbits width in bits +/// @tparam MinBits min bits +/// @tparam MaxBits max bits template @@ -475,9 +500,13 @@ struct make_from_integral_value } }; -/// Reconstruct `x0 XOR x1` via the integral bridge. Prefer this over +/// @brief Reconstruct `x0 XOR x1` via the integral bridge. Prefer this over /// `static_cast(x0 ^ x1)`: for `keyword`, `operator^` yields the parent /// `modint`, which cannot convert back through the private keyword ctor. +/// @tparam T value type +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @return Reconstruct `x0 XOR x1` via the integral bridge template HEDLEY_NO_THROW constexpr T xor_input_shares(T x0, T x1) noexcept @@ -507,8 +536,12 @@ static constexpr IntegralT get_node_mask(InputT mask, std::size_t level_index) return static_cast(to_int(mask) >> (level_index-1 + dpf_type::lg_outputs_per_leaf)); } -/// Logical right shift. Offsets at or past the width yield 0 (a `>>` of that +/// @brief Logical right shift. Offsets at or past the width yield 0 (a `>>` of that /// width is undefined for the native unsigned types). +/// @tparam IntegralT integral type +/// @param value the value to convert or store +/// @param offset the public offset +/// @return Logical right shift template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -519,7 +552,11 @@ constexpr IntegralT shift_right(IntegralT value, std::size_t offset) noexcept return static_cast(value >> offset); } -/// Floor of `from_inclusive / 2^lg_opl`. `lg_opl` is `log2(outputs_per_leaf)`. +/// @brief Floor of `from_inclusive / 2^lg_opl`. `lg_opl` is `log2(outputs_per_leaf)`. +/// @tparam IntegralT integral type +/// @param from_inclusive the `from_inclusive` +/// @param lg_opl the `lg_opl` +/// @return Floor of `from_inclusive / 2^lg_opl` template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -530,11 +567,15 @@ constexpr IntegralT leaf_node_floor(IntegralT from_inclusive, std::size_t lg_opl return shift_right(from_inclusive, lg_opl); } -/// Exclusive leaf index of an inclusive input `to_inclusive`. -/// `2^lg_opl` outputs share a leaf. When `to_inclusive + 1` does not fit in +/// @brief Exclusive leaf index of an inclusive input `to_inclusive`. +/// @details `2^lg_opl` outputs share a leaf. When `to_inclusive + 1` does not fit in /// `IntegralT`, the exclusive node index is `2^(width - lg_opl)`. That value /// itself does not fit when `lg_opl == 0`; the returned 0 is that saturated /// end (`[from, 2^width)`), which `split_leaf_nodes` interprets. +/// @tparam IntegralT integral type +/// @param to_inclusive the `to_inclusive` +/// @param lg_opl the `lg_opl` +/// @return Exclusive leaf index of an inclusive input `to_inclusive` template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -553,9 +594,15 @@ constexpr IntegralT leaf_node_ceil_exclusive(IntegralT to_inclusive, std::size_t return quotient_ceiling(next, opl); } -/// Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and, +/// @brief Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and, /// for interval splitting, its `tree_level`) explicitly. The classic wrappers /// below forward the deepest-slot packing (`DpfKey::lg_outputs_per_leaf`). +/// @tparam InputT input domain type +/// @tparam size_t size type +/// @param from the inclusive start of the range +/// @param lg_opl the `lg_opl` +/// @return Multi-level flavor: caller passes the slot's `lg(outputs-per-leaf)` (and, for interval +/// splitting, its `tree_level`) explicitly template , bitlength_of_v>> @@ -591,8 +638,9 @@ static constexpr IntegralT get_to_node(InputT to) return get_to_node_at(to, DpfKey::lg_outputs_per_leaf); } -/// One half-open leaf-node range. `to_node == 0` with a nonzero `count` is the +/// @brief One half-open leaf-node range. `to_node == 0` with a nonzero `count` is the /// saturated end `[from_node, 2^width)`. +/// @tparam IntegralT integral type template struct node_segment { @@ -609,9 +657,14 @@ struct node_segments std::size_t total = 0; }; -/// True when the inclusive walk `[from, to]` wraps the low `bits` of the +/// @brief True when the inclusive walk `[from, to]` wraps the low `bits` of the /// domain. Comparison is on the post-MSB-flip bit pattern. Leaf ids alone /// cannot carry this: packing can put a wrapping pair into `from_node <= to_node`. +/// @tparam IntegralT integral type +/// @param from the inclusive start of the range +/// @param to the `to` +/// @param bits the packed bits +/// @return True when the inclusive walk `[from, to]` wraps the low `bits` of the domain template inline bool interval_wraps(IntegralT from, IntegralT to, std::size_t bits) { @@ -626,18 +679,25 @@ inline bool interval_wraps(IntegralT from, IntegralT to, std::size_t bits) return from > to; } -/// Split an inclusive output interval, already reduced to leaf ids, into one +/// @brief Split an inclusive output interval, already reduced to leaf ids, into one /// or two half-open walks. A linearized `from_node > to_node` wraps the node /// id space `[0, 2^depth)`. A saturated `to_node == 0` means the exclusive end /// is `2^{bitwidth(IntegralT)}`, which is the whole id space when `depth` is /// that width. /// /// `input_wraps` is the order of the original inputs, before leaf coarsening. -/// The buffer is still two runs, `[from_node, 2^depth)` then `[0, to_node)`, +/// @details The buffer is still two runs, `[from_node, 2^depth)` then `[0, to_node)`, /// even when packing makes `from_node <= to_node`. In that case the runs /// overlap on the shared leaf: the iterable's preclip consumes the start of /// the first copy and its length stops inside the second. Collapsing the /// overlap into one forward segment writes the wrong leaves. +/// @tparam IntegralT integral type +/// @param from_node the `from_node` +/// @param to_node the `to_node` +/// @param depth the tree depth +/// @param input_wraps the `input_wraps` +/// @return the returned `node_segments` +/// @throws std::length_error if `DPF leaf domain does not fit in size_t` template inline node_segments split_leaf_nodes(IntegralT from_node, IntegralT to_node, std::size_t depth, bool input_wraps = false) @@ -745,7 +805,13 @@ static std::size_t get_leafnodes_in_output_interval(InputT from, InputT to) static_cast(DpfKey::depth), wraps).total; } -/// Historical name used by the test suite. +/// @brief Historical name used by the test suite. +/// @tparam DpfKey DPF key type +/// @tparam InputT input domain type +/// @tparam IntegralT integral type +/// @param from the inclusive start of the range +/// @param to the `to` +/// @return Historical name used by the test suite template @@ -1092,6 +1158,7 @@ struct countr_zero return suffix_len; } }; +HEDLEY_PRAGMA(GCC diagnostic pop) // template <> // struct countl_zero @@ -1113,7 +1180,6 @@ struct countr_zero // return prefix_len; // } // }; -HEDLEY_PRAGMA(GCC diagnostic pop) template struct is_xor_wrapper : std::false_type {}; @@ -1121,9 +1187,10 @@ struct is_xor_wrapper : std::false_type {}; template static constexpr bool is_xor_wrapper_v = is_xor_wrapper::value; -/// Sub-byte DPF outputs whose lanes are packed inside a leaf node +/// @brief Sub-byte DPF outputs whose lanes are packed inside a leaf node /// (`dpf::bit` is 1, `dpf::twobit` is 2, `dpf::nyble` is 4). The leaf /// image is the buffer image: interval eval memcpy's the node. +/// @tparam T value type template struct is_packed_subbyte : std::false_type {}; @@ -1147,7 +1214,10 @@ constexpr auto data(T & bar) noexcept // NOLINT(runtime/references) return std::data(bar); } -/// Pointer overload. Constness of `bar` is the constness of `T`. +/// @brief Pointer overload. Constness of `bar` is the constness of `T`. +/// @tparam T value type +/// @param bar the `bar` +/// @return Pointer overload template HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE @@ -1269,6 +1339,39 @@ auto get_common_part_hash(const std::array & correction_wo return digest; } +template +auto get_common_part_hash(const std::array & correction_words, + const std::array & correction_advice, + const LeafTupleT & leaf_tuple, + const WildcardMaskT & wildcard_mask, + const ExtraT & extra) +{ + using zero_type = unsigned char; + static constexpr zero_type zero{}; + + SHA256 h; + digest_type digest; + + h.add(&correction_words, sizeof(correction_words)); + h.add(&correction_advice, sizeof(correction_advice)); + if constexpr (std::tuple_size_v > 0) + h.add(&extra, sizeof(extra)); + std::apply([&h, &wildcard_mask](auto const & ...leaf) + { + std::apply([&h, &leaf...](auto ...is_wildcard) + { + (h.add(!is_wildcard ? reinterpret_cast(&leaf.get()) : &zero, !is_wildcard ? sizeof(leaf.get()) : sizeof(zero)), ...); + }, wildcard_mask); + }, leaf_tuple); + + h.getHash(digest.data()); + return digest; +} + template auto get_common_part_hash(const DpfKey & dpf) { @@ -1284,6 +1387,7 @@ struct has_operators_plus_minus : public std::false_type { }; /// @brief True when `a + b` and `a - b` are valid expressions. /// Overload sets are accepted; taking the address of `operator+` /// is not, because that fails when `+` or `-` is overloaded. +/// @tparam OutputT output type template struct has_operators_plus_minus`, one output of `N` lanes added componentwise. +/// @details Each lane lives in the ring of `T` (`+` and `-` wrap the way `T` +/// wraps). A leaf adds every lane and does not carry from one lane +/// into the next. `T` is an ordinary output type: an integer, +/// `modint`, `fixedpoint`, `twobit`, `nyble`, or `xor_wrapper`. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license. + +#ifndef LIBDPF_INCLUDE_DPF_VEC_HPP__ +#define LIBDPF_INCLUDE_DPF_VEC_HPP__ + +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" + +#include "dpf/leaf_arithmetic.hpp" +#include "dpf/utils.hpp" + +namespace dpf +{ + +/// @brief `N` lanes of `T`, combined componentwise with no carry between lanes. +/// @tparam T lane type. Its `+`, `-`, and `*` are used per lane +/// @tparam N number of lanes +template +struct vec +{ + static_assert(N > 0, "dpf::vec needs at least one lane"); + static constexpr bool dpf_vec = true; + /// @brief Number of lanes. + static constexpr std::size_t lane_count = N; + /// @brief Type of one lane. + using lane_type = T; + + /// @brief Lane storage, index 0 in the least-significant lane. + std::array lanes{}; + + /// @brief Value-initialize every lane. + HEDLEY_NO_THROW + constexpr vec() noexcept = default; + + /// @brief Mutable lane `i`. + /// @param i the lane index + /// @return a reference to that lane + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr T & operator[](std::size_t i) noexcept { return lanes[i]; } + + /// @brief Lane `i`. + /// @param i the lane index + /// @return a reference to that lane + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr const T & operator[](std::size_t i) const noexcept { return lanes[i]; } + + /// @brief Negate every lane. + /// @return the negated vector + HEDLEY_ALWAYS_INLINE + constexpr vec operator-() const + noexcept(noexcept(static_cast(-std::declval()))) + { + vec out; + for (std::size_t i = 0; i < N; ++i) + out.lanes[i] = static_cast(-lanes[i]); + return out; + } + + /// @brief Add each lane, with no carry into the next lane. + /// @param rhs the right-hand vector + /// @return the lane-wise sum + HEDLEY_ALWAYS_INLINE + constexpr vec operator+(const vec & rhs) const + noexcept(noexcept(std::declval() + std::declval())) + { + vec out; + for (std::size_t i = 0; i < N; ++i) + out.lanes[i] = static_cast(lanes[i] + rhs.lanes[i]); + return out; + } + + /// @brief Subtract each lane, with no borrow from the next lane. + /// @param rhs the right-hand vector + /// @return the lane-wise difference + HEDLEY_ALWAYS_INLINE + constexpr vec operator-(const vec & rhs) const + noexcept(noexcept(std::declval() - std::declval())) + { + vec out; + for (std::size_t i = 0; i < N; ++i) + out.lanes[i] = static_cast(lanes[i] - rhs.lanes[i]); + return out; + } + + /// @brief Multiply each lane. + /// @param rhs the right-hand vector + /// @return the lane-wise product + HEDLEY_ALWAYS_INLINE + constexpr vec operator*(const vec & rhs) const + noexcept(noexcept(std::declval() * std::declval())) + { + vec out; + for (std::size_t i = 0; i < N; ++i) + out.lanes[i] = static_cast(lanes[i] * rhs.lanes[i]); + return out; + } + + /// @brief Lane-wise equality. + /// @param rhs the right-hand vector + /// @return `true` when every lane matches + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr bool operator==(const vec & rhs) const noexcept + { + for (std::size_t i = 0; i < N; ++i) + { + if (!(lanes[i] == rhs.lanes[i])) + return false; + } + return true; + } + + /// @brief Lane-wise inequality. + /// @param rhs the right-hand vector + /// @return `true` when some lane differs + HEDLEY_NO_THROW + HEDLEY_ALWAYS_INLINE + constexpr bool operator!=(const vec & rhs) const noexcept + { + return !(*this == rhs); + } +}; + +namespace utils +{ + +template +struct bitlength_of> + : std::integral_constant * N> {}; + +} // namespace utils + +namespace leaf_arithmetic +{ +namespace vec_detail +{ + +template +struct is_std_array : std::false_type {}; +template +struct is_std_array> : std::true_type {}; + +/// @brief Add or subtract every stored lane. A node wider than the vector is a +/// sequence of vectors, then a tail of leftover lanes. Bytes that do not +/// fill a lane are XOR-combined so a random pad still cancels. +/// @tparam T lane type +/// @tparam N number of lanes +/// @tparam Op lane operation +/// @param dst the destination +/// @param a the `a` +/// @param b the `b` +/// @param nbytes the number of bytes +/// @param op the `op` +template +HEDLEY_ALWAYS_INLINE +void apply_bytes(unsigned char * dst, const unsigned char * a, + const unsigned char * b, std::size_t nbytes, Op op) +{ + using V = dpf::vec; + std::size_t off = 0; + const std::size_t nvec = nbytes / sizeof(V); + for (std::size_t i = 0; i < nvec; ++i) + { + V va, vb; + std::memcpy(&va, a + off, sizeof(V)); + std::memcpy(&vb, b + off, sizeof(V)); + V vc = op(va, vb); + std::memcpy(dst + off, &vc, sizeof(V)); + off += sizeof(V); + } + while (off + sizeof(T) <= nbytes) + { + T va, vb; + std::memcpy(&va, a + off, sizeof(T)); + std::memcpy(&vb, b + off, sizeof(T)); + T vc = op(va, vb); + std::memcpy(dst + off, &vc, sizeof(T)); + off += sizeof(T); + } + for (; off < nbytes; ++off) + dst[off] = static_cast(a[off] ^ b[off]); +} + +template +HEDLEY_ALWAYS_INLINE +NodeT apply_node(const NodeT & a, const NodeT & b, Op op) +{ + alignas(NodeT) unsigned char ca[sizeof(NodeT)]; + alignas(NodeT) unsigned char cb[sizeof(NodeT)]; + alignas(NodeT) unsigned char cc[sizeof(NodeT)]; + std::memcpy(ca, &a, sizeof(NodeT)); + std::memcpy(cb, &b, sizeof(NodeT)); + apply_bytes(cc, ca, cb, sizeof(NodeT), op); + NodeT out; + std::memcpy(&out, cc, sizeof(NodeT)); + return out; +} + +} // namespace vec_detail + +template +struct add_t, NodeT, + std::enable_if_t + && !vec_detail::is_std_array::value>> +{ + HEDLEY_ALWAYS_INLINE + auto operator()(const NodeT & a, const NodeT & b) const + { + return vec_detail::apply_node(a, b, + [](const auto & x, const auto & y) { return x + y; }); + } +}; + +template +struct add_t, std::array> +{ + auto operator()(const std::array & a, const std::array & b) const + { + std::array c{}; + auto * dst = reinterpret_cast(c.data()); + vec_detail::apply_bytes(dst, + reinterpret_cast(a.data()), + reinterpret_cast(b.data()), + sizeof(c), + [](const auto & x, const auto & y) { return x + y; }); + return c; + } +}; + +template +struct subtract_t, NodeT, + std::enable_if_t + && !vec_detail::is_std_array::value>> +{ + HEDLEY_ALWAYS_INLINE + auto operator()(const NodeT & a, const NodeT & b) const + { + return vec_detail::apply_node(a, b, + [](const auto & x, const auto & y) { return x - y; }); + } +}; + +template +struct subtract_t, std::array> +{ + auto operator()(const std::array & a, const std::array & b) const + { + std::array c{}; + auto * dst = reinterpret_cast(c.data()); + vec_detail::apply_bytes(dst, + reinterpret_cast(a.data()), + reinterpret_cast(b.data()), + sizeof(c), + [](const auto & x, const auto & y) { return x - y; }); + return c; + } +}; + +} // namespace leaf_arithmetic + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_VEC_HPP__ diff --git a/include/dpf/verifiable.hpp b/include/dpf/verifiable.hpp new file mode 100644 index 0000000..5366467 --- /dev/null +++ b/include/dpf/verifiable.hpp @@ -0,0 +1,366 @@ +/// @file dpf/verifiable.hpp +/// @brief Verifiable evaluation tokens and extractable-key helpers. +/// @details VDPF proof fold follows de Castro–Polychroniadou (hash-based +/// correction seeds, 2λ-bit tokens, equality Verify). Extractable +/// checks are public-part equality, ROM-style leaf XOF, and an +/// \(\mathbb{F}_{2^{61}-1}\) weight-1 subset sketch. Phantom tags +/// `dpf::verifiable` / `dpf::extractable` live in placement.hpp. +/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) +/// @license Released under a GNU General Public v2.0 (GPLv2) license; +/// see [LICENSE.md](@ref license) for details. + +#ifndef LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__ +#define LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include "hedley/hedley.h" +#include "simde/simde/x86/avx2.h" +#include "portable-snippets/exact-int/exact-int.h" + +#include "dpf/placement.hpp" +#include "dpf/prg_aes.hpp" +#include "dpf/fp61.hpp" +#include "dpf/xor_wrapper.hpp" +#include "dpf/twiddle.hpp" +#include "dpf/utils.hpp" + +namespace dpf +{ + +/// 4λ = 64-byte correction seed (four AES blocks). +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") +using cs_block = std::array; +/// 2λ = 32-byte proof token (two AES blocks). +using proof_token = std::array; +HEDLEY_PRAGMA(GCC diagnostic pop) + +namespace detail +{ +namespace vdpf +{ + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +simde__m128i mmo(simde__m128i seed, psnip_uint32_t pos) noexcept +{ + return prg::aes128::eval(seed, pos); +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +cs_block hash_level_seed(std::size_t level, simde__m128i seed) noexcept +{ + const simde__m128i tagged = simde_mm_xor_si128(seed, + simde_mm_set_epi64x(static_cast(0x56), + static_cast(level))); + return cs_block{ + mmo(tagged, 0), + mmo(tagged, 1), + mmo(tagged, 2), + mmo(tagged, 3)}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +cs_block hash_node(std::size_t level, psnip_uint64_t x_bits, + simde__m128i seed) noexcept +{ + const simde__m128i tagged = simde_mm_xor_si128(seed, + simde_mm_set_epi64x(static_cast(0x5600 | (level & 0xff)), + static_cast(x_bits))); + return cs_block{ + mmo(tagged, 0), + mmo(tagged, 1), + mmo(tagged, 2), + mmo(tagged, 3)}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +cs_block make_cs(std::size_t level, psnip_uint64_t prefix_bits, + simde__m128i s0, simde__m128i s1) noexcept +{ + const auto h0v = hash_node(level, prefix_bits, s0); + const auto h1v = hash_node(level, prefix_bits, s1); + return cs_block{ + simde_mm_xor_si128(h0v[0], h1v[0]), + simde_mm_xor_si128(h0v[1], h1v[1]), + simde_mm_xor_si128(h0v[2], h1v[2]), + simde_mm_xor_si128(h0v[3], h1v[3])}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +cs_block correct(cs_block pi_tilde, const cs_block & cs, + bool t) noexcept +{ + if (!t) + return pi_tilde; + return cs_block{ + simde_mm_xor_si128(pi_tilde[0], cs[0]), + simde_mm_xor_si128(pi_tilde[1], cs[1]), + simde_mm_xor_si128(pi_tilde[2], cs[2]), + simde_mm_xor_si128(pi_tilde[3], cs[3])}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +proof_token h0(const cs_block & in) noexcept +{ + const simde__m128i a = simde_mm_xor_si128(in[0], in[2]); + const simde__m128i b = simde_mm_xor_si128(in[1], in[3]); + return proof_token{mmo(a, 0x48), mmo(b, 0x48)}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +proof_token xor_proof(proof_token a, proof_token b) noexcept +{ + return proof_token{ + simde_mm_xor_si128(a[0], b[0]), + simde_mm_xor_si128(a[1], b[1])}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +proof_token zero_proof() noexcept +{ + return proof_token{simde_mm_setzero_si128(), simde_mm_setzero_si128()}; +} + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +void fold_node(proof_token & pi, std::size_t level, + psnip_uint64_t x_bits, simde__m128i seed, const cs_block & cs) noexcept +{ + const bool t = static_cast(dpf::get_lo_bit(seed)); + const cs_block tilde = hash_node(level, x_bits, seed); + const cs_block corrected = correct(tilde, cs, t); + cs_block mixed{ + simde_mm_xor_si128(pi[0], corrected[0]), + simde_mm_xor_si128(pi[1], corrected[1]), + corrected[2], + corrected[3]}; + pi = xor_proof(pi, h0(mixed)); +} + +HEDLEY_NO_THROW +inline void leaf_xof(simde__m128i seed, simde__m128i * HEDLEY_RESTRICT out, + psnip_uint32_t count, psnip_uint32_t pos = 0) noexcept +{ + const simde__m128i tagged = simde_mm_xor_si128(seed, + simde_mm_set_epi64x(0x45, 0)); + for (psnip_uint32_t i = 0; i < count; ++i) + out[i] = mmo(tagged, pos + i); +} + +/// Drop-in exterior PRG for leaf stretch under `dpf::extractable`. +template +struct extractable_leaf_prg +{ + using block_type = typename BasePRG::block_type; + HEDLEY_NO_THROW + static void eval(block_type seed, block_type * HEDLEY_RESTRICT out, + psnip_uint32_t count, psnip_uint32_t pos = 0) noexcept + { + leaf_xof(seed, out, count, pos); + } +}; + +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +bool proof_equal(const proof_token & a, const proof_token & b) noexcept +{ + return std::memcmp(&a, &b, sizeof(proof_token)) == 0; +} + +template +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +void init_proof(proof_token & pi, const KeyT & /*key*/) noexcept +{ + // Running proof starts at 0; each fold mixes in corrected leaf digests. + pi = zero_proof(); +} + +} // namespace vdpf +} // namespace detail + +/// @brief A proof token the caller owns, passed into evaluation. +struct prove_ref +{ + /// @brief The token updated by the evaluation. + proof_token & token; + /// @brief Bind `t`. + /// @param t the token to update + HEDLEY_NO_THROW + explicit prove_ref(proof_token & t) noexcept : token{t} { } +}; + +/// @brief Bind `t` as the proof accumulator for one evaluation. +/// @param t the token to update +/// @return a `prove_ref` bound to `t` +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +prove_ref prove(proof_token & t) noexcept +{ + return prove_ref{t}; +} + +/// @brief Whether two proof tokens are identical. +/// @param a the first token +/// @param b the second token +/// @return `true` when every byte matches +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_PURE +bool verify(const proof_token & a, const proof_token & b) noexcept +{ + return detail::vdpf::proof_equal(a, b); +} + +/// @brief Fold two batches of proof tokens and compare them. +/// @tparam Range0 range of `proof_token` for party 0 +/// @tparam Range1 range of `proof_token` for party 1 +/// @param left party 0 tokens, in evaluation order +/// @param right party 1 tokens, in the same order +/// @return `false` when the ranges differ in length or the folded tokens differ +template +bool verify_batch(Range0 && left, Range1 && right) +{ + proof_token a = detail::vdpf::zero_proof(); + proof_token b = detail::vdpf::zero_proof(); + auto it0 = std::begin(left); + auto it1 = std::begin(right); + const auto end0 = std::end(left); + const auto end1 = std::end(right); + for (; it0 != end0 && it1 != end1; ++it0, ++it1) + { + a = detail::vdpf::xor_proof(a, *it0); + b = detail::vdpf::xor_proof(b, *it1); + a[0] = detail::vdpf::mmo(a[0], 1); + b[0] = detail::vdpf::mmo(b[0], 1); + } + if (it0 != end0 || it1 != end1) + return false; + return verify(a, b); +} + +/// @brief Whether two keys publish the same correction words, advice, and hash. +/// @tparam KeyT0 key type of party 0 +/// @tparam KeyT1 key type of party 1 +/// @param k0 party 0 key +/// @param k1 party 1 key +/// @return `false` when a public field differs +template +bool same_public_part(const KeyT0 & k0, const KeyT1 & k1) +{ + static_assert(KeyT0::is_verifiable == KeyT1::is_verifiable, + "same_public_part: mismatched verifiable flags"); + if (std::memcmp(k0.correction_words().data(), k1.correction_words().data(), + sizeof(typename KeyT0::correction_words_array)) != 0) + return false; + if (std::memcmp(k0.correction_advice().data(), k1.correction_advice().data(), + sizeof(typename KeyT0::correction_advice_array)) != 0) + return false; + if constexpr (KeyT0::is_verifiable) + { + if (std::memcmp(k0.correction_seeds().data(), + k1.correction_seeds().data(), + sizeof(typename KeyT0::correction_seeds_array)) != 0) + return false; + } + return std::memcmp(&k0.common_part_hash(), &k1.common_part_hash(), + sizeof(digest_type)) == 0; +} + +struct sketch_share +{ + fp61 z1{}; + fp61 z2{}; + fp61 z3{}; +}; + +/// @brief Weight-1 subset sketch of payloads `ys` against challenges `rs`. +/// @tparam YRange range of integers convertible to `fp61` +/// @tparam RRange range of challenges, one per payload +/// @param ys the payloads +/// @param rs the challenges +/// @return the three folded moments. A short range stops at the shorter end +template +sketch_share sketch_fold(YRange && ys, RRange && rs) +{ + sketch_share out{}; + auto iy = std::begin(ys); + auto ir = std::begin(rs); + const auto ey = std::end(ys); + const auto er = std::end(rs); + for (; iy != ey && ir != er; ++iy, ++ir) + { + const fp61 y{*iy}; + const fp61 r{*ir}; + const fp61 r2 = r * r; + out.z1 = out.z1 + y; + out.z2 = out.z2 + y * r; + out.z3 = out.z3 + y * r2; + } + return out; +} + +/// @brief Whether `s0 - s1` is a weight-1 subset sketch. +/// @param s0 party 0's folded sketch +/// @param s1 party 1's folded sketch +/// @return `true` when `z2² = z1 · z3` after the shares are opened +HEDLEY_NO_THROW +HEDLEY_ALWAYS_INLINE +HEDLEY_CONST +bool sketch_verify(sketch_share s0, sketch_share s1) noexcept +{ + const fp61 z1 = s0.z1 - s1.z1; + const fp61 z2 = s0.z2 - s1.z2; + const fp61 z3 = s0.z3 - s1.z3; + return (z2 * z2) == (z1 * z3); +} + +template +struct has_dpf_fp61 : std::false_type +{ }; +template +struct has_dpf_fp61::dpf_fp61)>> + : std::bool_constant::dpf_fp61> +{ }; + +template +struct extractable_codomain_ok + : std::bool_constant< + (utils::bitlength_of_v> >= 128) + || has_dpf_fp61::value> +{ }; +template +struct extractable_codomain_ok, void> + : extractable_codomain_ok +{ }; +template +inline constexpr bool extractable_codomain_ok_v = + extractable_codomain_ok>::value; + +} // namespace dpf + +#endif // LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__ diff --git a/include/dpf/wildcard.hpp b/include/dpf/wildcard.hpp index 857c0c7..3ffe9b9 100644 --- a/include/dpf/wildcard.hpp +++ b/include/dpf/wildcard.hpp @@ -82,6 +82,7 @@ template static constexpr wildcard_value wildcard{}; /// @details A trait class that provides the member constant `value` which is /// equal to `true`, if `T` is a specialization of the `wildcard_value` /// template and `false` otherwise. +/// @tparam T value type /// @see dpf::is_wildcard_v template struct is_wildcard : std::false_type { }; template struct is_wildcard> : std::true_type { }; @@ -194,6 +195,7 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") static constexpr auto m256i = wildcard; using m256d_t = wildcard_value; static constexpr auto m256d = wildcard; +HEDLEY_PRAGMA(GCC diagnostic pop) // using m512_t = wildcard_value; // static constexpr auto m512 = wildcard; @@ -201,7 +203,6 @@ HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") // static constexpr auto m512i = wildcard; // using m512d_t = wildcard_value; // static constexpr auto m512d = wildcard; -HEDLEY_PRAGMA(GCC diagnostic pop) using ieee_float_t = wildcard_value; /// @brief Placeholder for a `float` whose leaf group is bitwise XOR @@ -257,12 +258,15 @@ namespace utils { /// @brief specializes `dpf::utils::bitlength_of` for `dpf::wildcard_value` +/// @tparam T value type template struct bitlength_of> : public bitlength_of { }; /// @brief specializes `dpf::utils::bitlength_of_output` for `dpf::wildcard_value` +/// @tparam T value type +/// @tparam NodeT GGM node type template struct bitlength_of_output, NodeT> diff --git a/include/dpf/xor_wrapper.hpp b/include/dpf/xor_wrapper.hpp index 5bd4299..bdcff96 100644 --- a/include/dpf/xor_wrapper.hpp +++ b/include/dpf/xor_wrapper.hpp @@ -57,6 +57,7 @@ struct xor_wrapper constexpr xor_wrapper(xor_wrapper &&) noexcept = default; /// @brief Value c'tor + /// @param v the `v` // cppcheck-suppress noExplicitConstructor HEDLEY_NO_THROW constexpr xor_wrapper(value_type v) noexcept : value{v} { } // NOLINT(runtime/explicit) @@ -896,6 +897,7 @@ template struct is_xor_wrapper> : std::true_type {}; /// @brief specializes `dpf::utils::bitlength_of` for `xor_wrapper` +/// @tparam T value type template struct bitlength_of> : public bitlength_of @@ -953,23 +955,27 @@ namespace std /// @{ /// @details specializes `std::numeric_limits` for `xor_wrapper` +/// @tparam T value type template class numeric_limits> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for `xor_wrapper const` +/// @tparam T value type template class numeric_limits const> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `xor_wrapper volatile` +/// @brief `xor_wrapper volatile` +/// @tparam T value type template class numeric_limits volatile> : public numeric_limits> {}; /// @details specializes `std::numeric_limits` for -/// `xor_wrapper const volatile` +/// @brief `xor_wrapper const volatile` +/// @tparam T value type template class numeric_limits const volatile> : public numeric_limits> {}; diff --git a/include/dpf/zip_iterable.hpp b/include/dpf/zip_iterable.hpp index 139aa42..e6b7ad4 100644 --- a/include/dpf/zip_iterable.hpp +++ b/include/dpf/zip_iterable.hpp @@ -1,6 +1,6 @@ /// @file dpf/zip_iterable.hpp /// @brief defines the `dpf::zip_itrable` class and associated helpers -/// @details +/// @details Walks several iterables in lockstep and yields one tuple per step. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; diff --git a/include/grotto/constant_lut.hpp b/include/grotto/constant_lut.hpp index eb896e6..7660a9c 100644 --- a/include/grotto/constant_lut.hpp +++ b/include/grotto/constant_lut.hpp @@ -25,7 +25,7 @@ namespace grotto { -/// Appendix D gadgets whose polynomial degree is 0 and whose max error is 0. +/// @brief Appendix D gadgets whose polynomial degree is 0 and whose max error is 0. enum class exact_constant { signum, @@ -36,7 +36,7 @@ enum class exact_constant zero, nonzero, ilogb, - /// `ceil(log2(|x|))`. Exact powers of two agree with `ilogb`; every other + /// @brief `ceil(log2(|x|))`. Exact powers of two agree with `ilogb`; every other /// positive magnitude is one larger. Zero uses the same `-64` sentinel. ceil_ilogb, ilog10, @@ -51,20 +51,21 @@ struct constant_lut using raw_type = Raw; - /// Signed piece starts. `bounds.front()` is `numeric_limits::min()`, + /// @brief Signed piece starts. `bounds.front()` is `numeric_limits::min()`, /// and the starts are strictly increasing. std::vector bounds; - /// `values[i]` is the function on `[bounds[i], next)`, where `next` is + /// @brief `values[i]` is the function on `[bounds[i], next)`, where `next` is /// `bounds[i + 1]` or one past `numeric_limits::max()` for the last piece. std::vector values; HEDLEY_NO_THROW std::size_t linear_parts() const noexcept { return values.size(); } - /// Pieces after joining the first and last when they carry the same value. - /// Those two meet across the signed wrap, which is how the paper counts + /// @brief Pieces after joining the first and last when they carry the same value. + /// @details Those two meet across the signed wrap, which is how the paper counts /// parts for `zero` and `nonzero` (2, not 3). + /// @return Pieces after joining the first and last when they carry the same value HEDLEY_NO_THROW std::size_t wrapped_parts() const noexcept { @@ -115,7 +116,7 @@ constexpr bool shift_fits(u128 value, unsigned shift) noexcept return shift < 128 && value <= (~u128{0} >> shift); } -/// 10^0 .. 10^19. Every ilog10 projection reads this one table. +/// @brief 10^0 .. 10^19. Every ilog10 projection reads this one table. inline constexpr std::uint64_t pow10[] = { 1ull, 10ull, @@ -159,7 +160,10 @@ inline bool magnitude_ge_pow10(u128 mag, int k, unsigned fractional_bits) noexce return mag * scale >= (u128{1} << fractional_bits); } -/// Smallest positive magnitude whose base-10 log is at least `k`. +/// @brief Smallest positive magnitude whose base-10 log is at least `k`. +/// @param k the `k` +/// @param fractional_bits the number of fractional bits +/// @return Smallest positive magnitude whose base-10 log is at least `k` HEDLEY_NO_THROW inline u128 first_magnitude_at_least_pow10(int k, unsigned fractional_bits) noexcept { @@ -315,8 +319,8 @@ void push_pow2_cuts(std::vector & cuts) detail::push_both_signs(detail::u128{1} << k, cuts); } -/// floor(log2(|raw|)) - F, with -64 on the |x| <= 2^{-64} class (including 0). -/// One exponent program; fractional precision only shifts the stored exponent. +/// @brief floor(log2(|raw|)) - F, with -64 on the |x| <= 2^{-64} class (including 0). +/// @details One exponent program; fractional precision only shifts the stored exponent. template <> struct exact_lut { @@ -347,7 +351,7 @@ struct exact_lut } }; -/// ceil(log2(|x|)). Same powers-of-two cuts as `ilogb`; exact powers keep the +/// @brief ceil(log2(|x|)). Same powers-of-two cuts as `ilogb`; exact powers keep the /// floor exponent and every other magnitude steps up by one. template <> struct exact_lut @@ -387,7 +391,7 @@ struct exact_lut } }; -/// floor(log10(|x|)), with -19 on |x| <= 10^{-19}. Thresholds come from `pow10`. +/// @brief floor(log10(|x|)), with -19 on |x| <= 10^{-19}. Thresholds come from `pow10`. template <> struct exact_lut { @@ -424,8 +428,8 @@ struct exact_lut } }; -/// 64-bit leading-zero count of trunc(x). Negatives are 0; a zero integer part is 64. -/// Exponent k of the integer part maps to 63-k after a shift of `fractional_bits`. +/// @brief 64-bit leading-zero count of trunc(x). Negatives are 0; a zero integer part is 64. +/// @details Exponent k of the integer part maps to 63-k after a shift of `fractional_bits`. template <> struct exact_lut { @@ -461,8 +465,8 @@ struct exact_lut } }; -/// 64-bit redundant sign bits of trunc(x) toward zero. -/// Positive q uses 62-floor(log2(q)); negative q uses 62-floor(log2(q-1)). +/// @brief 64-bit redundant sign bits of trunc(x) toward zero. +/// @details Positive q uses 62-floor(log2(q)); negative q uses 62-floor(log2(q-1)). template <> struct exact_lut { @@ -564,7 +568,7 @@ constant_lut make_exact_constant_lut(exact_constant which, unsigned fractio throw std::invalid_argument("unknown exact constant"); } -/// Comparison against a public threshold. Two pieces; the cut sits on `bound` +/// @brief Comparison against a public threshold. Two pieces; the cut sits on `bound` /// (`lt` / `geq`) or just after it (`leq` / `gt`). enum class threshold_cmp { @@ -600,7 +604,12 @@ constant_lut make_threshold_lut(Raw bound, threshold_cmp kind) [=](Raw raw) { return evaluate_threshold(raw, bound, kind); }); } -/// `1` on the inclusive clip window `[low, high]`, `0` outside it. +/// @brief `1` on the inclusive clip window `[low, high]`, `0` outside it. +/// @tparam Raw underlying representation +/// @param low the lower endpoint +/// @param high the upper endpoint +/// @return `1` on the inclusive clip window `[low, high]`, `0` outside it +/// @throws std::invalid_argument if `low > high` template constant_lut make_interval_lut(Raw low, Raw high) { @@ -614,13 +623,20 @@ constant_lut make_interval_lut(Raw low, Raw high) [=](Raw raw) { return raw >= low && raw <= high ? std::int64_t{1} : std::int64_t{0}; }); } -/// `floor(min(max(raw, low), high) / modulus)`, division toward -infinity. +/// @brief `floor(min(max(raw, low), high) / modulus)`, division toward -infinity. /// /// `modulus`, `low`, and `high` are in the same raw units as the domain, so /// one program covers every fractional precision: a mathematical step `M` /// with `F` fractional bits is the raw modulus `M << F`. The paper's /// `quot(M, T1, T2)` is this function. Piece count is about `(high-low)/modulus`; /// the build rejects windows that would need more than 2^16 pieces. +/// @tparam Raw underlying representation +/// @param raw the underlying integer +/// @param modulus the public modulus +/// @param low the lower endpoint +/// @param high the upper endpoint +/// @return `floor(min(max(raw, low), high) / modulus)`, division toward -infinity +/// @throws std::invalid_argument if `modulus must be positive` template std::int64_t evaluate_clipped_quotient(Raw raw, Raw modulus, Raw low, Raw high) { diff --git a/include/grotto/dyadic_lut.hpp b/include/grotto/dyadic_lut.hpp index ffa6906..5027729 100644 --- a/include/grotto/dyadic_lut.hpp +++ b/include/grotto/dyadic_lut.hpp @@ -26,12 +26,12 @@ namespace grotto { -/// Sentinel raw value for `ilogb(0)` and `ilog10(0)`. +/// @brief Sentinel raw value for `ilogb(0)` and `ilog10(0)`. inline constexpr std::int64_t ilog_of_zero = std::numeric_limits::min(); -/// `make_msb_lut(i)` allows `i` in `[0, msb_bit_limit)`. -/// Bit 0 is two intervals; bit 7 is 256. +/// @brief `make_msb_lut(i)` allows `i` in `[0, msb_bit_limit)`. +/// @details Bit 0 is two intervals; bit 7 is 256. inline constexpr unsigned msb_bit_limit = 8; namespace detail @@ -162,7 +162,11 @@ inline u128 pow10_u128(int exponent) return p; } -/// `mag / 2^k >= 10^e`. +/// @brief `mag / 2^k >= 10^e`. +/// @param mag the magnitude +/// @param fractional_bits the number of fractional bits +/// @param exponent the exponent +/// @return `mag / 2^k >= 10^e` inline bool magnitude_ge_pow10(u128 mag, unsigned fractional_bits, int exponent) { if (mag == 0) @@ -393,9 +397,14 @@ easy_lut make_ilog10_lut(unsigned fractional_bits = 0) }); } -/// Bit `index` counting down from the most significant bit of `Raw`. -/// Index 0 is the sign bit. Larger indexes are refused: the bit is constant +/// @brief Bit `index` counting down from the most significant bit of `Raw`. +/// @details Index 0 is the sign bit. Larger indexes are refused: the bit is constant /// on `2^{index+1}` intervals. +/// @tparam Raw underlying representation +/// @param index the index +/// @param fractional_bits the number of fractional bits +/// @return Bit `index` counting down from the most significant bit of `Raw` +/// @throws std::invalid_argument if `only the most significant bits are piecewise-cheap` template HEDLEY_WARN_UNUSED_RESULT easy_lut make_msb_lut(unsigned index, unsigned fractional_bits = 0) diff --git a/include/grotto/easy_lut.hpp b/include/grotto/easy_lut.hpp index ce4cce9..7491158 100644 --- a/include/grotto/easy_lut.hpp +++ b/include/grotto/easy_lut.hpp @@ -23,7 +23,8 @@ namespace grotto { -/// Piece `y_raw = round((c0 + c1·raw + c2·raw²) / den)`. +/// @brief Piece `y_raw = round((c0 + c1·raw + c2·raw²) / den)`. +/// @tparam Raw underlying representation template struct easy_lut { @@ -176,8 +177,11 @@ easy_lut make_relu_lut(unsigned fractional_bits = 0) }); } -/// Negative side is `x / 2^shift`, rounded to nearest, ties away from zero. -/// `shift == 0` is the identity. The slope does not depend on fractional width. +/// @brief Negative side is `x / 2^shift`, rounded to nearest, ties away from zero. +/// @details `shift == 0` is the identity. The slope does not depend on fractional width. +/// @tparam Raw underlying representation +/// @param shift the bit shift +/// @return Negative side is `x / 2^shift`, rounded to nearest, ties away from zero template easy_lut make_leaky_relu_lut(unsigned shift) { @@ -248,7 +252,10 @@ easy_lut make_hardtanh_lut(unsigned fractional_bits) return make_clip_lut(fractional_bits, -1, 1); } -/// `0` on `[-1, 1]`, `x - 1` above, `x + 1` below. +/// @brief `0` on `[-1, 1]`, `x - 1` above, `x + 1` below. +/// @tparam Raw underlying representation +/// @param fractional_bits the number of fractional bits +/// @return `0` on `[-1, 1]`, `x - 1` above, `x + 1` below template easy_lut make_softshrink_lut(unsigned fractional_bits) { @@ -270,7 +277,10 @@ easy_lut make_softshrink_lut(unsigned fractional_bits) }); } -/// `0` on `[-1, 1]`, identity outside. Lambda is the integer 1. +/// @brief `0` on `[-1, 1]`, identity outside. Lambda is the integer 1. +/// @tparam Raw underlying representation +/// @param fractional_bits the number of fractional bits +/// @return `0` on `[-1, 1]`, identity outside template easy_lut make_hardshrink_lut(unsigned fractional_bits) { @@ -290,7 +300,11 @@ easy_lut make_hardshrink_lut(unsigned fractional_bits) }); } -/// `0` left of `-3`, `1` right of `3`, `(x + 3) / 6` between, rounded. +/// @brief `0` left of `-3`, `1` right of `3`, `(x + 3) / 6` between, rounded. +/// @tparam Raw underlying representation +/// @param fractional_bits the number of fractional bits +/// @return `0` left of `-3`, `1` right of `3`, `(x + 3) / 6` between, rounded +/// @throws std::invalid_argument if `fractional width does not fit` template easy_lut make_hardsigmoid_lut(unsigned fractional_bits) { @@ -316,7 +330,11 @@ easy_lut make_hardsigmoid_lut(unsigned fractional_bits) }); } -/// `0` left of `-3`, `x` right of `3`, `x(x + 3) / 6` between, rounded. +/// @brief `0` left of `-3`, `x` right of `3`, `x(x + 3) / 6` between, rounded. +/// @tparam Raw underlying representation +/// @param fractional_bits the number of fractional bits +/// @return `0` left of `-3`, `x` right of `3`, `x(x + 3) / 6` between, rounded +/// @throws std::invalid_argument if `fractional width does not fit the denominator` template easy_lut make_hardswish_lut(unsigned fractional_bits) { diff --git a/include/grotto/fixedpoint.hpp b/include/grotto/fixedpoint.hpp index 4becb00..0058be0 100644 --- a/include/grotto/fixedpoint.hpp +++ b/include/grotto/fixedpoint.hpp @@ -1,6 +1,5 @@ /// @file grotto/fixedpoint.hpp -/// @brief -/// @details +/// @brief Fixed-point values stored in an integer backend. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; @@ -40,6 +39,8 @@ namespace detail /// @brief Integer value of an already-rounded finite double, as a 256-bit word. /// Values that do not fit saturate to all-ones. +/// @param rounded the `rounded` +/// @return Integer value of an already-rounded finite double, as a 256-bit word HEDLEY_NO_THROW inline uint256_t uint256_from_rounded_double(double rounded) noexcept { @@ -98,7 +99,11 @@ struct is_static_castable(std::declval()))>> : std::true_type {}; -/// Low `bits` of `wide`, saturated to all-ones when `wide` does not fit. +/// @brief Low `bits` of `wide`, saturated to all-ones when `wide` does not fit. +/// @tparam Raw underlying representation +/// @tparam Bits bits +/// @param wide the `wide` +/// @return Low `bits` of `wide`, saturated to all-ones when `wide` does not fit template HEDLEY_NO_THROW Raw saturate_low_bits(uint256_t wide) noexcept @@ -202,8 +207,13 @@ inline IntegralType rounded_double_to_integral(double rounded) noexcept } } -/// Shift an integer into fixed-point raw form: `value * 2^FractionalBits`, +/// @brief Shift an integer into fixed-point raw form: `value * 2^FractionalBits`, /// wrapping in the backend's two's-complement encoding. One shift; no `double`. +/// @tparam IntegralType underlying integral type +/// @tparam FractionalBits number of fractional bits +/// @tparam T value type +/// @param integer_value the `integer_value` +/// @return the returned `IntegralType` template @@ -227,8 +237,11 @@ inline constexpr bool is_signed_rep_v = std::is_signed_v || std::is_same_v; -/// Two's-complement negate via the unsigned width. Defined for the +/// @brief Two's-complement negate via the unsigned width. Defined for the /// most-negative value (wraps); signed `-x` would be UB there. +/// @tparam IntegralType underlying integral type +/// @param x the `x` +/// @return Two's-complement negate via the unsigned width template HEDLEY_ALWAYS_INLINE HEDLEY_CONST @@ -253,8 +266,12 @@ constexpr IntegralType raw_abs(IntegralType x) noexcept return x; } -/// Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` / +/// @brief Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` / /// `std::fmod`). Zero divisor → 0; this type has no NaN. +/// @tparam IntegralType underlying integral type +/// @param a the `a` +/// @param b the `b` +/// @return Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` / `std::fmod`) template HEDLEY_ALWAYS_INLINE HEDLEY_CONST @@ -276,7 +293,7 @@ HEDLEY_NO_THROW auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept; /// @tparam FractionalBits Number of fractional bits used in the fixed-point -/// representation. +/// @brief representation. /// @tparam IntegralType The underlying integral type used for the fixed-point /// representation. template current rounding mode for the least-significant bit. + /// @param desired the `desired` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint(double desired) noexcept // NOLINT (implicit c'tor) @@ -333,6 +353,9 @@ public: /// @details `fixedpoint(3)` is the mathematical value 3 (raw encoding /// `3 << fractional_bits`), not a raw word. One shift; no `double`. /// Use `from_raw` for a bit-exact encoding. + /// @tparam T value type + /// @tparam T value type + /// @param integer_value the `integer_value` template @@ -345,6 +368,8 @@ public: { } /// @brief Bit-exact construction from the backend integer encoding. + /// @param raw the underlying integer + /// @return Bit-exact construction from the backend integer encoding HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW static constexpr fixedpoint from_raw(integral_type raw) noexcept @@ -354,24 +379,30 @@ public: /// @} - /// @name Assignment operators - /// @brief Assign a new value to a fixed-point number - /// {@ +/// @name Assignment operators +/// @brief Assign a new value to a fixed-point number +/// @{ /// @brief Copy assignment /// @details Assigns the fixed-point with a copy of `other` + /// @param other the value to compare or copy + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(const fixedpoint & other) noexcept = default; /// @brief Move assignment /// @details Assigns the fixed-point with a copy of `other` using move semantics. + /// @param other the value to compare or copy + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(fixedpoint && other) noexcept = default; /// @brief Value assignment /// @details Assigns the fixed-point with a value determined by `desired`, using the current rounding mode for the least-significant bit.. + /// @param desired the `desired` + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator=(const double & desired) noexcept @@ -386,6 +417,7 @@ public: ~fixedpoint() = default; /// @brief Cast to `double` + /// @return Cast to `double` HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE @@ -402,8 +434,11 @@ public: return static_cast(this->integral_representation() & mask); } - /// Bit test against another encoding (DPF writes `mask & x` with both + /// @brief Bit test against another encoding (DPF writes `mask & x` with both /// sides the input type when `msb_mask` is a `fixedpoint`). + /// @param mask the bit mask + /// @return Bit test against another encoding (DPF writes `mask & x` with both sides the input + /// type when `msb_mask` is a `fixedpoint`) HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -412,7 +447,8 @@ public: return static_cast(value & mask.value); } - /// Bitwise complement of the encoding. `std::bit_not` uses this. + /// @brief Bitwise complement of the encoding. `std::bit_not` uses this. + /// @return Bitwise complement of the encoding HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -423,7 +459,8 @@ public: ~static_cast(value))); } - /// Next / previous representable encoding (one ULP). + /// @brief Next / previous representable encoding (one ULP). + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator++() noexcept @@ -458,7 +495,7 @@ public: return tmp; } - /// Logical shift of the encoding. DPF walks `msb_mask` with `>>`; a + /// @brief Logical shift of the encoding. DPF walks `msb_mask` with `>>`; a /// signed arithmetic shift would sign-extend the MSB and break that. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -497,6 +534,7 @@ public: /// @brief Access underlying integral representation /// @details If the represented fixed-point number is `x`, then this /// function returns an `integral_type` whose value is `x*2**fractional_bits`. + /// @return Access underlying integral representation HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -506,6 +544,7 @@ public: } /// @brief Unary negation operator + /// @return Unary negation operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -516,6 +555,8 @@ public: /// @brief Binary addition operator /// @details Computes the sum of two fixed-point numbers + /// @param rhs the right-hand operand + /// @return Binary addition operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -525,6 +566,8 @@ public: } /// @brief Binary addition assignment operator + /// @param rhs the right-hand operand + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator+=(fixedpoint rhs) noexcept @@ -534,6 +577,8 @@ public: } /// @brief Binary subtraction operator + /// @param rhs the right-hand operand + /// @return Binary subtraction operator HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE @@ -543,6 +588,8 @@ public: } /// @brief Binary addition assignment operator + /// @param rhs the right-hand operand + /// @return `*this` HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr fixedpoint & operator-=(fixedpoint rhs) noexcept @@ -552,6 +599,9 @@ public: } /// @brief Binary multiplication operator + /// @tparam FractionalBits1 fractional bits1 + /// @param rhs the right-hand operand + /// @return Binary multiplication operator template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW @@ -701,6 +751,8 @@ public: // struct make_fixed_from_integral_type_tag {}; /// @brief Determine if a floating-point is within range + /// @param d the `d` + /// @return Determine if a floating-point is within range HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW static constexpr bool is_in_range(double d) noexcept @@ -734,6 +786,12 @@ public: /// @brief Bit test with the mask on the left. DPF key generation and /// evaluation write `mask & x`. +/// @tparam FractionalBits number of fractional bits +/// @tparam IntegralType underlying integral type +/// @tparam Mask mask +/// @param mask the bit mask +/// @param x the `x` +/// @return Bit test with the mask on the left template @@ -797,7 +855,11 @@ static constexpr auto make_fixed(double d) } /// @brief Creates a fixed-point number from a double with bounds checking. -/// @throws std::range_error If the input double is outside the representable +/// @tparam FractionalBits number of fractional bits +/// @tparam IntegralType underlying integral type +/// @param d the `d` +/// @return Creates a fixed-point number from a double with bounds checking +/// @throws std::range_error if the input double is outside the representable /// range of the fixed-point number. template @@ -1562,6 +1624,8 @@ struct flip_msb_for_input> namespace dpf::leaf_arithmetic { +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template struct add_t, simde__m128i> { @@ -1617,6 +1681,7 @@ struct multiply_t, simde__m256i return multiply_t{}(a, b.integral_representation()); } }; +HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace dpf::leaf_arithmetic diff --git a/include/grotto/fixedpoint_mul.hpp b/include/grotto/fixedpoint_mul.hpp index 8c3fa3b..3ce0305 100644 --- a/include/grotto/fixedpoint_mul.hpp +++ b/include/grotto/fixedpoint_mul.hpp @@ -34,6 +34,12 @@ namespace grotto /// zero-extend. Sign-extending a narrower signed operand, and replicating the /// product sign when `modulus_bits > multiply_bits`, are plaintext steps the /// MPC protocol has to reproduce (they are not local on additive shares). +/// @tparam IntegerBits number of integer bits, including the sign +/// @tparam FractionalBits number of fractional bits +/// @tparam LhsFractionalBits lhs fractional bits +/// @tparam LhsIntegral lhs integral +/// @tparam RhsFractionalBits rhs fractional bits +/// @tparam RhsIntegral rhs integral template ; static constexpr bool operands_signed = lhs_signed || rhs_signed; - /// Right shift applied to the raw product. Negative means a left shift. + /// @brief Right shift applied to the raw product. Negative means a left shift. static constexpr int align_shift = static_cast(LhsFractionalBits) + static_cast(RhsFractionalBits) - static_cast(FractionalBits); - /// Bits of the product that the shift reads. Zero when a left shift + /// @brief Bits of the product that the shift reads. Zero when a left shift /// moves every product bit out of the output. static constexpr int modulus_bits_signed = align_shift >= 0 ? align_shift + static_cast(out_bits) @@ -64,14 +70,14 @@ struct fixed_mul_plan static constexpr unsigned modulus_bits = modulus_bits_signed > 0 ? static_cast(modulus_bits_signed) : 0u; - /// Full two's-complement product fits in this many bits. + /// @brief Full two's-complement product fits in this many bits. static constexpr unsigned product_bits = lhs_width + rhs_width; static constexpr unsigned multiply_bits = modulus_bits < product_bits ? modulus_bits : product_bits; static constexpr unsigned limbs = multiply_bits == 0u ? 0u : (multiply_bits + 63u) / 64u; - /// Signed storage exists through 128 bits. A wider window is the same + /// @brief Signed storage exists through 128 bits. A wider window is the same /// residue held in an unsigned fixed-point. static constexpr bool result_is_signed = operands_signed && out_bits <= 128u; @@ -197,7 +203,14 @@ constexpr void store_raw_limbs(const T & value, std::uint64_t out[4]) noexcept } } -/// Low `dest_bits` of `value`, sign-extended when `value` is a narrower signed integer. +/// @brief Low `dest_bits` of `value`, sign-extended when `value` is a narrower signed integer. +/// @tparam T value type +/// @param value the value to convert or store +/// @param src_bits the `src_bits` +/// @param is_signed the `is_signed` +/// @param dest_bits the `dest_bits` +/// @param dest the destination +/// @param nlimbs the `nlimbs` template HEDLEY_NO_THROW constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed, @@ -222,7 +235,11 @@ constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed mask_to_bits(dest, nlimbs, dest_bits); } -/// Product modulo `2^(64*nlimbs)`, using exactly `nlimbs` limbs of each operand. +/// @brief Product modulo `2^(64*nlimbs)`, using exactly `nlimbs` limbs of each operand. +/// @param out the output buffer +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @param nlimbs the `nlimbs` HEDLEY_NO_THROW constexpr void mul_low_limbs(std::uint64_t * out, const std::uint64_t * lhs, const std::uint64_t * rhs, unsigned nlimbs) noexcept @@ -328,15 +345,21 @@ constexpr T limbs_to_integral(const std::uint64_t * limbs) noexcept } // namespace detail /// @brief Multiply two fixed-point values into a chosen integer and fraction width. -/// @tparam IntegerBits Integer bits kept in the result, including the sign bit -/// when the result is signed. Bits above this wrap. +/// @details The result is held in the smallest fixed-point word that can store +/// `IntegerBits + FractionalBits`. A signed word is used when either operand +/// is signed and the window is at most 128 bits; otherwise the window is the +/// unsigned residue. +/// @tparam IntegerBits Integer bits kept in the result, including the sign bit +/// when the result is signed. Bits above this wrap. /// @tparam FractionalBits Fraction bits kept in the result. Lower fraction bits -/// of the exact product are discarded (floored). -/// -/// The result is held in the smallest fixed-point word that can store -/// `IntegerBits + FractionalBits`. A signed word is used when either operand -/// is signed and the window is at most 128 bits; otherwise the window is the -/// unsigned residue. +/// of the exact product are discarded (floored). +/// @tparam LhsFractionalBits fractional bits of the left operand +/// @tparam LhsIntegral integral type of the left operand +/// @tparam RhsFractionalBits fractional bits of the right operand +/// @tparam RhsIntegral integral type of the right operand +/// @param lhs the left-hand operand +/// @param rhs the right-hand operand +/// @return the product at the requested width template -/// @brief -/// @details +/// @brief Domain, degree, and pole hints for cleartext gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -10,6 +9,8 @@ #ifndef LIBDPF_INCLUDE_GROTTO_GADGET_HINTS_HPP__ #define LIBDPF_INCLUDE_GROTTO_GADGET_HINTS_HPP__ +#include "hedley/hedley.h" + #include #include #include diff --git a/include/grotto/gadgets.hpp b/include/grotto/gadgets.hpp index cd88ce3..c7ec9c5 100644 --- a/include/grotto/gadgets.hpp +++ b/include/grotto/gadgets.hpp @@ -1,7 +1,6 @@ /// @file grotto/gadgets.hpp /// @author Ryan Henry -/// @brief -/// @details +/// @brief Umbrella include for the gadget reference headers that are enabled. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -9,6 +8,10 @@ #ifndef LIBDPF_INCLUDE_GROTTO_GADGETS_HPP__ #define LIBDPF_INCLUDE_GROTTO_GADGETS_HPP__ +// Cleartext gadget functors are the 2019 reference layer. Fixed-point +// evaluation lives in the LUTs: `eval_reduced` (including `expm1` and +// `log1p`), `eval_window`, `make_*_lut`, and `exact_constant`. A functor +// whose map those tables implement is deprecated. // #include "grotto/gadgets/activations.hpp" // #include "grotto/gadgets/binary.hpp" #include "grotto/gadgets/decimal.hpp" diff --git a/include/grotto/gadgets/activations.hpp b/include/grotto/gadgets/activations.hpp index a2bbd26..1650f59 100644 --- a/include/grotto/gadgets/activations.hpp +++ b/include/grotto/gadgets/activations.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the activations gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/celu.hpp b/include/grotto/gadgets/activations/celu.hpp index 2097db5..a08ad92 100644 --- a/include/grotto/gadgets/activations/celu.hpp +++ b/include/grotto/gadgets/activations/celu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/celu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `celu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/elish.hpp b/include/grotto/gadgets/activations/elish.hpp index f785823..0b2360d 100644 --- a/include/grotto/gadgets/activations/elish.hpp +++ b/include/grotto/gadgets/activations/elish.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/elish.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `elish`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,11 @@ namespace grotto namespace gadgets { -struct elish +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) elish { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/elu.hpp b/include/grotto/gadgets/activations/elu.hpp index 233c224..c99aad0 100644 --- a/include/grotto/gadgets/activations/elu.hpp +++ b/include/grotto/gadgets/activations/elu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/elu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `elu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/gelu.hpp b/include/grotto/gadgets/activations/gelu.hpp index 48effc1..7a252fc 100644 --- a/include/grotto/gadgets/activations/gelu.hpp +++ b/include/grotto/gadgets/activations/gelu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/gelu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `gelu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct gelu +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) gelu { template T operator()(T x) @@ -42,6 +46,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/hardelish.hpp b/include/grotto/gadgets/activations/hardelish.hpp index 5c74fa5..b3bb05b 100644 --- a/include/grotto/gadgets/activations/hardelish.hpp +++ b/include/grotto/gadgets/activations/hardelish.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/hardelish.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `hardelish`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/hardshrink.hpp b/include/grotto/gadgets/activations/hardshrink.hpp index 6737a1c..61a1437 100644 --- a/include/grotto/gadgets/activations/hardshrink.hpp +++ b/include/grotto/gadgets/activations/hardshrink.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/hardshrink.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `hardshrink`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,9 +20,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double hardshrink_default_lambda = 0.5; template -struct hardshrink +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardshrink_lut) hardshrink { template T operator()(T x) @@ -45,6 +49,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { {0,1}, {0}, {0,1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/hardsigmoid.hpp b/include/grotto/gadgets/activations/hardsigmoid.hpp index 07782b2..6fbbf3e 100644 --- a/include/grotto/gadgets/activations/hardsigmoid.hpp +++ b/include/grotto/gadgets/activations/hardsigmoid.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/hardsigmoid.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `hardsigmoid`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct hardsigmoid +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardsigmoid_lut) hardsigmoid { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { {0}, {0.5,1/6.0}, {1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/hardswish.hpp b/include/grotto/gadgets/activations/hardswish.hpp index e649175..ccc2315 100644 --- a/include/grotto/gadgets/activations/hardswish.hpp +++ b/include/grotto/gadgets/activations/hardswish.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/hardswish.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `hardswish`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct hardswish +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardswish_lut) hardswish { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { {0}, {0,0.5,1/6.0}, {0,1,0} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/hardtanh.hpp b/include/grotto/gadgets/activations/hardtanh.hpp index f9829cf..cba54dd 100644 --- a/include/grotto/gadgets/activations/hardtanh.hpp +++ b/include/grotto/gadgets/activations/hardtanh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/hardtanh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `hardtanh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct hardtanh +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardtanh_lut) hardtanh { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { {-1}, {0,1}, {1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/leakyrelu.hpp b/include/grotto/gadgets/activations/leakyrelu.hpp index 9e18247..42689a5 100644 --- a/include/grotto/gadgets/activations/leakyrelu.hpp +++ b/include/grotto/gadgets/activations/leakyrelu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/leakyrelu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `leakyrelu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,10 +20,14 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double leakyrelu_default_negative_slope = 0.01; static constexpr double leakyrelu_zero_negative_slope = 0.0; template -struct leakyrelu +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_leaky_relu_lut) leakyrelu { template T operator()(T x) @@ -46,6 +50,8 @@ struct gadget_hints> inline static constexpr std::array canonical_polys[] = { {0,negative_slope}, {0,1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/lecun_tanh.hpp b/include/grotto/gadgets/activations/lecun_tanh.hpp index 8ce8447..1778e56 100644 --- a/include/grotto/gadgets/activations/lecun_tanh.hpp +++ b/include/grotto/gadgets/activations/lecun_tanh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/lecun_tanh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `lecun_tanh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/logsigmoid.hpp b/include/grotto/gadgets/activations/logsigmoid.hpp index 92e3a52..fd65d6d 100644 --- a/include/grotto/gadgets/activations/logsigmoid.hpp +++ b/include/grotto/gadgets/activations/logsigmoid.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/logsigmoid.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `logsigmoid`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,11 @@ namespace grotto namespace gadgets { -struct logsigmoid +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) logsigmoid { template T operator()(T x) { return std::log(sigmoid{}(x)); } @@ -40,6 +44,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/mish.hpp b/include/grotto/gadgets/activations/mish.hpp index 30d09b3..c76fb24 100644 --- a/include/grotto/gadgets/activations/mish.hpp +++ b/include/grotto/gadgets/activations/mish.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/mish.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `mish`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,11 @@ namespace grotto namespace gadgets { -struct mish +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) mish { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/one_minus_sigmoid.hpp b/include/grotto/gadgets/activations/one_minus_sigmoid.hpp index eafe721..3629c11 100644 --- a/include/grotto/gadgets/activations/one_minus_sigmoid.hpp +++ b/include/grotto/gadgets/activations/one_minus_sigmoid.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/activations/one_minus_relu.hpp +/// @file grotto/gadgets/activations/one_minus_sigmoid.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `one_minus_sigmoid`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,10 @@ namespace grotto namespace gadgets { -struct one_minus_sigmoid +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) one_minus_sigmoid { template T operator()(T x) @@ -43,6 +46,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/relu.hpp b/include/grotto/gadgets/activations/relu.hpp index 786ea75..ce4befb 100644 --- a/include/grotto/gadgets/activations/relu.hpp +++ b/include/grotto/gadgets/activations/relu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/relu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `relu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,10 @@ namespace grotto namespace gadgets { -using relu = leakyrelu; +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +using relu HEDLEY_DEPRECATED_FOR(2026, grotto::make_relu_lut) = leakyrelu; +HEDLEY_DIAGNOSTIC_POP } // namespace gadgets diff --git a/include/grotto/gadgets/activations/relu6.hpp b/include/grotto/gadgets/activations/relu6.hpp index 3246f14..3088f1c 100644 --- a/include/grotto/gadgets/activations/relu6.hpp +++ b/include/grotto/gadgets/activations/relu6.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/relu6.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `relu6`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,9 +20,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double relu6_default_clip = 6; template -struct relu6 +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_relu6_lut) relu6 { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { 0, {0,1}, clip }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/selu.hpp b/include/grotto/gadgets/activations/selu.hpp index 70209c5..b3b1a82 100644 --- a/include/grotto/gadgets/activations/selu.hpp +++ b/include/grotto/gadgets/activations/selu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/selu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `selu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/serf.hpp b/include/grotto/gadgets/activations/serf.hpp index c06b3dc..7c58875 100644 --- a/include/grotto/gadgets/activations/serf.hpp +++ b/include/grotto/gadgets/activations/serf.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/serf.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `serf`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,7 +21,11 @@ namespace grotto namespace gadgets { -struct serf +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) serf { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/sigmoid.hpp b/include/grotto/gadgets/activations/sigmoid.hpp index d9e7e15..3b7fd85 100644 --- a/include/grotto/gadgets/activations/sigmoid.hpp +++ b/include/grotto/gadgets/activations/sigmoid.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/sigmoid.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sigmoid`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sigmoid +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) sigmoid { template T operator()(T x) { return 1/(1+std::exp(-x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/silu.hpp b/include/grotto/gadgets/activations/silu.hpp index 7b21fa0..d0acfea 100644 --- a/include/grotto/gadgets/activations/silu.hpp +++ b/include/grotto/gadgets/activations/silu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/silu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `silu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct silu +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) silu { template T operator()(T x) { return x*sigmoid{}(x); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/smoothstep.hpp b/include/grotto/gadgets/activations/smoothstep.hpp index 4eb4652..31e7a20 100644 --- a/include/grotto/gadgets/activations/smoothstep.hpp +++ b/include/grotto/gadgets/activations/smoothstep.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/smoothstep.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `smoothstep`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,9 +20,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double smoothstep_default_gamma = 1; template -struct smoothstep +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) smoothstep { template T operator()(T x) @@ -46,6 +50,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { 0, {-2/(gamma*gamma*gamma),0,3/(2*gamma),0.5}, 1 }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/softminus.hpp b/include/grotto/gadgets/activations/softminus.hpp index bbbdc42..5d4059b 100644 --- a/include/grotto/gadgets/activations/softminus.hpp +++ b/include/grotto/gadgets/activations/softminus.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/softminus.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `softminus`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -21,9 +21,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double softminus_default_beta = 1; template -struct softminus +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) softminus { template T operator()(T x) @@ -45,6 +49,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/softplus.hpp b/include/grotto/gadgets/activations/softplus.hpp index fae8b85..db88523 100644 --- a/include/grotto/gadgets/activations/softplus.hpp +++ b/include/grotto/gadgets/activations/softplus.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/softplus.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `softplus`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,9 +20,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double softplus_default_beta = 1; template -struct softplus +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) softplus { template T operator()(T x) @@ -45,6 +49,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/softshrink.hpp b/include/grotto/gadgets/activations/softshrink.hpp index d597c12..1be9ef6 100644 --- a/include/grotto/gadgets/activations/softshrink.hpp +++ b/include/grotto/gadgets/activations/softshrink.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/softshrink.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `softshrink`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,9 +20,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + static constexpr double softshrink_default_lambda = 0.5; template -struct softshrink +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_softshrink_lut) softshrink { template T operator()(T x) @@ -46,6 +50,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { {lambda,1}, {0}, {-lambda,1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/softsign.hpp b/include/grotto/gadgets/activations/softsign.hpp index 5cdd57c..db36e01 100644 --- a/include/grotto/gadgets/activations/softsign.hpp +++ b/include/grotto/gadgets/activations/softsign.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/softsign.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `softsign`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/squared_relu.hpp b/include/grotto/gadgets/activations/squared_relu.hpp index 436e82f..b296bf2 100644 --- a/include/grotto/gadgets/activations/squared_relu.hpp +++ b/include/grotto/gadgets/activations/squared_relu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/squared_relu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `squared_relu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct squared_relu +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_squared_relu_lut) squared_relu { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { {0}, {0,0,1} }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/starrelu.hpp b/include/grotto/gadgets/activations/starrelu.hpp index c283ddc..0f68262 100644 --- a/include/grotto/gadgets/activations/starrelu.hpp +++ b/include/grotto/gadgets/activations/starrelu.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/starrelu.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `starrelu`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/activations/tanhexp.hpp b/include/grotto/gadgets/activations/tanhexp.hpp index 9159a5b..0105952 100644 --- a/include/grotto/gadgets/activations/tanhexp.hpp +++ b/include/grotto/gadgets/activations/tanhexp.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/tanhexp.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `tanhexp`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct tanhexp +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) tanhexp { template T operator()(T x) { return x*std::tanh(std::exp(x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/activations/tanhshrink.hpp b/include/grotto/gadgets/activations/tanhshrink.hpp index 80393fc..eeae8e4 100644 --- a/include/grotto/gadgets/activations/tanhshrink.hpp +++ b/include/grotto/gadgets/activations/tanhshrink.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/activations/tanhshrink.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `tanhshrink`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/binary.hpp b/include/grotto/gadgets/binary.hpp index 3446947..d9b9499 100644 --- a/include/grotto/gadgets/binary.hpp +++ b/include/grotto/gadgets/binary.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/binary.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the binary gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/binary/bit.hpp b/include/grotto/gadgets/binary/bit.hpp index d5e1a12..0104d47 100644 --- a/include/grotto/gadgets/binary/bit.hpp +++ b/include/grotto/gadgets/binary/bit.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/binary/bt.hpp +/// @file grotto/gadgets/binary/bit.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `bit`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/binary/countl_zero.hpp b/include/grotto/gadgets/binary/countl_zero.hpp index db0474c..f40d38e 100644 --- a/include/grotto/gadgets/binary/countl_zero.hpp +++ b/include/grotto/gadgets/binary/countl_zero.hpp @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct countl_zero +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) countl_zero { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array hint_canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/binary/prefix.hpp b/include/grotto/gadgets/binary/prefix.hpp index a372ff4..65ba663 100644 --- a/include/grotto/gadgets/binary/prefix.hpp +++ b/include/grotto/gadgets/binary/prefix.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/binary/prefix.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `prefix`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/binary/sgn.hpp b/include/grotto/gadgets/binary/sgn.hpp index 91e2ac8..d7feb22 100644 --- a/include/grotto/gadgets/binary/sgn.hpp +++ b/include/grotto/gadgets/binary/sgn.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/binary/sg.hpp +/// @file grotto/gadgets/binary/sgn.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sgn`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sgn +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) sgn { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { -1, 0, 1 }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/decimal.hpp b/include/grotto/gadgets/decimal.hpp index 8e8e525..b431bdd 100644 --- a/include/grotto/gadgets/decimal.hpp +++ b/include/grotto/gadgets/decimal.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/decimal.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the decimal gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/decimal/dec_ceil.hpp b/include/grotto/gadgets/decimal/dec_ceil.hpp index 9637ef0..86f6e55 100644 --- a/include/grotto/gadgets/decimal/dec_ceil.hpp +++ b/include/grotto/gadgets/decimal/dec_ceil.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/decimal/dec_ceil.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `dec_ceil`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/decimal/dec_floor.hpp b/include/grotto/gadgets/decimal/dec_floor.hpp index 2caead7..17ba3e0 100644 --- a/include/grotto/gadgets/decimal/dec_floor.hpp +++ b/include/grotto/gadgets/decimal/dec_floor.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/decimal/dec_floor.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `dec_floor`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/decimal/dec_width.hpp b/include/grotto/gadgets/decimal/dec_width.hpp index ae58d04..a324c99 100644 --- a/include/grotto/gadgets/decimal/dec_width.hpp +++ b/include/grotto/gadgets/decimal/dec_width.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/decimal/dec_width.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `dec_width`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/decimal/has_single_digit.hpp b/include/grotto/gadgets/decimal/has_single_digit.hpp index 57d8f38..55627f1 100644 --- a/include/grotto/gadgets/decimal/has_single_digit.hpp +++ b/include/grotto/gadgets/decimal/has_single_digit.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/decimal/has_single_digit.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `has_single_digit`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary.hpp b/include/grotto/gadgets/elementary.hpp index c0fe364..0f68135 100644 --- a/include/grotto/gadgets/elementary.hpp +++ b/include/grotto/gadgets/elementary.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the elementary gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/abs.hpp b/include/grotto/gadgets/elementary/abs.hpp index f2ac494..68607ea 100644 --- a/include/grotto/gadgets/elementary/abs.hpp +++ b/include/grotto/gadgets/elementary/abs.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/abs.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `abs`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct abs +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_abs_lut) abs { template T operator()(T x) { return std::abs(static_cast(x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { { -1, 0 }, { 1, 0 } }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/elementary/approx.hpp b/include/grotto/gadgets/elementary/approx.hpp index 7394427..5ed9070 100644 --- a/include/grotto/gadgets/elementary/approx.hpp +++ b/include/grotto/gadgets/elementary/approx.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/approx.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `approx`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/boxcar.hpp b/include/grotto/gadgets/elementary/boxcar.hpp index a26faa6..e5418f9 100644 --- a/include/grotto/gadgets/elementary/boxcar.hpp +++ b/include/grotto/gadgets/elementary/boxcar.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/boxcar.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `boxcar`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/clip.hpp b/include/grotto/gadgets/elementary/clip.hpp index 22d80c5..c2a5115 100644 --- a/include/grotto/gadgets/elementary/clip.hpp +++ b/include/grotto/gadgets/elementary/clip.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/elemwntary/clip.hpp +/// @file grotto/gadgets/elementary/clip.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `clip`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -19,9 +19,13 @@ namespace grotto namespace gadgets { +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + template -struct clip +struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_clip_lut) clip { static_assert(lower <= upper); template @@ -43,6 +47,8 @@ struct gadget_hints> static constexpr std::array canonical_polys[] = { { lower, 0 }, { 0, 1 }, { upper, 0 } }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/elementary/eq.hpp b/include/grotto/gadgets/elementary/eq.hpp index c74fe60..ca4e426 100644 --- a/include/grotto/gadgets/elementary/eq.hpp +++ b/include/grotto/gadgets/elementary/eq.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/eq.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `eq`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/geq.hpp b/include/grotto/gadgets/elementary/geq.hpp index 42604b7..ffc9e17 100644 --- a/include/grotto/gadgets/elementary/geq.hpp +++ b/include/grotto/gadgets/elementary/geq.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/geq.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `geq`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/gt.hpp b/include/grotto/gadgets/elementary/gt.hpp index 5a30eec..431143d 100644 --- a/include/grotto/gadgets/elementary/gt.hpp +++ b/include/grotto/gadgets/elementary/gt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/gt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `gt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/identity.hpp b/include/grotto/gadgets/elementary/identity.hpp index 17f2943..93fd0b3 100644 --- a/include/grotto/gadgets/elementary/identity.hpp +++ b/include/grotto/gadgets/elementary/identity.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/identity.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `identity`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/leq.hpp b/include/grotto/gadgets/elementary/leq.hpp index fef33eb..c315de5 100644 --- a/include/grotto/gadgets/elementary/leq.hpp +++ b/include/grotto/gadgets/elementary/leq.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/elementary/leqw.hpp +/// @file grotto/gadgets/elementary/leq.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `leq`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/lt.hpp b/include/grotto/gadgets/elementary/lt.hpp index d3e330a..237c52d 100644 --- a/include/grotto/gadgets/elementary/lt.hpp +++ b/include/grotto/gadgets/elementary/lt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/lt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `lt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/negative.hpp b/include/grotto/gadgets/elementary/negative.hpp index dcd97c0..648bd08 100644 --- a/include/grotto/gadgets/elementary/negative.hpp +++ b/include/grotto/gadgets/elementary/negative.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/negative.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `negative`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double lt_target_zero = 0; -using negative = lt; +using negative HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = lt; } // namespace gadgets diff --git a/include/grotto/gadgets/elementary/neq.hpp b/include/grotto/gadgets/elementary/neq.hpp index 305234c..fb96932 100644 --- a/include/grotto/gadgets/elementary/neq.hpp +++ b/include/grotto/gadgets/elementary/neq.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/neq.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `neq`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/nonnegative.hpp b/include/grotto/gadgets/elementary/nonnegative.hpp index 551fc74..4518746 100644 --- a/include/grotto/gadgets/elementary/nonnegative.hpp +++ b/include/grotto/gadgets/elementary/nonnegative.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/nonnegative.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `nonnegative`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double geq_target_zero = 0; -using nonnegative = geq; +using nonnegative HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = geq; } // namespace gadgets diff --git a/include/grotto/gadgets/elementary/nonpositive.hpp b/include/grotto/gadgets/elementary/nonpositive.hpp index 0edd7fb..6baeb0c 100644 --- a/include/grotto/gadgets/elementary/nonpositive.hpp +++ b/include/grotto/gadgets/elementary/nonpositive.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/nonpositive.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `nonpositive`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double leq_target_zero = 0; -using nonpositive = leq; +using nonpositive HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = leq; } // namespace gadgets diff --git a/include/grotto/gadgets/elementary/nonzero.hpp b/include/grotto/gadgets/elementary/nonzero.hpp index 6982082..e664038 100644 --- a/include/grotto/gadgets/elementary/nonzero.hpp +++ b/include/grotto/gadgets/elementary/nonzero.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/nonzero.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `nonzero`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double neq_target_zero = 0; -using nonzero = neq; +using nonzero HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = neq; } // namespace gadgets diff --git a/include/grotto/gadgets/elementary/pmone.hpp b/include/grotto/gadgets/elementary/pmone.hpp index 28012c3..d4d3beb 100644 --- a/include/grotto/gadgets/elementary/pmone.hpp +++ b/include/grotto/gadgets/elementary/pmone.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/pmone.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `pmone`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/positive.hpp b/include/grotto/gadgets/elementary/positive.hpp index c64f273..26688f3 100644 --- a/include/grotto/gadgets/elementary/positive.hpp +++ b/include/grotto/gadgets/elementary/positive.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/positive.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `positive`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double gt_target_zero = 0; -using positive = gt; +using positive HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = gt; } // namespace gadgets diff --git a/include/grotto/gadgets/elementary/rect.hpp b/include/grotto/gadgets/elementary/rect.hpp index 63b9e84..e38737d 100644 --- a/include/grotto/gadgets/elementary/rect.hpp +++ b/include/grotto/gadgets/elementary/rect.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/rect.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `rect`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/step.hpp b/include/grotto/gadgets/elementary/step.hpp index fbb4949..13be4aa 100644 --- a/include/grotto/gadgets/elementary/step.hpp +++ b/include/grotto/gadgets/elementary/step.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/step.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `step`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/ternary.hpp b/include/grotto/gadgets/elementary/ternary.hpp index d540ab3..5ae168b 100644 --- a/include/grotto/gadgets/elementary/ternary.hpp +++ b/include/grotto/gadgets/elementary/ternary.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/ternary.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ternary`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/elementary/zero.hpp b/include/grotto/gadgets/elementary/zero.hpp index 97f89b5..28eca5e 100644 --- a/include/grotto/gadgets/elementary/zero.hpp +++ b/include/grotto/gadgets/elementary/zero.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/elementary/zero.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `zero`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -17,7 +17,7 @@ namespace gadgets { static constexpr double eq_target_zero = 0; -using zero = eq; +using zero HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = eq; } // namespace gadgets diff --git a/include/grotto/gadgets/exponential.hpp b/include/grotto/gadgets/exponential.hpp index dababf9..f6d3aa5 100644 --- a/include/grotto/gadgets/exponential.hpp +++ b/include/grotto/gadgets/exponential.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/exponential.hpp /// @author Ryan Henry -/// @brief +/// @brief Deprecated cleartext `exp` / `exp2` / `exp10`. Use `eval_reduced`. Includes the exponential gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/exponential/exp.hpp b/include/grotto/gadgets/exponential/exp.hpp index d7c390f..7d774d8 100644 --- a/include/grotto/gadgets/exponential/exp.hpp +++ b/include/grotto/gadgets/exponential/exp.hpp @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct exp +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp { template T operator()(T x) { return std::min(std::exp(static_cast(x)), std::numeric_limits::max()); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array * canonical_polys = nullptr; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/exponential/exp10.hpp b/include/grotto/gadgets/exponential/exp10.hpp index 2ebdd0a..c83c6c8 100644 --- a/include/grotto/gadgets/exponential/exp10.hpp +++ b/include/grotto/gadgets/exponential/exp10.hpp @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct exp10 +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp10 { template T operator()(T x) { return std::min(std::pow(10, static_cast(x)), std::numeric_limits::max()); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array * hint_canonical_polys = nullptr; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/exponential/exp2.hpp b/include/grotto/gadgets/exponential/exp2.hpp index 31128a5..7d2039f 100644 --- a/include/grotto/gadgets/exponential/exp2.hpp +++ b/include/grotto/gadgets/exponential/exp2.hpp @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct exp2 +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp2 { template T operator()(T x) { return std::min(std::exp2(static_cast(x)), std::numeric_limits::max()); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array * canonical_polys = nullptr; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic.hpp b/include/grotto/gadgets/hyperbolic.hpp index ba15273..89e090f 100644 --- a/include/grotto/gadgets/hyperbolic.hpp +++ b/include/grotto/gadgets/hyperbolic.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the hyperbolic gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/acosh.hpp b/include/grotto/gadgets/hyperbolic/acosh.hpp index 5af0838..4158712 100644 --- a/include/grotto/gadgets/hyperbolic/acosh.hpp +++ b/include/grotto/gadgets/hyperbolic/acosh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/acosh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acosh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/acoth.hpp b/include/grotto/gadgets/hyperbolic/acoth.hpp index 59b36c2..950b5dc 100644 --- a/include/grotto/gadgets/hyperbolic/acoth.hpp +++ b/include/grotto/gadgets/hyperbolic/acoth.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/acoth.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acoth`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/acsch.hpp b/include/grotto/gadgets/hyperbolic/acsch.hpp index 8bd40c1..dd02bd2 100644 --- a/include/grotto/gadgets/hyperbolic/acsch.hpp +++ b/include/grotto/gadgets/hyperbolic/acsch.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/acsch.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acsch`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/asech.hpp b/include/grotto/gadgets/hyperbolic/asech.hpp index 9b7e2a5..fc3ab56 100644 --- a/include/grotto/gadgets/hyperbolic/asech.hpp +++ b/include/grotto/gadgets/hyperbolic/asech.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/asech.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `asech`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/asinh.hpp b/include/grotto/gadgets/hyperbolic/asinh.hpp index aed95d6..8da7822 100644 --- a/include/grotto/gadgets/hyperbolic/asinh.hpp +++ b/include/grotto/gadgets/hyperbolic/asinh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/asinh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `asinh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/atanh.hpp b/include/grotto/gadgets/hyperbolic/atanh.hpp index 941081d..48d944e 100644 --- a/include/grotto/gadgets/hyperbolic/atanh.hpp +++ b/include/grotto/gadgets/hyperbolic/atanh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/atanh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `atanh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/hyperbolic/cosh.hpp b/include/grotto/gadgets/hyperbolic/cosh.hpp index 68b6623..9907a70 100644 --- a/include/grotto/gadgets/hyperbolic/cosh.hpp +++ b/include/grotto/gadgets/hyperbolic/cosh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/cosh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `cosh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct cosh +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) cosh { template T operator()(T x) { return std::min(std::cosh(static_cast(x)), std::numeric_limits::max()); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic/coth.hpp b/include/grotto/gadgets/hyperbolic/coth.hpp index 2d07d98..30ed95b 100644 --- a/include/grotto/gadgets/hyperbolic/coth.hpp +++ b/include/grotto/gadgets/hyperbolic/coth.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/coth.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `coth`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct coth +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) coth { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic/csch.hpp b/include/grotto/gadgets/hyperbolic/csch.hpp index d94b27a..088c435 100644 --- a/include/grotto/gadgets/hyperbolic/csch.hpp +++ b/include/grotto/gadgets/hyperbolic/csch.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/csch.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `csch`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct csch +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) csch { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic/sech.hpp b/include/grotto/gadgets/hyperbolic/sech.hpp index cf21b51..8ca1650 100644 --- a/include/grotto/gadgets/hyperbolic/sech.hpp +++ b/include/grotto/gadgets/hyperbolic/sech.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/sech.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sech`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sech +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sech { template T operator()(T x) { return 1/std::cosh(static_cast(x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic/sinh.hpp b/include/grotto/gadgets/hyperbolic/sinh.hpp index 68a87f8..3901354 100644 --- a/include/grotto/gadgets/hyperbolic/sinh.hpp +++ b/include/grotto/gadgets/hyperbolic/sinh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/sinh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sinh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sinh +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sinh { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/hyperbolic/tanh.hpp b/include/grotto/gadgets/hyperbolic/tanh.hpp index a66a65c..9445e5b 100644 --- a/include/grotto/gadgets/hyperbolic/tanh.hpp +++ b/include/grotto/gadgets/hyperbolic/tanh.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/hyperbolic/tanh.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `tanh`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct tanh +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) tanh { template T operator()(T x) { return std::tanh(static_cast(x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm.hpp b/include/grotto/gadgets/logarithm.hpp index a38c6fe..12bea49 100644 --- a/include/grotto/gadgets/logarithm.hpp +++ b/include/grotto/gadgets/logarithm.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the logarithm gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/logarithm/ilog10.hpp b/include/grotto/gadgets/logarithm/ilog10.hpp index b035ee2..f21cbfd 100644 --- a/include/grotto/gadgets/logarithm/ilog10.hpp +++ b/include/grotto/gadgets/logarithm/ilog10.hpp @@ -1,6 +1,7 @@ /// @file grotto/gadgets/logarithm/ilog10.hpp -/// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ilog10`. +/// @details The previous body of this header defined a second `ilog256`. +/// Integer log10 is `exact_constant::ilog10`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,29 +21,34 @@ namespace grotto namespace gadgets { -struct ilog256 +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) ilog10 { template T operator()(T x) { - if (x <= 0) return 0; // TODO - return std::ceil(std::log2(static_cast(x))/8); + if (x <= 0) return 0; + return std::floor(std::log10(static_cast(x))); } }; template <> -struct gadget_hints +struct gadget_hints { static constexpr double min = std::exp2(-63); static constexpr double max = std::numeric_limits::max(); - static constexpr double hint_poles[] = { }; - static constexpr double hint_interesting_points[] = { 0 }; + static constexpr double poles[] = { 0 }; + static constexpr double interesting_points[] = { 0 }; static constexpr unsigned degree = 0; static constexpr bool has_canonical_representation = false; static constexpr double canonical_bounds[] = { }; - static constexpr std::array hint_canonical_polys[] = { }; + static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm/ilog16.hpp b/include/grotto/gadgets/logarithm/ilog16.hpp index e380bb6..f7d25b5 100644 --- a/include/grotto/gadgets/logarithm/ilog16.hpp +++ b/include/grotto/gadgets/logarithm/ilog16.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/ilog16.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ilog16`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/logarithm/ilog256.hpp b/include/grotto/gadgets/logarithm/ilog256.hpp index b62bc2a..56bc125 100644 --- a/include/grotto/gadgets/logarithm/ilog256.hpp +++ b/include/grotto/gadgets/logarithm/ilog256.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/ilog256.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ilog256`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/logarithm/ilogb.hpp b/include/grotto/gadgets/logarithm/ilogb.hpp index fc0ecc7..0567bb3 100644 --- a/include/grotto/gadgets/logarithm/ilogb.hpp +++ b/include/grotto/gadgets/logarithm/ilogb.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/ilogb.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ilogb`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct ilogb +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) ilogb { template T operator()(T x) @@ -300,6 +304,8 @@ struct gadget_hints }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm/lg.hpp b/include/grotto/gadgets/logarithm/lg.hpp index 71b6488..b400f61 100644 --- a/include/grotto/gadgets/logarithm/lg.hpp +++ b/include/grotto/gadgets/logarithm/lg.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/lg.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `lg`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct lg +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) lg { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm/ln.hpp b/include/grotto/gadgets/logarithm/ln.hpp index f7ec988..31bf016 100644 --- a/include/grotto/gadgets/logarithm/ln.hpp +++ b/include/grotto/gadgets/logarithm/ln.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/ln.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `ln`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct ln +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) ln { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm/log10.hpp b/include/grotto/gadgets/logarithm/log10.hpp index 9af82e9..2766c50 100644 --- a/include/grotto/gadgets/logarithm/log10.hpp +++ b/include/grotto/gadgets/logarithm/log10.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/log10.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `log10`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct log10 +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) log10 { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array hint_canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/logarithm/logn.hpp b/include/grotto/gadgets/logarithm/logn.hpp index 5418633..e433d92 100644 --- a/include/grotto/gadgets/logarithm/logn.hpp +++ b/include/grotto/gadgets/logarithm/logn.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/logn.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `logn`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/logarithm/logstar.hpp b/include/grotto/gadgets/logarithm/logstar.hpp index 55f30e1..3e55ea2 100644 --- a/include/grotto/gadgets/logarithm/logstar.hpp +++ b/include/grotto/gadgets/logarithm/logstar.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/logarithm/logstar.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `logstar`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc.hpp b/include/grotto/gadgets/misc.hpp index cd85148..bdcf1d9 100644 --- a/include/grotto/gadgets/misc.hpp +++ b/include/grotto/gadgets/misc.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the misc gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/entropy.hpp b/include/grotto/gadgets/misc/entropy.hpp index 716e13c..405907c 100644 --- a/include/grotto/gadgets/misc/entropy.hpp +++ b/include/grotto/gadgets/misc/entropy.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/entropy.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `entropy`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/erf.hpp b/include/grotto/gadgets/misc/erf.hpp index 1d2261a..5d41658 100644 --- a/include/grotto/gadgets/misc/erf.hpp +++ b/include/grotto/gadgets/misc/erf.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/erf.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `erf`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct erf +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) erf { template T operator()(T x) { std::erf(static_cast(x)); } @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array hint_canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/misc/erfc.hpp b/include/grotto/gadgets/misc/erfc.hpp index 341d567..6fa2b22 100644 --- a/include/grotto/gadgets/misc/erfc.hpp +++ b/include/grotto/gadgets/misc/erfc.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/erfc.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `erfc`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,14 +20,18 @@ namespace grotto namespace gadgets { -struct erf +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) erfc { template - T operator()(T x) { std::erfc(static_cast(x)); } + T operator()(T x) { return std::erfc(static_cast(x)); } }; template <> -struct gadget_hints +struct gadget_hints { static constexpr double min = std::numeric_limits::min(); static constexpr double max = std::numeric_limits::max(); @@ -39,6 +43,8 @@ struct gadget_hints static constexpr std::array hint_canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/misc/expint.hpp b/include/grotto/gadgets/misc/expint.hpp index 8f362fc..94f91a9 100644 --- a/include/grotto/gadgets/misc/expint.hpp +++ b/include/grotto/gadgets/misc/expint.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/expint.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `expint`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/gamma.hpp b/include/grotto/gadgets/misc/gamma.hpp index 9781139..1b1055a 100644 --- a/include/grotto/gadgets/misc/gamma.hpp +++ b/include/grotto/gadgets/misc/gamma.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/gamma.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `gamma`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/i0.hpp b/include/grotto/gadgets/misc/i0.hpp index dcfcbea..a936e99 100644 --- a/include/grotto/gadgets/misc/i0.hpp +++ b/include/grotto/gadgets/misc/i0.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/i0.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `i0`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/lgamma.hpp b/include/grotto/gadgets/misc/lgamma.hpp index 12a6a13..7a67c47 100644 --- a/include/grotto/gadgets/misc/lgamma.hpp +++ b/include/grotto/gadgets/misc/lgamma.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/lgamma.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `lgamma`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/reimann_zeta.hpp b/include/grotto/gadgets/misc/reimann_zeta.hpp index b539b38..f62eecf 100644 --- a/include/grotto/gadgets/misc/reimann_zeta.hpp +++ b/include/grotto/gadgets/misc/reimann_zeta.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/reimann_zeta.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `reimann_zeta`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/misc/sinc.hpp b/include/grotto/gadgets/misc/sinc.hpp index 868c7c2..cdbf093 100644 --- a/include/grotto/gadgets/misc/sinc.hpp +++ b/include/grotto/gadgets/misc/sinc.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/misc/sinc.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sinc`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers.hpp b/include/grotto/gadgets/powers.hpp index 9c92599..fa7fac9 100644 --- a/include/grotto/gadgets/powers.hpp +++ b/include/grotto/gadgets/powers.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the powers gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers/cbrt.hpp b/include/grotto/gadgets/powers/cbrt.hpp index 5c5d035..5600dc4 100644 --- a/include/grotto/gadgets/powers/cbrt.hpp +++ b/include/grotto/gadgets/powers/cbrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/cbrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `cbrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers/icbrt.hpp b/include/grotto/gadgets/powers/icbrt.hpp index ea3c27e..5ebf97c 100644 --- a/include/grotto/gadgets/powers/icbrt.hpp +++ b/include/grotto/gadgets/powers/icbrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/icbrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `icbrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers/iqtrt.hpp b/include/grotto/gadgets/powers/iqtrt.hpp index 34a4398..8a31910 100644 --- a/include/grotto/gadgets/powers/iqtrt.hpp +++ b/include/grotto/gadgets/powers/iqtrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/iqtrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `iqtrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers/isqrt.hpp b/include/grotto/gadgets/powers/isqrt.hpp index cb63580..11db13d 100644 --- a/include/grotto/gadgets/powers/isqrt.hpp +++ b/include/grotto/gadgets/powers/isqrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/isqrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `isqrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct isqrt +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) isqrt { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/powers/qtrt.hpp b/include/grotto/gadgets/powers/qtrt.hpp index f963a47..b27a455 100644 --- a/include/grotto/gadgets/powers/qtrt.hpp +++ b/include/grotto/gadgets/powers/qtrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/qtrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `qtrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/powers/reciprocal.hpp b/include/grotto/gadgets/powers/reciprocal.hpp index 8d8e3b6..6b521e2 100644 --- a/include/grotto/gadgets/powers/reciprocal.hpp +++ b/include/grotto/gadgets/powers/reciprocal.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/reciprocal.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `reciprocal`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct reciprocal +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) reciprocal { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/powers/sqrt.hpp b/include/grotto/gadgets/powers/sqrt.hpp index e2bcb46..221b541 100644 --- a/include/grotto/gadgets/powers/sqrt.hpp +++ b/include/grotto/gadgets/powers/sqrt.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/powers/sqrt.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sqrt`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sqrt +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sqrt { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/quantile.hpp b/include/grotto/gadgets/quantile.hpp index 0dbc6cc..d7c9c13 100644 --- a/include/grotto/gadgets/quantile.hpp +++ b/include/grotto/gadgets/quantile.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/quantile.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the quantile gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/cauchy.hpp b/include/grotto/gadgets/quantile/cauchy.hpp index 9b55b4a..a237c80 100644 --- a/include/grotto/gadgets/quantile/cauchy.hpp +++ b/include/grotto/gadgets/quantile/cauchy.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/cauchy.hpp +/// @file grotto/gadgets/quantile/cauchy.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `cauchy`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/chisquared.hpp b/include/grotto/gadgets/quantile/chisquared.hpp index 611eb52..bb61995 100644 --- a/include/grotto/gadgets/quantile/chisquared.hpp +++ b/include/grotto/gadgets/quantile/chisquared.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/chisquared.hpp +/// @file grotto/gadgets/quantile/chisquared.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `chisquared`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/exponential.hpp b/include/grotto/gadgets/quantile/exponential.hpp index 80787ba..94f4d66 100644 --- a/include/grotto/gadgets/quantile/exponential.hpp +++ b/include/grotto/gadgets/quantile/exponential.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/exponential.hpp +/// @file grotto/gadgets/quantile/exponential.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `exponential`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/gamma.hpp b/include/grotto/gadgets/quantile/gamma.hpp index 061c5ff..773aa43 100644 --- a/include/grotto/gadgets/quantile/gamma.hpp +++ b/include/grotto/gadgets/quantile/gamma.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/gamma.hpp +/// @file grotto/gadgets/quantile/gamma.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `gamma`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/gaussian.hpp b/include/grotto/gadgets/quantile/gaussian.hpp index 0065eb2..de6fbb0 100644 --- a/include/grotto/gadgets/quantile/gaussian.hpp +++ b/include/grotto/gadgets/quantile/gaussian.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/gaussian.hpp +/// @file grotto/gadgets/quantile/gaussian.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `gaussian`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/geometric.hpp b/include/grotto/gadgets/quantile/geometric.hpp index 920eab6..3facd7e 100644 --- a/include/grotto/gadgets/quantile/geometric.hpp +++ b/include/grotto/gadgets/quantile/geometric.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/geometric.hpp +/// @file grotto/gadgets/quantile/geometric.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `geometric`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/laplace.hpp b/include/grotto/gadgets/quantile/laplace.hpp index c85d9d8..86113ac 100644 --- a/include/grotto/gadgets/quantile/laplace.hpp +++ b/include/grotto/gadgets/quantile/laplace.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/laplace.hpp +/// @file grotto/gadgets/quantile/laplace.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `laplace`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/logistic.hpp b/include/grotto/gadgets/quantile/logistic.hpp index b1d9d85..32ef909 100644 --- a/include/grotto/gadgets/quantile/logistic.hpp +++ b/include/grotto/gadgets/quantile/logistic.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/logistic.hpp +/// @file grotto/gadgets/quantile/logistic.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `logistic`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/lognormal.hpp b/include/grotto/gadgets/quantile/lognormal.hpp index a33ef09..57cd476 100644 --- a/include/grotto/gadgets/quantile/lognormal.hpp +++ b/include/grotto/gadgets/quantile/lognormal.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/lognormal.hpp +/// @file grotto/gadgets/quantile/lognormal.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `lognormal`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/pareto.hpp b/include/grotto/gadgets/quantile/pareto.hpp index 2d5d52a..5db724e 100644 --- a/include/grotto/gadgets/quantile/pareto.hpp +++ b/include/grotto/gadgets/quantile/pareto.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/pareto.hpp +/// @file grotto/gadgets/quantile/pareto.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `pareto`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/poisson.hpp b/include/grotto/gadgets/quantile/poisson.hpp index 4bacb23..037a0ff 100644 --- a/include/grotto/gadgets/quantile/poisson.hpp +++ b/include/grotto/gadgets/quantile/poisson.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/poisson.hpp +/// @file grotto/gadgets/quantile/poisson.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `poisson`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/powerlaw.hpp b/include/grotto/gadgets/quantile/powerlaw.hpp index 6b632cb..44b8973 100644 --- a/include/grotto/gadgets/quantile/powerlaw.hpp +++ b/include/grotto/gadgets/quantile/powerlaw.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/powerlaw.hpp +/// @file grotto/gadgets/quantile/powerlaw.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `powerlaw`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/quantile/standard_t.hpp b/include/grotto/gadgets/quantile/standard_t.hpp index fad084d..a58397b 100644 --- a/include/grotto/gadgets/quantile/standard_t.hpp +++ b/include/grotto/gadgets/quantile/standard_t.hpp @@ -1,6 +1,6 @@ -/// @file grotto/gadgets/quatile/standard_t.hpp +/// @file grotto/gadgets/quantile/standard_t.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `standard_t`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric.hpp b/include/grotto/gadgets/trigonometric.hpp index 69621b6..954b217 100644 --- a/include/grotto/gadgets/trigonometric.hpp +++ b/include/grotto/gadgets/trigonometric.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric.hpp /// @author Ryan Henry -/// @brief +/// @brief Includes the trigonometric gadget references. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/acos.hpp b/include/grotto/gadgets/trigonometric/acos.hpp index 0c5bd68..903c29c 100644 --- a/include/grotto/gadgets/trigonometric/acos.hpp +++ b/include/grotto/gadgets/trigonometric/acos.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/acos.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acos`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct acos +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) acos { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/acot.hpp b/include/grotto/gadgets/trigonometric/acot.hpp index 853bd0a..68d1b38 100644 --- a/include/grotto/gadgets/trigonometric/acot.hpp +++ b/include/grotto/gadgets/trigonometric/acot.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/acot.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acot`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/acsc.hpp b/include/grotto/gadgets/trigonometric/acsc.hpp index 24674cf..bb6cb9f 100644 --- a/include/grotto/gadgets/trigonometric/acsc.hpp +++ b/include/grotto/gadgets/trigonometric/acsc.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/acsc.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `acsc`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/asec.hpp b/include/grotto/gadgets/trigonometric/asec.hpp index f43d899..1ead1b2 100644 --- a/include/grotto/gadgets/trigonometric/asec.hpp +++ b/include/grotto/gadgets/trigonometric/asec.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/asec.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `asec`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/asin.hpp b/include/grotto/gadgets/trigonometric/asin.hpp index 7c6ea76..aaa76a7 100644 --- a/include/grotto/gadgets/trigonometric/asin.hpp +++ b/include/grotto/gadgets/trigonometric/asin.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/asin.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `asin`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct asin +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) asin { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/atan.hpp b/include/grotto/gadgets/trigonometric/atan.hpp index 247e818..22f4262 100644 --- a/include/grotto/gadgets/trigonometric/atan.hpp +++ b/include/grotto/gadgets/trigonometric/atan.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/atan.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `atan`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/cos.hpp b/include/grotto/gadgets/trigonometric/cos.hpp index 043979b..e87386b 100644 --- a/include/grotto/gadgets/trigonometric/cos.hpp +++ b/include/grotto/gadgets/trigonometric/cos.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/cos.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `cos`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct cos +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) cos { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/cot.hpp b/include/grotto/gadgets/trigonometric/cot.hpp index 8d038b9..3cb8955 100644 --- a/include/grotto/gadgets/trigonometric/cot.hpp +++ b/include/grotto/gadgets/trigonometric/cot.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/cot.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `cot`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct csc +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) cot { template T operator()(T x) @@ -33,7 +37,7 @@ struct csc }; template <> -struct gadget_hints +struct gadget_hints { static constexpr double min = 0; static constexpr double max = M_PI; @@ -45,6 +49,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/csc.hpp b/include/grotto/gadgets/trigonometric/csc.hpp index 33e91e8..6b2d9c3 100644 --- a/include/grotto/gadgets/trigonometric/csc.hpp +++ b/include/grotto/gadgets/trigonometric/csc.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/csc.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `csc`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct csc +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) csc { template T operator()(T x) @@ -45,6 +49,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/deg2rad.hpp b/include/grotto/gadgets/trigonometric/deg2rad.hpp index f7620c6..3aeb8bf 100644 --- a/include/grotto/gadgets/trigonometric/deg2rad.hpp +++ b/include/grotto/gadgets/trigonometric/deg2rad.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/deg2rad.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `deg2rad`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. diff --git a/include/grotto/gadgets/trigonometric/rad2deg.hpp b/include/grotto/gadgets/trigonometric/rad2deg.hpp index 5425251..563ac4a 100644 --- a/include/grotto/gadgets/trigonometric/rad2deg.hpp +++ b/include/grotto/gadgets/trigonometric/rad2deg.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/rad2deg.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `rad2deg`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -53,4 +53,4 @@ struct gadget_hints // radians = degrees / 180 * M_PI -// degrees = radians / M_PI * 180 \ No newline at end of file +// degrees = radians / M_PI * 180 diff --git a/include/grotto/gadgets/trigonometric/sec.hpp b/include/grotto/gadgets/trigonometric/sec.hpp index 90b6a08..56271b8 100644 --- a/include/grotto/gadgets/trigonometric/sec.hpp +++ b/include/grotto/gadgets/trigonometric/sec.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/sec.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sec`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sec +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sec { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/sin.hpp b/include/grotto/gadgets/trigonometric/sin.hpp index d3d7a41..3384d9f 100644 --- a/include/grotto/gadgets/trigonometric/sin.hpp +++ b/include/grotto/gadgets/trigonometric/sin.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/sin.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `sin`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct sin +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sin { template T operator()(T x) @@ -43,6 +47,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/gadgets/trigonometric/tan.hpp b/include/grotto/gadgets/trigonometric/tan.hpp index 0a8c1fb..24516ee 100644 --- a/include/grotto/gadgets/trigonometric/tan.hpp +++ b/include/grotto/gadgets/trigonometric/tan.hpp @@ -1,6 +1,6 @@ /// @file grotto/gadgets/trigonometric/tan.hpp /// @author Ryan Henry -/// @brief +/// @brief Cleartext reference for `tan`. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -20,7 +20,11 @@ namespace grotto namespace gadgets { -struct tan +HEDLEY_DIAGNOSTIC_PUSH +HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + + +struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) tan { template T operator()(T x) @@ -44,6 +48,8 @@ struct gadget_hints static constexpr std::array canonical_polys[] = { }; }; +HEDLEY_DIAGNOSTIC_POP + } // namespace gadgets } // namespace grotto diff --git a/include/grotto/hexfloat.hpp b/include/grotto/hexfloat.hpp index 212d28f..f9e4c70 100644 --- a/include/grotto/hexfloat.hpp +++ b/include/grotto/hexfloat.hpp @@ -1,7 +1,7 @@ /// @file grotto/hexfloat.hpp /// @author Ryan Henry /// @brief Hexadecimal floating-point formatting for IEEE doubles and wide fixed-point words. -/// @details +/// @details Prints IEEE doubles and wide fixed-point words in hexadecimal. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -225,9 +225,14 @@ inline int highest_bit(const std::uint64_t * limbs) noexcept return -1; } -/// Exact hexfloat of a two's-complement word whose binary point sits `fractional_bits` +/// @brief Exact hexfloat of a two's-complement word whose binary point sits `fractional_bits` /// below bit 0. The fraction keeps every bit below the leading 1, so a 256-bit word /// round-trips. +/// @tparam T value type +/// @param value the value to convert or store +/// @param fractional_bits the number of fractional bits +/// @return Exact hexfloat of a two's-complement word whose binary point sits `fractional_bits` +/// below bit 0 template std::string format_hexfloat(const T & value, int fractional_bits) { @@ -330,9 +335,14 @@ inline void shift_right_limbs(std::vector & limbs, unsigned shift limbs.swap(out); } -/// Parse `±0x.p±` into the low `width` bits of +/// @brief Parse `±0x.p±` into the low `width` bits of /// significand * 2^(exponent + fractional_bits), floored toward -inf on the /// discarded fraction and wrapped modulo 2^width. +/// @tparam T value type +/// @param text the `text` +/// @param fractional_bits the number of fractional bits +/// @return the returned `T` +/// @throws std::invalid_argument if `empty hexfloat` template T integer_from_hexfloat(std::string_view text, int fractional_bits) { diff --git a/include/grotto/nmod.hpp b/include/grotto/nmod.hpp index d344130..f29e04e 100644 --- a/include/grotto/nmod.hpp +++ b/include/grotto/nmod.hpp @@ -28,16 +28,23 @@ namespace nmod_detail using u128 = unsigned __int128; } +/// @brief Quotient and truncated fractional residue of one reduction. struct nmod_result { - /// `floor({x/M} * 2^residue_bits)`, in `[0, 2^residue_bits)`. + /// @brief `floor({x/M} * 2^residue_bits)`, in `[0, 2^residue_bits)`. std::int64_t residue = 0; - /// `floor(x/M)`. + /// @brief `floor(x/M)`. std::int64_t quotient = 0; }; -/// `x_raw / 2^x_bits` modulo `M`, with `1/M ≈ recip_raw / 2^recip_bits`. -/// `recip_raw` is a positive magnitude of at most 128 bits. +/// @brief `x_raw / 2^x_bits` modulo `M`, with `1/M ≈ recip_raw / 2^recip_bits`. +/// @details `recip_raw` is a positive magnitude of at most 128 bits. +/// @param x_raw the signed integer significand +/// @param x_bits the fractional width of `x_raw` +/// @param recip_raw the positive magnitude of the rounded reciprocal +/// @param recip_bits the fractional width of `recip_raw` +/// @param residue_bits the fractional bits kept in the residue +/// @return `x_raw / 2^x_bits` modulo `M`, with `1/M ≈ recip_raw / 2^recip_bits` /// @throws std::invalid_argument if the reciprocal is zero or a width is illegal. /// @throws std::overflow_error if the quotient does not fit in `int64_t`. HEDLEY_WARN_UNUSED_RESULT @@ -174,9 +181,14 @@ inline nmod_result nmod(std::int64_t x_raw, unsigned x_bits, return out; } -/// Modulus `2^{-exp}` by an exact shift. Positive `exp` multiplies by +/// @brief Modulus `2^{-exp}` by an exact shift. Positive `exp` multiplies by /// `2^exp` (`exp`'s `2^{-13}` split). Zero splits at the integer (`2^x`). -/// Negative `exp` is a modulus above one. +/// @details Negative `exp` is a modulus above one. +/// @param x_raw the signed integer significand +/// @param x_bits the fractional width of `x_raw` +/// @param exp the power-of-two exponent +/// @param residue_bits the fractional bits kept in the residue +/// @return Modulus `2^{-exp}` by an exact shift /// @throws std::overflow_error if `exp` does not fit the reciprocal width. HEDLEY_WARN_UNUSED_RESULT inline nmod_result nmod_pow2(std::int64_t x_raw, unsigned x_bits, int exp, diff --git a/include/grotto/offset_horner.hpp b/include/grotto/offset_horner.hpp index a92ce42..cd425f1 100644 --- a/include/grotto/offset_horner.hpp +++ b/include/grotto/offset_horner.hpp @@ -60,7 +60,8 @@ T offset_horner_group_sub(T a, T b) noexcept return static_cast(static_cast(static_cast(a) - static_cast(b))); } -/// `eta = x - r` and `center = 2r`, both in the input group. +/// @brief `eta = x - r` and `center = 2r`, both in the input group. +/// @tparam T value type template struct offset_horner_x_plus_r { @@ -218,8 +219,11 @@ T domain_min() noexcept return T{0}; } -/// Public center-space cut where `center + eta` crosses the domain end. -/// Empty when that cut is outside the domain, including `eta == 0`. +/// @brief Public center-space cut where `center + eta` crosses the domain end. +/// @details Empty when that cut is outside the domain, including `eta == 0`. +/// @tparam T value type +/// @param eta the `eta` +/// @return Public center-space cut where `center + eta` crosses the domain end template HEDLEY_NO_THROW std::optional carry_threshold(T eta) noexcept @@ -246,7 +250,11 @@ std::optional carry_threshold(T eta) noexcept } } -/// `center + kappa` is the wrapped representative, as a mathematical integer. +/// @brief `center + kappa` is the wrapped representative, as a mathematical integer. +/// @tparam T value type +/// @param left the `left` +/// @param eta the `eta` +/// @return `center + kappa` is the wrapped representative, as a mathematical integer template HEDLEY_NO_THROW int64_t kappa_for(T left, T eta) noexcept @@ -363,9 +371,14 @@ std::vector> prepare_pieces( return out; } -/// `out[k]` sums to the polynomial at the wrapped input. It is +/// @brief `out[k]` sums to the polynomial at the wrapped input. It is /// `center^k` times the public binomial coefficient of `kappa`, not a /// coefficient you Horner-evaluate at `eta`. +/// @tparam Degree degree +/// @param seg the `seg` +/// @param coeff the public coefficient +/// @param kappa the `kappa` +/// @return `out[k]` sums to the polynomial at the wrapped input template std::array contributions( const std::array, Degree + 1> & seg, @@ -386,7 +399,9 @@ std::array contributions( } // namespace offset_horner_detail -/// Both parties' comparison keys and wrap-piece shares for one center. +/// @brief Both parties' comparison keys and wrap-piece shares for one center. +/// @tparam InputT input domain type +/// @tparam Degree degree template struct offset_horner_keys { @@ -398,9 +413,9 @@ struct offset_horner_keys using key_pair = decltype(dpf::make_dpf(std::declval(), dpf::gt(uint64_t{0}))); InputT center{}; - /// `keys[m]` is `gt(center^m)` keyed at `center`. `.first` is party 0. + /// @brief `keys[m]` is `gt(center^m)` keyed at `center`. `.first` is party 0. std::array keys; - /// Random additive split of `center^m`, indexed `[power][party]`. + /// @brief Random additive split of `center^m`, indexed `[power][party]`. std::array, Degree + 1> wrap_share{}; }; @@ -424,9 +439,17 @@ offset_horner_keys make_offset_horner_keys(InputT center) return mat; } -/// Cleartext binomial coefficients of the selected refined piece in the +/// @brief Cleartext binomial coefficients of the selected refined piece in the /// variable `center`: Horner at `lift(center)` is the polynomial at the /// wrapped input. +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @param center the `center` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param eta the `eta` +/// @return Cleartext binomial coefficients of the selected refined piece in the variable `center`: +/// Horner at `lift(center)` is the polynomial at the wrapped input template std::array offset_horner_clear_coefficients( InputT center, @@ -447,7 +470,14 @@ std::array offset_horner_clear_coefficients( piece.coeff, static_cast(piece.kappa)); } -/// Cleartext value of the selected piece at the wrapped `center + eta`. +/// @brief Cleartext value of the selected piece at the wrapped `center + eta`. +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @param center the `center` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param eta the `eta` +/// @return Cleartext value of the selected piece at the wrapped `center + eta` template uint64_t offset_horner_clear( InputT center, @@ -459,7 +489,18 @@ uint64_t offset_horner_clear( return offset_horner_detail::horner_at(c, offset_horner_detail::lift(center)); } -/// `Party` selects `.first` or `.second` of each key pair. +/// @brief `Party` selects `.first` or `.second` of each key pair. +/// @tparam Party party index, `0` or `1` +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @tparam KeyPair key pair +/// @param keys the party keys +/// @param wrap_share the `wrap_share` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param eta the `eta` +/// @return `Party` selects `.first` or `.second` of each key pair +/// @throws std::invalid_argument if `one comparison key per power` template std::array offset_horner_coefficient_share( const std::vector & keys, @@ -492,7 +533,15 @@ std::array offset_horner_coefficient_share( return contributions(seg, ordered, kappa); } -/// One party's coefficient shares. `Party` is 0 or 1. +/// @brief One party's coefficient shares. `Party` is 0 or 1. +/// @tparam Party party index, `0` or `1` +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @param mat the `mat` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param eta the `eta` +/// @return One party's coefficient shares template std::array offset_horner_coefficient_share( const offset_horner_keys & mat, @@ -506,10 +555,12 @@ std::array offset_horner_coefficient_share( keys, mat.wrap_share, knots, coeff, eta); } -/// Both parties' Horner shares from one joint Doerner–Shelat generation. -/// `center0 XOR center1` is the comparison point, in geneval's share +/// @brief Both parties' Horner shares from one joint Doerner–Shelat generation. +/// @details `center0 XOR center1` is the comparison point, in geneval's share /// convention (the signed MSB of `center0` is flipped before the XOR, and /// flipped back here). `eta` is already public. +/// @tparam Degree degree +/// @tparam InputT input domain type template struct geneval_offset_horner_result { @@ -523,8 +574,12 @@ struct geneval_offset_horner_result uint64_t value1 = 0; }; -/// Logical comparison point for geneval's XOR shares. Matches `make_dpf(P)` +/// @brief Logical comparison point for geneval's XOR shares. Matches `make_dpf(P)` /// when `center1 = P XOR center0` or when `center0 = P` and `center1 = 0`. +/// @tparam InputT input domain type +/// @param center0 the `center0` +/// @param center1 the `center1` +/// @return Logical comparison point for geneval's XOR shares template InputT geneval_offset_horner_center(InputT center0, InputT center1) { @@ -602,12 +657,22 @@ geneval_offset_horner_result geneval_at( } // namespace offset_horner_detail -/// Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`. -/// Comparison keys are opened with the same local Doerner–Shelat protocol +/// @brief Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`. +/// @details Comparison keys are opened with the same local Doerner–Shelat protocol /// geneval uses for its correction words. The value dot uses the per-piece /// carry shift and is local. /// A value-correction word is required on every level of the secret path, so /// this does not stop early the way a leaf trie does. +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @tparam Rng rng +/// @param center0 the `center0` +/// @param center1 the `center1` +/// @param eta the `eta` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param rng the Doerner–Shelat randomness tapes +/// @return Geneval-style offset Horner template @@ -629,13 +694,27 @@ geneval_offset_horner_result geneval_offset_horner( const std::vector> & coeff) { using block = typename dpf::prg::aes128::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{ dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) return geneval_offset_horner( center0, center1, eta, knots, coeff, std::move(rng)); } -/// Additive shares of the center: `center0 + center1` is the comparison point. +/// @brief Additive shares of the center: `center0 + center1` is the comparison point. +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @tparam Rng rng +/// @param arith_input_t the `arith_input_t` +/// @param center0 the `center0` +/// @param center1 the `center1` +/// @param eta the `eta` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param rng the Doerner–Shelat randomness tapes +/// @return Additive shares of the center: `center0 + center1` is the comparison point template @@ -650,10 +729,21 @@ geneval_offset_horner_result geneval_offset_horner( true, center0, center1, center, eta, knots, coeff, std::move(rng)); } -/// Additive shares of the input `x` and the mask `r`. Reconstructs +/// @brief Additive shares of the input `x` and the mask `r`. Reconstructs /// `eta = x - r` and passes additive shares of `center = 2r` (`2·r0`, `2·r1`) /// to arithmetic `geneval_cmp`. Returns both parties' Horner shares of the /// cubic at `x + r` (the group element `x + r`). +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @tparam Rng rng +/// @param x0 the `x0` +/// @param x1 the `x1` +/// @param r0 the party 0's share of the mask +/// @param r1 the party 1's share of the mask +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param rng the Doerner–Shelat randomness tapes +/// @return Additive shares of the input `x` and the mask `r` template @@ -680,14 +770,25 @@ geneval_offset_horner_result geneval_offset_horner( const std::vector> & coeff) { using block = typename dpf::prg::aes128::block_type; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{ dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) return geneval_offset_horner( x0, x1, r0, r1, knots, coeff, std::move(rng)); } -/// One party's share of the cubic at the wrapped `center + eta`. -/// Sum the coefficient shares; they are already scaled by `center^k`. +/// @brief One party's share of the cubic at the wrapped `center + eta`. +/// @details Sum the coefficient shares; they are already scaled by `center^k`. +/// @tparam Party party index, `0` or `1` +/// @tparam Degree degree +/// @tparam InputT input domain type +/// @param mat the `mat` +/// @param knots the `knots` +/// @param coeff the public coefficient +/// @param eta the `eta` +/// @return One party's share of the cubic at the wrapped `center + eta` template uint64_t offset_horner_eval( const offset_horner_keys & mat, diff --git a/include/grotto/offset_iterable.hpp b/include/grotto/offset_iterable.hpp index aaa1be8..1db8115 100644 --- a/include/grotto/offset_iterable.hpp +++ b/include/grotto/offset_iterable.hpp @@ -1,7 +1,7 @@ /// @file grotto/offset_iterable.hpp /// @author Ryan Henry -/// @brief defines `dpf::rotated_iterable` and associated helpers -/// @details +/// @brief A sorted range shifted by a public offset, viewed through `rotation_iterable`. +/// @details The rotation starts at the first entry strictly greater than the offset. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -44,7 +44,7 @@ struct offset_iterable HEDLEY_ALWAYS_INLINE constexpr offset_iterable(wrapped_iterator begin, wrapped_iterator end, size_type offset) - : rotated_iterable_{begin, end, std::distance(begin, + : rotation_{begin, end, std::distance(begin, std::upper_bound(begin, end, offset, std::less_equal{}))}, offset_{offset} { @@ -54,13 +54,13 @@ struct offset_iterable HEDLEY_ALWAYS_INLINE constexpr value_type operator[](size_type index) const { - return rotated_iterable_[index] - offset_; + return rotation_[index] - offset_; } HEDLEY_ALWAYS_INLINE constexpr auto rotation() const { - return rotated_iterable_; + return rotation_; } HEDLEY_ALWAYS_INLINE @@ -71,11 +71,11 @@ struct offset_iterable iterator begin() { - return iterator{std::begin(rotated_iterable_), offset_}; + return iterator{std::begin(rotation_), offset_}; } const_iterator begin() const { - return const_iterator{std::begin(rotated_iterable_), offset_}; + return const_iterator{std::begin(rotation_), offset_}; } constexpr const_iterator cbegin() const { @@ -84,12 +84,12 @@ struct offset_iterable iterator end() { - return iterator{std::end(rotated_iterable_), offset_}; + return iterator{std::end(rotation_), offset_}; } const_iterator end() const { - return const_iterator{std::end(rotated_iterable_), offset_}; + return const_iterator{std::end(rotation_), offset_}; } constexpr const_iterator cend() const @@ -98,7 +98,7 @@ struct offset_iterable } private: - dpf::rotation_iterable rotated_iterable_; + dpf::rotation_iterable rotation_; size_type offset_; }; diff --git a/include/grotto/prefix_parity.hpp b/include/grotto/prefix_parity.hpp index 0985f5e..21968ec 100644 --- a/include/grotto/prefix_parity.hpp +++ b/include/grotto/prefix_parity.hpp @@ -1,7 +1,6 @@ /// @file grotto/prefix_parity.hpp /// @author Ryan Henry -/// @brief -/// @details +/// @brief Prefix-parity and signed-prefix shares from a comparison key. /// @copyright Copyright (c) 2019-2023 Ryan Henry and others /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref GPLv2) for details. @@ -77,8 +76,11 @@ static auto prefix_parities(const DpfKey & dpf, const std::array; + HEDLEY_PRAGMA(GCC diagnostic pop) constexpr std::size_t align = std::max( DpfKey::lg_outputs_per_leaf, leaf_bit_lg); const std::size_t tz = dpf::utils::countr_zero{}(current_endpoint); @@ -103,7 +105,7 @@ static auto prefix_parities(const DpfKey & dpf, const std::array> shift; const bool bit = !!(bit_mask & tx); path[level] = DpfKey::traverse_interior(path[level - 1], - dpf.correction_word(level - 1, bit), bit); + dpf.correction_word(level - 1, bit), bit, + DpfKey::tree::is_last_level(level - 1, DpfKey::depth)); } dpf::detail::path_note_filled_to(path, nbits); @@ -341,8 +352,15 @@ static auto signed_prefix_parities(const DpfKey & dpf, } } -/// Runtime-length form of `signed_prefix_parities`. `out[i]` receives the same +/// @brief Runtime-length form of `signed_prefix_parities`. `out[i]` receives the same /// share a one-element call would return for `endpoints[i]`. +/// @tparam InputT input domain type +/// @tparam DpfKey DPF key type +/// @param dpf the DPF key +/// @param endpoints the `endpoints` +/// @param n the `n` +/// @param out the output buffer +/// @throws std::invalid_argument if `key has no comparison channel` template static void signed_prefix_parities_into(const DpfKey & dpf, @@ -414,7 +432,8 @@ static void signed_prefix_parities_into(const DpfKey & dpf, const auto bit_mask = key_type::msb_mask >> shift; const bool bit = !!(bit_mask & tx); path[level] = DpfKey::traverse_interior(path[level - 1], - dpf.correction_word(level - 1, bit), bit); + dpf.correction_word(level - 1, bit), bit, + DpfKey::tree::is_last_level(level - 1, DpfKey::depth)); } dpf::detail::path_note_filled_to(path, nbits); @@ -448,12 +467,20 @@ static void signed_prefix_parities_into(const DpfKey & dpf, } } -/// One-hot segment shares for a unit `gt` comparison (`if_true = 1`, `if_false = 0`). +/// @brief One-hot segment shares for a unit `gt` comparison (`if_true = 1`, `if_false = 0`). /// /// `endpoints` is sorted ascending. Piece `i < n-1` is `[endpoints[i], endpoints[i+1])` /// and the last piece wraps. Party 0 + party 1 is `1` on the piece that contains /// the key's target and `0` on the others, so a public dot product with LUT /// constants is an additive share of the function value (not of its negation). +/// @tparam InputT input domain type +/// @tparam DpfKey DPF key type +/// @tparam NumParts num parts +/// @tparam input_type input type +/// @param dpf the DPF key +/// @param endpoints the `endpoints` +/// @return One-hot segment shares for a unit `gt` comparison (`if_true = 1`, `if_false = 0`) +/// @throws std::invalid_argument if `key has no comparison channel` template (n_bin)); } -inline std::int64_t fractional_raw(std::int64_t raw, unsigned fractional_bits, std::int64_t & whole) +HEDLEY_NO_THROW +constexpr std::int64_t fractional_raw(std::int64_t raw, unsigned fractional_bits, + std::int64_t & whole) noexcept { const std::int64_t one = one_raw(fractional_bits); std::int64_t q = raw / one; @@ -303,8 +314,13 @@ struct angle std::int64_t frac_raw; }; -/// `{ |x| * multiplier }` at this precision, with the integer part reduced +/// @brief `{ |x| * multiplier }` at this precision, with the integer part reduced /// only as far as the low bits the quadrant logic reads. +/// @param fractional_bits the number of fractional bits +/// @param raw the underlying integer +/// @param multiplier_64 the `multiplier_64` +/// @return `{ |x| * multiplier }` at this precision, with the integer part reduced only as far as +/// the low bits the quadrant logic reads inline angle reduce_positive(unsigned fractional_bits, std::int64_t raw, u128 multiplier_64) { const u128 scaled = magnitude_of(raw) * multiplier_64; @@ -480,7 +496,10 @@ inline hyp sinh_cosh(unsigned fractional_bits, std::int64_t raw) return hyp{sh, ch}; } -/// `ln(2^{k+1} ± 1) / 2`, the saturation threshold used by `tanh` and `coth`. +/// @brief `ln(2^{k+1} ± 1) / 2`, the saturation threshold used by `tanh` and `coth`. +/// @param fractional_bits the number of fractional bits +/// @param plus the `plus` +/// @return `ln(2^{k+1} ± 1) / 2`, the saturation threshold used by `tanh` and `coth` inline std::int64_t beta_raw(unsigned fractional_bits, bool plus) { u128 ln = u128{fractional_bits + 1} * ln2_64; @@ -492,11 +511,141 @@ inline std::int64_t beta_raw(unsigned fractional_bits, bool plus) return round_mag(ln, 65u - fractional_bits, false); } -inline int half_pow_of(int power) +HEDLEY_CONST +HEDLEY_NO_THROW +constexpr int half_pow_of(int power) noexcept { return (power & 1) != 0 ? (power - 1) / 2 : power / 2; } +inline __int128 div_round_i128(__int128 num, int den) +{ + const bool neg = num < 0; + const auto mag = static_cast(neg ? -num : num); + const auto d = static_cast(den); + const u128 quot = (mag + d / 2) / d; + return neg ? -static_cast<__int128>(quot) : static_cast<__int128>(quot); +} + +inline __int128 shr_round_i128(__int128 num, unsigned shift) +{ + if (shift == 0) + return num; + const bool neg = num < 0; + auto mag = static_cast(neg ? -num : num); + mag = (mag + (u128{1} << (shift - 1))) >> shift; + return neg ? -static_cast<__int128>(mag) : static_cast<__int128>(mag); +} + +/// @brief `expm1` on `|x| < ln 2`, summed at `k+48` fractional bits. +/// @param fractional_bits the number of fractional bits +/// @param raw the underlying integer +/// @return `expm1` on `|x| < ln 2`, summed at `k+48` fractional bits +inline std::int64_t expm1_series(unsigned fractional_bits, std::int64_t raw) +{ + constexpr unsigned extra = 48; + __int128 power = static_cast<__int128>(raw) << extra; + __int128 acc = 0; + for (int n = 1; n <= 24; ++n) + { + const __int128 term = div_round_i128(power, n); + acc += term; + power = shr_round_i128(term * static_cast<__int128>(raw), fractional_bits); + if (power == 0) + break; + } + return round_i128(acc, extra); +} + +inline std::int64_t eval_expm1(unsigned fractional_bits, std::int64_t raw) +{ + if (raw == 0) + return 0; + // Match `exp`: precisions below the 2^{-13} reduction evaluate one + // scale up and round once, so the power-of-two lift is not rounded early. + if (fractional_bits < 13) + { + const int lift = static_cast(16u - fractional_bits); + const __int128 lifted_arg = static_cast<__int128>(raw) << lift; + if (lifted_arg > INT64_MAX || lifted_arg < INT64_MIN) + throw std::overflow_error("range lut: exponent overflow"); + const std::int64_t lifted = eval_expm1(16, static_cast(lifted_arg)); + return round_i128(lifted, static_cast(lift)); + } + const std::int64_t ln2 = ln2_raw(fractional_bits); + if (raw > -ln2 && raw < ln2) + return expm1_series(fractional_bits, raw); + + std::int64_t n_bin = raw / ln2; + std::int64_t remainder = raw - n_bin * ln2; + if (remainder < 0) + { + remainder += ln2; + --n_bin; + } + while (remainder >= ln2) + { + remainder -= ln2; + ++n_bin; + } + const std::int64_t exp_r = eval_exp_at_scale(fractional_bits, remainder); + const std::int64_t one = one_raw(fractional_bits); + if (n_bin >= 0) + { + const __int128 wide = static_cast<__int128>(shift_pow2(exp_r, static_cast(n_bin))) - one; + if (wide > INT64_MAX || wide < INT64_MIN) + throw std::overflow_error("range lut: exponent overflow"); + return static_cast(wide); + } + const int places = static_cast(-n_bin); + if (places > static_cast(fractional_bits) + 1) + return -one; + return shift_pow2(exp_r, -places) - one; +} + +/// @brief `log1p` on `|x| <= 1/2`. Every term of a negative argument is negative. +/// @param fractional_bits the number of fractional bits +/// @param raw the underlying integer +/// @return `log1p` on `|x| <= 1/2` +inline std::int64_t log1p_series(unsigned fractional_bits, std::int64_t raw) +{ + constexpr unsigned extra = 48; + const bool xneg = raw < 0; + const std::int64_t mag_raw = xneg ? -raw : raw; + __int128 power = static_cast<__int128>(mag_raw) << extra; + __int128 acc = 0; + for (int n = 1; n <= 80; ++n) + { + const __int128 term = div_round_i128(power, n); + const bool neg = xneg || (n % 2 == 0); + acc += neg ? -term : term; + power = shr_round_i128(power * static_cast<__int128>(mag_raw), fractional_bits); + if (power == 0) + break; + } + return round_i128(acc, extra); +} + +inline std::int64_t eval_log1p(unsigned fractional_bits, std::int64_t raw) +{ + const std::int64_t one = one_raw(fractional_bits); + if (raw == 0) + return 0; + if (raw <= -one) + throw std::domain_error("range lut: log1p argument is <= -1"); + const std::int64_t half = one >> 1; + if (raw >= -half && raw <= half) + return log1p_series(fractional_bits, raw); + if (raw > INT64_MAX - one) + { + const std::int64_t ln_x = eval_ln_positive(fractional_bits, raw); + const u128 num = u128{1} << (2u * fractional_bits); + const auto corr = static_cast((num + static_cast(raw) / 2) / static_cast(raw)); + return ln_x + corr; + } + return eval_ln_positive(fractional_bits, one + raw); +} + } // namespace range_detail HEDLEY_WARN_UNUSED_RESULT @@ -667,6 +816,10 @@ inline std::int64_t eval_reduced(reduced which, unsigned fractional_bits, std::i principal::invsq, fractional_bits, part.mantissa_raw); return shift_pow2(square, -2 * part.power); } + case reduced::expm1: + return eval_expm1(fractional_bits, raw); + case reduced::log1p: + return eval_log1p(fractional_bits, raw); } throw std::invalid_argument("range lut: unknown map"); } diff --git a/include/grotto/window_lut.hpp b/include/grotto/window_lut.hpp index 9c8d386..0775d6c 100644 --- a/include/grotto/window_lut.hpp +++ b/include/grotto/window_lut.hpp @@ -151,8 +151,11 @@ inline unsigned __int128 isqrt_floor(unsigned __int128 n) return x; } -/// `round(sqrt(v / 2^{k+1}) * 2^{k+extra})`, `v > 0`. Eight extra bits so the +/// @brief `round(sqrt(v / 2^{k+1}) * 2^{k+extra})`, `v > 0`. Eight extra bits so the /// half-angle identity can absorb the square root before the final rounding. +/// @param fractional_bits the number of fractional bits +/// @param magnitude the magnitude +/// @return `round(sqrt(v / 2^{k+1}) * 2^{k+extra})`, `v > 0` inline std::int64_t sqrt_half_scale_fine(unsigned fractional_bits, std::int64_t magnitude) { constexpr unsigned extra = 8; @@ -210,7 +213,7 @@ inline std::int64_t eval_asin_abs(unsigned fractional_bits, std::int64_t magnitu return out < 0 ? 0 : out; } -/// Surplus fractional bits on probit-tail knots. `u = ln(p)` is stored as +/// @brief Surplus fractional bits on probit-tail knots. `u = ln(p)` is stored as /// `round(u * 2^{k+probit_tail_extra})`. inline constexpr unsigned probit_tail_extra = 10; @@ -227,7 +230,10 @@ static constexpr std::uint64_t probit_ln_anchor_mag[32] = { 1190525582320469641ull, 885613779509420443ull, 585660112482476600ull, 290505910572683730ull, }; -/// `round_half_away(ln(probability / 2^k) * 2^{k+10})`. +/// @brief `round_half_away(ln(probability / 2^k) * 2^{k+10})`. +/// @param fractional_bits the number of fractional bits +/// @param probability the `probability` +/// @return `round_half_away(ln(probability / 2^k) * 2^{k+10})` inline std::int64_t probit_ln_argument(unsigned fractional_bits, std::int64_t probability) { const auto bits = static_cast(probability); @@ -253,7 +259,14 @@ inline std::int64_t probit_ln_argument(unsigned fractional_bits, std::int64_t pr return round_half_away_i128(ln_x, 54u - fractional_bits); } -/// Horner, then one extra right shift so a tail argument at scale `k+10` rounds onto scale `k`. +/// @brief Horner, then one extra right shift so a tail argument at scale `k+10` rounds onto scale `k`. +/// @param piece the `piece` +/// @param q the `q` +/// @param raw the underlying integer +/// @param fractional_bits the number of fractional bits +/// @param extra_shift the `extra_shift` +/// @return Horner, then one extra right shift so a tail argument at scale `k+10` rounds onto scale +/// `k` inline std::int64_t eval_cubic_extra( const cubic_bits & piece, unsigned q, std::int64_t raw, unsigned fractional_bits, unsigned extra_shift) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2fe7923..83b0869 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,6 +63,13 @@ find_package(Threads REQUIRED) target_link_libraries(random_test Threads::Threads) add_executable(prg_lowmc_test tests/prg_lowmc_test.cpp) add_executable(prg_chacha_test tests/prg_chacha_test.cpp) +add_executable(prg_aes_ccr_test tests/prg_aes_ccr_test.cpp) +add_executable(half_tree_test tests/half_tree_test.cpp) +add_executable(constrained_cmp_test tests/constrained_cmp_test.cpp) +add_executable(fp61_test tests/fp61_test.cpp) +add_executable(arith_payload_test tests/arith_payload_test.cpp) +add_executable(verifiable_test tests/verifiable_test.cpp) +add_executable(multipoint_test tests/multipoint_test.cpp) add_executable(secret_share_test tests/secret_share_test.cpp) add_executable(beaver_test tests/beaver_test.cpp) add_executable(constant_lut_test tests/constant_lut_test.cpp) @@ -108,6 +115,13 @@ gtest_discover_tests(context_blast_test) gtest_discover_tests(random_test) gtest_discover_tests(prg_lowmc_test) gtest_discover_tests(prg_chacha_test) +gtest_discover_tests(prg_aes_ccr_test) +gtest_discover_tests(half_tree_test) +gtest_discover_tests(constrained_cmp_test) +gtest_discover_tests(fp61_test) +gtest_discover_tests(arith_payload_test) +gtest_discover_tests(verifiable_test) +gtest_discover_tests(multipoint_test) gtest_discover_tests(secret_share_test) gtest_discover_tests(beaver_test) gtest_discover_tests(constant_lut_test) @@ -121,4 +135,6 @@ gtest_discover_tests(window_lut_test) gtest_discover_tests(offset_horner_test) add_executable(ic_test tests/ic_test.cpp) gtest_discover_tests(ic_test) +add_executable(wide_payload_test tests/wide_payload_test.cpp) +gtest_discover_tests(wide_payload_test) gtest_discover_tests(corner_gaps_test) diff --git a/test/tests/arith_payload_test.cpp b/test/tests/arith_payload_test.cpp new file mode 100644 index 0000000..a294b46 --- /dev/null +++ b/test/tests/arith_payload_test.cpp @@ -0,0 +1,370 @@ +#include + +#include "dpf.hpp" + +#include +#include + +namespace +{ + +simde__m128i g_roots[16]; +int g_ri = 0; +simde__m128i take_root() { return g_roots[g_ri++]; } + +struct Pad +{ + uint64_t n = 1; + simde__m128i block() + { + auto v = simde_mm_set_epi64x(static_cast(n), + static_cast(n * 9 + 3)); + n += 2; + return v; + } + uint8_t bit() { return static_cast(n++ & 1u); } +}; + +void reset_roots() +{ + g_ri = 0; + for (int i = 0; i < 16; ++i) + g_roots[i] = simde_mm_set_epi64x(0x2222 * (i + 1), 0xBEEF0000u + i * 13); +} + +template +T bare(const T & v) +{ + return v; +} + +template +T bare(const dpf::secret_share & s) +{ + return s.raw(); +} + +template +auto recon(const A & a, const B & b) +{ + using T = decltype(bare(a)); + return static_cast(bare(a) - bare(b)); +} + +template +auto ev(const Key & key, In x) +{ + return bare(*dpf::eval_point(key, x)); +} + +} // namespace + +TEST(ArithPayload, DsXorIndexMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint32_t; + const in_t alpha = 0x2a; + const in_t x0 = 0x55; + const in_t x1 = static_cast(alpha ^ x0); + const out_t beta = 0x01020304; + const out_t y0 = 0x00010002; + const out_t y1 = static_cast(beta - y0); + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_output, x0, x1, rng, y0, y1); + + using key_t = std::decay_t; + EXPECT_EQ(std::memcmp(&dealer.first.root(), &ds.first.root(), + sizeof(simde__m128i)), 0); + EXPECT_EQ(std::memcmp(&dealer.second.root(), &ds.second.root(), + sizeof(simde__m128i)), 0); + for (std::size_t i = 0; i < key_t::depth; ++i) + { + EXPECT_EQ(std::memcmp(&dealer.first.correction_word(i), + &ds.first.correction_word(i), sizeof(simde__m128i)), 0) + << "cw " << i; + EXPECT_EQ(dealer.first.correction_advice(i), + ds.first.correction_advice(i)) + << "advice " << i; + } + EXPECT_EQ(std::memcmp(&dealer.first.leaf(), &ds.first.leaf(), + sizeof(dealer.first.leaf())), 0); + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)), + q == alpha ? beta : out_t{}) + << i; + EXPECT_EQ(ev(ds.first, q), ev(dealer.first, q)) << "p0 " << i; + EXPECT_EQ(ev(ds.second, q), ev(dealer.second, q)) << "p1 " << i; + } +} + +TEST(ArithPayload, DsArithIndexMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0xc0; + const in_t x0 = 0x40; + const in_t x1 = static_cast(alpha - x0); + const out_t beta = 9; + const out_t y0 = 3; + const out_t y1 = 6; + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_input, dpf::arith_output, + x0, x1, rng, y0, y1); + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)), + q == alpha ? beta : out_t{}) + << i; + EXPECT_EQ(ev(ds.first, q), ev(dealer.first, q)) << "p0 " << i; + EXPECT_EQ(ev(ds.second, q), ev(dealer.second, q)) << "p1 " << i; + } +} + +TEST(ArithPayload, GenevalXorSharedBeta) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x33; + const in_t x0 = 0x0f; + const in_t x1 = static_cast(alpha ^ x0); + const out_t beta = 0x77; + const out_t y0 = 0x10; + const out_t y1 = static_cast(beta - y0); + + reset_roots(); + auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness g_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto g = dpf::geneval_point(dpf::arith_output, x0, x1, alpha, g_rng, y0, y1); + + EXPECT_TRUE(g.leaf_live); + ASSERT_EQ(g.party0.size(), 1u); + EXPECT_EQ(recon(g.party0[0], g.party1[0]), beta); + EXPECT_EQ(g.party0[0], ev(keys.first, alpha)); + EXPECT_EQ(g.party1[0], ev(keys.second, alpha)); +} + +TEST(ArithPayload, GenevalArithSharedBeta) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x90; + const in_t x0 = 0x20; + const in_t x1 = static_cast(alpha - x0); + const out_t beta = 3; + const out_t y0 = 1; + const out_t y1 = 2; + + reset_roots(); + auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness g_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto g = dpf::geneval_point(dpf::arith_input, dpf::arith_output, x0, x1, + alpha, g_rng, y0, y1); + + EXPECT_TRUE(g.leaf_live); + ASSERT_EQ(g.party0.size(), 1u); + EXPECT_EQ(recon(g.party0[0], g.party1[0]), beta); + EXPECT_EQ(g.party0[0], ev(keys.first, alpha)); + EXPECT_EQ(g.party1[0], ev(keys.second, alpha)); +} + +TEST(ArithPayload, XorWrapperSharesMatchDealer) +{ + using in_t = std::uint8_t; + using out_t = dpf::xor_wrapper; + const in_t alpha = 0x11; + const in_t x0 = 0x55; + const in_t x1 = static_cast(alpha ^ x0); + const out_t beta{0x0a0b0c0du}; + const out_t y0{0x01020304u}; + const out_t y1 = beta ^ y0; + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_output, x0, x1, rng, y0, y1); + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + const auto got = dpf::reconstruct(*dpf::eval_point(ds.first, q), + *dpf::eval_point(ds.second, q)); + const auto expect = dpf::reconstruct(*dpf::eval_point(dealer.first, q), + *dpf::eval_point(dealer.second, q)); + EXPECT_EQ(got, expect) << i; + EXPECT_EQ(got, q == alpha ? beta : out_t{}) << i; + } + + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness g_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto g = dpf::geneval_point(dpf::arith_output, x0, x1, alpha, g_rng, y0, y1); + EXPECT_TRUE(g.leaf_live); + ASSERT_EQ(g.party0.size(), 1u); + EXPECT_EQ(g.party0[0] ^ g.party1[0], beta); +} + +TEST(ArithPayload, MultiBlockUint256MatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = uint256_t; + const in_t alpha = 0x2a; + const in_t x0 = 0x0f; + const in_t x1 = static_cast(alpha ^ x0); + const out_t beta = (out_t{1} << 200) + out_t{0xabcdefu}; + const out_t y0 = (out_t{1} << 180) + out_t{0x1111u}; + const out_t y1 = beta - y0; + + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + EXPECT_GT((dpf::block_length_of_leaf_v), 1u); + HEDLEY_PRAGMA(GCC diagnostic pop) + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + beta); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_output, x0, x1, rng, y0, y1); + + EXPECT_EQ(std::memcmp(&dealer.first.leaf(), &ds.first.leaf(), + sizeof(dealer.first.leaf())), 0); + + for (int i = 0; i < 256; i += 17) + { + const in_t q = static_cast(i); + EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)), + q == alpha ? beta : out_t{}) + << i; + EXPECT_EQ(ev(ds.first, q), ev(dealer.first, q)) << "p0 " << i; + EXPECT_EQ(ev(ds.second, q), ev(dealer.second, q)) << "p1 " << i; + } +} + +TEST(ArithPayload, IncrementalMultiSlotArithBeta) +{ + using in_t = std::uint8_t; + const in_t alpha = 0x44; + const in_t x0 = 0x12; + const in_t x1 = static_cast(alpha ^ x0); + const std::uint16_t b0 = 0x1111; + const std::uint16_t b1 = 0x2222; + const std::uint16_t y00 = 0x0100; + const std::uint16_t y01 = static_cast(b0 - y00); + const std::uint16_t y10 = 0x0003; + const std::uint16_t y11 = static_cast(b1 - y10); + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + dpf::at<8>(b0, b1)); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_output, x0, x1, rng, + dpf::at<8>(dpf::arith_beta{y00, y01}, + dpf::arith_beta{y10, y11})); + + auto r0 = [&](auto & k0, auto & k1, in_t q) { + return recon(*dpf::eval_point(dpf::out<0, 8>, k0, q), + *dpf::eval_point(dpf::out<0, 8>, k1, q)); + }; + auto r1 = [&](auto & k0, auto & k1, in_t q) { + return recon(*dpf::eval_point(dpf::out<1, 8>, k0, q), + *dpf::eval_point(dpf::out<1, 8>, k1, q)); + }; + + for (int i = 0; i < 256; i += 13) + { + const in_t q = static_cast(i); + EXPECT_EQ(r0(ds.first, ds.second, q), r0(dealer.first, dealer.second, q)) + << "s0 " << i; + EXPECT_EQ(r1(ds.first, ds.second, q), r1(dealer.first, dealer.second, q)) + << "s1 " << i; + EXPECT_EQ(r0(ds.first, ds.second, q), + q == alpha ? b0 : std::uint16_t{0}) + << i; + EXPECT_EQ(r1(ds.first, ds.second, q), + q == alpha ? b1 : std::uint16_t{0}) + << i; + } +} + +TEST(ArithPayload, MixedArithBetaAndWildcard) +{ + using in_t = std::uint8_t; + const in_t alpha = 0x70; + const in_t x0 = 0x01; + const in_t x1 = static_cast(alpha ^ x0); + const std::uint8_t beta = 9; + const std::uint8_t y0 = 2; + const std::uint8_t y1 = 7; + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, + dpf::at<8>(beta, dpf::wildcard_value{})); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_output, x0, x1, rng, + dpf::at<8>(dpf::arith_beta{y0, y1}, + dpf::wildcard_value{})); + + using key_t = std::decay_t; + static_assert(dpf::is_wildcard_v>); + + for (int i = 0; i < 256; i += 19) + { + const in_t q = static_cast(i); + const auto got = recon(*dpf::eval_point(dpf::out<0, 8>, ds.first, q), + *dpf::eval_point(dpf::out<0, 8>, ds.second, q)); + const auto expect = + recon(*dpf::eval_point(dpf::out<0, 8>, dealer.first, q), + *dpf::eval_point(dpf::out<0, 8>, dealer.second, q)); + EXPECT_EQ(got, expect) << i; + EXPECT_EQ(got, q == alpha ? beta : std::uint8_t{0}) << i; + } +} diff --git a/test/tests/beaver_test.cpp b/test/tests/beaver_test.cpp index b8b97c3..f63c2ed 100644 --- a/test/tests/beaver_test.cpp +++ b/test/tests/beaver_test.cpp @@ -103,6 +103,7 @@ TEST(Beaver, ProductTwo) Counter rng; s.sample(rng); EXPECT_EQ(rng.draws, 5); // two input blinds * 2 draws + one product share + EXPECT_EQ(s.preprocessing_count(), 3u); EXPECT_EQ(s.monomial_count(), 1u); EXPECT_EQ(s.round_of(z), 1); EXPECT_EQ(s.monomial({{x, 1u}, {y, 1u}}).open(), @@ -150,7 +151,8 @@ TEST(Beaver, MulSquareIsOneRound) s.sample(rng); EXPECT_EQ(s.round_of(z), 1); EXPECT_EQ(s.wire_count(), 3u); - // λx², λa λx, λa λx². One blind for both x factors. + // λx², λa λx, λa λx². One blind for both x factors. The output is unpinned. + EXPECT_EQ(s.preprocessing_count(), 5u); EXPECT_EQ(s.monomial_count(), 3u); EXPECT_EQ(rng.draws, 7); // two input blinds * 2 + three product shares EXPECT_EQ(s.monomial({{x, 2u}}).open(), @@ -194,9 +196,10 @@ TEST(Beaver, DotAggregatesCrossTerm) auto z = s.dot({x0, x1, x2}, {y0, y1, y2}); Counter rng; s.sample(rng); - EXPECT_EQ(s.monomial_count(), 0u); + EXPECT_EQ(s.monomial_count(), 1u); EXPECT_EQ(s.round_of(z), 1); - // 6 input blinds, two draws each, plus one aggregated cross draw + // Six input blinds and one fused cross. Three separate products would be three crosses. + EXPECT_EQ(s.preprocessing_count(), 7u); EXPECT_EQ(rng.draws, 13); auto cross = s.lambda(x0).open() * s.lambda(y0).open() + s.lambda(x1).open() * s.lambda(y1).open() @@ -220,13 +223,427 @@ TEST(Beaver, DotReusesAPair) auto z = s.dot({a, a}, {b, b}); Counter rng; s.sample(rng); - EXPECT_EQ(s.monomial_count(), 0u); + EXPECT_EQ(s.monomial_count(), 1u); + EXPECT_EQ(s.preprocessing_count(), 3u); + EXPECT_EQ(rng.draws, 5); + EXPECT_EQ(s.dot_cross(z).open(), + u64{2} * s.lambda(a).open() * s.lambda(b).open()); s.bind(a, u64{3}, rng); s.bind(b, u64{4}, rng); s.evaluate(); EXPECT_EQ(s.open(z), 24u); } +TEST(Beaver, InnerProductMatchesTheSumAndIsOneCross) +{ + const u64 x[] = {0, 2, 5}; + const u64 y[] = {4, 0, 7}; + u64 expect = 0; + for (int i = 0; i < 3; ++i) + expect += x[i] * y[i]; + + session64 fused; + std::vector fx; + std::vector fy; + for (int i = 0; i < 3; ++i) + { + fx.push_back(fused.input()); + fy.push_back(fused.input()); + } + auto dot = fused.dot(fx, fy); + auto as_sum = fused(fx[0] * fy[0] + fx[1] * fy[1] + fx[2] * fy[2]); + EXPECT_EQ(fused.round_of(dot), 1); + EXPECT_EQ(fused.round_of(as_sum), 1); + EXPECT_EQ(fused.preprocessing_count(), 7u); + EXPECT_EQ(fused.monomial_count(), 1u); + + session64 separate; + std::vector sx; + std::vector sy; + std::vector prods; + for (int i = 0; i < 3; ++i) + { + sx.push_back(separate.input()); + sy.push_back(separate.input()); + } + for (int i = 0; i < 3; ++i) + prods.push_back(separate(sx[i] * sy[i])); + auto summed = separate(prods[0] + prods[1] + prods[2]); + EXPECT_EQ(separate.preprocessing_count(), 9u); + EXPECT_LT(fused.preprocessing_count(), separate.preprocessing_count()); + + Counter rf; + Counter rs; + fused.sample(rf); + separate.sample(rs); + for (int i = 0; i < 3; ++i) + { + fused.bind(fx[i], x[i], rf); + fused.bind(fy[i], y[i], rf); + separate.bind(sx[i], x[i], rs); + separate.bind(sy[i], y[i], rs); + } + fused.evaluate(); + separate.evaluate(); + EXPECT_EQ(fused.open(dot), expect); + EXPECT_EQ(fused.open(as_sum), expect); + EXPECT_EQ(separate.open(summed), expect); + u64 cross = 0; + for (int i = 0; i < 3; ++i) + cross += fused.lambda(fx[i]).open() * fused.lambda(fy[i]).open(); + EXPECT_EQ(fused.dot_cross(dot).open(), cross); + + const auto monos = fused.monomial_count(); + const auto prep = fused.preprocessing_count(); + auto again = fused.dot(fx, fy); + EXPECT_EQ(fused.monomial_count(), monos); + EXPECT_EQ(fused.preprocessing_count(), prep); + fused.sample(rf); + fused.evaluate(); + EXPECT_EQ(fused.open(again), expect); +} + +TEST(Beaver, InnerProductPeelsASharedFactor) +{ + session64 s; + auto a = s.input(); + auto x = s.input(); + auto y = s.input(); + auto z = s.input(); + auto dotted = s.dot({a, a, a}, {x, y, z}); + EXPECT_EQ(s.round_of(dotted), 2); + EXPECT_EQ(s.preprocessing_count(), 3u); + + session64 hand; + auto ha = hand.input(); + auto hx = hand.input(); + auto hy = hand.input(); + auto hz = hand.input(); + auto written = hand(ha * (hx + hy + hz)); + EXPECT_EQ(hand.round_of(written), 2); + EXPECT_EQ(hand.preprocessing_count(), s.preprocessing_count()); + EXPECT_LT(s.preprocessing_count(), 7u); + + Counter rng; + s.sample(rng); + s.bind(a, u64{3}, rng); + s.bind(x, u64{4}, rng); + s.bind(y, u64{5}, rng); + s.bind(z, u64{6}, rng); + s.evaluate(); + EXPECT_EQ(s.open(dotted), 3u * (4u + 5u + 6u)); +} + +TEST(Beaver, InnerProductOfSquaresIsOneShare) +{ + auto got = run_poly(2, [](session64 & s, const std::vector & in) { + return s.dot({in[0], in[1]}, {in[0], in[1]}); + }, {6, 7}); + EXPECT_EQ(got.value, 36u + 49u); + EXPECT_EQ(got.rounds, 1); + EXPECT_EQ(got.prep, 3u); + EXPECT_EQ(got.monos, 1u); +} + +TEST(Beaver, ScaledInnerProductUsesThePolynomialSchedule) +{ + session64 automatic; + auto sgn = automatic.input(); + auto x0 = automatic.input(); + auto x1 = automatic.input(); + auto y0 = automatic.input(); + auto y1 = automatic.input(); + auto y = automatic(sgn * dpf::beavers::dot({x0, x1}, {y0, y1})); + + session64 hand; + auto hs = hand.input(); + auto hx0 = hand.input(); + auto hx1 = hand.input(); + auto hy0 = hand.input(); + auto hy1 = hand.input(); + auto inner = hand.dot({hx0, hx1}, {hy0, hy1}); + auto outer = hand(hs * inner); + EXPECT_EQ(automatic.preprocessing_count(), hand.preprocessing_count()); + EXPECT_EQ(automatic.round_of(y), hand.round_of(outer)); + EXPECT_LT(automatic.preprocessing_count(), 9u); + + const u64 sv = 3, a = 2, b = 5, c = 4, d = 6; + Counter ra; + Counter rh; + automatic.sample(ra); + hand.sample(rh); + automatic.bind(sgn, sv, ra); + automatic.bind(x0, a, ra); + automatic.bind(x1, b, ra); + automatic.bind(y0, c, ra); + automatic.bind(y1, d, ra); + hand.bind(hs, sv, rh); + hand.bind(hx0, a, rh); + hand.bind(hx1, b, rh); + hand.bind(hy0, c, rh); + hand.bind(hy1, d, rh); + automatic.evaluate(); + hand.evaluate(); + const u64 expect = sv * (a * c + b * d); + EXPECT_EQ(automatic.open(y), expect); + EXPECT_EQ(hand.open(outer), expect); + + session64 scaled; + auto sx0 = scaled.input(); + auto sx1 = scaled.input(); + auto sy0 = scaled.input(); + auto sy1 = scaled.input(); + auto plain = scaled.dot({sx0, sx1}, {sy0, sy1}); + auto triple = scaled(u64{3} * dpf::beavers::dot({sx0, sx1}, {sy0, sy1})); + EXPECT_EQ(scaled.preprocessing_count(), 5u); + EXPECT_EQ(scaled.monomial_count(), 1u); + (void)plain; + Counter rs; + scaled.sample(rs); + scaled.bind(sx0, a, rs); + scaled.bind(sx1, b, rs); + scaled.bind(sy0, c, rs); + scaled.bind(sy1, d, rs); + scaled.evaluate(); + EXPECT_EQ(scaled.open(triple), 3u * (a * c + b * d)); + EXPECT_EQ(scaled.open(plain), a * c + b * d); +} + +TEST(Beaver, InnerProductsOnModintAndXor) +{ + using M = dpf::modint<17>; + dpf::beavers::session ms; + auto mx0 = ms.input(); + auto mx1 = ms.input(); + auto my0 = ms.input(); + auto my1 = ms.input(); + auto my = ms.dot({mx0, mx1}, {my0, my1}); + EXPECT_EQ(ms.preprocessing_count(), 5u); + EXPECT_EQ(ms.monomial_count(), 1u); + Seq mr; + ms.sample(mr); + ms.bind(mx0, M{10}, mr); + ms.bind(mx1, M{9}, mr); + ms.bind(my0, M{8}, mr); + ms.bind(my1, M{7}, mr); + ms.evaluate(); + EXPECT_EQ(ms.open(my), M{10} * M{8} + M{9} * M{7}); + + using W = dpf::xor_wrapper; + dpf::beavers::session xs; + auto xx = xs.input(); + auto xy = xs.input(); + auto yx = xs.input(); + auto yy = xs.input(); + auto dot = xs.dot({xx, xy}, {yx, yy}); + EXPECT_EQ(xs.preprocessing_count(), 5u); + XorSeq xr; + xs.sample(xr); + const W a{0b11110000u}; + const W b{0b11001100u}; + const W c{0b10101010u}; + const W d{0b11111111u}; + xs.bind(xx, a, xr); + xs.bind(xy, b, xr); + xs.bind(yx, c, xr); + xs.bind(yy, d, xr); + xs.evaluate(); + EXPECT_EQ(xs.open(dot), a * c + b * d); +} + +TEST(Beaver, InnerProductOfEarlierProductsIsALaterRound) +{ + session64 s; + auto a = s.input(); + auto b = s.input(); + auto c = s.input(); + auto d = s.input(); + auto ab = s(a * b); + auto cd = s(c * d); + s.pin(ab); + s.pin(cd); + Counter rng; + s.sample(rng); + auto lab = s.lambda(ab); + s.bind(a, u64{2}, rng); + s.bind(b, u64{3}, rng); + s.bind(c, u64{4}, rng); + s.bind(d, u64{5}, rng); + auto both = s.dot({ab, cd}, {ab, cd}); + EXPECT_EQ(s.round_of(both), 2); + const auto draws = rng.draws; + s.sample(rng); + EXPECT_EQ(s.lambda(ab), lab); + EXPECT_EQ(rng.draws, draws + 1); + s.evaluate(); + EXPECT_EQ(s.open(ab), 6u); + EXPECT_EQ(s.open(cd), 20u); + EXPECT_EQ(s.open(both), 6u * 6u + 20u * 20u); +} + +TEST(Beaver, TriplesUseNoExtraShares) +{ + { + session64 s; + auto a = s.input(); + auto b = s.input(); + auto ab = s(a * b); + s.pin(ab); + EXPECT_EQ(s.preprocessing_count(), 4u); + Counter rng; + s.sample(rng); + s.bind(a, u64{6}, rng); + s.bind(b, u64{7}, rng); + s.evaluate(); + EXPECT_EQ(s.open(ab), 42u); + EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}}).open(), + s.lambda(a).open() * s.lambda(b).open()); + } + { + session64 s; + auto a = s.input(); + auto b = s.input(); + auto c = s.input(); + auto abc = s(a * b * c); + s.pin(abc); + EXPECT_EQ(s.preprocessing_count(), 8u); + Counter rng; + s.sample(rng); + s.bind(a, u64{2}, rng); + s.bind(b, u64{3}, rng); + s.bind(c, u64{5}, rng); + s.evaluate(); + EXPECT_EQ(s.open(abc), 30u); + EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}}).open(), + s.lambda(a).open() * s.lambda(b).open()); + EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}, {c, 1u}}).open(), + s.lambda(a).open() * s.lambda(b).open() * s.lambda(c).open()); + } + { + session64 s; + auto x = s.input(); + auto x2 = s(x * x); + s.pin(x2); + EXPECT_EQ(s.preprocessing_count(), 3u); + Counter rng; + s.sample(rng); + s.bind(x, u64{9}, rng); + s.evaluate(); + EXPECT_EQ(s.open(x2), 81u); + } + { + session64 s; + auto a = s.input(); + auto x = s.input(); + auto ax2 = s(a * x * x); + s.pin(ax2); + EXPECT_EQ(s.preprocessing_count(), 6u); + Counter rng; + s.sample(rng); + s.bind(a, u64{4}, rng); + s.bind(x, u64{3}, rng); + s.evaluate(); + EXPECT_EQ(s.open(ax2), 36u); + EXPECT_EQ(s.monomial({{a, 1u}, {x, 2u}}).open(), + s.lambda(a).open() * s.lambda(x).open() * s.lambda(x).open()); + } + { + session64 s; + std::vector x; + std::vector y; + for (int i = 0; i < 3; ++i) + { + x.push_back(s.input()); + y.push_back(s.input()); + } + auto unpinned = s.dot(x, y); + EXPECT_EQ(s.preprocessing_count(), 7u); + s.pin(unpinned); + EXPECT_EQ(s.preprocessing_count(), 8u); + Counter rng; + s.sample(rng); + const u64 xv[] = {1, 2, 3}; + const u64 yv[] = {4, 5, 6}; + for (int i = 0; i < 3; ++i) + { + s.bind(x[i], xv[i], rng); + s.bind(y[i], yv[i], rng); + } + s.evaluate(); + EXPECT_EQ(s.open(unpinned), 32u); + } + { + session64 s; + auto scalar = s.input(); + auto v0 = s.input(); + auto v1 = s.input(); + auto v2 = s.input(); + auto z = s.scale(scalar, {v0, v1, v2}); + EXPECT_EQ(s.preprocessing_count(), 7u); + for (auto out : z) + s.pin(out); + EXPECT_EQ(s.preprocessing_count(), 10u); + Counter rng; + s.sample(rng); + s.bind(scalar, u64{3}, rng); + s.bind(v0, u64{4}, rng); + s.bind(v1, u64{5}, rng); + s.bind(v2, u64{0}, rng); + s.evaluate(); + EXPECT_EQ(s.open(z[0]), 12u); + EXPECT_EQ(s.open(z[1]), 15u); + EXPECT_EQ(s.open(z[2]), 0u); + } + { + session64 s; + auto bit = s.bit(); + auto scalar = s.input(); + auto out = s.bit_mul(bit, scalar); + s.pin(out); + EXPECT_EQ(s.preprocessing_count(), 4u); + Counter rng; + s.sample(rng); + s.bind(bit, u64{1}, rng); + s.bind(scalar, u64{19}, rng); + s.evaluate(); + EXPECT_EQ(s.open(out), 19u); + } + { + session64 s; + auto bit = s.bit(); + auto when1 = s.input(); + auto when0 = s.input(); + auto out = s.mux(bit, when1, when0); + s.pin(out); + EXPECT_EQ(s.preprocessing_count(), 6u); + Counter rng; + s.sample(rng); + s.bind(bit, u64{0}, rng); + s.bind(when1, u64{8}, rng); + s.bind(when0, u64{9}, rng); + s.evaluate(); + EXPECT_EQ(s.open(out), 9u); + } +} + +TEST(Beaver, InnerProductRejectsABadShape) +{ + session64 s; + auto x = s.input(); + auto y = s.input(); + auto z = s.input(); + EXPECT_THROW((void)[&] { + return s.dot(std::initializer_list{}, std::initializer_list{}); + }(), std::invalid_argument); + EXPECT_THROW((void)[&] { return s.dot({x}, {y, z}); }(), std::invalid_argument); + session64 other; + auto w = other.input(); + EXPECT_THROW((void)[&] { return s.dot({x}, {w}); }(), std::invalid_argument); + auto dotted = s.dot({x}, {y}); + EXPECT_THROW((void)[&] { return s.dot_cross(dotted); }(), std::logic_error); + EXPECT_THROW((void)[&] { return s.dot_cross(x); }(), std::invalid_argument); +} + TEST(Beaver, ScaleSharesScalarBlind) { dpf::beavers::session s; @@ -240,6 +657,7 @@ TEST(Beaver, ScaleSharesScalarBlind) s.sample(rng); EXPECT_EQ(z.size(), 4u); EXPECT_EQ(s.monomial_count(), 4u); + EXPECT_EQ(s.preprocessing_count(), 9u); EXPECT_EQ(s.round_of(z[0]), 1); // 1 scalar + 4 lanes + 4 outputs = 9 wires * 2, plus 4 cross terms EXPECT_EQ(rng.draws, 14); diff --git a/test/tests/blocked_dcf_test.cpp b/test/tests/blocked_dcf_test.cpp index da539fe..91929ae 100644 --- a/test/tests/blocked_dcf_test.cpp +++ b/test/tests/blocked_dcf_test.cpp @@ -1,6 +1,7 @@ #include #include "dpf.hpp" +#include "dpf/blocked_dcf.hpp" #include "dpf/json.hpp" #include "grotto/offset_horner.hpp" #include "grotto/prefix_parity.hpp" @@ -238,7 +239,10 @@ TEST(BlockedDcf, DealerMatchesDoernerShelat) dpf::root_sampler_t{take_root}, dpf::block_width<4>(dpf::lt(uint64_t{15}, uint64_t{2}))); reset_tape(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::block_width<4>(dpf::lt(uint64_t{15}, uint64_t{2}))); @@ -336,7 +340,10 @@ TEST(BlockedDcf, GenevalOpensCheckpointWords) const uint8_t x0 = 1; const uint8_t x1 = static_cast(alpha ^ x0); reset_tape(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) std::array ends{{0, 20, 21}}; auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng, dpf::block_width<4>(dpf::lt(uint64_t{5}))); @@ -350,6 +357,85 @@ TEST(BlockedDcf, GenevalOpensCheckpointWords) EXPECT_EQ((g.party0[2] + g.party1[2]) & g.mask, 0u); } +TEST(BlockedDcf, UnevenScheduleMatchesDenseComparison) +{ + const uint16_t alpha = 0x0B4C; + auto dense = dpf::make_dpf(alpha, dpf::lt(uint64_t{9}, uint64_t{2})); + auto blocked = dpf::make_dpf(alpha, + dpf::block_width<4>(dpf::lt(uint64_t{9}, uint64_t{2}))); + using KT = std::decay_t; + EXPECT_EQ(KT::cmp_h, 14u); + EXPECT_EQ(KT::cmp_block, 4u); + EXPECT_NE(KT::cmp_h % KT::cmp_block, 0u); + EXPECT_EQ(blocked.first.value_cw().size(), KT::cmp_checkpoints); + using sched = dpf::detail::blocked::schedule; + ASSERT_GE(sched::count, 2u); + bool uneven = false; + const auto first_step = sched::depths[1] - sched::depths[0]; + for (std::size_t i = 1; i < sched::count; ++i) + { + if (sched::depths[i] - sched::depths[i - 1] != first_step) + uneven = true; + } + EXPECT_TRUE(uneven); + for (uint32_t x = 0; x < 65536u; ++x) + { + const auto q = static_cast(x); + ASSERT_EQ(recon_cmp(blocked.first, blocked.second, q), + recon_cmp(dense.first, dense.second, q)) + << x; + } +} + +TEST(BlockedDcf, WideCheckpointFrontierMatchesDense) +{ + const uint32_t alpha = 0x01020304u; + // `cmp_q` is 2, so `lt_at<19>` checkpoints at height 17. One block of 17 + // parks a root sibling 16 levels above that checkpoint. + auto dense = dpf::make_dpf(alpha, dpf::lt_at<19>(uint64_t{5}, uint64_t{1})); + auto blocked = dpf::make_dpf(alpha, + dpf::block_width<17>(dpf::lt_at<19>(uint64_t{5}, uint64_t{1}))); + using KT = std::decay_t; + EXPECT_GE(KT::cmp_h, 17u); + EXPECT_EQ(KT::cmp_checkpoints, 1u); + const uint32_t pts[] = { + 0u, 1u, alpha - 1u, alpha, alpha + 1u, 0x80000000u, 0xffffffffu + }; + for (uint32_t x : pts) + { + EXPECT_EQ(recon_cmp(blocked.first, blocked.second, x), + recon_cmp(dense.first, dense.second, x)) + << std::hex << x; + } + + const uint32_t from = 100, to = 101; + auto buf0 = dpf::make_output_buffer(dpf::cmp, blocked.first, from, to); + auto buf1 = dpf::make_output_buffer(dpf::cmp, blocked.second, from, to); + dpf::eval_interval(dpf::cmp, blocked.first, from, to, buf0); + dpf::eval_interval(dpf::cmp, blocked.second, from, to, buf1); + EXPECT_EQ(recon(buf0[0], buf1[0]) & blocked.first.cmp().mask, + recon_cmp(blocked.first, blocked.second, from)); + EXPECT_EQ(recon(buf0[1], buf1[1]) & blocked.first.cmp().mask, + recon_cmp(blocked.first, blocked.second, to)); +} + +TEST(BlockedDcf, PathRecipesStayOnThePerLevelChannel) +{ + const uint8_t alpha = 0x3C; + EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<4>(dpf::lcp(uint64_t{1}))), + std::invalid_argument); + EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<4>(dpf::break_bit(uint64_t{3}))), + std::invalid_argument); + EXPECT_THROW(dpf::make_dpf(alpha, + dpf::block_width<4>(dpf::prefix_with_length<4>(uint64_t{1}))), + std::invalid_argument); + EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<2>(dpf::path_paint( + [](std::size_t matched, uint64_t, bool) { + return static_cast(matched); + }))), + std::invalid_argument); +} + TEST(BlockedDcf, GrottoPrefixSegmentAndHorner) { const uint16_t alpha = 1000; diff --git a/test/tests/constrained_cmp_test.cpp b/test/tests/constrained_cmp_test.cpp new file mode 100644 index 0000000..40afeb1 --- /dev/null +++ b/test/tests/constrained_cmp_test.cpp @@ -0,0 +1,64 @@ +#include + +#include "dpf.hpp" + +#include +#include +#include + +TEST(ConstrainedCmp, LemmaAdjacentPairs) +{ + // |x0 − x1| = 1 ⇒ local_ccmp = 1{x0 < x1}. + EXPECT_EQ(dpf::local_ccmp(0, 1), 1u); + EXPECT_EQ(dpf::local_ccmp(1, 0), 0u); + EXPECT_EQ(dpf::local_ccmp(2, 3), 1u); + EXPECT_EQ(dpf::local_ccmp(3, 2), 0u); + EXPECT_EQ(dpf::local_ccmp(100, 101), 1u); + EXPECT_EQ(dpf::local_ccmp(101, 100), 0u); + // Control-bit pairs used by arith leaf open (t0 ⊕ t1 = 1). + EXPECT_EQ(dpf::local_ccmp(0, 1), 1u); // g = t1 + EXPECT_EQ(dpf::local_ccmp(1, 0), 0u); // g = t1 +} + +TEST(ConstrainedCmp, RandomAdjacentBatch) +{ + std::mt19937_64 rng{0xC0FFEEULL}; + std::uniform_int_distribution dist(0, (1ull << 40) - 2); + for (int i = 0; i < 256; ++i) + { + const std::uint64_t a = dist(rng); + const std::uint64_t b = a + 1; + EXPECT_EQ(dpf::local_ccmp(a, b), 1u) << a << " < " << b; + EXPECT_EQ(dpf::local_ccmp(b, a), 0u) << b << " > " << a; + EXPECT_EQ(dpf::local_ccmp_int(a, b), 1u); + EXPECT_EQ(dpf::local_ccmp_int(b, a), 0u); + } +} + +TEST(ConstrainedCmp, PartyTermsMatchTheAndInputs) +{ + uint8_t z0 = 9, z1 = 9, l = 9; + dpf::detail::ccmp_party_terms(0b10, 0, z0, z1, l); + EXPECT_EQ(l, 0u); + EXPECT_EQ(z0, 1u); + EXPECT_EQ(z1, 1u); + dpf::detail::ccmp_party_terms(0b11, 1, z0, z1, l); + EXPECT_EQ(l, 1u); + EXPECT_EQ(z0, 1u); + EXPECT_EQ(z1, static_cast(1u ^ 1u ^ 1u)); +} + +TEST(ConstrainedCmp, AdjacentAtTheTopOfUint64) +{ + const std::uint64_t top = std::numeric_limits::max(); + EXPECT_EQ(dpf::local_ccmp(top - 1, top), 1u); + EXPECT_EQ(dpf::local_ccmp(top, top - 1), 0u); +} + +TEST(ConstrainedCmp, ProtocolHook) +{ + dpf::detail::urandom_pad_rng pads{}; + dpf::local_cw_protocol proto{pads}; + EXPECT_EQ(proto.open_ccmp(0, 1), 1u); + EXPECT_EQ(proto.open_ccmp(1, 0), 0u); +} diff --git a/test/tests/context_blast_test.cpp b/test/tests/context_blast_test.cpp index daacd25..ba4e5b5 100644 --- a/test/tests/context_blast_test.cpp +++ b/test/tests/context_blast_test.cpp @@ -94,7 +94,7 @@ TEST(ContextBlast, ModintEdgesAndFullDomain) every_point(m7{64}, dpf::xor_wrapper{0x00ff}, 128); for (unsigned a : {0u, 1u, 511u, 512u, 1023u}) - every_point(m10{a}, uint32_t{0xabcdu}, 1024); + every_point(m10{static_cast(a)}, uint32_t{0xabcdu}, 1024); every_point(m9{0}, uint16_t{2}, 512); every_point(m9{511}, uint16_t{2}, 512); @@ -140,11 +140,11 @@ TEST(ContextBlast, BitstringPointIntervalSequence) const uint32_t y = 0x11111111u; for (unsigned a : {0u, 1u, 31u, 32u, 63u}) { - bs alpha{a}; + bs alpha{static_cast(a)}; auto [k0, k1] = dpf::make_dpf(alpha, y); for (unsigned i = 0; i < 64; ++i) { - bs q{i}; + bs q{static_cast(i)}; EXPECT_EQ(opened(ev(k0, q), ev(k1, q)), q == alpha ? y : 0u) << i; } bs from{0}; @@ -280,8 +280,11 @@ TEST(ContextBlast, XorWrapperAndGenevalIntegers) const int32_t secret = -2; const int32_t x1 = secret ^ x0; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto g = dpf::geneval_point(x0, x1, secret, dpf::ds_randomness{take_root, Pad{}}, X{7}); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(g.leaf_live); EXPECT_EQ(g.live_levels, std::decay_t::depth); EXPECT_EQ(opened(g.party0[0], g.party1[0]), X{7}); @@ -289,8 +292,11 @@ TEST(ContextBlast, XorWrapperAndGenevalIntegers) const int32_t far = 100; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto off = dpf::geneval_point(x0, x1, far, dpf::ds_randomness{take_root, Pad{}}, X{7}); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_FALSE(off.leaf_live); EXPECT_GT(off.live_levels, 0u); EXPECT_LT(off.live_levels, off.correction_words.size()); @@ -307,8 +313,11 @@ TEST(ContextBlast, XorWrapperAndGenevalIntegers) uint64_t{99}); reset_roots(); const uint32_t qs[] = {0u, 1u, u, u ^ 1u, 0xffffffffu}; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto sq = dpf::geneval_sequence(u0, u1, std::begin(qs), std::end(qs), dpf::ds_randomness{take_root, Pad{}}, uint64_t{99}); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(sq.leaf_live); for (std::size_t i = 0; i < sq.live_levels; ++i) EXPECT_EQ(std::memcmp(&sq.correction_words[i], &uk.first.correction_word(i), diff --git a/test/tests/corner_gaps_test.cpp b/test/tests/corner_gaps_test.cpp index d963ab4..8251132 100644 --- a/test/tests/corner_gaps_test.cpp +++ b/test/tests/corner_gaps_test.cpp @@ -384,7 +384,10 @@ TEST(CornerGaps, PrgRejectsUint32Seam) EXPECT_EQ(std::memcmp(&out[i], &one, sizeof(one)), 0) << i; } + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_THROW((dpf::randomness::detail::lane_codec::fill( seed, static_cast(UINT32_MAX) - 1u, out, 4)), std::invalid_argument); + HEDLEY_PRAGMA(GCC diagnostic pop) } diff --git a/test/tests/dyadic_lut_test.cpp b/test/tests/dyadic_lut_test.cpp index 479e467..0820943 100644 --- a/test/tests/dyadic_lut_test.cpp +++ b/test/tests/dyadic_lut_test.cpp @@ -213,3 +213,17 @@ TEST(DyadicLut, Int64Edges) EXPECT_EQ(bit(std::int64_t{-5}), 1); EXPECT_EQ(bit(std::int64_t{5}), 0); } + +TEST(DyadicLut, RejectsAFractionalWidthThatDoesNotFit) +{ + EXPECT_THROW(grotto::make_signum_lut(63), std::invalid_argument); + EXPECT_THROW(grotto::make_positive_lut(63), std::invalid_argument); + EXPECT_THROW(grotto::make_ilogb_lut(64), std::invalid_argument); +} + +TEST(DyadicLut, RejectsAnEncodedValueThatDoesNotFit) +{ + // clz(0) is 64. 64 << 57 does not fit in int64; 64 << 56 does. + EXPECT_THROW(grotto::make_clz_lut(57), std::overflow_error); + EXPECT_NO_THROW(grotto::make_clz_lut(56)); +} diff --git a/test/tests/fp61_test.cpp b/test/tests/fp61_test.cpp new file mode 100644 index 0000000..fca0880 --- /dev/null +++ b/test/tests/fp61_test.cpp @@ -0,0 +1,57 @@ +#include + +#include +#include +#include + +#include "dpf/fp61.hpp" + +namespace +{ + +using dpf::fp61; +using dpf::fp61_mod; + +} // namespace + +TEST(Fp61, ReduceCanonicalizesTheModulus) +{ + EXPECT_EQ(fp61::reduce(0), 0u); + EXPECT_EQ(fp61::reduce(1), 1u); + EXPECT_EQ(fp61::reduce(fp61_mod), 0u); + EXPECT_EQ(fp61::reduce(fp61_mod + 1), 1u); + EXPECT_EQ(fp61::reduce(std::uint64_t{1} << 61), 1u); + EXPECT_EQ(fp61::reduce(std::numeric_limits::max()), 7u); + EXPECT_EQ(fp61::reduce(fp61::reduce(fp61_mod + 9)), fp61::reduce(9)); +} + +TEST(Fp61, ArithmeticWrapsInTheField) +{ + const fp61 a{fp61_mod - 1}; + const fp61 b{3}; + EXPECT_EQ((a + b).raw(), 2u); + EXPECT_EQ((b - a).raw(), 4u); + EXPECT_EQ((-a).raw(), 1u); + EXPECT_EQ((-fp61{0}).raw(), 0u); + EXPECT_EQ((a - a).raw(), 0u); + EXPECT_EQ((-(-a)).raw(), a.raw()); + EXPECT_EQ((a * fp61{1}).raw(), a.raw()); + EXPECT_EQ((a * fp61{0}).raw(), 0u); + EXPECT_EQ((fp61{2} * fp61{3}).raw(), 6u); +} + +TEST(Fp61, MultiplicationDistributes) +{ + const fp61 a{1000}; + const fp61 b{fp61_mod - 5}; + const fp61 c{17}; + EXPECT_EQ(((a + b) * c).raw(), (a * c + b * c).raw()); + EXPECT_EQ((a * b).raw(), (b * a).raw()); +} + +TEST(Fp61, StreamPrintsTheReducedValue) +{ + std::ostringstream os; + os << fp61{fp61_mod + 4}; + EXPECT_EQ(os.str(), "4"); +} diff --git a/test/tests/geneval_test.cpp b/test/tests/geneval_test.cpp index 0a9da4c..e345fa1 100644 --- a/test/tests/geneval_test.cpp +++ b/test/tests/geneval_test.cpp @@ -86,6 +86,8 @@ std::size_t live_through_lcp(std::size_t lcp, std::size_t depth) return std::min(depth, lcp + 1); } +HEDLEY_PRAGMA(GCC diagnostic push) +HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template void expect_prefix_words(const Key & key, const std::vector> & cws, @@ -112,6 +114,7 @@ dpf::ds_randomness rng() { return {take_root, Pad{}}; } +HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace @@ -508,7 +511,10 @@ TEST(Geneval, DoernerShelatKeyAgreesOnLivePrefix) out_t y = 1; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness ds_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y); reset_roots(); auto g = dpf::geneval_point(x0, x1, query, rng(), y); @@ -631,7 +637,10 @@ TEST(Geneval, XorSplitAndPadStreamDoNotChangeLiveWords) auto check = [&](in_t x0, auto pad, const char * name) { in_t x1 = static_cast(alpha ^ x0); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness r{take_root, pad}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto g = dpf::geneval_point(x0, x1, query, r, y); EXPECT_EQ(g.live_levels, live) << name; expect_prefix_words(keys.first, g.correction_words, g.correction_advice, @@ -1190,7 +1199,10 @@ TEST(Geneval, DoernerShelatOnTargetSharesMatch) const out_t y = 0x9f3c; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness ds_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y); reset_roots(); auto g = dpf::geneval_point(x0, x1, alpha, rng(), y); @@ -1251,7 +1263,10 @@ TEST(Geneval, CmpLeqGeqNonzeroElseAndDomainMin) auto spec_ds = spec; auto spec_g = spec; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness ds_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, spec_ds); reset_roots(); auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng(), spec_g); diff --git a/test/tests/half_tree_test.cpp b/test/tests/half_tree_test.cpp new file mode 100644 index 0000000..37d35d1 --- /dev/null +++ b/test/tests/half_tree_test.cpp @@ -0,0 +1,322 @@ +#include + +#include "dpf.hpp" + +#include +#include +#include + +namespace +{ + +using ht_prg = dpf::prg::aes128_ccr; +using leaf_prg = dpf::prg::aes128; + +simde__m128i g_roots[8]; +int g_ri = 0; +simde__m128i take_root() { return g_roots[g_ri++]; } + +struct Pad +{ + uint64_t n = 1; + simde__m128i block() + { + auto v = simde_mm_set_epi64x(static_cast(n), + static_cast(n * 9 + 3)); + n += 2; + return v; + } + uint8_t bit() { return static_cast(n++ & 1u); } +}; + +void reset_roots() +{ + g_ri = 0; + for (int i = 0; i < 8; ++i) + g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xA5A50000u + i * 17); +} + +template +T bare(const T & v) +{ + return v; +} + +template +T bare(const dpf::secret_share & s) +{ + return s.raw(); +} + +template +auto recon(const A & a, const B & b) +{ + using T = decltype(bare(a)); + return static_cast(bare(a) - bare(b)); +} + +template +auto ev(const Key & key, In x) +{ + return bare(*dpf::eval_point(key, x)); +} + +} // namespace + +TEST(HalfTree, TraitsSelectedByCcrPrg) +{ + static_assert(dpf::tree_traits::is_half_tree); + static_assert(!dpf::tree_traits::is_half_tree); + static_assert(dpf::tree_traits::last_level_differs); + static_assert(!dpf::tree_traits::stores_mid_advice); +} + +TEST(HalfTree, DealerPointAndFullDomain) +{ + using in_t = std::uint8_t; + using out_t = std::uint32_t; + const in_t alpha = 0x2a; + const out_t beta = 0x01020304; + + auto [k0, k1] = dpf::make_dpf(alpha, beta); + using key_t = std::decay_t; + static_assert(key_t::tree::is_half_tree); + + // Mid-level advice unused; last level may pack BGI-style advice. + for (std::size_t i = 0; i + 1 < key_t::depth; ++i) + EXPECT_EQ(k0.correction_advice(i), 0) << "mid advice " << i; + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + const out_t got = recon(ev(k0, q), ev(k1, q)); + EXPECT_EQ(got, q == alpha ? beta : out_t{}) << i; + } + + auto [buf0, it0] = dpf::eval_full(k0); + auto [buf1, it1] = dpf::eval_full(k1); + (void)it0; + (void)it1; + for (int i = 0; i < 256; ++i) + { + const out_t got = recon(buf0[i], buf1[i]); + EXPECT_EQ(got, static_cast(i) == alpha ? beta : out_t{}) << i; + } +} + +TEST(HalfTree, OutputParityVsBgi) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x7e; + const out_t beta = 0xabcd; + + auto [h0, h1] = dpf::make_dpf(alpha, beta); + auto [b0, b1] = dpf::make_dpf(alpha, beta); + + // Keys differ (different tree), but reconstructed outputs match. + EXPECT_NE(std::memcmp(&h0.root(), &b0.root(), sizeof(simde__m128i)), 0); + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + const out_t ht = recon(ev(h0, q), ev(h1, q)); + const out_t bgi = recon(ev(b0, q), ev(b1, q)); + EXPECT_EQ(ht, bgi) << i; + EXPECT_EQ(ht, q == alpha ? beta : out_t{}) << i; + } +} + +TEST(HalfTree, DoernerShelatXorMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x11; + const in_t x0 = 0x55; + const in_t x1 = static_cast(alpha ^ x0); + const out_t y = 0x42; + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, + dpf::root_sampler_t{take_root}, y); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness ds_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y); + + using key_t = std::decay_t; + EXPECT_EQ(std::memcmp(&dealer.first.root(), &ds.first.root(), + sizeof(simde__m128i)), 0); + EXPECT_EQ(std::memcmp(&dealer.second.root(), &ds.second.root(), + sizeof(simde__m128i)), 0); + for (std::size_t i = 0; i < key_t::depth; ++i) + { + EXPECT_EQ(std::memcmp(&dealer.first.correction_word(i), + &ds.first.correction_word(i), sizeof(simde__m128i)), 0) + << "cw " << i; + EXPECT_EQ(dealer.first.correction_advice(i), + ds.first.correction_advice(i)) + << "advice " << i; + } + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)), + q == alpha ? y : out_t{}) + << i; + } +} + +TEST(HalfTree, DoernerShelatArithMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0xc0; + const in_t x0 = 0x40; + const in_t x1 = static_cast(alpha - x0); + const out_t y = 9; + + reset_roots(); + auto dealer = dpf::make_dpf(alpha, + dpf::root_sampler_t{take_root}, y); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness ds_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_input, + x0, x1, ds_rng, y); + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)), + q == alpha ? y : out_t{}) + << i; + EXPECT_EQ(ev(ds.first, q), ev(dealer.first, q)) << "p0 " << i; + EXPECT_EQ(ev(ds.second, q), ev(dealer.second, q)) << "p1 " << i; + } +} + +TEST(HalfTree, GenevalPointMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x33; + const in_t x0 = 0x0f; + const in_t x1 = static_cast(alpha ^ x0); + const out_t y = 0x77; + + reset_roots(); + auto keys = dpf::make_dpf(alpha, + dpf::root_sampler_t{take_root}, y); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness g_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto g = dpf::geneval_point(x0, x1, alpha, g_rng, y); + + using key_t = std::decay_t; + EXPECT_TRUE(g.leaf_live); + EXPECT_EQ(g.live_levels, key_t::depth); + for (std::size_t i = 0; i < g.live_levels; ++i) + { + EXPECT_EQ(std::memcmp(&g.correction_words[i], + &keys.first.correction_word(i), sizeof(simde__m128i)), 0) + << "cw " << i; + EXPECT_EQ(g.correction_advice[i], keys.first.correction_advice(i)) + << "advice " << i; + } + ASSERT_EQ(g.party0.size(), 1u); + EXPECT_EQ(g.party0[0], ev(keys.first, alpha)); + EXPECT_EQ(g.party1[0], ev(keys.second, alpha)); + EXPECT_EQ(recon(g.party0[0], g.party1[0]), y); +} + +TEST(HalfTree, GenevalArithPointMatchesDealer) +{ + using in_t = std::uint8_t; + using out_t = std::uint16_t; + const in_t alpha = 0x90; + const in_t x0 = 0x20; + const in_t x1 = static_cast(alpha - x0); + const out_t y = 3; + + reset_roots(); + auto keys = dpf::make_dpf(alpha, + dpf::root_sampler_t{take_root}, y); + reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness g_rng{take_root, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto g = dpf::geneval_point(dpf::arith_input, x0, x1, + alpha, g_rng, y); + + EXPECT_TRUE(g.leaf_live); + ASSERT_EQ(g.party0.size(), 1u); + EXPECT_EQ(recon(g.party0[0], g.party1[0]), y); + EXPECT_EQ(g.party0[0], ev(keys.first, alpha)); + EXPECT_EQ(g.party1[0], ev(keys.second, alpha)); +} + +TEST(HalfTree, IncrementalCmpSmoke) +{ + using in_t = std::uint8_t; + using ht_prg = dpf::prg::aes128_ccr; + using leaf_prg = dpf::prg::aes128; + const in_t alpha = 0x40; + const uint64_t if_true = 4; + const uint64_t if_false = 1; + + auto ht = dpf::make_dpf(alpha, + dpf::lt(if_true, if_false)); + auto bgi = dpf::make_dpf(alpha, + dpf::lt(if_true, if_false)); + + using key_t = std::decay_t; + static_assert(key_t::tree::is_half_tree); + for (std::size_t i = 0; i + 1 < key_t::depth; ++i) + EXPECT_EQ(ht.first.correction_advice(i), 0) << "mid advice " << i; + + const uint64_t mask = ht.first.cmp().mask; + auto recon_cmp = [&](const auto & k0, const auto & k1, in_t q) { + return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), + dpf::eval_point(dpf::cmp, k1, q)) + & mask; + }; + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + const auto ht_got = recon_cmp(ht.first, ht.second, q); + const auto bgi_got = recon_cmp(bgi.first, bgi.second, q); + const uint64_t expect = (q < alpha) ? if_true : if_false; + EXPECT_EQ(ht_got, expect) << i; + EXPECT_EQ(ht_got, bgi_got) << "parity " << i; + } +} + +TEST(HalfTree, IncrementalPlacementSmoke) +{ + using in_t = std::uint8_t; + const in_t alpha = 0x40; + auto keys = dpf::make_dpf(alpha, dpf::at<8>(uint8_t{7})); + + using key_t = std::decay_t; + static_assert(key_t::tree::is_half_tree); + for (std::size_t i = 0; i + 1 < key_t::depth; ++i) + EXPECT_EQ(keys.first.correction_advice(i), 0) << "mid advice " << i; + + for (int i = 0; i < 256; ++i) + { + const in_t q = static_cast(i); + const auto got = recon(*dpf::eval_point(keys.first, q), + *dpf::eval_point(keys.second, q)); + EXPECT_EQ(got, q == alpha ? uint8_t{7} : uint8_t{0}) << i; + } +} diff --git a/test/tests/ic_test.cpp b/test/tests/ic_test.cpp index fd37bce..f3ae988 100644 --- a/test/tests/ic_test.cpp +++ b/test/tests/ic_test.cpp @@ -3,6 +3,7 @@ #include "dpf.hpp" #include +#include #include #include @@ -40,6 +41,39 @@ void expect_domain(Input r, Input p, Input q, Beta if_true, Beta if_false, } } +struct IcPad +{ + simde__m128i block() { return dpf::uniform_sample(); } + uint8_t bit() { return static_cast(dpf::uniform_sample() & 1u); } +}; + +using IcRng = dpf::ds_randomness), IcPad>; + +IcRng ic_rng() +{ + return {&dpf::uniform_sample, {}}; +} + +template +void expect_samples(Input r, Input p, Input q, uint32_t if_true, uint32_t if_false, + std::initializer_list xs) +{ + auto keys = dpf::make_dpf(r, dpf::ic(p, q, if_true, if_false)); + const uint64_t nmask = keys.first.input_mask; + const uint64_t rb = static_cast(r); + const uint64_t pb = static_cast(p); + const uint64_t qb = static_cast(q); + for (Input x : xs) + { + const uint64_t got = static_cast(dpf::reconstruct( + dpf::eval_point(dpf::ic, keys.first, x), + dpf::eval_point(dpf::ic, keys.second, x))) & 0xffffffffu; + const uint64_t want = oracle(static_cast(x), rb, pb, qb, nmask, + if_true, if_false, 0xffffffffu); + EXPECT_EQ(got, want) << "r=" << rb << " x=" << static_cast(x); + } +} + } // namespace TEST(Ic, Uint8FullDomainCorners) @@ -222,3 +256,92 @@ TEST(Ic, BitPayload) EXPECT_EQ(static_cast(y), w >= 1 && w <= 3) << x; } } + +TEST(Ic, Uint16FullDomain) +{ + expect_domain(uint16_t{0x0100}, uint16_t{20}, uint16_t{400}, + uint32_t{9}, uint32_t{3}, 0xffffffffu); + expect_domain(uint16_t{0xFF00}, uint16_t{0}, uint16_t{1}, + uint32_t{1}, uint32_t{0}, 0xffffffffu); +} + +TEST(Ic, WideMasksSampleTheWrap) +{ + const uint32_t p32 = 10, q32 = 1000; + const uint32_t r32 = 0xFFFFFFF0u; + expect_samples(r32, p32, q32, 7u, 2u, { + 0u, 1u, p32, q32, q32 + 1u, r32, r32 - 1u, r32 + 1u, + 0x80000000u, 0xffffffffu + }); + + const uint64_t p64 = 1, q64 = 3; + const uint64_t r64 = ~uint64_t{0}; + expect_samples(r64, p64, q64, 5u, 4u, { + 0ull, 1ull, 2ull, 3ull, 4ull, r64, r64 - 1ull, + uint64_t{1} << 63 + }); + auto keys = dpf::make_dpf(r64, dpf::ic(p64, q64, uint32_t{5}, uint32_t{4})); + EXPECT_EQ(keys.first.input_mask, ~uint64_t{0}); +} + +TEST(Ic, AdditiveDoernerShelatMatchesDealer) +{ + auto check = [](uint8_t r0, uint8_t r1, uint8_t p, uint8_t q, + uint32_t beta, uint32_t fals) { + const uint8_t r = static_cast(r0 + r1); + auto dealer = dpf::make_dpf(r, dpf::ic(p, q, beta, fals)); + auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_input, r0, r1, ic_rng(), + dpf::ic(p, q, beta, fals)); + for (int x = 0; x < 256; ++x) + { + const auto dealer_y = dpf::reconstruct( + dpf::eval_point(dpf::ic, dealer.first, static_cast(x)), + dpf::eval_point(dpf::ic, dealer.second, static_cast(x))); + const auto ds_y = dpf::reconstruct( + dpf::eval_point(dpf::ic, ds.first, static_cast(x)), + dpf::eval_point(dpf::ic, ds.second, static_cast(x))); + EXPECT_EQ(dealer_y, ds_y) << "r0=" << int(r0) << " r1=" << int(r1) + << " x=" << x; + } + }; + check(9, 100, 3, 50, 4, 0); + check(200, 100, 0, 255, 8, 1); + check(200, 200, 10, 20, 6, 2); + check(0, 0, 1, 1, 1, 0); + check(1, 0, 0, 0, 3, 9); +} + +TEST(Ic, AdditiveGeneval) +{ + const uint8_t r0 = 250, r1 = 20, p = 4, q = 8; + const uint32_t beta = 11, fals = 2; + const uint8_t r = static_cast(r0 + r1); + const uint8_t queries[] = {0, 1, 4, 8, 9, 255}; + auto opened = dpf::geneval_ic(dpf::arith_input, r0, r1, + std::begin(queries), std::end(queries), ic_rng(), + dpf::ic(p, q, beta, fals)); + ASSERT_EQ(opened.party0.size(), 6u); + for (std::size_t i = 0; i < 6; ++i) + { + const uint64_t got = (opened.party0[i] + opened.party1[i]) & 0xffffffffu; + const uint64_t w = static_cast(static_cast(queries[i] - r)); + const uint64_t want = (w >= p && w <= q) ? beta : fals; + EXPECT_EQ(got, want) << i; + } + + const uint8_t none[] = {0}; + auto empty = dpf::geneval_ic(dpf::arith_input, r0, r1, + std::begin(none), std::begin(none), ic_rng(), dpf::ic(p, q, beta)); + EXPECT_TRUE(empty.party0.empty()); + EXPECT_EQ(empty.live_levels, 0u); +} + +TEST(Ic, IntervalRejectsDescendingEndpoints) +{ + auto keys = dpf::make_dpf(uint8_t{4}, dpf::ic(uint8_t{1}, uint8_t{6}, uint32_t{1})); + auto buf = dpf::make_output_buffer(dpf::ic, keys.first, 1); + EXPECT_THROW(dpf::eval_interval(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}, buf), + std::invalid_argument); + EXPECT_THROW(dpf::make_output_buffer(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}), + std::invalid_argument); +} diff --git a/test/tests/incremental_json_test.cpp b/test/tests/incremental_json_test.cpp index 293d975..74476e6 100644 --- a/test/tests/incremental_json_test.cpp +++ b/test/tests/incremental_json_test.cpp @@ -7,6 +7,11 @@ #include "dpf.hpp" #include "dpf/json.hpp" +static_assert(NLOHMANN_JSON_VERSION_MAJOR == 3 + && NLOHMANN_JSON_VERSION_MINOR == 12 + && NLOHMANN_JSON_VERSION_PATCH == 0, + "JSON tests build against nlohmann 3.12.0"); + namespace { @@ -17,15 +22,16 @@ TEST(IncrementalJsonTest, CmpKeyRoundTrips) const uint32_t alpha = 0x00abcdefu; const uint64_t yt = 42u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt)); - using KT = std::decay_t; - static_assert(KT::is_multilevel, "cmp key must be multi-level"); - ASSERT_EQ(KT::num_outputs, 0u); + using KT0 = std::decay_t; + using KT1 = std::decay_t; + static_assert(KT0::is_multilevel, "cmp key must be multi-level"); + ASSERT_EQ(KT0::num_outputs, 0u); const std::string s0 = dpf::json::to_json(k0); const std::string s1 = dpf::json::to_json(k1); - auto r0 = dpf::json::from_json(s0); - auto r1 = dpf::json::from_json(s1); + auto r0 = dpf::json::from_json(s0); + auto r1 = dpf::json::from_json(s1); const uint64_t mask = k0.cmp().mask; EXPECT_EQ(r0.cmp().nbits, k0.cmp().nbits); @@ -48,10 +54,11 @@ TEST(IncrementalJsonTest, CmpGeqRoundTrips) const uint32_t alpha = 100u; const uint64_t yt = 5u, yf = 9u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::geq(yt, yf)); - using KT = std::decay_t; + using KT0 = std::decay_t; + using KT1 = std::decay_t; - auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); - auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); const uint64_t mask = k0.cmp().mask; auto r = [&](uint32_t q) { @@ -62,4 +69,184 @@ TEST(IncrementalJsonTest, CmpGeqRoundTrips) EXPECT_EQ(r(101u), yt); } +// A single-output classic key, including its leaf share. +TEST(IncrementalJsonTest, ClassicPointRoundTrips) +{ + const uint32_t alpha = 0x00abcdefu; + const uint32_t y = 99u; + auto [k0, k1] = dpf::make_dpf(alpha, y); + using P0 = std::decay_t; + using P1 = std::decay_t; + + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + + EXPECT_EQ(static_cast(dpf::reconstruct( + *dpf::eval_point(r0, alpha), *dpf::eval_point(r1, alpha))), y); + EXPECT_EQ(static_cast(dpf::reconstruct( + *dpf::eval_point(r0, alpha ^ 1u), *dpf::eval_point(r1, alpha ^ 1u))), 0u); +} + +// Multi-lane outputs are stored as leaf blocks, not truncated integers. +TEST(IncrementalJsonTest, VecOutputRoundTrips) +{ + using out_t = dpf::vec; + const std::uint16_t alpha = 0x1234; + out_t y; + y[0] = 1; + y[1] = 0xffffffffu; + y[2] = 7; + y[3] = 100; + auto [k0, k1] = dpf::make_dpf(alpha, y); + using P0 = std::decay_t; + using P1 = std::decay_t; + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + auto at = [&](std::uint16_t x) { + return dpf::reconstruct(*dpf::eval_point(r0, x), *dpf::eval_point(r1, x)); + }; + EXPECT_EQ(at(alpha), y); + EXPECT_EQ(at(0), out_t{}); + EXPECT_EQ(at(static_cast(alpha + 1)), out_t{}); +} + +// `at<>` keys carry leaf outputs the comparison-only path used to drop. +TEST(IncrementalJsonTest, AtOutputRoundTrips) +{ + const uint32_t x = 0x00abcdefu; + auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one)); + using P0 = std::decay_t; + using P1 = std::decay_t; + static_assert(P0::is_multilevel, "at<> key must be multi-level"); + ASSERT_GT(P0::num_outputs, 0u); + + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + EXPECT_TRUE(static_cast(dpf::reconstruct( + *dpf::eval_point(dpf::out<0, 10>, r0, x), + *dpf::eval_point(dpf::out<0, 10>, r1, x)))); + const uint32_t neighbor = x ^ (1u << (32 - 10)); + EXPECT_FALSE(static_cast(dpf::reconstruct( + *dpf::eval_point(dpf::out<0, 10>, r0, neighbor), + *dpf::eval_point(dpf::out<0, 10>, r1, neighbor)))); +} + +// Point output and comparison channel on one key. +TEST(IncrementalJsonTest, OutputAndCmpRoundTrips) +{ + const uint32_t alpha = 0x00abcdefu; + const uint64_t yt = 42u; + auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(yt)); + using P0 = std::decay_t; + using P1 = std::decay_t; + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + + EXPECT_EQ(static_cast(dpf::reconstruct( + *dpf::eval_point(r0, alpha), *dpf::eval_point(r1, alpha))), 7u); + EXPECT_EQ(static_cast(dpf::reconstruct( + *dpf::eval_point(r0, alpha ^ 1u), *dpf::eval_point(r1, alpha ^ 1u))), 0u); + + const uint64_t mask = k0.cmp().mask; + auto recon_cmp = [&](uint32_t q) { + return dpf::reconstruct(dpf::eval_point(dpf::cmp, r0, q), + dpf::eval_point(dpf::cmp, r1, q)) & mask; + }; + EXPECT_EQ(recon_cmp(alpha - 1u), yt); + EXPECT_EQ(recon_cmp(alpha), 0u); + EXPECT_EQ(recon_cmp(alpha + 1u), 0u); +} + +// Comparison payloads wider than 64 bits stay in the correction words. +TEST(IncrementalJsonTest, Uint128CmpRoundTrips) +{ + using beta = simde_uint128; + const std::uint8_t alpha = 0x20; + const beta hi = (beta{1} << 80) + 9; + const beta lo = beta{3}; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(hi, lo)); + using P0 = std::decay_t; + using P1 = std::decay_t; + const std::string s0 = dpf::json::to_json(k0); + EXPECT_NE(s0.find("\"cw_last\":["), std::string::npos); + + auto r0 = dpf::json::from_json(s0); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + EXPECT_EQ(r0.cw_last_word(), k0.cw_last_word()); + EXPECT_EQ(r0.cmp_addend_word(), k0.cmp_addend_word()); + EXPECT_EQ(r0.value_cw(), k0.value_cw()); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + const beta got = dpf::reconstruct( + dpf::eval_point(dpf::cmp, r0, q), + dpf::eval_point(dpf::cmp, r1, q)); + const beta want = q > alpha ? hi : lo; + EXPECT_EQ(got, want) << int(q); + } +} + +// iDCF prefix corrections are part of the same wide word. +TEST(IncrementalJsonTest, Uint128IdcfRoundTrips) +{ + using beta = simde_uint128; + const std::uint8_t alpha = 0x6e; + const beta y = (beta{1} << 100) + 13; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::idcf(dpf::gt(y))); + using P0 = std::decay_t; + using P1 = std::decay_t; + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + EXPECT_EQ(r0.prefix_cws(), k0.prefix_cws()); + for (std::uint8_t q : {std::uint8_t{0}, std::uint8_t{0x60}, alpha, std::uint8_t{0x70}}) + { + const beta got = dpf::reconstruct( + dpf::eval_point(dpf::cmp, r0, q), + dpf::eval_point(dpf::cmp, r1, q)); + const beta want = dpf::reconstruct( + dpf::eval_point(dpf::cmp, k0, q), + dpf::eval_point(dpf::cmp, k1, q)); + EXPECT_EQ(got, want) << int(q); + } +} + +// Wildcard coefficients survive, so assign_cmp still works after a round-trip. +TEST(IncrementalJsonTest, WildcardCmpAssignAfterRoundTrip) +{ + const uint32_t alpha = 0x00abcdefu; + const uint64_t yt = 42u; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(dpf::wildcard_value{})); + using P0 = std::decay_t; + using P1 = std::decay_t; + static_assert(P0::cmp_is_wildcard, "expected a wildcard comparison key"); + + auto r0 = dpf::json::from_json(dpf::json::to_json(k0)); + auto r1 = dpf::json::from_json(dpf::json::to_json(k1)); + EXPECT_FALSE(r0.cmp_assigned()); + EXPECT_EQ(r0.value_cw_coeff(), k0.value_cw_coeff()); + EXPECT_EQ(r0.cw_last_coeff_word(), k0.cw_last_coeff_word()); + + dpf::assign_cmp(r0, r1, yt); + EXPECT_TRUE(r0.cmp_assigned()); + const uint64_t mask = k0.cmp().mask; + auto r = [&](uint32_t q) { + return dpf::reconstruct(dpf::eval_point(dpf::cmp, r0, q), + dpf::eval_point(dpf::cmp, r1, q)) & mask; + }; + EXPECT_EQ(r(alpha - 1u), yt); + EXPECT_EQ(r(0u), yt); + EXPECT_EQ(r(alpha), 0u); + EXPECT_EQ(r(alpha + 1u), 0u); + + auto a0 = dpf::json::from_json(dpf::json::to_json(r0)); + auto a1 = dpf::json::from_json(dpf::json::to_json(r1)); + EXPECT_TRUE(a0.cmp_assigned()); + auto again = [&](uint32_t q) { + return dpf::reconstruct(dpf::eval_point(dpf::cmp, a0, q), + dpf::eval_point(dpf::cmp, a1, q)) & mask; + }; + EXPECT_EQ(again(alpha - 1u), yt); + EXPECT_EQ(again(alpha), 0u); +} + } // namespace diff --git a/test/tests/incremental_test.cpp b/test/tests/incremental_test.cpp index e84d0a7..e5662db 100644 --- a/test/tests/incremental_test.cpp +++ b/test/tests/incremental_test.cpp @@ -447,7 +447,10 @@ TEST_F(IncrementalDpfTest, DealerMatchesDoernerShelat) dpf::xor_wrapper{0xcafe}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{3}, uint8_t{5}), @@ -458,7 +461,10 @@ TEST_F(IncrementalDpfTest, DealerMatchesDoernerShelat) EXPECT_TRUE(same_incr_key(dealer.second, ds.second)); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rngb{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds2 = dpf::make_dpf_doerner_shelat(x0, x1, rngb, dpf::at<10>(dpf::bit::one), dpf::at<14>(uint8_t{3}, uint8_t{5}), @@ -483,7 +489,10 @@ TEST_F(IncrementalDpfTest, IntermediateWildcardSameKey) uint64_t{42}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::at<16>(wc), uint64_t{42}); @@ -615,7 +624,10 @@ TEST_F(IncrementalDpfTest, DsEvalAgreesWithDealer) dpf::at<10>(dpf::bit::one), uint32_t{99}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), uint32_t{99}); @@ -796,7 +808,10 @@ TEST_F(IncrementalDpfTest, FixedpointDealerMatchesDoernerShelat) fp16::from_raw(0x00030000)); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<12>(y, z), dpf::at<16>(wc), fp16::from_raw(0x00030000)); @@ -1010,7 +1025,10 @@ TEST_F(IncrementalDpfTest, LocalCwProtocolMatchesDsRandomness) uint32_t x1 = x ^ x0; reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto via_rng = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), uint32_t{5}); @@ -1156,7 +1174,10 @@ TEST_F(IncrementalDpfTest, CmpDealerMatchesDoernerShelat) dpf::lt(uint64_t{42})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::lt(uint64_t{42})); @@ -1335,7 +1356,10 @@ TEST_F(IncrementalDpfTest, CmpValueCwGroupWidthDsParity) dpf::lt(uint16_t{42})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::lt(uint16_t{42})); using KT = std::decay_t; @@ -1627,7 +1651,10 @@ TEST_F(IncrementalDpfTest, DsWildcardCmpMatchesDealerThenAssign) dpf::root_sampler_t{take_root}, dpf::lt(dpf::wildcard_value{})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng, dpf::lt(dpf::wildcard_value{})); @@ -1686,7 +1713,10 @@ TEST_F(IncrementalDpfTest, DsWildcardCmpWithAtAndNarrowPayload) dpf::at<16>(uint16_t{9}), dpf::lt(dpf::wildcard_value{})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng, dpf::at<16>(uint16_t{9}), dpf::lt(dpf::wildcard_value{})); diff --git a/test/tests/lane_blast_test.cpp b/test/tests/lane_blast_test.cpp index b572cae..c73b5d9 100644 --- a/test/tests/lane_blast_test.cpp +++ b/test/tests/lane_blast_test.cpp @@ -392,7 +392,10 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) dpf::root_sampler_t{take_root}, y); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto on = dpf::geneval_point(x0, x1, alpha, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) expect_live_words(keys.first, on); EXPECT_TRUE(on.leaf_live); EXPECT_EQ(opened(on.party0[0], on.party1[0]), y); @@ -401,8 +404,11 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) const in_t neighbor = static_cast(alpha ^ 1u); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto lane = dpf::geneval_point(x0, x1, neighbor, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(lane.leaf_live); expect_live_words(keys.first, lane); EXPECT_EQ(opened(lane.party0[0], lane.party1[0]), out_t{}); @@ -410,7 +416,10 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) const in_t far = 200; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto off = dpf::geneval_point(x0, x1, far, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_FALSE(off.leaf_live); EXPECT_LT(off.live_levels, off.correction_words.size()); expect_live_words(keys.first, off); @@ -418,8 +427,11 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) EXPECT_NE(off.party0[0], ev(keys.first, far)); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto iv = dpf::geneval_interval(x0, x1, in_t{41}, in_t{50}, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(iv.leaf_live); expect_live_words(keys.first, iv); ASSERT_EQ(iv.party0.size(), 10u); @@ -427,7 +439,10 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) EXPECT_EQ(opened(iv.party0[i], iv.party1[i]), out_t{}); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto full = dpf::geneval_full(x0, x1, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_EQ(full.party0.size(), 256u); EXPECT_TRUE(full.leaf_live); expect_live_words(keys.first, full); @@ -440,8 +455,11 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes) const in_t seq[] = {0, 255, alpha, neighbor, alpha}; reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto sq = dpf::geneval_sequence(x0, x1, std::begin(seq), std::end(seq), dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(sq.leaf_live); expect_live_words(keys.first, sq); for (std::size_t i = 0; i < 5; ++i) @@ -463,15 +481,21 @@ TEST(LaneBlast, GenevalNybleArithAndSigned) auto keys = dpf::make_dpf(secret, dpf::root_sampler_t{take_root}, y); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto g = dpf::geneval_point(dpf::arith_input, a0, a1, secret, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(g.leaf_live); expect_live_words(keys.first, g); EXPECT_EQ(opened(g.party0[0], g.party1[0]), y); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto miss = dpf::geneval_point(dpf::arith_input, a0, a1, in_t{250}, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_EQ(opened(miss.party0[0], miss.party1[0]), out_t{}); expect_live_words(keys.first, miss); } @@ -484,7 +508,10 @@ TEST(LaneBlast, GenevalNybleArithAndSigned) auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, y); reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto full = dpf::geneval_full(x0, x1, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_EQ(full.party0.size(), 256u); EXPECT_TRUE(full.leaf_live); expect_live_words(keys.first, full); @@ -497,8 +524,11 @@ TEST(LaneBlast, GenevalNybleArithAndSigned) EXPECT_EQ(full.party0[i], ev(keys.first, v)); } reset_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto iv = dpf::geneval_interval(x0, x1, in_t{-2}, in_t{2}, dpf::ds_randomness{take_root, Pad{}}, y); + HEDLEY_PRAGMA(GCC diagnostic pop) ASSERT_EQ(iv.party0.size(), 5u); for (int q = -2; q <= 2; ++q) { diff --git a/test/tests/multipoint_test.cpp b/test/tests/multipoint_test.cpp new file mode 100644 index 0000000..5f5bed8 --- /dev/null +++ b/test/tests/multipoint_test.cpp @@ -0,0 +1,180 @@ +#include + +#include +#include +#include +#include + +#include "dpf.hpp" + +using Interior = dpf::prg::aes128; +using Exterior = dpf::prg::aes128; + +namespace +{ + +template +void expect_points(const Key0 & k0, const Key1 & k1, + const std::vector & alphas, const std::vector & betas) +{ + for (std::size_t i = 0; i < alphas.size(); ++i) + { + EXPECT_EQ(dpf::reconstruct(dpf::eval_multipoint(k0, alphas[i]), + dpf::eval_multipoint(k1, alphas[i])), + betas[i]); + } +} + +} // namespace + +TEST(Multipoint, PlainPointsAndZeros) +{ + using Input = std::uint8_t; + const std::vector alphas{1, 2, 9, 40}; + const std::vector betas{7, 11, 1000, 3}; + auto [k0, k1] = dpf::make_multipoint(alphas, betas); + EXPECT_FALSE(decltype(k0)::is_verifiable); + EXPECT_TRUE(decltype(k0)::is_multipoint); + EXPECT_EQ(k0.bucket_count, k1.bucket_count); + EXPECT_EQ(k0.bucket_domain, k1.bucket_domain); + EXPECT_GT(k0.bucket_count, alphas.size()); + + expect_points(k0, k1, alphas, betas); + for (Input x = 0; x < 255; ++x) + { + const bool hot = x == 1 || x == 2 || x == 9 || x == 40; + const auto y = dpf::reconstruct(dpf::eval_multipoint(k0, x), + dpf::eval_multipoint(k1, x)); + if (!hot) + EXPECT_EQ(y, 0u); + } + EXPECT_EQ(dpf::reconstruct(dpf::eval_multipoint(k0, Input{255}), + dpf::eval_multipoint(k1, Input{255})), + 0u); +} + +TEST(Multipoint, VerifiableFullDomain) +{ + using Input = std::uint8_t; + const std::vector alphas{4, 8, 15, 16}; + const std::vector betas{1, 2, 3, 4}; + auto [k0, k1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{}); + EXPECT_TRUE(decltype(k0)::is_verifiable); + + dpf::proof_token batch0{}, batch1{}; + std::array xs{}; + for (int i = 0; i < 256; ++i) + xs[static_cast(i)] = static_cast(i); + std::vector y0(xs.size()); + std::vector y1(xs.size()); + dpf::eval_multipoint(k0, xs, y0.begin(), dpf::prove(batch0)); + dpf::eval_multipoint(k1, xs, y1.begin(), dpf::prove(batch1)); + EXPECT_TRUE(dpf::verify(batch0, batch1)); + + for (std::size_t i = 0; i < xs.size(); ++i) + { + std::uint64_t expect = 0; + for (std::size_t p = 0; p < alphas.size(); ++p) + if (alphas[p] == xs[i]) + expect = betas[p]; + EXPECT_EQ(dpf::reconstruct(y0[i], y1[i]), expect); + } + + dpf::proof_token a0{}, a1{}; + dpf::audit_multipoint(k0, dpf::prove(a0)); + dpf::audit_multipoint(k1, dpf::prove(a1)); + EXPECT_TRUE(dpf::verify(a0, a1)); +} + +TEST(Multipoint, TamperedBucketRejects) +{ + using Input = std::uint8_t; + const std::vector alphas{3, 5, 7, 9}; + const std::vector betas{1, 1, 1, 1}; + auto [k0, k1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{}); + + auto & cs = const_cast(k0.buckets[0].correction_seeds()[0]); + cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1)); + + dpf::proof_token a0{}, a1{}; + dpf::audit_multipoint(k0, dpf::prove(a0)); + dpf::audit_multipoint(k1, dpf::prove(a1)); + EXPECT_FALSE(dpf::verify(a0, a1)); +} + +TEST(Multipoint, XorAndHalfTree) +{ + using Input = std::uint8_t; + using Ht = dpf::prg::aes128_ccr; + const std::array alphas{10, 20, 30, 40}; + const std::array, 4> betas{ + dpf::xor_wrapper{0x11u}, + dpf::xor_wrapper{0x22u}, + dpf::xor_wrapper{0x33u}, + dpf::xor_wrapper{0x44u}}; + auto [k0, k1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{}); + EXPECT_TRUE(std::decay_t::tree::is_half_tree); + + for (std::size_t i = 0; i < alphas.size(); ++i) + { + dpf::proof_token p0{}, p1{}; + const auto y0 = dpf::eval_multipoint(k0, alphas[i], dpf::prove(p0)); + const auto y1 = dpf::eval_multipoint(k1, alphas[i], dpf::prove(p1)); + EXPECT_EQ(dpf::reconstruct(y0, y1), betas[i]); + EXPECT_TRUE(dpf::verify(p0, p1)); + } + EXPECT_EQ(dpf::reconstruct(dpf::eval_multipoint(k0, Input{0}), + dpf::eval_multipoint(k1, Input{0})), + dpf::xor_wrapper{0}); +} + +TEST(Multipoint, Remark1Packing) +{ + using Input = std::uint16_t; + std::vector alphas; + std::vector betas; + alphas.reserve(32); + betas.reserve(32); + for (int i = 0; i < 32; ++i) + { + alphas.push_back(static_cast(100 + i * 17)); + betas.push_back(static_cast(i + 1)); + } + auto [k0, k1] = dpf::make_multipoint(alphas, betas); + EXPECT_LE(k0.bucket_count, alphas.size() * 2); + expect_points(k0, k1, alphas, betas); + EXPECT_EQ(dpf::reconstruct(dpf::eval_multipoint(k0, Input{1}), + dpf::eval_multipoint(k1, Input{1})), + 0u); +} + +TEST(Multipoint, RejectsAnEmptyListAndALengthMismatch) +{ + const std::vector alphas{1, 2}; + const std::vector betas{1}; + EXPECT_THROW( + { + auto keys = dpf::make_multipoint(alphas, betas); + (void)keys; + }, + std::invalid_argument); + EXPECT_THROW( + { + auto keys = dpf::make_multipoint( + std::vector{}, std::vector{}); + (void)keys; + }, + std::invalid_argument); +} + +TEST(Multipoint, RejectsDuplicates) +{ + const std::vector alphas{1, 2, 1, 4}; + const std::vector betas{1, 2, 3, 4}; + EXPECT_THROW( + { + auto keys = dpf::make_multipoint(alphas, betas); + (void)keys; + }, + std::invalid_argument); +} diff --git a/test/tests/nmod_test.cpp b/test/tests/nmod_test.cpp index 8961c94..8db93bc 100644 --- a/test/tests/nmod_test.cpp +++ b/test/tests/nmod_test.cpp @@ -168,6 +168,19 @@ TEST(Nmod, RejectsAZeroReciprocalAndAHugeQuotient) std::overflow_error); } +TEST(Nmod, ScalePastTheProductWindow) +{ + const auto pos = grotto::nmod(1, 200, 1, 60, 4); + EXPECT_EQ(pos.quotient, 0); + EXPECT_EQ(pos.residue, 0); + + const auto neg = grotto::nmod(-1, 200, 1, 60, 4); + EXPECT_EQ(neg.quotient, -1); + EXPECT_EQ(neg.residue, 15); + + EXPECT_THROW(grotto::nmod(1, 100001u, 1, 0, 4), std::invalid_argument); +} + TEST(Nmod, MostNegativeInputModuloOne) { const auto split = grotto::nmod(INT64_MIN, 0, 1, 0, 4); diff --git a/test/tests/packed_lane_test.cpp b/test/tests/packed_lane_test.cpp index 2f8b412..277cc07 100644 --- a/test/tests/packed_lane_test.cpp +++ b/test/tests/packed_lane_test.cpp @@ -172,6 +172,8 @@ TEST(PackedLane, ScalarRing) EXPECT_EQ(10_nyble, dpf::nyble{10}); EXPECT_EQ(dpf::utils::bitlength_of_v, 2u); EXPECT_EQ(dpf::utils::bitlength_of_v, 4u); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ((dpf::utils::bitlength_of_output_v), 2u); EXPECT_EQ((dpf::utils::bitlength_of_output_v), 4u); EXPECT_EQ((dpf::outputs_per_leaf_v), 64u); @@ -179,6 +181,7 @@ TEST(PackedLane, ScalarRing) EXPECT_EQ((dpf::outputs_per_leaf_v), 32u); EXPECT_EQ((dpf::outputs_per_leaf_v), 64u); EXPECT_EQ((dpf::lg_outputs_per_leaf_v), 7u); + HEDLEY_PRAGMA(GCC diagnostic pop) } TEST(PackedLane, SimdMatchesScalar) @@ -229,8 +232,11 @@ TEST(PackedLane, LeafFunctorsMatchSimd) auto via4 = dpf::lane_arith::mul_epi4(a256, dpf::nyble{7}); EXPECT_EQ(std::memcmp(&scaled, &via4, sizeof(via4)), 0); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::array aa{a128, b128}; std::array bb{b128, a128}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto both = dpf::add_leaf(aa, bb); auto e0 = dpf::lane_arith::add_epi4(a128, b128); auto e1 = dpf::lane_arith::add_epi4(b128, a128); diff --git a/test/tests/path_recipe_test.cpp b/test/tests/path_recipe_test.cpp index e55a8c0..769171e 100644 --- a/test/tests/path_recipe_test.cpp +++ b/test/tests/path_recipe_test.cpp @@ -209,8 +209,11 @@ TEST(PathRecipe, DoernerShelatAndGenevalMatchLength) lcp_len(static_cast(x), alpha)); } + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{ dpf::uniform_sample, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) const uint8_t ends[] = {0x00, 0x91, 0xFF}; auto g = dpf::geneval_cmp(x0, x1, std::begin(ends), std::end(ends), rng, dpf::lcp(uint64_t{1})); diff --git a/test/tests/prg_aes_ccr_test.cpp b/test/tests/prg_aes_ccr_test.cpp new file mode 100644 index 0000000..9a1e1ee --- /dev/null +++ b/test/tests/prg_aes_ccr_test.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include +#include +#include + +#include "dpf.hpp" +#include "simde/simde/x86/avx2.h" + +namespace +{ + +bool blocks_equal(simde__m128i a, simde__m128i b) +{ + return simde_mm_movemask_epi8(simde_mm_cmpeq_epi8(a, b)) == 0xFFFF; +} + +simde__m128i block_from_lanes(std::uint64_t lo, std::uint64_t hi) +{ + return simde_mm_set_epi64x(static_cast(hi), + static_cast(lo)); +} + +} // namespace + +TEST(AesCcrPrg, SigmaRoundTrip) +{ + using prg = dpf::prg::aes128_ccr; + const simde__m128i samples[] = { + block_from_lanes(0, 0), + block_from_lanes(1, 0), + block_from_lanes(0, 1), + block_from_lanes(0x0123456789abcdefull, 0xfedcba9876543210ull), + block_from_lanes(~0ull, ~0ull), + }; + for (simde__m128i x : samples) + { + EXPECT_TRUE(blocks_equal(prg::sigma_inv(prg::sigma(x)), x)); + EXPECT_TRUE(blocks_equal(prg::sigma(prg::sigma_inv(x)), x)); + EXPECT_TRUE(blocks_equal(prg::sigma_prime(x), + simde_mm_xor_si128(prg::sigma(x), x))); + } +} + +TEST(AesCcrPrg, HashIsAesMmoOfSigma) +{ + using prg = dpf::prg::aes128_ccr; + simde__m128i x = block_from_lanes(0x1111222233334444ull, 0x5555666677778888ull); + EXPECT_TRUE(blocks_equal(prg::hash(x), + dpf::prg::aes128::eval(prg::sigma(x), 0))); + EXPECT_TRUE(blocks_equal(prg::H(x), prg::hash(x))); + EXPECT_TRUE(blocks_equal(prg::eval(x, 0), prg::hash(x))); + EXPECT_TRUE(blocks_equal(prg::eval(x, 3), + dpf::prg::aes128::eval(prg::sigma(x), 3))); +} + +TEST(AesCcrPrg, Eval01IsHalfTreeChildren) +{ + using prg = dpf::prg::aes128_ccr; + simde__m128i seed = block_from_lanes(0x0123456789abcdefull, 0xfedcba9876543210ull); + auto kids = prg::eval01(seed); + const simde__m128i h = prg::hash(seed); + EXPECT_TRUE(blocks_equal(kids[0], h)); + EXPECT_TRUE(blocks_equal(kids[1], simde_mm_xor_si128(h, seed))); + EXPECT_TRUE(blocks_equal(simde_mm_xor_si128(kids[0], kids[1]), seed)); +} + +TEST(AesCcrPrg, TwoTweakDiffersFromHalfExpand) +{ + using prg = dpf::prg::aes128_ccr; + simde__m128i seed = block_from_lanes(7, 9); + auto half = prg::eval01(seed); + auto tw = prg::eval01_twotweak(seed); + const simde__m128i base = dpf::unset_lo_bit(seed); + EXPECT_TRUE(blocks_equal(tw[0], prg::hash(base))); + EXPECT_TRUE(blocks_equal(tw[1], prg::hash(dpf::set_lo_bit(base)))); + EXPECT_FALSE(blocks_equal(half[0], tw[0])); // half hashes full seed + EXPECT_FALSE(blocks_equal(half[1], tw[1])); +} + +TEST(AesCcrPrg, HashX4MatchesScalar) +{ + using prg = dpf::prg::aes128_ccr; + alignas(16) simde__m128i in[4] = { + block_from_lanes(1, 2), + block_from_lanes(3, 4), + block_from_lanes(5, 6), + block_from_lanes(7, 8), + }; + alignas(16) simde__m128i out[4]; + prg::hash_x4(in, out); + for (int i = 0; i < 4; ++i) + EXPECT_TRUE(blocks_equal(out[i], prg::hash(in[i]))) << i; +} + +TEST(AesCcrPrg, Eval01X4MatchesScalar) +{ + using prg = dpf::prg::aes128_ccr; + alignas(16) simde__m128i seeds[4] = { + block_from_lanes(10, 20), + block_from_lanes(30, 40), + block_from_lanes(50, 60), + block_from_lanes(70, 80), + }; + alignas(16) simde__m128i left[4], right[4]; + prg::eval01_x4(seeds, left, right); + for (int i = 0; i < 4; ++i) + { + auto kids = prg::eval01(seeds[i]); + EXPECT_TRUE(blocks_equal(left[i], kids[0])) << i; + EXPECT_TRUE(blocks_equal(right[i], kids[1])) << i; + } +} + +TEST(AesCcrPrg, BulkEvalMatchesScalarAndRejectsAWrappingSpan) +{ + using prg = dpf::prg::aes128_ccr; + simde__m128i seed = block_from_lanes(0xabcdu, 0x1234u); + alignas(16) simde__m128i out[3]; + out[0] = block_from_lanes(1, 1); + prg::eval(seed, out, 0); + EXPECT_TRUE(blocks_equal(out[0], block_from_lanes(1, 1))); + prg::eval(seed, out, 3, 5); + for (int i = 0; i < 3; ++i) + EXPECT_TRUE(blocks_equal(out[i], prg::eval(seed, static_cast(5 + i)))) + << i; + EXPECT_THROW(prg::eval(seed, out, 2, 0xffffffffu), std::invalid_argument); +} + +TEST(AesCcrPrg, HalfTreeTagIsPresent) +{ + static_assert(std::is_void_v); + SUCCEED(); +} diff --git a/test/tests/prg_chacha_test.cpp b/test/tests/prg_chacha_test.cpp index 7562097..1fa0974 100644 --- a/test/tests/prg_chacha_test.cpp +++ b/test/tests/prg_chacha_test.cpp @@ -243,3 +243,41 @@ TEST(ChachaPrg, ExpandAndBufferedReplay) EXPECT_EQ(replay.get<0>(), s0); EXPECT_NE(s0, streamed.get<0>()); } + +template +void expect_chacha_comparison_and_interval() +{ + const std::uint8_t alpha = 0x3C; + auto [c0, c1] = dpf::make_dpf(alpha, dpf::lt(std::uint32_t{7}, std::uint32_t{1})); + for (int i = 0; i < 256; ++i) + { + const auto x = static_cast(i); + const auto got = dpf::reconstruct( + dpf::eval_point(dpf::cmp, c0, x), + dpf::eval_point(dpf::cmp, c1, x)); + EXPECT_EQ(static_cast(got) & 0xffffffffu, + x < alpha ? 7u : 1u) + << "x=" << i; + } + + const std::uint8_t r = 9, p = 2, q = 5; + auto keys = dpf::make_dpf(r, dpf::ic(p, q, std::uint32_t{4}, std::uint32_t{1})); + for (int i = 0; i < 256; ++i) + { + const auto x = static_cast(i); + const auto got = dpf::reconstruct( + dpf::eval_point(dpf::ic, keys.first, x), + dpf::eval_point(dpf::ic, keys.second, x)); + const auto w = static_cast(x - r); + EXPECT_EQ(static_cast(got) & 0xffffffffu, + (w >= p && w <= q) ? 4u : 1u) + << "x=" << i; + } +} + +TEST(ChachaPrg, ComparisonAndIntervalKeys) +{ + expect_chacha_comparison_and_interval(); + expect_chacha_comparison_and_interval(); + expect_chacha_comparison_and_interval(); +} diff --git a/test/tests/range_lut_test.cpp b/test/tests/range_lut_test.cpp index 8014060..173119a 100644 --- a/test/tests/range_lut_test.cpp +++ b/test/tests/range_lut_test.cpp @@ -34,6 +34,8 @@ long double truth_of(grotto::reduced which, long double x) case grotto::reduced::inv: return 1.0L / x; case grotto::reduced::rsqrt: return 1.0L / std::sqrt(x); case grotto::reduced::invsq: return 1.0L / (x * x); + case grotto::reduced::expm1: return expm1l(x); + case grotto::reduced::log1p: return log1pl(x); } return 0; } @@ -64,6 +66,8 @@ const char * name_of(grotto::reduced which) case grotto::reduced::inv: return "inv"; case grotto::reduced::rsqrt: return "rsqrt"; case grotto::reduced::invsq: return "invsq"; + case grotto::reduced::expm1: return "expm1"; + case grotto::reduced::log1p: return "log1p"; } return "?"; } @@ -244,3 +248,99 @@ TEST(RangeLut, RejectsPolesAndNonPositiveLogarithms) EXPECT_THROW(grotto::eval_reduced(grotto::reduced::csch, 16, 0), std::domain_error); EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 7, 32), std::invalid_argument); } + +TEST(RangeLut, TanAndSecPolesThrow) +{ + const unsigned k = 16; + auto pole_of = [&](grotto::reduced which) { + const std::int64_t lo = raw_of(-8.0L, k); + const std::int64_t hi = raw_of(8.0L, k); + for (std::int64_t raw = lo; raw <= hi; ++raw) + { + try + { + (void)grotto::eval_reduced(which, k, raw); + } + catch (const std::domain_error &) + { + return raw; + } + } + return std::int64_t{0}; + }; + for (auto which : {grotto::reduced::tan, grotto::reduced::sec}) + { + const std::int64_t raw = pole_of(which); + ASSERT_NE(raw, 0) << name_of(which); + EXPECT_THROW(grotto::eval_reduced(which, k, raw), std::domain_error) + << name_of(which) << " raw=" << raw; + if (raw > raw_of(-8.0L, k)) + { + EXPECT_NO_THROW(grotto::eval_reduced(which, k, raw - 1)) + << name_of(which); + } + } +} + +TEST(RangeLut, Exp10RejectsAnIntegerPowerPast18) +{ + const unsigned k = 16; + int first_overflow = -1; + for (int n = 0; n <= 20; ++n) + { + try + { + (void)grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(static_cast(n), k)); + } + catch (const std::overflow_error &) + { + first_overflow = n; + break; + } + } + ASSERT_GT(first_overflow, 0); + ASSERT_LE(first_overflow, 19); + EXPECT_NO_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, + raw_of(static_cast(first_overflow - 1), k))); + EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(19.0L, k)), + std::overflow_error); + EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(-19.0L, k)), + std::overflow_error); +} + +TEST(RangeLut, Expm1AndLog1pTrackLibm) +{ + const long double expm1_samples[] = { + -4.0L, -2.0L, -1.0L, -0.7L, -0.5L, -0.25L, -0.125L, -0.015625L, + 0.0L, 0.015625L, 0.125L, 0.25L, 0.5L, 0.7L, 1.0L, 1.5L, 2.0L, + }; + const long double log1p_samples[] = { + -0.75L, -0.5L, -0.25L, -0.125L, -0.015625L, 0.0L, 0.015625L, + 0.125L, 0.25L, 0.5L, 1.0L, 1.5L, 3.0L, 7.5L, 16.0L, 100.0L, + }; + for (unsigned k : grotto::principal_precisions) + { + expect_ulps(grotto::reduced::expm1, k, std::ldexp(1.0L, -static_cast(k)), 2.0L); + expect_ulps(grotto::reduced::expm1, k, -std::ldexp(1.0L, -static_cast(k)), 2.0L); + expect_ulps(grotto::reduced::log1p, k, std::ldexp(1.0L, -static_cast(k)), 2.0L); + expect_ulps(grotto::reduced::log1p, k, -std::ldexp(1.0L, -static_cast(k)), 2.0L); + for (long double x : expm1_samples) + expect_ulps(grotto::reduced::expm1, k, x, 20.0L); + for (long double x : log1p_samples) + expect_ulps(grotto::reduced::log1p, k, x, 8.0L); + } +} + +TEST(RangeLut, Expm1AndLog1pFixpoints) +{ + for (unsigned k : grotto::principal_precisions) + { + EXPECT_EQ(grotto::eval_reduced(grotto::reduced::expm1, k, 0), 0); + EXPECT_EQ(grotto::eval_reduced(grotto::reduced::log1p, k, 0), 0); + const std::int64_t neg_one = -raw_of(1.0L, k); + EXPECT_THROW(grotto::eval_reduced(grotto::reduced::log1p, k, neg_one), std::domain_error); + EXPECT_THROW(grotto::eval_reduced(grotto::reduced::log1p, k, neg_one - 1), std::domain_error); + const std::int64_t far = raw_of(-40.0L, k); + EXPECT_EQ(grotto::eval_reduced(grotto::reduced::expm1, k, far), neg_one); + } +} diff --git a/test/tests/stress_scenarios_test.cpp b/test/tests/stress_scenarios_test.cpp index a0409f6..8712a07 100644 --- a/test/tests/stress_scenarios_test.cpp +++ b/test/tests/stress_scenarios_test.cpp @@ -639,7 +639,10 @@ TEST_F(StressScenariosTest, ClassicMixedDealerMatchesDoernerShelat) dpf::root_sampler_t{take_root}, uint32_t{7}, dpf::xor_wrapper{0xdeadbeef}, uint32_t{42}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, uint32_t{7}, dpf::xor_wrapper{0xdeadbeef}, uint32_t{42}); @@ -974,7 +977,10 @@ TEST_F(StressScenariosTest, CmpPackedAtDeepestUnderDoernerShelat) uint32_t{9}, dpf::lt(uint64_t{42})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<14>(uint8_t{3}, uint8_t{5}), uint32_t{9}, @@ -1455,7 +1461,7 @@ TEST_F(StressScenariosTest, ModintMultilevelWithCmp) // Full-domain cmp on the 12-bit input: true-value 4 below threshold, 1 at/above. for (unsigned q = 0; q < 0x1000u; q += 0x11u) { - in_t qi{q}; + in_t qi{static_cast(q)}; const auto got = dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, qi), dpf::eval_point(dpf::cmp, k1, qi)) & mask; const auto want = (q < 0x2abu) ? uint64_t{4} : uint64_t{1}; EXPECT_EQ(got, want) << "q=" << q; @@ -1509,7 +1515,10 @@ TEST_F(StressScenariosTest, DsIdentityManyLevelsMixedWidthsXorAdditiveCmpLt) uint32_t{9}, dpf::lt(uint64_t{42})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<8>(dpf::bit::one), dpf::at<16>(uint8_t{3}, dpf::xor_wrapper{0xa5}), @@ -1551,7 +1560,10 @@ TEST_F(StressScenariosTest, DsIdentityPacked16xU8DeepestGeq) uint32_t{5}, dpf::geq(uint16_t{100}, uint16_t{1})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<12>( uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}, @@ -1580,7 +1592,10 @@ TEST_F(StressScenariosTest, DsIdentityClassicMultiLeafXorAdditiveBytes) dpf::root_sampler_t{take_root}, uint32_t{7}, dpf::xor_wrapper{0xdeadbeef}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, uint32_t{7}, dpf::xor_wrapper{0xdeadbeef}); @@ -1605,7 +1620,10 @@ TEST_F(StressScenariosTest, DsIdentityIntermediateWildcardConcreteSiblings) dpf::at<16>(uint16_t{5}, wc), // concrete sibling next to the wildcard uint32_t{9}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<10>(dpf::bit::one), dpf::at<16>(uint16_t{5}, wc), @@ -1637,7 +1655,10 @@ TEST_F(StressScenariosTest, DsIdentityFixedpointAtWithCmp) fp16::from_raw(0x00030000), dpf::lt(uint64_t{1000})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<12>(y), fp16::from_raw(0x00030000), @@ -1684,7 +1705,10 @@ TEST_F(StressScenariosTest, DsIdentityAesInteriorLowmcExteriorMultilevel) dpf::root_sampler_t{take_root}, dpf::at<12>(uint8_t{9}), uint16_t{4}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat( x0, x1, rng, dpf::at<12>(uint8_t{9}), uint16_t{4}); @@ -1711,7 +1735,10 @@ TEST_F(StressScenariosTest, DsIdentityWildcardCmpThenAssign) uint32_t{7}, dpf::geq(dpf::wildcard_value{})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<12>(uint8_t{5}), uint32_t{7}, @@ -1787,7 +1814,10 @@ TEST_F(StressScenariosTest, DsIdentityModintMultilevelXorAdditiveCmp) uint16_t{9}, dpf::lt(uint16_t{4}, uint16_t{1})); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<6>(uint8_t{5}, dpf::xor_wrapper{0x3c}), uint16_t{9}, @@ -1817,7 +1847,10 @@ TEST_F(StressScenariosTest, DsIdentityKeywordMultilevel) dpf::root_sampler_t{take_root}, dpf::at<6>(uint8_t{1}), uint16_t{2}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<6>(uint8_t{1}), uint16_t{2}); @@ -2091,7 +2124,10 @@ TEST_F(StressScenariosTest, DsIdentityPackedSmallWildcardAtNonTerminal) dpf::at<14>(w, w, w, w, uint8_t{9}, w, w, w), uint32_t{5}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::at<14>(w, w, w, w, uint8_t{9}, w, w, w), uint32_t{5}); @@ -2174,7 +2210,10 @@ TEST_F(StressScenariosTest, DsIdentityPackedWildcardLowmcExteriorThenAssign) dpf::at<12>(w, w, w, w), uint16_t{77}); reset_tape_roots(); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness rng{take_root, {}}; + HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat( x0, x1, rng, dpf::at<12>(w, w, w, w), uint16_t{77}); diff --git a/test/tests/types_test.cpp b/test/tests/types_test.cpp index 2d65adf..9e93cf3 100644 --- a/test/tests/types_test.cpp +++ b/test/tests/types_test.cpp @@ -189,8 +189,11 @@ TEST(TypeTraits, IntegralSelectionAndBuiltinWidths) EXPECT_EQ(dpf::utils::bitlength_of_v, 128u); EXPECT_EQ(dpf::utils::bitlength_of_v, 128u); EXPECT_EQ(dpf::utils::bitlength_of_v, 256u); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ(dpf::utils::bitlength_of_v, 128u); EXPECT_EQ(dpf::utils::bitlength_of_v, 256u); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_EQ((dpf::utils::bitlength_of_v>), 128u); EXPECT_TRUE(dpf::utils::uses_signed_msb_v); @@ -272,6 +275,8 @@ TEST(TypeTraits, BoundaryWidthsAndOutputPacking) static_assert(boundary_width_traits<255>()); static_assert(boundary_width_traits<256>()); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ((dpf::utils::bitlength_of_output_v), 8u); EXPECT_EQ((dpf::utils::bitlength_of_output_v), 16u); EXPECT_EQ((dpf::utils::bitlength_of_output_v), 32u); @@ -281,7 +286,10 @@ TEST(TypeTraits, BoundaryWidthsAndOutputPacking) EXPECT_EQ((dpf::utils::bitlength_of_output_v), 1u); EXPECT_EQ((dpf::utils::bitlength_of_output_v, simde__m128i>), 16u); EXPECT_EQ((dpf::utils::bitlength_of_output_v, simde__m128i>), 32u); + HEDLEY_PRAGMA(GCC diagnostic pop) + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ((dpf::outputs_per_leaf_v), 16u); EXPECT_EQ((dpf::outputs_per_leaf_v), 4u); EXPECT_EQ((dpf::outputs_per_leaf_v), 128u); @@ -289,6 +297,7 @@ TEST(TypeTraits, BoundaryWidthsAndOutputPacking) EXPECT_EQ((dpf::block_length_of_leaf_v), 1u); EXPECT_EQ((dpf::block_length_of_leaf_v), 2u); EXPECT_EQ((dpf::block_length_of_leaf_v, simde__m128i>), 2u); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(std::is_trivially_copyable_v); EXPECT_TRUE(std::is_trivially_copyable_v); @@ -463,7 +472,10 @@ TEST(Bit, ConversionArithmeticAndStreams) EXPECT_EQ(unchanged, dpf::bit::one); EXPECT_EQ(dpf::utils::bitlength_of_v, 1u); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ((dpf::utils::bitlength_of_output_v), 1u); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_TRUE(std::numeric_limits::is_specialized); EXPECT_EQ(std::numeric_limits::digits, 1); EXPECT_EQ(std::numeric_limits::digits, 1); @@ -751,7 +763,10 @@ TEST(Wildcard, TraitsAndDeferredValues) EXPECT_TRUE(dpf::is_wildcard_v>); EXPECT_TRUE((std::is_same_v>>, int>)); EXPECT_EQ(dpf::utils::bitlength_of_v>, 32u); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_EQ((dpf::utils::bitlength_of_output_v, simde__m256i>), 1u); + HEDLEY_PRAGMA(GCC diagnostic pop) EXPECT_FALSE(dpf::concrete_value>{}(dpf::wildcard_value{}).has_value()); EXPECT_EQ(dpf::concrete_value{}(7u), std::optional{7u}); @@ -865,8 +880,11 @@ TEST(LeafArithmetic, GroupsWiderThanOneLane) auto pack_sum = [](auto value) { using value_type = decltype(value); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") constexpr auto blocks = dpf::block_length_of_leaf_v; std::array node{}; + HEDLEY_PRAGMA(GCC diagnostic pop) static_assert(sizeof(node) == sizeof(value_type)); std::memcpy(node.data(), &value, sizeof(value)); return node; @@ -953,14 +971,20 @@ TEST(LeafArithmetic, XorGroupsAndModintLanes) psnip_uint32_t one_bits = 0; std::memcpy(&one_bits, &one, sizeof(one_bits)); expect_lanes128(dpf::multiply_leaf(ones, one), one_bits); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_TRUE(same128(dpf::leaf_arithmetic::add_t{}(ones, ones), simde_mm_setzero_si128())); + HEDLEY_PRAGMA(GCC diagnostic pop) float two = 2.0f; simde__m128i one_node{}; simde__m128i two_node{}; std::memcpy(&one_node, &one, sizeof(one)); std::memcpy(&two_node, &two, sizeof(two)); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto xored = dpf::leaf_arithmetic::add_t{}(one_node, two_node); + HEDLEY_PRAGMA(GCC diagnostic pop) float xored_float = 0; std::memcpy(&xored_float, &xored, sizeof(xored_float)); EXPECT_NE(xored_float, one + two); @@ -970,9 +994,12 @@ TEST(LeafArithmetic, XorGroupsAndModintLanes) std::memcpy(&dbl_bits, &dbl, sizeof(dbl_bits)); auto dbl_scaled = dpf::multiply_leaf(simde_mm_set1_epi64x(-1), dbl); EXPECT_EQ(lane128(dbl_scaled, 0), dbl_bits); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") EXPECT_TRUE(same256(dpf::leaf_arithmetic::add_t{}( splat256(dbl_bits), splat256(dbl_bits)), simde_mm256_setzero_si256())); + HEDLEY_PRAGMA(GCC diagnostic pop) auto wildcard_sum = dpf::add_leaf>( splat128(40), splat128(2)); @@ -996,8 +1023,11 @@ TEST(CustomTypes, InputWalkAndOutputLeaf) auto sum = dpf::add_leaf(splat128(9), splat128(4)); auto diff = dpf::subtract_leaf(splat128(9), splat128(4)); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto prod = dpf::leaf_arithmetic::multiply_t{}( splat128(6), static_cast(7)); + HEDLEY_PRAGMA(GCC diagnostic pop) expect_lanes128(sum, 13); expect_lanes128(diff, 5); expect_lanes128(prod, 42); diff --git a/test/tests/verifiable_test.cpp b/test/tests/verifiable_test.cpp new file mode 100644 index 0000000..caee397 --- /dev/null +++ b/test/tests/verifiable_test.cpp @@ -0,0 +1,211 @@ +#include + +#include +#include +#include +#include + +#include "dpf.hpp" + +using Interior = dpf::prg::aes128; +using Exterior = dpf::prg::aes128; + +TEST(Verifiable, HonestPointAccepts) +{ + using Input = std::uint8_t; + const Input alpha = 0x2a; + const std::uint64_t beta = 7; + auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::verifiable{}); + EXPECT_TRUE(decltype(k0)::is_verifiable); + EXPECT_FALSE(decltype(k0)::is_multilevel); + + dpf::proof_token pi0{}, pi1{}; + const auto y0 = *dpf::eval_point(k0, alpha, dpf::prove(pi0)); + const auto y1 = *dpf::eval_point(k1, alpha, dpf::prove(pi1)); + EXPECT_EQ(dpf::reconstruct(y0, y1), beta); + EXPECT_TRUE(dpf::verify(pi0, pi1)); + + dpf::proof_token q0{}, q1{}; + const Input other = static_cast(alpha ^ 1); + EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, other, dpf::prove(q0)), + *dpf::eval_point(k1, other, dpf::prove(q1))), + 0); + EXPECT_TRUE(dpf::verify(q0, q1)); +} + +TEST(Verifiable, TamperedCwRejects) +{ + using Input = std::uint8_t; + auto [k0, k1] = dpf::make_dpf(Input{3}, + std::uint64_t{1}, dpf::verifiable{}); + + // Flip one bit of a public correction word on party 0's view of the + // shared CW array by rebuilding an otherwise-identical key is hard; + // instead flip cs after the fact via const_cast of the seed storage. + auto & cs = const_cast(k0.correction_seeds()[0]); + cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1)); + + dpf::proof_token pi0{}, pi1{}; + (void)*dpf::eval_point(k0, Input{3}, dpf::prove(pi0)); + (void)*dpf::eval_point(k1, Input{3}, dpf::prove(pi1)); + EXPECT_FALSE(dpf::verify(pi0, pi1)); +} + +TEST(Verifiable, BatchVerify) +{ + using Input = std::uint8_t; + auto [k0, k1] = dpf::make_dpf(Input{1}, + std::uint64_t{9}, dpf::verifiable{}); + + std::vector left, right; + for (Input x = 0; x < 8; ++x) + { + dpf::proof_token a{}, b{}; + (void)*dpf::eval_point(k0, x, dpf::prove(a)); + (void)*dpf::eval_point(k1, x, dpf::prove(b)); + left.push_back(a); + right.push_back(b); + } + EXPECT_TRUE(dpf::verify_batch(left, right)); + left[2][0] = simde_mm_xor_si128(left[2][0], simde_mm_set1_epi8(0xff)); + EXPECT_FALSE(dpf::verify_batch(left, right)); + right.pop_back(); + EXPECT_FALSE(dpf::verify_batch(left, right)); +} + +TEST(Verifiable, HalfTreeXorPayload) +{ + using Input = std::uint16_t; + using Ht = dpf::prg::aes128_ccr; + const Input alpha = 0x0101; + auto [k0, k1] = dpf::make_dpf(alpha, + dpf::xor_wrapper{0xdeadbeefull}, dpf::verifiable{}); + EXPECT_TRUE(decltype(k0)::tree::is_half_tree); + + dpf::proof_token pi0{}, pi1{}; + const auto y0 = *dpf::eval_point(k0, alpha, dpf::prove(pi0)); + const auto y1 = *dpf::eval_point(k1, alpha, dpf::prove(pi1)); + EXPECT_EQ(dpf::reconstruct(y0, y1), dpf::xor_wrapper{0xdeadbeefull}); + EXPECT_TRUE(dpf::verify(pi0, pi1)); +} + +TEST(Verifiable, SamePublicPart) +{ + using Input = std::uint8_t; + auto [k0, k1] = dpf::make_dpf(Input{5}, + std::uint64_t{2}, dpf::verifiable{}); + EXPECT_TRUE(dpf::same_public_part(k0, k1)); + + auto & cw = const_cast::interior_node &>( + k0.correction_words()[0]); + cw = simde_mm_xor_si128(cw, simde_mm_set1_epi8(1)); + EXPECT_FALSE(dpf::same_public_part(k0, k1)); +} + +TEST(Verifiable, DefaultKeyUnchangedLayout) +{ + using Input = std::uint8_t; + auto [a0, a1] = dpf::make_dpf(Input{1}, std::uint64_t{3}); + auto [b0, b1] = dpf::make_dpf(Input{1}, std::uint64_t{3}, + dpf::verifiable{}); + EXPECT_FALSE(decltype(a0)::is_verifiable); + EXPECT_TRUE(decltype(b0)::is_verifiable); + EXPECT_EQ(sizeof(a0.correction_words()), sizeof(b0.correction_words())); + EXPECT_EQ(std::tuple_size_v, 0u); + EXPECT_GT(std::tuple_size_v, 0u); +} + +TEST(Extractable, Fp61ReconstructAndSketch) +{ + using Input = std::uint8_t; + const Input alpha = 0x11; + const dpf::fp61 beta{42}; + auto [k0, k1] = dpf::make_dpf(alpha, beta, + dpf::extractable{}, dpf::verifiable{}); + EXPECT_TRUE(decltype(k0)::is_extractable); + EXPECT_TRUE(decltype(k0)::is_verifiable); + EXPECT_TRUE(dpf::same_public_part(k0, k1)); + + const auto y0 = *dpf::eval_point(k0, alpha); + const auto y1 = *dpf::eval_point(k1, alpha); + EXPECT_EQ(dpf::reconstruct(y0, y1), beta); + + std::array pts{0x10, 0x11, 0x12, 0x13}; + std::array r{ + dpf::fp61{3}, dpf::fp61{5}, dpf::fp61{7}, dpf::fp61{11}}; + std::array s0{}, s1{}; + for (std::size_t i = 0; i < pts.size(); ++i) + { + s0[i] = (*dpf::eval_point(k0, pts[i])).raw(); + s1[i] = (*dpf::eval_point(k1, pts[i])).raw(); + } + auto sk0 = dpf::sketch_fold(s0, r); + auto sk1 = dpf::sketch_fold(s1, r); + EXPECT_TRUE(dpf::sketch_verify(sk0, sk1)); + + // Two hot points: forge by XORing a second beta into another share. + s0[0] = s0[0] + beta; + sk0 = dpf::sketch_fold(s0, r); + sk1 = dpf::sketch_fold(s1, r); + EXPECT_FALSE(dpf::sketch_verify(sk0, sk1)); +} + +TEST(Extractable, IncrementalPrefix) +{ + using Input = std::uint16_t; + const Input alpha = 0x00ab; + auto [k0, k1] = dpf::make_dpf(alpha, + dpf::at<8>(dpf::fp61{1}), dpf::extractable{}); + EXPECT_TRUE(decltype(k0)::is_extractable); + EXPECT_TRUE(decltype(k0)::is_multilevel); + + const auto p0 = *dpf::eval_point(dpf::out<0>, k0, alpha); + const auto p1 = *dpf::eval_point(dpf::out<0>, k1, alpha); + EXPECT_EQ(dpf::reconstruct(p0, p1), dpf::fp61{1}); +} + +TEST(Verifiable, IntervalProve) +{ + using Input = std::uint8_t; + auto [k0, k1] = dpf::make_dpf(Input{0x20}, + std::uint64_t{1}, dpf::verifiable{}); + dpf::proof_token a{}, b{}; + dpf::prove_interval(k0, Input{0x1c}, Input{0x24}, dpf::prove(a)); + dpf::prove_interval(k1, Input{0x1c}, Input{0x24}, dpf::prove(b)); + EXPECT_TRUE(dpf::verify(a, b)); +} + +TEST(Verifiable, DoernerShelatProve) +{ + using Input = std::uint8_t; + const Input alpha = 0x44; + const std::uint64_t beta = 5; + Input x0 = 0x12; + Input x1 = static_cast(alpha ^ x0); + struct Pad + { + std::uint64_t n = 1; + simde__m128i block() + { + auto v = simde_mm_set_epi64x(static_cast(n), + static_cast(n * 9 + 3)); + n += 2; + return v; + } + std::uint8_t bit() { return static_cast(n++ & 1u); } + }; + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + dpf::ds_randomness rng{ + dpf::uniform_sample, Pad{}}; + HEDLEY_PRAGMA(GCC diagnostic pop) + auto [s0, s1] = dpf::make_dpf_doerner_shelat( + x0, x1, rng, beta, dpf::verifiable{}); + + EXPECT_TRUE(decltype(s0)::is_verifiable); + dpf::proof_token a{}, b{}; + EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(s0, alpha, dpf::prove(a)), + *dpf::eval_point(s1, alpha, dpf::prove(b))), + beta); + EXPECT_TRUE(dpf::verify(a, b)); +} diff --git a/test/tests/wide_payload_test.cpp b/test/tests/wide_payload_test.cpp new file mode 100644 index 0000000..2945cf0 --- /dev/null +++ b/test/tests/wide_payload_test.cpp @@ -0,0 +1,345 @@ +#include + +#include "dpf.hpp" +#include "grotto/fixedpoint.hpp" + +#include +#include + +namespace +{ + +template +Beta recon_cmp(const A & a, const B & b, X x) +{ + return dpf::reconstruct( + dpf::eval_point(dpf::cmp, a, x), + dpf::eval_point(dpf::cmp, b, x)); +} + +template +Beta recon_prefix4(const A & a, const B & b, X x) +{ + return dpf::reconstruct( + dpf::eval_point<4, Beta>(dpf::cmp_prefix<4>, a, x), + dpf::eval_point<4, Beta>(dpf::cmp_prefix<4>, b, x)); +} + +} // namespace + +TEST(WidePayload, VectorPointAddsComponentwise) +{ + using out_t = dpf::vec; + const std::uint16_t alpha = 0x1234; + out_t y; + y[0] = 1; + y[1] = 0xffffffffu; + y[2] = 7; + y[3] = 100; + auto [k0, k1] = dpf::make_dpf(alpha, y); + auto at = [&](std::uint16_t x) { + return dpf::reconstruct(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)); + }; + EXPECT_EQ(at(alpha), y); + EXPECT_EQ(at(0), out_t{}); + EXPECT_EQ(at(static_cast(alpha + 1)), out_t{}); +} + +TEST(WidePayload, Uint128Comparison) +{ + using beta = simde_uint128; + const std::uint8_t alpha = 0x20; + const beta hi = (beta{1} << 80) + 9; + const beta lo = beta{3}; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(hi, lo)); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + const beta got = recon_cmp(k0, k1, q); + const beta want = q > alpha ? hi : lo; + EXPECT_EQ(got, want) << int(q); + } +} + +TEST(WidePayload, FixedpointComparison) +{ + using beta = grotto::fixedpoint<8, std::uint32_t>; + const std::uint8_t alpha = 10; + const beta hi = beta::from_raw(0x01020304u); + const beta lo = beta::from_raw(0x00000007u); + auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(hi, lo)); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{9}), hi); + EXPECT_EQ(recon_cmp(k0, k1, alpha), lo); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{11}), lo); +} + +TEST(WidePayload, WideFixedpointComparison) +{ + using beta = grotto::fixedpoint<4, simde_uint128>; + const std::uint8_t alpha = 4; + const beta hi = beta::from_raw((simde_uint128{1} << 70) + 11); + const beta lo = beta::from_raw(simde_uint128{2}); + auto [k0, k1] = dpf::make_dpf(alpha, dpf::geq(hi, lo)); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{3}), lo); + EXPECT_EQ(recon_cmp(k0, k1, alpha), hi); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{5}), hi); +} + +TEST(WidePayload, VectorComparison) +{ + using beta = dpf::vec; + const std::uint8_t alpha = 7; + beta hi; + hi[0] = 9; + hi[1] = 1000; + hi[2] = 4; + beta lo; + lo[1] = 1; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(hi, lo)); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{6}), lo); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{8}), hi); +} + +TEST(WidePayload, IntervalContainmentUint128) +{ + using beta = simde_uint128; + const std::uint8_t r = 20; + const beta hi = (beta{1} << 96) + 5; + auto [k0, k1] = dpf::make_dpf(r, dpf::ic(std::uint8_t{3}, std::uint8_t{5}, hi)); + auto at = [&](std::uint8_t opened) { + return dpf::reconstruct( + dpf::eval_point(dpf::ic, k0, opened), + dpf::eval_point(dpf::ic, k1, opened)); + }; + EXPECT_EQ(at(23), hi); + EXPECT_EQ(at(25), hi); + EXPECT_EQ(at(22), beta{}); + EXPECT_EQ(at(26), beta{}); +} + +TEST(WidePayload, IdcfUint128MatchesFullComparison) +{ + using beta = simde_uint128; + const std::uint8_t alpha = 0x6e; + const beta y = (beta{1} << 100) + 13; + auto [i0, i1] = dpf::make_dpf(alpha, dpf::idcf(dpf::gt(y))); + auto [f0, f1] = dpf::make_dpf(alpha, dpf::gt(y)); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + EXPECT_EQ(recon_cmp(i0, i1, q), recon_cmp(f0, f1, q)); + const bool above = (q >> 4) > (alpha >> 4); + const beta prefix = recon_prefix4(i0, i1, q); + const beta want = above ? y : beta{}; + EXPECT_EQ(prefix, want) << int(q); + } +} + +TEST(WidePayload, DealerMatchesDoernerShelat) +{ + using beta = simde_uint128; + const std::uint16_t alpha = 0x0102; + const std::uint16_t x0 = 0x00f0; + const std::uint16_t x1 = static_cast(alpha ^ x0); + const beta y = (beta{1} << 77) + 4; + auto dealer = dpf::make_dpf(alpha, dpf::gt(y)); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + auto ds = dpf::make_dpf_doerner_shelat(x0, x1, + dpf::ds_randomness{ + dpf::uniform_sample, {}}, + dpf::gt(y)); + HEDLEY_PRAGMA(GCC diagnostic pop) + for (std::uint16_t q : {std::uint16_t{0}, std::uint16_t{0x0101}, alpha, + std::uint16_t{0x0103}, std::uint16_t{0xffff}}) + { + const beta from_dealer = recon_cmp(dealer.first, dealer.second, q); + const beta from_ds = recon_cmp(ds.first, ds.second, q); + EXPECT_EQ(from_dealer, from_ds) << q; + } +} + +TEST(WidePayload, XorWrapperComparison) +{ + using beta = dpf::xor_wrapper; + const std::uint8_t alpha = 9; + const beta hi{0x01020304u}; + const beta lo{0x0000000fu}; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(hi, lo)); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{8}), lo); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{10}), hi); +} + +TEST(WidePayload, WildcardAssignUint128) +{ + using beta = simde_uint128; + const std::uint8_t alpha = 15; + const beta hi = (beta{1} << 90) + 8; + const beta lo = beta{2}; + const auto wild = dpf::wildcard; + auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(wild)); + dpf::assign_cmp(k0, k1, hi, lo); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{14}), lo); + EXPECT_EQ(recon_cmp(k0, k1, std::uint8_t{16}), hi); +} + +TEST(WidePayload, IntervalContainmentUint128FalseAndTopBound) +{ + using beta = simde_uint128; + const std::uint8_t r = 10; + const beta hi = (beta{1} << 90) + 8; + const beta lo = beta{3}; + auto [k0, k1] = dpf::make_dpf(r, + dpf::ic(std::uint8_t{5}, std::uint8_t{255}, hi, lo)); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + const auto w = static_cast(q - r); + const beta want = (w >= 5) ? hi : lo; + EXPECT_EQ(dpf::reconstruct( + dpf::eval_point(dpf::ic, k0, q), + dpf::eval_point(dpf::ic, k1, q)), + want) << int(q); + } +} + +TEST(WidePayload, IntervalContainmentXorGroup) +{ + using beta = dpf::xor_wrapper; + const std::uint8_t r = 40; + const beta hi{0x11111111u}; + const beta lo{0x01010101u}; + auto [k0, k1] = dpf::make_dpf(r, + dpf::ic(std::uint8_t{1}, std::uint8_t{255}, hi, lo)); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + const auto w = static_cast(q - r); + const beta want = (w >= 1) ? hi : lo; + EXPECT_EQ(dpf::reconstruct( + dpf::eval_point(dpf::ic, k0, q), + dpf::eval_point(dpf::ic, k1, q)), + want) << int(q); + } +} + +TEST(WidePayload, IntervalContainmentVector) +{ + using beta = dpf::vec; + beta hi; + hi[0] = 9; + hi[1] = 0xffffffffu; + beta lo; + lo[0] = 1; + lo[1] = 2; + const std::uint8_t r = 8; + auto [k0, k1] = dpf::make_dpf(r, + dpf::ic(std::uint8_t{2}, std::uint8_t{4}, hi, lo)); + auto at = [&](std::uint8_t opened) { + return dpf::reconstruct( + dpf::eval_point(dpf::ic, k0, opened), + dpf::eval_point(dpf::ic, k1, opened)); + }; + EXPECT_EQ(at(10), hi); + EXPECT_EQ(at(12), hi); + EXPECT_EQ(at(9), lo); + EXPECT_EQ(at(13), lo); +} + +TEST(WidePayload, IntervalWildcardAssignUint128) +{ + using beta = simde_uint128; + const std::uint8_t r = 15; + const beta hi = (beta{1} << 90) + 8; + const beta lo = beta{2}; + auto [k0, k1] = dpf::make_dpf(r, + dpf::ic(std::uint8_t{2}, std::uint8_t{8}, dpf::wildcard)); + EXPECT_THROW(dpf::eval_point(dpf::ic, k0, std::uint8_t{0}), std::invalid_argument); + dpf::assign_cmp(k0, k1, hi, lo); + auto at = [&](std::uint8_t opened) { + return dpf::reconstruct( + dpf::eval_point(dpf::ic, k0, opened), + dpf::eval_point(dpf::ic, k1, opened)); + }; + EXPECT_EQ(at(17), hi); + EXPECT_EQ(at(23), hi); + EXPECT_EQ(at(16), lo); + EXPECT_EQ(at(24), lo); +} + +TEST(WidePayload, IntervalDoernerShelatUint128) +{ + using beta = simde_uint128; + const std::uint8_t r0 = 9; + const std::uint8_t r1 = 100; + const std::uint8_t r = static_cast(r0 ^ r1); + const beta hi = (beta{1} << 70) + 4; + const beta lo = beta{1}; + auto dealer = dpf::make_dpf(r, dpf::ic(std::uint8_t{3}, std::uint8_t{50}, hi, lo)); + HEDLEY_PRAGMA(GCC diagnostic push) + HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") + auto ds = dpf::make_dpf_doerner_shelat(r0, r1, + dpf::ds_randomness{ + dpf::uniform_sample, {}}, + dpf::ic(std::uint8_t{3}, std::uint8_t{50}, hi, lo)); + HEDLEY_PRAGMA(GCC diagnostic pop) + for (int x = 0; x < 256; x += 17) + { + const auto q = static_cast(x); + EXPECT_EQ(dpf::reconstruct( + dpf::eval_point(dpf::ic, dealer.first, q), + dpf::eval_point(dpf::ic, dealer.second, q)), + dpf::reconstruct( + dpf::eval_point(dpf::ic, ds.first, q), + dpf::eval_point(dpf::ic, ds.second, q))) + << int(q); + } +} + +TEST(WidePayload, Modint96ComparisonAndInterval) +{ + using beta = dpf::modint<96>; + const beta hi{simde_uint128{1} << 80}; + const beta lo{simde_uint128{6}}; + const std::uint8_t alpha = 12; + auto cmp = dpf::make_dpf(alpha, dpf::lt(hi, lo)); + for (int x = 0; x < 256; ++x) + { + const auto q = static_cast(x); + const beta want = q < alpha ? hi : lo; + EXPECT_EQ(recon_cmp(cmp.first, cmp.second, q), want) << int(q); + } + + const std::uint8_t r = 4; + auto ic = dpf::make_dpf(r, dpf::ic(std::uint8_t{1}, std::uint8_t{3}, hi, lo)); + auto at = [&](std::uint8_t opened) { + return dpf::reconstruct( + dpf::eval_point(dpf::ic, ic.first, opened), + dpf::eval_point(dpf::ic, ic.second, opened)); + }; + EXPECT_EQ(at(5), hi); + EXPECT_EQ(at(7), hi); + EXPECT_EQ(at(4), lo); + EXPECT_EQ(at(8), lo); +} + +TEST(WidePayload, VectorLaneArithmeticDoesNotCarry) +{ + dpf::vec a; + dpf::vec b; + a[0] = 0xffffffffu; + a[1] = 1u; + b[0] = 2u; + b[1] = 3u; + const auto sum = a + b; + EXPECT_EQ(sum[0], 1u); + EXPECT_EQ(sum[1], 4u); + const auto prod = a * b; + EXPECT_EQ(prod[0], 0xfffffffeu); + EXPECT_EQ(prod[1], 3u); + const auto neg = -a; + EXPECT_EQ(neg[0], 1u); + EXPECT_EQ(neg[1], 0xffffffffu); + EXPECT_EQ(a, a); + EXPECT_NE(a, b); +} diff --git a/thirdparty/thirdparty.dox b/thirdparty/thirdparty.dox new file mode 100644 index 0000000..59dd558 --- /dev/null +++ b/thirdparty/thirdparty.dox @@ -0,0 +1 @@ +// Registers thirdparty with Doxygen so `@dir` can document the submodule root.