/// @file dpf/path_sketch.hpp /// @brief VIDPF-style path-consistency sketches for incremental / IDPF keys. /// @details Leaf one-hot is `sketch_fold` in verifiable.hpp. This header /// checks that prefix payloads form a single spine: each depth is /// weight-1 (or a single nonzero weight), and each parent equals the /// sum of its children. Used by Poplar / Mastic one-time verification. /// @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_PATH_SKETCH_HPP__ #define LIBDPF_INCLUDE_DPF_PATH_SKETCH_HPP__ #include #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/dpf_key.hpp" #include "dpf/eval_target.hpp" #include "dpf/eval_walk.hpp" #include "dpf/fp61.hpp" #include "dpf/verifiable.hpp" namespace dpf { namespace detail { namespace path_sketch_impl { template HEDLEY_ALWAYS_INLINE fp61 value_to_fp61(const Y & y) noexcept { if constexpr (std::is_same_v, fp61>) return y; else return fp61{static_cast(y)}; } template struct has_raw_method : std::false_type { }; template struct has_raw_method().raw())>> : std::true_type { }; template HEDLEY_ALWAYS_INLINE fp61 share_to_fp61(const Share & s) noexcept { if constexpr (std::is_same_v, fp61>) return s; else if constexpr (std::is_integral_v>) return fp61{static_cast(s)}; else if constexpr (has_raw_method>::value) return value_to_fp61(s.raw()); else return value_to_fp61(s); } } // namespace path_sketch_impl } // namespace detail /// @brief Weight-1 sketch of the `2^N` prefix shares at output `I`. /// @details Opens to a valid `sketch_fold` iff exactly one prefix is nonzero /// (unit path) or a single weight w sits on one prefix (weighted /// IDPF). Consumes `2^N` challenges from `rs`. template sketch_share sketch_path_level(out_t, const KeyT & key, const fp61 * rs, std::size_t n) { static_assert(is_multilevel_key_v, "sketch_path_level: key must be multilevel / idpf"); constexpr std::size_t nprefix = std::size_t{1} << N; if (rs == nullptr || n < nprefix) throw std::invalid_argument("sketch_path_level: need 2^N challenges"); auto [buf, it] = eval_prefixes(out_t{}, key); (void)buf; std::vector ys; ys.reserve(nprefix); std::size_t i = 0; for (auto a = std::begin(it); a != std::end(it) && i < nprefix; ++a, ++i) ys.push_back(detail::path_sketch_impl::share_to_fp61(*a)); if (ys.size() != nprefix) throw std::runtime_error("sketch_path_level: prefix count mismatch"); return sketch_fold(ys, std::vector(rs, rs + nprefix)); } /// @brief Random linear check that parent[p] = left[2p] + right[2p+1]. /// @details Parent slot `I` has prefix length `N`; child is slot `I+1` with /// length `N+1`. Consumes `2^N` challenges. Opens to 0 when the /// two levels are consistent. template fp61 sketch_path_parent(out_t, out_t, const KeyT & key, const fp61 * gamma, std::size_t n) { static_assert(is_multilevel_key_v, "sketch_path_parent: key must be multilevel / idpf"); constexpr std::size_t nparent = std::size_t{1} << N; if (gamma == nullptr || n < nparent) throw std::invalid_argument("sketch_path_parent: need 2^N challenges"); auto [bp, ip] = eval_prefixes(out_t{}, key); auto [bc, ic] = eval_prefixes(out_t{}, key); (void)bp; (void)bc; std::vector parents; std::vector children; parents.reserve(nparent); children.reserve(nparent * 2); for (auto a = std::begin(ip); a != std::end(ip); ++a) parents.push_back(detail::path_sketch_impl::share_to_fp61(*a)); for (auto a = std::begin(ic); a != std::end(ic); ++a) children.push_back(detail::path_sketch_impl::share_to_fp61(*a)); if (parents.size() != nparent || children.size() != nparent * 2) throw std::runtime_error("sketch_path_parent: prefix count mismatch"); fp61 gap{}; for (std::size_t p = 0; p < nparent; ++p) { const fp61 rel = parents[p] - children[2 * p] - children[2 * p + 1]; gap = gap + gamma[p] * rel; } return gap; } /// @brief Whether two parent-gap shares open to zero. HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_CONST bool sketch_path_verify_parent(fp61 g0, fp61 g1) noexcept { return (g0 - g1).raw() == 0; } namespace detail { namespace path_sketch_impl { template struct verify_levels { template static bool run(const K0 & k0, const K1 & k1, const fp61 *& rs, std::size_t & left) { constexpr std::size_t N = I + 1; constexpr std::size_t nprefix = std::size_t{1} << N; if (left < nprefix) return false; const auto s0 = sketch_path_level(out_t{}, k0, rs, left); const auto s1 = sketch_path_level(out_t{}, k1, rs, left); if (!sketch_verify(s0, s1)) return false; rs += nprefix; left -= nprefix; if constexpr (I + 1 < Depth) { constexpr std::size_t nparent = std::size_t{1} << N; if (left < nparent) return false; const fp61 g0 = sketch_path_parent(out_t{}, out_t{}, k0, rs, left); const fp61 g1 = sketch_path_parent(out_t{}, out_t{}, k1, rs, left); if (!sketch_path_verify_parent(g0, g1)) return false; rs += nparent; left -= nparent; return verify_levels::run(k0, k1, rs, left); } return true; } }; } // namespace path_sketch_impl } // namespace detail /// @brief One-time VIDPF path check for an IDPF of consecutive depths 1..Depth. /// @details Challenge layout, consumed in order for I = 0 .. Depth-1: /// - `2^(I+1)` challenges for the weight-1 sketch at `out` /// - if I+1 < Depth: `2^(I+1)` challenges for the parent relation /// between `out` and `out` /// @tparam Depth number of IDPF prefix slots (`idpf` with Depth betas) template bool verify_idpf_path(const Key0 & k0, const Key1 & k1, const fp61 * rs, std::size_t n) { static_assert(Depth >= 1, "verify_idpf_path: Depth >= 1"); static_assert(is_multilevel_key_v && is_multilevel_key_v, "verify_idpf_path: multilevel keys"); if (!same_public_part(k0, k1)) return false; if (rs == nullptr) return false; const fp61 * cursor = rs; std::size_t left = n; return detail::path_sketch_impl::verify_levels::run( k0, k1, cursor, left); } /// @brief Convenience overload taking a contiguous challenge vector. template bool verify_idpf_path(const Key0 & k0, const Key1 & k1, const std::vector & rs) { return verify_idpf_path(k0, k1, rs.data(), rs.size()); } /// @brief How many fp61 challenges `verify_idpf_path` consumes. HEDLEY_CONST HEDLEY_ALWAYS_INLINE constexpr std::size_t path_sketch_challenge_count(std::size_t depth) noexcept { std::size_t n = 0; for (std::size_t i = 0; i < depth; ++i) { const std::size_t nprefix = std::size_t{1} << (i + 1); n += nprefix; if (i + 1 < depth) n += nprefix; } return n; } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_PATH_SKETCH_HPP__