/// @file dpf/grow.hpp /// @brief Dealer-side growth of an existing `dpf_key`: one more level, or one /// more output on a level the tree already has. /// @details `extend` and `add_output` take both parties' keys (same dealer view /// as `make_dpf`), return a new `party_key` pair whose type is the old /// key plus the new material, and keep earlier correction words / /// leaves / comparison words. Specs are the same objects `make_dpf` /// accepts. Memoizer overloads skip the rewalk when both path /// memoizers are already filled through the frontier. Interactive /// Doerner–Shelat growth is `extend_ds` / `add_output_ds` in /// grow_ds.hpp. /// @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_GROW_HPP__ #define LIBDPF_INCLUDE_DPF_GROW_HPP__ #include #include #include #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/dpf_key.hpp" #include "dpf/incremental.hpp" #include "dpf/path_memoizer.hpp" #include "dpf/placement.hpp" #include "dpf/secret_share.hpp" #include "dpf/tree_traits.hpp" #include "dpf/twiddle.hpp" #include "dpf/utils.hpp" #include "dpf/verifiable.hpp" namespace dpf { namespace detail { namespace grow_impl { template struct has_placed_tuple : std::false_type { }; template struct has_placed_tuple> : std::true_type { }; template inline constexpr bool has_placed_tuple_v = has_placed_tuple>::value; template using bare_key_t = unwrap_party_key_t; template HEDLEY_ALWAYS_INLINE bool try_advance(Node & s0, Node & s1, Node cw, psnip_uint8_t advice, bool bit) { const bool t0 = static_cast(get_lo_bit(s0)); const bool t1 = static_cast(get_lo_bit(s1)); const bool is_last = false; const auto kids0 = Tree::expand(s0, is_last); const auto kids1 = Tree::expand(s1, is_last); const Node n0 = Tree::advance(s0, kids0, cw, advice, bit, t0, is_last); const Node n1 = Tree::advance(s1, kids1, cw, advice, bit, t1, is_last); if (get_lo_bit(n0) == get_lo_bit(n1)) return false; s0 = n0; s1 = n1; return true; } /// @brief Rewalk Gen to `stop` using stored CWs. Path bits are recovered: only /// the programmed direction keeps the parties' control bits distinct. template void rewalk_to(const Key & k0, const Key & k1, typename Key::interior_node & s0, typename Key::interior_node & s1, std::size_t stop, bool * path_out = nullptr) { using tree = typename Key::tree; using node = typename Key::interior_node; s0 = k0.root(); s1 = k1.root(); const auto & cws = k0.correction_words(); const auto & adv = k0.correction_advice(); for (std::size_t level = 0; level < stop; ++level) { bool found = false; node a0 = s0; node a1 = s1; if (try_advance(a0, a1, cws[level], adv[level], false)) { s0 = a0; s1 = a1; if (path_out) path_out[level] = false; found = true; } else { a0 = s0; a1 = s1; if (try_advance(a0, a1, cws[level], adv[level], true)) { s0 = a0; s1 = a1; if (path_out) path_out[level] = true; found = true; } } if (!found) throw std::logic_error("grow: path bit recovery failed"); } } template HEDLEY_NO_THROW constexpr bool bit_at(InputT x, std::size_t level) noexcept { constexpr auto bitlen = utils::bitlength_of_v; const auto to_int = utils::to_integral_type{}; using I = typename utils::to_integral_type::integral_type; const I xi = static_cast(to_int(x)); return static_cast( utils::shift_right(xi, bitlen - 1 - level) & I{1}); } /// @brief Read both parties' seeds at `need` from path memoizers. The caller /// must already have filled each memo through `need` (e.g. via /// `eval_point` / `ensure_level`); this does not walk the tree. template void frontier_from_memos(const Key & k0, const Key & k1, Memo0 & m0, Memo1 & m1, typename Key::input_type xx, std::size_t need, typename Key::interior_node & s0, typename Key::interior_node & s1, bool * path_out) { if (need > Key::depth) throw std::invalid_argument("grow: memoizer level past key depth"); static_assert(detail::has_path_high_water::value && detail::has_path_high_water::value, "grow: memoizer overloads require a path memoizer with filled_to"); if (m0.filled_to() < need) throw std::invalid_argument( "grow: party-0 memoizer is not filled to the required level"); if (m1.filled_to() < need) throw std::invalid_argument( "grow: party-1 memoizer is not filled to the required level"); (void)k0; (void)k1; for (std::size_t level = 0; level < Key::depth && path_out != nullptr; ++level) path_out[level] = bit_at(xx, level); s0 = m0[need]; s1 = m1[need]; } template void read_snap_from_memos(Memo0 & m0, Memo1 & m1, std::size_t depth, Node * snap0, Node * snap1, bool * snap_sign) { for (std::size_t lvl = 0; lvl <= depth; ++lvl) { snap0[lvl] = m0[lvl]; snap1[lvl] = m1[lvl]; snap_sign[lvl] = static_cast(get_lo_bit(snap0[lvl])); } } template auto cat_placed(const OldPlaced &, NewPlaced && neu, std::index_sequence, std::index_sequence) { // Old payload values are not stored on the key; only types matter for the // concatenated placed tuple used to name the new key. Dummy defaults. return std::tuple_cat( std::make_tuple(std::tuple_element_t{}...), std::make_tuple(std::get(std::forward(neu))...)); } template auto cat_wrappers(OldWraps && old_w, NewWraps && new_w, std::index_sequence, std::index_sequence) { return std::make_tuple( std::get(std::forward(old_w))..., std::get(std::forward(new_w))...); } template auto cat_addends(const OldAdds & old_a, NewAdds && new_a, std::index_sequence, std::index_sequence) { return std::make_tuple( std::get(old_a)..., std::get(std::forward(new_a))...); } template void copy_cmp_common(const OldKey & src, typename NewKey::value_cw_array & vcw, typename NewKey::value_cw_array & vcoeff, typename NewKey::tail_array & tail, typename NewKey::tail_array & tcoeff, typename NewKey::prefix_cw_array & prefix, typename NewKey::prefix_cw_array & pcoeff, typename NewKey::value_cw_word & cw_last, typename NewKey::value_cw_word & cw_last_coeff) { if constexpr (OldKey::cmp_depth > 0 && NewKey::cmp_depth > 0) { const auto & old_vcw = src.value_cw(); const auto & old_coeff = src.value_cw_coeff(); constexpr std::size_t ncopy = OldKey::value_cw_len < NewKey::value_cw_len ? OldKey::value_cw_len : NewKey::value_cw_len; for (std::size_t i = 0; i < ncopy; ++i) { vcw[i] = old_vcw[i]; if constexpr (OldKey::cmp_is_wildcard && NewKey::cmp_is_wildcard) vcoeff[i] = old_coeff[i]; } const auto & ot = src.tail_cw(); const auto & otc = src.tail_coeff(); constexpr std::size_t nt = OldKey::cmp_tail < NewKey::cmp_tail ? OldKey::cmp_tail : NewKey::cmp_tail; for (std::size_t i = 0; i < nt; ++i) { tail[i] = ot[i]; if constexpr (OldKey::cmp_is_wildcard && NewKey::cmp_is_wildcard) tcoeff[i] = otc[i]; } if constexpr (OldKey::cmp_idcf && NewKey::cmp_idcf) { const auto & op = src.prefix_cws(); const auto & opc = src.prefix_cw_coeff(); constexpr std::size_t np = OldKey::prefix_cw_len < NewKey::prefix_cw_len ? OldKey::prefix_cw_len : NewKey::prefix_cw_len; for (std::size_t i = 0; i < np; ++i) { prefix[i] = op[i]; if constexpr (OldKey::cmp_is_wildcard && NewKey::cmp_is_wildcard) pcoeff[i] = opc[i]; } } cw_last = src.cw_last_word(); cw_last_coeff = src.cw_last_coeff_word(); } } template struct grown_key_type { using old_placed = typename OldKey::placed_tuple; using new_placed = decltype(std::tuple_cat( std::declval(), std::declval())); static constexpr std::size_t cmp_depth = NewCmpDepth > 0 ? NewCmpDepth : OldKey::cmp_depth; static constexpr std::size_t cmp_out_bits = NewCmpDepth > 0 ? NewCmpOutBits : OldKey::cmp_out_bits; static constexpr bool cmp_wild = NewCmpDepth > 0 ? NewCmpWild : OldKey::cmp_is_wildcard; static constexpr std::size_t cmp_block = NewCmpDepth > 0 ? NewCmpBlock : OldKey::cmp_block; static constexpr bool cmp_idcf = NewCmpDepth > 0 ? NewCmpIdcf : OldKey::cmp_idcf; static constexpr bool is_verifiable = NewVerifiable || OldKey::is_verifiable; static constexpr bool is_extractable = NewExtractable || OldKey::is_extractable; using type = incr::incr_dpf_key_of_t; }; template auto grow_impl(const K0 & pk0, const K1 & pk1, Memo0 * m0, Memo1 * m1, bool bit, InputT x, bool have_pre, typename bare_key_t::interior_node * pre_cw, psnip_uint8_t * pre_advice, typename bare_key_t::interior_node * pre_s0, typename bare_key_t::interior_node * pre_s1, Specs &&... specs) { using old_key = bare_key_t; static_assert(std::is_same_v>, "grow: both keys must have the same type"); static_assert(has_placed_tuple_v, "grow: key must be a multi-level / incremental dpf_key"); static_assert(old_key::num_outputs > 0 || old_key::cmp_depth > 0, "grow: empty key"); using input_type = typename old_key::input_type; static_assert(std::is_same_v, input_type> || std::is_convertible_v, "grow: programmed point type mismatch"); constexpr auto bitlen = utils::bitlength_of_v; const old_key & k0 = static_cast(pk0); const old_key & k1 = static_cast(pk1); if constexpr (old_key::depth > 0) { if (std::memcmp(k0.correction_words().data(), k1.correction_words().data(), sizeof(typename old_key::correction_words_array)) != 0 || std::memcmp(k0.correction_advice().data(), k1.correction_advice().data(), sizeof(typename old_key::correction_advice_array)) != 0) throw std::invalid_argument("grow: keys are not a matching pair"); } dcf_runtime_spec dcf_spec{}; bool has_cmp = false; auto new_placed_part = detail::incr::flatten_args_and_cmp(dcf_spec, has_cmp, std::forward(specs)...); using new_placed_part_t = decltype(new_placed_part); constexpr std::size_t arg_cmp_depth = forced_cmp_depth_v; constexpr std::size_t arg_cmp_bits = forced_cmp_out_bits_v; constexpr bool arg_cmp_wild = forced_cmp_wild_v; constexpr std::size_t arg_cmp_block = forced_cmp_block_v; constexpr bool arg_cmp_idcf = forced_cmp_idcf_v; constexpr bool arg_verifiable = args_have_verifiable_v; constexpr bool arg_extractable = args_have_extractable_v; if constexpr (old_key::cmp_depth > 0 && arg_cmp_depth > 0) { static_assert(old_key::cmp_depth == arg_cmp_depth && old_key::cmp_block == arg_cmp_block && old_key::cmp_idcf == arg_cmp_idcf, "grow: comparison channel already present with a different shape"); } using grown = grown_key_type; using new_key = typename grown::type; using new_placed = typename grown::new_placed; using node = typename new_key::interior_node; using tree = typename new_key::tree; constexpr std::size_t old_n = old_key::num_outputs; constexpr std::size_t new_n = new_key::num_outputs; constexpr std::size_t added = new_n - old_n; if constexpr (DoExtend) { static_assert(new_key::depth == old_key::depth + 1, "extend: expected depth to grow by exactly one"); } else { static_assert(new_key::depth == old_key::depth, "add_output: comparison or output would deepen the tree; " "use extend or a shorter lt_at/gt_at/eq_at prefix"); } if constexpr (!DoExtend) { if constexpr (added == 0 && arg_cmp_depth == 0 && !arg_verifiable && !arg_extractable) throw std::invalid_argument("add_output: no new material"); } else { if (old_key::depth + 1 >= bitlen) throw std::invalid_argument( "extend: key is already as deep as the input type"); if (bit != bit_at(static_cast(x), old_key::depth)) throw std::invalid_argument( "extend: bit does not match the programmed point"); } // Reject specs that belong on an older level (extend) or on a missing // level (add_output). if constexpr (added > 0) { constexpr auto new_meta = new_key::meta; for (std::size_t i = old_n; i < new_n; ++i) { const auto lvl = new_meta[i].tree_level; if constexpr (DoExtend) { if (lvl != new_key::depth) throw std::invalid_argument( "extend: output prefix is not the new depth"); } else { if (lvl > old_key::depth) throw std::invalid_argument( "add_output: prefix needs a deeper tree"); } } } input_type xx = static_cast(x); utils::flip_msb_if_signed_integral(xx); node s0{}; node s1{}; std::array path{}; if constexpr (UseMemo) { static_assert(!std::is_void_v && !std::is_void_v, "grow: memoizer overload requires two memoizers"); frontier_from_memos(k0, k1, *m0, *m1, xx, old_key::depth, s0, s1, old_key::depth == 0 ? nullptr : path.data()); } else { rewalk_to(k0, k1, s0, s1, old_key::depth, old_key::depth == 0 ? nullptr : path.data()); (void)m0; (void)m1; } typename new_key::correction_words_array correction_words{}; typename new_key::correction_advice_array correction_advice{}; typename new_key::correction_seeds_array correction_seeds{}; for (std::size_t i = 0; i < old_key::depth; ++i) { correction_words[i] = k0.correction_words()[i]; correction_advice[i] = k0.correction_advice()[i]; } if constexpr (old_key::is_verifiable && new_key::is_verifiable) { for (std::size_t i = 0; i < old_key::depth; ++i) correction_seeds[i] = k0.correction_seeds()[i]; } else if constexpr (!old_key::is_verifiable && new_key::is_verifiable) { // Opting in: sample seeds for levels that already exist. node p0 = k0.root(); node p1 = k1.root(); for (std::size_t level = 0; level < old_key::depth; ++level) { const bool pb = path[level]; const bool is_last = tree::is_last_level(level, new_key::depth); const bool a0 = static_cast(get_lo_bit(p0)); const bool a1 = static_cast(get_lo_bit(p1)); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); p0 = tree::advance(p0, c0, correction_words[level], correction_advice[level], pb, a0, is_last); p1 = tree::advance(p1, c1, correction_words[level], correction_advice[level], pb, a1, is_last); const auto prefix = static_cast( utils::to_integral_type{}(xx) >> (bitlen - (level + 1))); if constexpr (new_key::cmp_block > 0) { correction_seeds[level] = detail::vdpf::make_cs( detail::blocked::fold_spine_tag | level, prefix, p0, p1); } else { correction_seeds[level] = detail::vdpf::make_cs(level, prefix, p0, p1); } } s0 = p0; s1 = p1; } if constexpr (DoExtend) { const std::size_t level = old_key::depth; if (have_pre) { correction_words[level] = *pre_cw; correction_advice[level] = *pre_advice; s0 = *pre_s0; s1 = *pre_s1; } else { const bool is_last = tree::is_last_level(level, new_key::depth); const bool a0 = static_cast(get_lo_bit(s0)); const bool a1 = static_cast(get_lo_bit(s1)); auto c0 = tree::expand(s0, is_last); auto c1 = tree::expand(s1, is_last); node cw{}; psnip_uint8_t advice = 0; tree::make_cw(cw, advice, c0, c1, s0, s1, bit, is_last); s0 = tree::advance(s0, c0, cw, advice, bit, a0, is_last); s1 = tree::advance(s1, c1, cw, advice, bit, a1, is_last); correction_words[level] = cw; correction_advice[level] = advice; } if constexpr (new_key::is_verifiable) { const auto prefix = static_cast( utils::to_integral_type{}(xx) >> (bitlen - (level + 1))); if constexpr (new_key::cmp_block > 0) { correction_seeds[level] = detail::vdpf::make_cs( detail::blocked::fold_spine_tag | level, prefix, s0, s1); } else { correction_seeds[level] = detail::vdpf::make_cs(level, prefix, s0, s1); } } } else { (void)have_pre; (void)pre_cw; (void)pre_advice; (void)pre_s0; (void)pre_s1; } // Full placed tuple for naming / meta; only the new slots' values are used // when planting leaves. new_placed placed = cat_placed(typename old_key::placed_tuple{}, std::move(new_placed_part), std::make_index_sequence{}, std::make_index_sequence{}); auto leaves0 = detail::incr::empty_leaves(std::make_index_sequence{}); auto beavers0 = detail::incr::empty_beavers(std::make_index_sequence{}); auto leaves1 = detail::incr::empty_leaves(std::make_index_sequence{}); auto beavers1 = detail::incr::empty_beavers(std::make_index_sequence{}); // Copy old raw leaves / beavers into the front slots. if constexpr (old_n > 0) { [&](std::index_sequence) { ((std::get(leaves0) = k0.template leaf(), std::get(beavers0) = k0.template beaver(), std::get(leaves1) = k1.template leaf(), std::get(beavers1) = k1.template beaver()), ...); }(std::make_index_sequence{}); } // When depth grows, groups that sat at the old deepest level move from // pos_base-start-0 to pos_base-start-2. Retarget their shared correction // words so eval of those slots stays share-identical. if constexpr (DoExtend && old_n > 0) { using exterior = typename new_key::exterior_prg; using node_ex = typename exterior::block_type; node p0 = k0.root(); node p1 = k1.root(); // Walk to each old-deepest group's level once. for (std::size_t level = 0; level < old_key::depth; ++level) { const bool pb = path[level]; const bool is_last = tree::is_last_level(level, new_key::depth); const bool a0 = static_cast(get_lo_bit(p0)); const bool a1 = static_cast(get_lo_bit(p1)); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); p0 = tree::advance(p0, c0, correction_words[level], correction_advice[level], pb, a0, is_last); p1 = tree::advance(p1, c1, correction_words[level], correction_advice[level], pb, a1, is_last); } const auto seed0 = dpf::unset_lo_2bits(p0); const auto seed1 = dpf::unset_lo_2bits(p1); const bool sign0 = static_cast(get_lo_bit(p0)); [&](std::index_sequence) { (([&] { constexpr std::size_t old_pos = old_key::meta[Is].pos_base; constexpr std::size_t new_pos = new_key::meta[Is].pos_base; if constexpr (old_pos == new_pos) return; using Out = typename new_key::template output_type_t; using Concrete = concrete_type_t; // Build a one-slot outputs tuple so make_leaf_mask indices match. using outs = std::tuple; const auto mask_old = dpf::make_leaf_mask( seed0, seed1, old_pos); const auto mask_new = dpf::make_leaf_mask( seed0, seed1, new_pos); auto & L0 = std::get(leaves0); auto & L1 = std::get(leaves1); if (sign0) { // L = naked - mask => L' = L + mask_old - mask_new L0 = dpf::subtract_leaf( dpf::add_leaf(L0, mask_old), mask_new); L1 = dpf::subtract_leaf( dpf::add_leaf(L1, mask_old), mask_new); } else { // L = mask - naked => L' = L - mask_old + mask_new L0 = dpf::add_leaf( dpf::subtract_leaf(L0, mask_old), mask_new); L1 = dpf::add_leaf( dpf::subtract_leaf(L1, mask_old), mask_new); } }()), ...); }(std::make_index_sequence{}); (void)sign0; } // Plant only groups that contain a newly added slot. A group that also // holds an older slot would need that slot's payload to rebuild the // packed leaf; reject that case. if constexpr (added > 0) { using MetaHolder = detail::incr::meta_holder; constexpr auto meta = new_key::meta; constexpr std::size_t ngroups = [] { std::size_t m = 0; for (std::size_t i = 0; i < new_n; ++i) m = std::max(m, new_key::meta[i].group_id + 1); return m; }(); constexpr auto order = detail::incr::build_group_order(meta, new_n); std::array snap0{}; std::array snap1{}; std::array snap_sign{}; if constexpr (UseMemo) { // Seeds through the old frontier come from the memoizers. After an // extend the new depth's node is already in s0/s1. read_snap_from_memos(*m0, *m1, old_key::depth, snap0.data(), snap1.data(), snap_sign.data()); if constexpr (DoExtend) { snap0[new_key::depth] = s0; snap1[new_key::depth] = s1; snap_sign[new_key::depth] = static_cast(get_lo_bit(s0)); } } else { node p0 = k0.root(); node p1 = k1.root(); snap0[0] = p0; snap1[0] = p1; snap_sign[0] = static_cast(get_lo_bit(p0)); for (std::size_t level = 0; level < new_key::depth; ++level) { const bool pb = (level < old_key::depth) ? path[level] : bit; const bool is_last = tree::is_last_level(level, new_key::depth); const bool a0 = static_cast(get_lo_bit(p0)); const bool a1 = static_cast(get_lo_bit(p1)); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); p0 = tree::advance(p0, c0, correction_words[level], correction_advice[level], pb, a0, is_last); p1 = tree::advance(p1, c1, correction_words[level], correction_advice[level], pb, a1, is_last); snap0[level + 1] = p0; snap1[level + 1] = p1; snap_sign[level + 1] = static_cast(get_lo_bit(p0)); } } detail::incr::for_each_index(std::make_index_sequence{}, [&](auto oi) { constexpr std::size_t G = order[decltype(oi)::value]; constexpr bool touches_new = [] { for (std::size_t i = old_n; i < new_n; ++i) if (new_key::meta[i].group_id == G) return true; return false; }(); constexpr bool touches_old = [] { for (std::size_t i = 0; i < old_n; ++i) if (new_key::meta[i].group_id == G) return true; return false; }(); if constexpr (!touches_new) return; if constexpr (touches_old) { throw std::invalid_argument( "grow: new output shares a packing group with an " "existing slot; use make_dpf for that pack"); } constexpr std::size_t lvl = [] { for (std::size_t i = 0; i < new_n; ++i) if (new_key::meta[i].group_id == G) return new_key::meta[i].tree_level; return std::size_t{0}; }(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using leaf_prg = std::conditional_t, typename new_key::exterior_prg>; detail::incr::gen_group(xx, dpf::unset_lo_2bits(snap0[lvl]), dpf::unset_lo_2bits(snap1[lvl]), static_cast(snap_sign[lvl]), static_cast(dpf::get_lo_bit(snap1[lvl])), placed, leaves0, beavers0, leaves1, beavers1); HEDLEY_PRAGMA(GCC diagnostic pop) }); } if constexpr (!old_key::is_extractable && new_key::is_extractable && old_n > 0 && added == 0) { throw std::invalid_argument( "grow: opting into extractable requires new outputs or a full " "rebuild via make_dpf"); } auto wrap0 = detail::incr::wrap_leaves(leaves0, beavers0, std::make_index_sequence{}); auto wrap1 = detail::incr::wrap_leaves(leaves1, beavers1, std::make_index_sequence{}); typename new_key::value_cw_array value_cws{}; typename new_key::value_cw_array value_cw_coeff{}; typename new_key::tail_array tail{}; typename new_key::tail_array tail_coeff{}; typename new_key::prefix_cw_array prefix_cw{}; typename new_key::prefix_cw_array prefix_coeff{}; typename new_key::value_cw_word cw_last{}; typename new_key::value_cw_word cw_last_coeff{}; typename new_key::value_cw_word cmp_add0{}; typename new_key::value_cw_word cmp_add1{}; copy_cmp_common(k0, value_cws, value_cw_coeff, tail, tail_coeff, prefix_cw, prefix_coeff, cw_last, cw_last_coeff); if constexpr (old_key::cmp_depth > 0) { cmp_add0 = k0.cmp_addend_word(); cmp_add1 = k1.cmp_addend_word(); } detail::cmp_meta cmp = k0.cmp(); // Add a comparison onto a key that had none (integral per-level path). if (has_cmp && old_key::cmp_depth == 0) { using namespace detail::dcf_impl; cmp.nbits = static_cast(dcf_spec.prefix ? dcf_spec.prefix : bitlen); cmp.mask = dcf_spec.mask; cmp.kind = dcf_spec.kind; cmp.active = true; cmp.incremental = dcf_spec.incremental; cmp.eval_as_ge = false; cmp.trivial = cmp_trivial::none; cmp.block_width = static_cast(new_key::cmp_block); cmp.tail_bits = static_cast(new_key::cmp_q); cmp.include_eq = false; const uint64_t delta = dcf_spec.beta & dcf_spec.mask; const uint64_t false_value = dcf_spec.false_value & dcf_spec.mask; unsigned __int128 thresh = static_cast( utils::to_integral_type{}(xx)); if (dcf_spec.prefix) thresh >>= (bitlen - dcf_spec.prefix); detail::incr::adjust_cmp_threshold(cmp, thresh, static_cast(cmp.nbits)); const std::size_t cmp_nbits = static_cast(cmp.nbits); if (cmp.trivial != cmp_trivial::none || new_key::cmp_block > 0 || dcf_spec.custom || dcf_spec.use_payload_group || is_paint_kind(dcf_spec.kind)) { throw std::invalid_argument( "add_output: comparison shape not supported on grow yet"); } uint64_t Va = 0; uint64_t Va1 = 0; node p0 = k0.root(); node p1 = k1.root(); for (std::size_t level = 0; level < new_key::depth; ++level) { const bool pb = (level < old_key::depth) ? path[level] : (DoExtend ? bit : bit_at(xx, level)); const bool is_last = tree::is_last_level(level, new_key::depth); const bool advice0 = static_cast(get_lo_bit(p0)); const bool advice1 = static_cast(get_lo_bit(p1)); const auto val0 = tree::expand_value(p0); const auto val1 = tree::expand_value(p1); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); if (level < cmp_nbits) { const int ai = static_cast( (thresh >> (cmp_nbits - 1 - level)) & 1); const uint64_t base = make_value_cw(val0[0], val0[1], val1[0], val1[1], static_cast(advice0), static_cast(advice1), ai, Va, delta, cmp.mask); value_cws[level] = static_cast(base); if constexpr (new_key::cmp_is_wildcard) { 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 + neg_m(base, cmp.mask)) & cmp.mask); } } p0 = tree::advance(p0, c0, correction_words[level], correction_advice[level], pb, advice0, is_last); p1 = tree::advance(p1, c1, correction_words[level], correction_advice[level], pb, advice1, is_last); if (level + 1 == cmp_nbits) { cw_last = static_cast( make_final_cw(p0, p1, static_cast(get_lo_bit(p1)), Va, cmp.mask, true)); if constexpr (new_key::cmp_is_wildcard) { const uint64_t l1 = make_final_cw(p0, p1, static_cast(get_lo_bit(p1)), Va1, cmp.mask, true); cw_last_coeff = static_cast( (l1 + neg_m(static_cast(cw_last), cmp.mask)) & cmp.mask); } } } uint64_t target = false_value; if (cmp.eval_as_ge) target = (delta + false_value) & cmp.mask; const uint64_t rblind = sample_addend_blind(cmp.mask, [] { return dpf::uniform_sample(); }); uint64_t a0 = 0, a1 = 0; detail::incr::split_cmp_addend(target, cmp.mask, rblind, a0, a1); cmp_add0 = static_cast(a0); cmp_add1 = static_cast(a1); } else if constexpr (DoExtend && old_key::cmp_depth > 0 && new_key::cmp_block == 0 && new_key::depth > old_key::depth) { // Extending a key that already has a comparison: append a value word // when the new level is still inside the comparison. Requires the // caller to re-pass the comparison spec so δ is known. if (has_cmp && cmp.active && cmp.trivial == cmp_trivial::none) { using namespace detail::dcf_impl; const std::size_t level = old_key::depth; const std::size_t cmp_nbits = static_cast(cmp.nbits); if (level < cmp_nbits) { const uint64_t delta = dcf_spec.beta & dcf_spec.mask; unsigned __int128 thresh = static_cast( utils::to_integral_type{}(xx)); if (cmp_nbits < bitlen) thresh >>= (bitlen - cmp_nbits); uint64_t Va = 0; uint64_t Va1 = 0; node p0 = k0.root(); node p1 = k1.root(); for (std::size_t L = 0; L < level; ++L) { const bool advice0 = static_cast(get_lo_bit(p0)); const bool advice1 = static_cast(get_lo_bit(p1)); const auto val0 = tree::expand_value(p0); const auto val1 = tree::expand_value(p1); const int ai = static_cast( (thresh >> (cmp_nbits - 1 - L)) & 1); (void)make_value_cw(val0[0], val0[1], val1[0], val1[1], static_cast(advice0), static_cast(advice1), ai, Va, delta, cmp.mask); if constexpr (new_key::cmp_is_wildcard) { (void)make_value_cw(val0[0], val0[1], val1[0], val1[1], static_cast(advice0), static_cast(advice1), ai, Va1, 1ULL, cmp.mask); } const bool pb = path[L]; const bool is_last = tree::is_last_level(L, new_key::depth); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); p0 = tree::advance(p0, c0, correction_words[L], correction_advice[L], pb, advice0, is_last); p1 = tree::advance(p1, c1, correction_words[L], correction_advice[L], pb, advice1, is_last); } const bool advice0 = static_cast(get_lo_bit(p0)); const bool advice1 = static_cast(get_lo_bit(p1)); const auto val0 = tree::expand_value(p0); const auto val1 = tree::expand_value(p1); const int ai = static_cast( (thresh >> (cmp_nbits - 1 - level)) & 1); const uint64_t base = make_value_cw(val0[0], val0[1], val1[0], val1[1], static_cast(advice0), static_cast(advice1), ai, Va, delta, cmp.mask); value_cws[level] = static_cast(base); if constexpr (new_key::cmp_is_wildcard) { 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 + neg_m(base, cmp.mask)) & cmp.mask); } if (level + 1 == cmp_nbits) { const bool is_last = tree::is_last_level(level, new_key::depth); auto c0 = tree::expand(p0, is_last); auto c1 = tree::expand(p1, is_last); p0 = tree::advance(p0, c0, correction_words[level], correction_advice[level], bit, advice0, is_last); p1 = tree::advance(p1, c1, correction_words[level], correction_advice[level], bit, advice1, is_last); cw_last = static_cast( make_final_cw(p0, p1, static_cast(get_lo_bit(p1)), Va, cmp.mask, true)); } } } } auto adds_new = detail::incr::extract_addends(placed, std::make_index_sequence{}); if constexpr (old_n > 0) { [&](std::index_sequence) { ((std::get(adds_new) = std::get(k0.public_addends)), ...); }(std::make_index_sequence{}); } input_type off0 = k0.offset_x.raw(); input_type off1 = k1.offset_x.raw(); new_key key0{k0.root(), correction_words, correction_advice, std::move(wrap0), off0, cmp, value_cws, static_cast(cw_last), static_cast(cmp_add0), adds_new, value_cw_coeff, static_cast(cw_last_coeff), tail, tail_coeff, prefix_cw, prefix_coeff, correction_seeds}; new_key key1{k1.root(), correction_words, correction_advice, std::move(wrap1), off1, cmp, value_cws, static_cast(cw_last), static_cast(cmp_add1), adds_new, value_cw_coeff, static_cast(cw_last_coeff), tail, tail_coeff, prefix_cw, prefix_coeff, correction_seeds}; if constexpr (old_key::cmp_depth > 0 || (arg_cmp_depth > 0)) { key0.set_cmp_scalars(cw_last, cmp_add0, cw_last_coeff); key1.set_cmp_scalars(cw_last, cmp_add1, cw_last_coeff); if constexpr (old_key::cmp_depth > 0) { key0.set_cmp_assigned(k0.cmp_assigned()); key1.set_cmp_assigned(k1.cmp_assigned()); } } return dpf::make_party_key_pair(std::move(key0), std::move(key1)); } } // namespace grow_impl } // namespace detail /// @brief Append one interior level and plant specs whose prefix is the new /// depth. `bit` is the next MSB-side path bit; `x` is the programmed /// point (must agree with `bit` at the new level) used for leaf lanes. /// \complexity O(d) rewalk of the existing spine plus one `make_cw` / `advance` /// and one exterior leaf plant per new packing group. d is the old /// depth. Copying prior correction words and leaves is O(d + m) for /// m existing outputs. /// \rounds none (dealer / joint view) /// \communication none template HEDLEY_WARN_UNUSED_RESULT auto extend(const K0 & k0, const K1 & k1, bool bit, InputT x, Specs &&... specs) { return detail::grow_impl::grow_impl(k0, k1, static_cast(nullptr), static_cast(nullptr), bit, x, false, static_cast::interior_node *>( nullptr), static_cast(nullptr), static_cast::interior_node *>( nullptr), static_cast::interior_node *>( nullptr), std::forward(specs)...); } /// @brief Append one interior level. Path bit taken from `x` at the old depth. template , bool> && !is_at_v> && !is_cmp_spec_v> && !is_verifiable_tag_v> && !is_extractable_tag_v>>> HEDLEY_WARN_UNUSED_RESULT auto extend(const K0 & k0, const K1 & k1, InputT x, Specs &&... specs) { using old_key = detail::grow_impl::bare_key_t; const bool bit = detail::grow_impl::bit_at(static_cast(x), old_key::depth); return extend(k0, k1, bit, x, std::forward(specs)...); } /// @brief Plant outputs on levels the tree already has. No new correction word. /// \complexity O(d) rewalk (or O(1) seed reads with filled memoizers) plus one /// exterior leaf plant per new packing group. No new interior CW. /// \rounds none /// \communication none template HEDLEY_WARN_UNUSED_RESULT auto add_output(const K0 & k0, const K1 & k1, InputT x, Specs &&... specs) { return detail::grow_impl::grow_impl(k0, k1, static_cast(nullptr), static_cast(nullptr), /*bit=*/false, x, false, static_cast::interior_node *>( nullptr), static_cast(nullptr), static_cast::interior_node *>( nullptr), static_cast::interior_node *>( nullptr), std::forward(specs)...); } /// @brief Like `extend`, but on-path seeds come from path memoizers (joint / /// 2+1 view). Memoizers must already be filled through the old depth /// for `x` (e.g. after `eval_point`); they are not walked here. /// \complexity O(1) seed reads at the frontier, then the same `make_cw` and /// leaf work as dealer `extend`. No O(d) rewalk. /// \rounds none /// \communication none template HEDLEY_WARN_UNUSED_RESULT auto extend(const K0 & k0, const K1 & k1, Memo0 & m0, Memo1 & m1, bool bit, InputT x, Specs &&... specs) { return detail::grow_impl::grow_impl(k0, k1, &m0, &m1, bit, x, false, static_cast::interior_node *>( nullptr), static_cast(nullptr), static_cast::interior_node *>( nullptr), static_cast::interior_node *>( nullptr), std::forward(specs)...); } template , bool> && !is_at_v> && !is_cmp_spec_v> && !is_verifiable_tag_v> && !is_extractable_tag_v>>> HEDLEY_WARN_UNUSED_RESULT auto extend(const K0 & k0, const K1 & k1, Memo0 & m0, Memo1 & m1, InputT x, Specs &&... specs) { using old_key = detail::grow_impl::bare_key_t; const bool bit = detail::grow_impl::bit_at(static_cast(x), old_key::depth); return extend(k0, k1, m0, m1, bit, x, std::forward(specs)...); } /// @brief Like `add_output`, reading on-path seeds from path memoizers. /// \complexity O(1) seed reads per planted level plus leaf plants. Memoizers /// must already be filled through each new slot's tree level. /// \rounds none /// \communication none template HEDLEY_WARN_UNUSED_RESULT auto add_output(const K0 & k0, const K1 & k1, Memo0 & m0, Memo1 & m1, InputT x, Specs &&... specs) { return detail::grow_impl::grow_impl(k0, k1, &m0, &m1, /*bit=*/false, x, false, static_cast::interior_node *>( nullptr), static_cast(nullptr), static_cast::interior_node *>( nullptr), static_cast::interior_node *>( nullptr), std::forward(specs)...); } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_GROW_HPP__