Checkpoint the party/runtime stack before share-program and malicious-mode work.

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>
This commit is contained in:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

View file

@ -1,6 +1,7 @@
/// @file grotto/prefix_parity.hpp
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @brief Prefix-parity and signed-prefix shares from a comparison key.
/// @note Following Storrier, Vadapalli, Lyons, and Henry, ePrint 2023/108: prefix parity along one point or comparison key, the step they use to replace a DCF per spline piece.
/// @copyright Copyright (c) 2019-2023 Ryan Henry and others
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
@ -20,6 +21,7 @@
#include "grotto/offset_iterable.hpp"
#include "dpf/path_memoizer.hpp"
#include "dpf/utils.hpp"
#include "dpf/verifiable.hpp"
#include <stdexcept>
#include <type_traits>
@ -27,6 +29,14 @@
namespace grotto
{
/// @brief Parity of the advice bits in `node` strictly below the bit offset of `x`.
/// @tparam NodeT exterior-node type
/// @tparam InputT input type used to place `x` inside the node
/// @param node advice-bit node
/// @param x query, used only for its offset within the node
/// @return the XOR-parity of those bits
/// @see grotto::prefix_parities
/// \complexity XORs the limbs of `node` below that offset, then one `parity64`. `Θ(limbs in the node)`, extra space `Θ(1)`.
template <typename NodeT,
typename InputT>
HEDLEY_NO_THROW
@ -36,24 +46,40 @@ auto parity_of_substring_prefix(const NodeT & node, InputT x) noexcept
std::size_t off = dpf::offset_within_block<dpf::bit, NodeT>(x);
auto div = std::lldiv(off, bits_per_limb);
auto parity = node[div.quot] & ((1ul << div.rem) - 1ul);
for (std::size_t i = 0; i < div.quot; ++i) parity ^= node[i];
for (std::size_t i = 0; i < static_cast<std::size_t>(div.quot); ++i) parity ^= node[i];
return psnip_builtin_parity64(parity);
}
/// \complexity One path walk per endpoint. `depth` is `bitlength` of the key's input type.
/// `for_each_offset` visits the endpoints in rotated order and `path_resume_for_level` restarts at the first level that differs, so a shared prefix is not walked twice.
/// With the early-stop flag (the default), a zero suffix does not descend below the leaf alignment.
/// Worst-case time is `Θ(E · depth)` interior steps for `E` endpoints. The memoizer stores one node per level, `Θ(depth)` extra space, plus the `E`-slot result.
/// \rounds None. The key and the endpoints are arguments.
/// \communication None.
/// @see grotto::signed_prefix_parities
/// @see grotto::offset_horner_eval
template <bool use_early_terminate_optimization = true,
typename InputT,
typename DpfKey,
std::size_t NumParts,
std::enable_if_t<std::is_same_v<InputT, typename DpfKey::input_type>, bool> = false>
static auto prefix_parities(const DpfKey & dpf, const std::array<InputT, NumParts> & endpoints)
static auto prefix_parities(const DpfKey & dpf, const std::array<InputT, NumParts> & endpoints,
dpf::proof_token * pi = nullptr)
{
static constexpr std::size_t num_parts = NumParts;
static constexpr std::size_t depth = DpfKey::depth;
using input_type = typename DpfKey::input_type;
static constexpr std::size_t input_bits = dpf::utils::bitlength_of_v<input_type>;
using interior_node = typename DpfKey::interior_node;
using exterior_node = typename DpfKey::exterior_node;
if (pi != nullptr)
{
if constexpr (!DpfKey::is_verifiable)
throw std::invalid_argument(
"prefix_parities(..., prove): key must carry dpf::verifiable");
dpf::detail::vdpf::init_proof(*pi, dpf);
}
exterior_node leaf;
auto path = make_basic_path_memoizer(dpf);
std::array<uint_fast8_t, depth+1> direction = { 0 }; // always "traverse left" to get to the root
@ -107,6 +133,18 @@ static auto prefix_parities(const DpfKey & dpf, const std::array<InputT, NumPart
direction[level_index+1] = bit;
path[level_index+1] = DpfKey::traverse_interior(path[level_index], dpf.correction_word(level_index, direction[level_index+1]), direction[level_index+1], DpfKey::tree::is_last_level(level_index, DpfKey::depth));
parity[level_index+1] = parity[level_index] ^ ((direction[level_index] ^ direction[level_index+1]) & dpf::get_lo_bit(path[level_index]));
if constexpr (DpfKey::is_verifiable)
{
if (pi != nullptr)
{
const auto x_bits = static_cast<psnip_uint64_t>(
dpf::utils::to_integral_type<input_type>{}(current_endpoint)
>> (input_bits - (level_index + 1)));
dpf::detail::vdpf::fold_node(*pi, level_index, x_bits,
path[level_index + 1],
dpf.correction_seeds()[level_index]);
}
}
}
}
dpf::detail::path_note_filled_to(path, to_level);
@ -125,14 +163,36 @@ static auto prefix_parities(const DpfKey & dpf, const std::array<InputT, NumPart
}
prefix_parities[which_part] = parity[depth]
^ (direction[depth] & dpf::get_lo_bit(path[depth])
^ ((direction[depth] & dpf::get_lo_bit(path[depth]))
^ parity_of_substring_prefix(leaf, current_endpoint));
}
});
if (pi != nullptr)
dpf::detail::vdpf::fold_output_binding(*pi, dpf);
return std::make_tuple(prefix_parities, new_first);
}
/// @brief Prefix parities with a VDPF proof fold on every traversed node.
/// \complexity One path walk per endpoint. `depth` is `bitlength` of the key's input type.
/// `for_each_offset` visits the endpoints in rotated order and `path_resume_for_level` restarts at the first level that differs, so a shared prefix is not walked twice.
/// With the early-stop flag (the default), a zero suffix does not descend below the leaf alignment.
/// Worst-case time is `Θ(E · depth)` interior steps for `E` endpoints. The memoizer stores one node per level, `Θ(depth)` extra space, plus the `E`-slot result.
/// \rounds None. The key and the endpoints are arguments.
/// \communication None.
/// @see grotto::signed_prefix_parities
/// @see grotto::offset_horner_eval
template <bool use_early_terminate_optimization = true,
typename InputT,
typename DpfKey,
std::size_t NumParts,
std::enable_if_t<std::is_same_v<InputT, typename DpfKey::input_type>, bool> = false>
static auto prefix_parities(const DpfKey & dpf, const std::array<InputT, NumParts> & endpoints,
dpf::prove_ref pr)
{
return prefix_parities<use_early_terminate_optimization>(dpf, endpoints, &pr.token);
}
template <typename DpfKey,
std::size_t NumParts>
static auto all_segment_parities_from_prefix_parities(const DpfKey & dpf,
@ -176,6 +236,9 @@ static auto specific_segment_parities_from_prefix_parities(const DpfKey & dpf,
return segment_parities;
}
/// \complexity One `prefix_parities` walk, then `Θ(E)` XORs to turn prefixes into segments. Same path cost as `prefix_parities`.
/// @see grotto::prefix_parities
/// @see grotto::signed_segment_parities
template <bool use_early_terminate_optimization = true,
typename InputT,
@ -244,20 +307,31 @@ uint64_t cmp_addend_raw(const KeyT & key) noexcept
/// @tparam NumParts num parts
/// @tparam input_type input type
/// @param dpf the DPF key
/// @param endpoints the `endpoints`
/// @param endpoints sorted public endpoints
/// @param pi proof token folded along the walk, or null
/// @return Additive prefix indicators from the key's DCF value sums
/// @throws std::invalid_argument if `key has no comparison channel`
/// \complexity One path walk per endpoint. `depth` is `bitlength` of the key's input type.
/// `for_each_offset` visits the endpoints in rotated order and `path_resume_for_level` restarts at the first level that differs, so a shared prefix is not walked twice.
/// With the early-stop flag (the default), a zero suffix does not descend below the leaf alignment.
/// Worst-case time is `Θ(E · depth)` interior steps for `E` endpoints. The memoizer stores one node per level, `Θ(depth)` extra space, plus the `E`-slot result.
/// \rounds None. The key and the endpoints are arguments.
/// \communication None.
/// @see grotto::signed_prefix_parities
/// @see grotto::offset_horner_eval
template <typename InputT,
typename DpfKey,
std::size_t NumParts,
std::enable_if_t<std::is_same_v<InputT, typename DpfKey::input_type>, bool> = false>
static auto signed_prefix_parities(const DpfKey & dpf,
const std::array<InputT, NumParts> & endpoints)
const std::array<InputT, NumParts> & endpoints,
dpf::proof_token * pi = nullptr)
{
if constexpr (!detail::key_has_cmp<DpfKey>::value)
{
(void)dpf;
(void)endpoints;
(void)pi;
throw std::invalid_argument("signed_prefix_parities: key has no comparison channel");
}
else if (!dpf.has_cmp())
@ -271,6 +345,13 @@ static auto signed_prefix_parities(const DpfKey & dpf,
}
else
{
if (pi != nullptr)
{
if constexpr (!DpfKey::is_verifiable)
throw std::invalid_argument(
"signed_prefix_parities(..., prove): key must carry dpf::verifiable");
dpf::detail::vdpf::init_proof(*pi, dpf);
}
using namespace dpf::detail::dcf_impl;
using key_type = dpf::unwrap_party_key_t<DpfKey>;
constexpr std::size_t depth = key_type::depth;
@ -304,7 +385,7 @@ static auto signed_prefix_parities(const DpfKey & dpf,
}
if constexpr (key_type::cmp_block > 0)
{
prefixes[which] = dpf::detail::blocked::eval_share(dpf, tx, path);
prefixes[which] = dpf::detail::blocked::eval_share(dpf, tx, path, pi);
continue;
}
@ -318,6 +399,17 @@ static auto signed_prefix_parities(const DpfKey & dpf,
path[level] = DpfKey::traverse_interior(path[level - 1],
dpf.correction_word(level - 1, bit), bit,
DpfKey::tree::is_last_level(level - 1, DpfKey::depth));
if constexpr (DpfKey::is_verifiable)
{
if (pi != nullptr)
{
const auto x_bits = static_cast<psnip_uint64_t>(
dpf::utils::to_integral_type<InputT>{}(tx)
>> (dpf::utils::bitlength_of_v<InputT> - level));
dpf::detail::vdpf::fold_node(*pi, level - 1, x_bits,
path[level], dpf.correction_seeds()[level - 1]);
}
}
}
dpf::detail::path_note_filled_to(path, nbits);
@ -348,23 +440,53 @@ static auto signed_prefix_parities(const DpfKey & dpf,
V = neg_m(V, mask);
prefixes[which] = (V + addend) & mask;
}
if (pi != nullptr)
dpf::detail::vdpf::fold_output_binding(*pi, dpf);
return prefixes;
}
}
/// \complexity One path walk per endpoint. `depth` is `bitlength` of the key's input type.
/// `for_each_offset` visits the endpoints in rotated order and `path_resume_for_level` restarts at the first level that differs, so a shared prefix is not walked twice.
/// With the early-stop flag (the default), a zero suffix does not descend below the leaf alignment.
/// Worst-case time is `Θ(E · depth)` interior steps for `E` endpoints. The memoizer stores one node per level, `Θ(depth)` extra space, plus the `E`-slot result.
/// \rounds None. The key and the endpoints are arguments.
/// \communication None.
/// @see grotto::signed_prefix_parities
/// @see grotto::offset_horner_eval
template <typename InputT,
typename DpfKey,
std::size_t NumParts,
std::enable_if_t<std::is_same_v<InputT, typename DpfKey::input_type>, bool> = false>
static auto signed_prefix_parities(const DpfKey & dpf,
const std::array<InputT, NumParts> & endpoints, dpf::prove_ref pr)
{
return signed_prefix_parities(dpf, endpoints, &pr.token);
}
/// @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 endpoints sorted public endpoints
/// @param n element count
/// @param out the output buffer
/// @param pi proof token folded along the walk, or null
/// @throws std::invalid_argument if `key has no comparison channel`
/// \complexity One path walk per endpoint. `depth` is `bitlength` of the key's input type.
/// `for_each_offset` visits the endpoints in rotated order and `path_resume_for_level` restarts at the first level that differs, so a shared prefix is not walked twice.
/// With the early-stop flag (the default), a zero suffix does not descend below the leaf alignment.
/// Worst-case time is `Θ(E · depth)` interior steps for `E` endpoints. The memoizer stores one node per level, `Θ(depth)` extra space, plus the `E`-slot result.
/// \rounds None. The key and the endpoints are arguments.
/// \communication None.
/// @see grotto::signed_prefix_parities
/// @see grotto::offset_horner_eval
template <typename InputT,
typename DpfKey>
static void signed_prefix_parities_into(const DpfKey & dpf,
const InputT * endpoints, std::size_t n, uint64_t * out)
const InputT * endpoints, std::size_t n, uint64_t * out,
dpf::proof_token * pi = nullptr)
{
if constexpr (!detail::key_has_cmp<DpfKey>::value)
{
@ -372,6 +494,7 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
(void)endpoints;
(void)n;
(void)out;
(void)pi;
throw std::invalid_argument("signed_prefix_parities: key has no comparison channel");
}
else if (!dpf.has_cmp())
@ -389,6 +512,13 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
}
else
{
if (pi != nullptr)
{
if constexpr (!DpfKey::is_verifiable)
throw std::invalid_argument(
"signed_prefix_parities(..., prove): key must carry dpf::verifiable");
dpf::detail::vdpf::init_proof(*pi, dpf);
}
using namespace dpf::detail::dcf_impl;
using key_type = dpf::unwrap_party_key_t<DpfKey>;
constexpr std::size_t depth = key_type::depth;
@ -420,7 +550,7 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
}
if constexpr (key_type::cmp_block > 0)
{
out[which] = dpf::detail::blocked::eval_share(dpf, tx, path);
out[which] = dpf::detail::blocked::eval_share(dpf, tx, path, pi);
continue;
}
@ -434,6 +564,17 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
path[level] = DpfKey::traverse_interior(path[level - 1],
dpf.correction_word(level - 1, bit), bit,
DpfKey::tree::is_last_level(level - 1, DpfKey::depth));
if constexpr (DpfKey::is_verifiable)
{
if (pi != nullptr)
{
const auto x_bits = static_cast<psnip_uint64_t>(
dpf::utils::to_integral_type<InputT>{}(tx)
>> (dpf::utils::bitlength_of_v<InputT> - level));
dpf::detail::vdpf::fold_node(*pi, level - 1, x_bits,
path[level], dpf.correction_seeds()[level - 1]);
}
}
}
dpf::detail::path_note_filled_to(path, nbits);
@ -464,6 +605,8 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
V = neg_m(V, mask);
out[which] = (V + addend) & mask;
}
if (pi != nullptr)
dpf::detail::vdpf::fold_output_binding(*pi, dpf);
}
}
@ -478,9 +621,12 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
/// @tparam NumParts num parts
/// @tparam input_type input type
/// @param dpf the DPF key
/// @param endpoints the `endpoints`
/// @param endpoints sorted public 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`
/// \complexity One `signed_prefix_parities` over the `NumParts` endpoints, then `Θ(NumParts)` subtractions. Same path cost.
/// @note Requires a `gt` comparison. The hot piece reconstructs to 1, the others to 0.
/// @see grotto::signed_prefix_parities
template <typename InputT,
typename DpfKey,
std::size_t NumParts,