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:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue