Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
610 lines
21 KiB
C++
610 lines
21 KiB
C++
/// @file dpf/blocked_dcf.hpp
|
|
/// @brief Blocked-checkpoint comparison: one ring word per block of levels.
|
|
/// @details The seed spine stays dense. Ring words are published only at a
|
|
/// public checkpoint schedule. Point eval expands parked siblings
|
|
/// up to the next checkpoint; a full-domain memoizer already holds
|
|
/// those nodes. `q` tail bits, when the comparison sets the key
|
|
/// depth, are a residual table on the node at height `h`.
|
|
/// @note Boyle, Chandran, Gilboa, Gupta, Ishai, Kumar, and Rathee (EUROCRYPT 2021, ePrint 2020/1392) publish a value-correction word on every level. This implementation is ahead of that DCF on payload size: one ring word every B levels, about B times fewer value words, with the same one-seed-word spine. Point evaluation expands the siblings between checkpoints.
|
|
/// @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_BLOCKED_DCF_HPP__
|
|
#define LIBDPF_INCLUDE_DPF_BLOCKED_DCF_HPP__
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "hedley/hedley.h"
|
|
|
|
#include "dpf/dcf.hpp"
|
|
#include "dpf/path_memoizer.hpp"
|
|
#include "dpf/tree_traits.hpp"
|
|
#include "dpf/twiddle.hpp"
|
|
#include "dpf/utils.hpp"
|
|
#include "dpf/verifiable.hpp"
|
|
|
|
namespace dpf
|
|
{
|
|
namespace detail
|
|
{
|
|
namespace blocked
|
|
{
|
|
|
|
/// @brief High bit set on `fold_node` level so blocked proofs diverge from native.
|
|
/// @details Must match the level passed to `make_cs` at blocked keygen. Parked
|
|
/// sibling folds reuse this tag with the sibling's tree depth so they
|
|
/// share `correction_seeds[depth-1]`.
|
|
inline constexpr std::size_t fold_spine_tag = std::size_t{1} << 15;
|
|
|
|
template <typename NodeT>
|
|
HEDLEY_ALWAYS_INLINE
|
|
void fold_spine_node(proof_token & pi, std::size_t level,
|
|
psnip_uint64_t x_bits, NodeT seed, const cs_block & cs) noexcept
|
|
{
|
|
detail::vdpf::fold_node(pi, fold_spine_tag | level, x_bits, seed, cs);
|
|
}
|
|
|
|
template <std::size_t H, std::size_t B>
|
|
struct schedule
|
|
{
|
|
static constexpr std::size_t count =
|
|
(B == 0 || H == 0) ? 0 : (H + B - 1) / B;
|
|
|
|
static constexpr auto depths = [] {
|
|
std::array<std::size_t, count == 0 ? 1 : count> cs{};
|
|
if constexpr (count == 0)
|
|
return cs;
|
|
const std::size_t base = H / count;
|
|
const std::size_t extra = H % count;
|
|
std::size_t acc = 0;
|
|
for (std::size_t i = 0; i < count; ++i)
|
|
{
|
|
acc += base + (i < extra ? 1 : 0);
|
|
cs[i] = acc;
|
|
}
|
|
return cs;
|
|
}();
|
|
|
|
HEDLEY_CONST
|
|
HEDLEY_NO_THROW
|
|
static constexpr bool contains(std::size_t depth) noexcept
|
|
{
|
|
for (std::size_t i = 0; i < count; ++i)
|
|
{
|
|
if (depths[i] == depth)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
HEDLEY_CONST
|
|
HEDLEY_NO_THROW
|
|
static constexpr std::size_t index(std::size_t depth) noexcept
|
|
{
|
|
for (std::size_t i = 0; i < count; ++i)
|
|
{
|
|
if (depths[i] == depth)
|
|
return i;
|
|
}
|
|
return static_cast<std::size_t>(-1);
|
|
}
|
|
};
|
|
|
|
template <typename PRG, typename Node>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
uint64_t rho_of(const Node & node, uint64_t mask) noexcept
|
|
{
|
|
auto kids = dpf::tree_traits<PRG>::expand_value(node);
|
|
return dcf_impl::convert_node(kids[0], mask);
|
|
}
|
|
|
|
HEDLEY_CONST
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
constexpr int control_sign(uint8_t t0, uint8_t t1) noexcept
|
|
{
|
|
return static_cast<int>(t0) - static_cast<int>(t1);
|
|
}
|
|
|
|
HEDLEY_CONST
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
constexpr uint64_t mul_sgn(int sgn, uint64_t v, uint64_t mask) noexcept
|
|
{
|
|
if (sgn > 0)
|
|
return v & mask;
|
|
if (sgn < 0)
|
|
return dcf_impl::neg_m(v, mask);
|
|
return 0;
|
|
}
|
|
|
|
/// @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
|
|
constexpr uint64_t sgn_coeff(int sgn, uint64_t mask) noexcept
|
|
{
|
|
if (sgn > 0)
|
|
return 1ULL & mask;
|
|
if (sgn < 0)
|
|
return dcf_impl::neg_m(1ULL, mask);
|
|
return 0;
|
|
}
|
|
|
|
template <typename PRG, typename Node>
|
|
HEDLEY_NO_THROW
|
|
uint64_t checkpoint_word(const Node & n0, const Node & n1, uint64_t beta,
|
|
uint64_t mask) noexcept
|
|
{
|
|
const int sgn = control_sign(
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n0)),
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n1)));
|
|
const uint64_t r0 = rho_of<PRG>(n0, mask);
|
|
const uint64_t r1 = rho_of<PRG>(n1, mask);
|
|
const uint64_t inner =
|
|
(beta + dcf_impl::neg_m(r0, mask) + r1) & mask;
|
|
return mul_sgn(sgn, inner, mask);
|
|
}
|
|
|
|
template <typename Node>
|
|
HEDLEY_PURE
|
|
HEDLEY_NO_THROW
|
|
uint64_t checkpoint_coeff(const Node & n0, const Node & n1,
|
|
uint64_t mask) noexcept
|
|
{
|
|
return sgn_coeff(control_sign(
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n0)),
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n1))), mask);
|
|
}
|
|
|
|
template <typename PRG, typename Node>
|
|
HEDLEY_NON_NULL(4)
|
|
HEDLEY_NO_THROW
|
|
void suffix_masks(const Node & seed, std::size_t q, uint64_t mask,
|
|
uint64_t * out) noexcept
|
|
{
|
|
Node cur[4]{};
|
|
Node nxt[8]{};
|
|
cur[0] = seed;
|
|
std::size_t n = 1;
|
|
for (std::size_t lvl = 0; lvl < q; ++lvl)
|
|
{
|
|
std::size_t m = 0;
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
auto kids = dpf::tree_traits<PRG>::expand_value(cur[i]);
|
|
nxt[m++] = kids[0];
|
|
nxt[m++] = kids[1];
|
|
}
|
|
for (std::size_t i = 0; i < m; ++i)
|
|
cur[i] = nxt[i];
|
|
n = m;
|
|
}
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
out[i] = dcf_impl::convert_node(cur[i], mask);
|
|
}
|
|
|
|
template <typename PRG, typename Node>
|
|
HEDLEY_NON_NULL(8)
|
|
HEDLEY_NO_THROW
|
|
void tail_words(const Node & n0, const Node & n1, uint64_t beta, uint64_t mask,
|
|
bool include_eq, uint64_t suffix, std::size_t q, uint64_t * words,
|
|
uint64_t * coeffs) noexcept
|
|
{
|
|
uint64_t u0[4]{};
|
|
uint64_t u1[4]{};
|
|
suffix_masks<PRG>(n0, q, mask, u0);
|
|
suffix_masks<PRG>(n1, q, mask, u1);
|
|
const int sgn = control_sign(
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n0)),
|
|
static_cast<uint8_t>(dpf::get_lo_bit(n1)));
|
|
const uint64_t coeff = sgn_coeff(sgn, mask);
|
|
const std::size_t n = std::size_t{1} << q;
|
|
for (std::size_t z = 0; z < n; ++z)
|
|
{
|
|
const bool pred = include_eq
|
|
? (z <= suffix)
|
|
: (z < suffix);
|
|
const uint64_t inner =
|
|
((pred ? beta : 0ULL) + dcf_impl::neg_m(u0[z], mask) + u1[z]) & mask;
|
|
words[z] = mul_sgn(sgn, inner, mask);
|
|
if (coeffs != nullptr)
|
|
coeffs[z] = pred ? coeff : 0ULL;
|
|
}
|
|
}
|
|
|
|
template <typename KeyT>
|
|
HEDLEY_NO_THROW
|
|
uint64_t add_membership(uint64_t acc, const typename KeyT::interior_node & node,
|
|
uint64_t word, uint64_t mask, int party) noexcept
|
|
{
|
|
using prg = typename KeyT::interior_prg;
|
|
const uint8_t t = static_cast<uint8_t>(dpf::get_lo_bit(node));
|
|
const uint64_t y =
|
|
(rho_of<prg>(node, mask) + (t ? word : 0ULL)) & mask;
|
|
return (acc + (party ? dcf_impl::neg_m(y, mask) : y)) & mask;
|
|
}
|
|
|
|
template <typename KeyT>
|
|
uint64_t add_frontier(uint64_t acc, const typename KeyT::interior_node & seed,
|
|
std::size_t from_depth, std::size_t to_depth, const KeyT & dpf, uint64_t word,
|
|
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<node> cur;
|
|
std::vector<node> nxt;
|
|
HEDLEY_PRAGMA(GCC diagnostic pop)
|
|
cur.push_back(seed);
|
|
for (std::size_t lvl = from_depth; lvl < to_depth; ++lvl)
|
|
{
|
|
nxt.clear();
|
|
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, is_last);
|
|
nxt.push_back(kids[0]);
|
|
nxt.push_back(kids[1]);
|
|
}
|
|
cur.swap(nxt);
|
|
}
|
|
for (const node & fs : cur)
|
|
acc = add_membership<KeyT>(acc, fs, word, mask, party);
|
|
return acc;
|
|
}
|
|
|
|
template <typename KeyT, typename InputT>
|
|
HEDLEY_CONST
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
constexpr uint64_t query_suffix(InputT tx, std::size_t nbits, std::size_t q) noexcept
|
|
{
|
|
if (q == 0)
|
|
return 0;
|
|
uint64_t z = 0;
|
|
auto bit_mask = KeyT::msb_mask >> (nbits - q);
|
|
for (std::size_t j = 0; j < q; ++j, bit_mask >>= 1)
|
|
z = (z << 1) | static_cast<uint64_t>(!!(bit_mask & tx));
|
|
return z;
|
|
}
|
|
|
|
template <typename KeyT>
|
|
HEDLEY_NO_THROW
|
|
uint64_t finish_share(const KeyT & dpf, uint64_t suffix, uint64_t acc,
|
|
const typename KeyT::interior_node & at_h, int party) noexcept
|
|
{
|
|
using namespace dcf_impl;
|
|
using prg = typename KeyT::interior_prg;
|
|
const auto & ch = dpf.cmp();
|
|
const uint64_t mask = ch.mask;
|
|
constexpr std::size_t q = KeyT::cmp_q;
|
|
constexpr std::size_t h = KeyT::cmp_h;
|
|
if constexpr (q == 0)
|
|
{
|
|
if (ch.include_eq)
|
|
{
|
|
constexpr auto wi = schedule<h, KeyT::cmp_block>::index(h);
|
|
acc = add_membership<KeyT>(acc, at_h, dpf.value_cw(wi), mask, party);
|
|
}
|
|
(void)suffix;
|
|
}
|
|
else
|
|
{
|
|
const uint64_t z = suffix;
|
|
uint64_t u[4]{};
|
|
suffix_masks<prg>(at_h, q, mask, u);
|
|
const uint8_t t = static_cast<uint8_t>(dpf::get_lo_bit(at_h));
|
|
const uint64_t y = (u[z] + (t ? dpf.tail_cw(z) : 0ULL)) & mask;
|
|
acc = (acc + (party ? neg_m(y, mask) : y)) & mask;
|
|
}
|
|
if (ch.eval_as_ge)
|
|
acc = neg_m(acc, mask);
|
|
const uint64_t add = [&]() -> uint64_t {
|
|
if constexpr (is_party_key_v<KeyT>)
|
|
return dpf.cmp_addend().raw();
|
|
else
|
|
return dpf.cmp_addend();
|
|
}();
|
|
return (acc + add) & mask;
|
|
}
|
|
|
|
template <typename KeyT, typename InputT, typename PathMemoizer>
|
|
uint64_t eval_share(const KeyT & dpf, InputT tx, PathMemoizer & path,
|
|
proof_token * pi = nullptr)
|
|
{
|
|
using node = typename KeyT::interior_node;
|
|
const auto & ch = dpf.cmp();
|
|
const uint64_t mask = ch.mask;
|
|
const uint64_t add = [&]() -> uint64_t {
|
|
if constexpr (is_party_key_v<KeyT>)
|
|
return dpf.cmp_addend().raw();
|
|
else
|
|
return dpf.cmp_addend();
|
|
}();
|
|
if (ch.trivial == cmp_trivial::always_true
|
|
|| ch.trivial == cmp_trivial::always_false)
|
|
return add & mask;
|
|
|
|
constexpr std::size_t h = KeyT::cmp_h;
|
|
using sched = schedule<h, KeyT::cmp_block>;
|
|
const std::size_t nbits = static_cast<std::size_t>(ch.nbits);
|
|
const int party = dpf::get_lo_bit(dpf.root()) ? 1 : 0;
|
|
|
|
// Expand the seed spine without native path folds; blocked proofs use
|
|
// domain-separated tags on newly filled levels only (path-memo safe).
|
|
const auto resume = dpf::detail::path_resume_for_level(path, dpf, tx, h);
|
|
dpf::detail::ensure_level(dpf, tx, path, h);
|
|
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && resume <= h)
|
|
{
|
|
constexpr auto input_bits =
|
|
utils::bitlength_of_v<typename KeyT::input_type>;
|
|
for (std::size_t level = resume; level <= h; ++level)
|
|
{
|
|
const auto x_bits = static_cast<psnip_uint64_t>(
|
|
utils::to_integral_type<typename KeyT::input_type>{}(tx)
|
|
>> (input_bits - level));
|
|
fold_spine_node(*pi, level - 1, x_bits, path[level],
|
|
dpf.correction_seeds()[level - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct parked
|
|
{
|
|
node seed;
|
|
std::size_t depth;
|
|
psnip_uint64_t prefix_bits;
|
|
};
|
|
parked pend[128];
|
|
std::size_t npend = 0;
|
|
|
|
uint64_t acc = 0;
|
|
auto bit_mask = KeyT::msb_mask;
|
|
// Value accumulation always walks from the root. Proof folds for parked
|
|
// siblings must not repeat depths already authenticated on a warm path:
|
|
// re-folding XORs the same contribution away (path-memo cancel bug).
|
|
for (std::size_t level = 0; level < h; ++level, bit_mask >>= 1)
|
|
{
|
|
const bool xi = !!(bit_mask & tx);
|
|
const node & parent = path[level];
|
|
const node right = KeyT::traverse_interior(parent,
|
|
dpf.correction_word(level, true), true,
|
|
KeyT::tree::is_last_level(level, KeyT::depth));
|
|
if (!xi)
|
|
{
|
|
pend[npend].seed = right;
|
|
pend[npend].depth = level + 1;
|
|
// Sibling is the right child of `parent`: path bits with low bit 1.
|
|
const auto path_bits = static_cast<psnip_uint64_t>(
|
|
utils::to_integral_type<typename KeyT::input_type>{}(tx)
|
|
>> (utils::bitlength_of_v<typename KeyT::input_type>
|
|
- (level + 1)));
|
|
pend[npend].prefix_bits = (path_bits & ~static_cast<psnip_uint64_t>(1))
|
|
| static_cast<psnip_uint64_t>(1);
|
|
++npend;
|
|
}
|
|
const std::size_t c = level + 1;
|
|
if (sched::contains(c))
|
|
{
|
|
const uint64_t word = dpf.value_cw(sched::index(c));
|
|
for (std::size_t p = 0; p < npend; ++p)
|
|
{
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && pend[p].depth >= resume)
|
|
{
|
|
// Tree depth (not checkpoint index): must match CS level.
|
|
fold_spine_node(*pi, pend[p].depth - 1, pend[p].prefix_bits,
|
|
pend[p].seed, dpf.correction_seeds()[pend[p].depth - 1]);
|
|
}
|
|
}
|
|
acc = add_frontier<KeyT>(acc, pend[p].seed, pend[p].depth, c,
|
|
dpf, word, mask, party);
|
|
}
|
|
npend = 0;
|
|
}
|
|
}
|
|
const uint64_t suffix = query_suffix<KeyT>(tx, nbits, KeyT::cmp_q);
|
|
return finish_share(dpf, suffix, acc, path[h], party);
|
|
}
|
|
|
|
template <typename KeyT, typename Integral, typename Memo>
|
|
HEDLEY_PURE
|
|
HEDLEY_NO_THROW
|
|
bool memo_has(const Memo & memo, Integral prefix, std::size_t depth,
|
|
Integral from_lane, Integral to_excl) noexcept
|
|
{
|
|
const auto shift = KeyT::cmp_depth - depth;
|
|
const auto from_p = from_lane >> shift;
|
|
if (prefix < from_p)
|
|
return false;
|
|
const auto idx = static_cast<std::size_t>(prefix - from_p);
|
|
const auto count = memo.get_nodes_at_level(depth, from_lane, to_excl);
|
|
return idx < count;
|
|
}
|
|
|
|
template <typename KeyT, typename Integral, typename Memo>
|
|
const typename KeyT::interior_node & memo_node(const Memo & memo, Integral prefix,
|
|
std::size_t depth, Integral from_lane)
|
|
{
|
|
const auto shift = KeyT::cmp_depth - depth;
|
|
const auto from_p = from_lane >> shift;
|
|
const auto idx = static_cast<std::size_t>(prefix - from_p);
|
|
return memo[depth][idx];
|
|
}
|
|
|
|
/// @brief Evaluate one lane from an interval memo; optionally fold a VDPF proof.
|
|
/// @details When `pi == nullptr` or the key is not verifiable, matches the
|
|
/// historical body (no folds). When set, folds path / covered
|
|
/// checkpoint / frontier seeds with `fold_spine_node`, skipping depths
|
|
/// already covered by `path` (same resume rule as `eval_share`).
|
|
/// Interval BFS prove must keep passing a null `pi` here.
|
|
template <typename KeyT, typename Integral, typename Memo,
|
|
typename PathMemoizer = ::dpf::basic_path_memoizer<KeyT>>
|
|
uint64_t eval_share_memo(const KeyT & dpf, Integral lane,
|
|
Integral from_lane, Integral to_excl, const Memo & memo,
|
|
proof_token * pi = nullptr, PathMemoizer * path = nullptr)
|
|
{
|
|
using node = typename KeyT::interior_node;
|
|
const auto & ch = dpf.cmp();
|
|
const uint64_t mask = ch.mask;
|
|
const uint64_t add = [&]() -> uint64_t {
|
|
if constexpr (is_party_key_v<KeyT>)
|
|
return dpf.cmp_addend().raw();
|
|
else
|
|
return dpf.cmp_addend();
|
|
}();
|
|
if (ch.trivial == cmp_trivial::always_true
|
|
|| ch.trivial == cmp_trivial::always_false)
|
|
return add & mask;
|
|
|
|
constexpr std::size_t h = KeyT::cmp_h;
|
|
using sched = schedule<h, KeyT::cmp_block>;
|
|
const int party = dpf::get_lo_bit(dpf.root()) ? 1 : 0;
|
|
const std::size_t nbits = static_cast<std::size_t>(ch.nbits);
|
|
|
|
std::size_t resume = 0;
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && path != nullptr)
|
|
{
|
|
const auto tx = static_cast<typename KeyT::input_type>(lane);
|
|
resume = dpf::detail::path_resume_for_level(*path, dpf, tx, h);
|
|
dpf::detail::path_note_filled_to(*path, h);
|
|
}
|
|
}
|
|
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && resume <= h)
|
|
{
|
|
Integral pfx = 0;
|
|
for (std::size_t depth = 0; depth <= h; ++depth)
|
|
{
|
|
if (depth >= resume && depth >= 1)
|
|
{
|
|
fold_spine_node(*pi, depth - 1,
|
|
static_cast<psnip_uint64_t>(pfx),
|
|
memo_node<KeyT>(memo, pfx, depth, from_lane),
|
|
dpf.correction_seeds()[depth - 1]);
|
|
}
|
|
if (depth < h)
|
|
{
|
|
const bool xi =
|
|
((lane >> (nbits - 1 - depth)) & Integral{1}) != 0;
|
|
pfx = static_cast<Integral>(
|
|
(pfx << 1) | (xi ? Integral{1} : Integral{0}));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct parked
|
|
{
|
|
node seed;
|
|
Integral prefix;
|
|
std::size_t depth;
|
|
};
|
|
parked pend[128];
|
|
std::size_t npend = 0;
|
|
|
|
uint64_t acc = 0;
|
|
Integral path_pref = 0;
|
|
for (std::size_t level = 0; level < h; ++level)
|
|
{
|
|
const bool xi = ((lane >> (nbits - 1 - level)) & Integral{1}) != 0;
|
|
const node & parent = memo_node<KeyT>(memo, path_pref, level, from_lane);
|
|
const Integral sib = static_cast<Integral>((path_pref << 1) | Integral{1});
|
|
if (!xi)
|
|
{
|
|
const node right = KeyT::traverse_interior(parent,
|
|
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;
|
|
++npend;
|
|
}
|
|
path_pref = static_cast<Integral>(
|
|
(path_pref << 1) | (xi ? Integral{1} : Integral{0}));
|
|
const std::size_t c = level + 1;
|
|
if (!sched::contains(c))
|
|
continue;
|
|
const uint64_t word = dpf.value_cw(sched::index(c));
|
|
for (std::size_t p = 0; p < npend; ++p)
|
|
{
|
|
const std::size_t extra = c - pend[p].depth;
|
|
const Integral leftmost =
|
|
static_cast<Integral>(pend[p].prefix << extra);
|
|
const Integral rightmost = static_cast<Integral>(
|
|
leftmost + static_cast<Integral>((Integral{1} << extra) - 1));
|
|
const bool covered =
|
|
memo_has<KeyT>(memo, leftmost, c, from_lane, to_excl)
|
|
&& memo_has<KeyT>(memo, rightmost, c, from_lane, to_excl);
|
|
if (covered && extra < 16)
|
|
{
|
|
const Integral nleaf = static_cast<Integral>(Integral{1} << extra);
|
|
for (Integral k = 0; k < nleaf; ++k)
|
|
{
|
|
const auto pref = static_cast<Integral>(leftmost + k);
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && c >= resume && c >= 1)
|
|
{
|
|
fold_spine_node(*pi, c - 1,
|
|
static_cast<psnip_uint64_t>(pref),
|
|
memo_node<KeyT>(memo, pref, c, from_lane),
|
|
dpf.correction_seeds()[c - 1]);
|
|
}
|
|
}
|
|
acc = add_membership<KeyT>(acc,
|
|
memo_node<KeyT>(memo, pref, c, from_lane), word, mask,
|
|
party);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if constexpr (KeyT::is_verifiable)
|
|
{
|
|
if (pi != nullptr && pend[p].depth >= resume
|
|
&& pend[p].depth >= 1)
|
|
{
|
|
fold_spine_node(*pi, pend[p].depth - 1,
|
|
static_cast<psnip_uint64_t>(pend[p].prefix),
|
|
pend[p].seed,
|
|
dpf.correction_seeds()[pend[p].depth - 1]);
|
|
}
|
|
}
|
|
acc = add_frontier<KeyT>(acc, pend[p].seed, pend[p].depth, c,
|
|
dpf, word, mask, party);
|
|
}
|
|
}
|
|
npend = 0;
|
|
}
|
|
const node & at_h = memo_node<KeyT>(memo, path_pref, h, from_lane);
|
|
const uint64_t suffix = static_cast<uint64_t>(lane)
|
|
& (KeyT::cmp_q == 0 ? 0ULL : ((1ULL << KeyT::cmp_q) - 1ULL));
|
|
return finish_share(dpf, suffix, acc, at_h, party);
|
|
}
|
|
|
|
} // namespace blocked
|
|
} // namespace detail
|
|
} // namespace dpf
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_BLOCKED_DCF_HPP__
|