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>
760 lines
25 KiB
C++
760 lines
25 KiB
C++
/// @file dpf/verifiable.hpp
|
|
/// @brief Verifiable evaluation tokens and extractable-key helpers.
|
|
/// @details VDPF proof fold follows de Castro and Polychroniadou, EUROCRYPT 2022
|
|
/// (ePrint 2021/580): hash-based correction seeds (their H outputs 4λ
|
|
/// bits), 2λ-bit tokens, equality Verify. Extractable
|
|
/// checks are public-part equality, ROM-style leaf XOF, and an
|
|
/// field of order `2^61 - 1` weight-1 subset sketch. Phantom tags
|
|
/// `dpf::verifiable` / `dpf::extractable` live in placement.hpp.
|
|
/// @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_VERIFIABLE_HPP__
|
|
#define LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "hedley/hedley.h"
|
|
#include "simde/simde/x86/avx2.h"
|
|
#include "portable-snippets/exact-int/exact-int.h"
|
|
|
|
#include "dpf/placement.hpp"
|
|
#include "dpf/prg_aes.hpp"
|
|
#include "dpf/fp61.hpp"
|
|
#include "dpf/xor_wrapper.hpp"
|
|
#include "dpf/twiddle.hpp"
|
|
#include "dpf/utils.hpp"
|
|
#include "dpf/random.hpp"
|
|
|
|
namespace dpf
|
|
{
|
|
|
|
/// 4λ = 64-byte correction seed (four AES blocks).
|
|
HEDLEY_PRAGMA(GCC diagnostic push)
|
|
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
|
using cs_block = std::array<simde__m128i, 4>;
|
|
/// 2λ = 32-byte proof token (two AES blocks).
|
|
using proof_token = std::array<simde__m128i, 2>;
|
|
HEDLEY_PRAGMA(GCC diagnostic pop)
|
|
|
|
namespace detail
|
|
{
|
|
namespace vdpf
|
|
{
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
simde__m128i mmo(simde__m128i seed, psnip_uint32_t pos) noexcept
|
|
{
|
|
return prg::aes128::eval(seed, pos);
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
cs_block hash_level_seed(std::size_t level, simde__m128i seed) noexcept
|
|
{
|
|
const simde__m128i tagged = simde_mm_xor_si128(seed,
|
|
simde_mm_set_epi64x(static_cast<psnip_int64_t>(0x56),
|
|
static_cast<psnip_int64_t>(level)));
|
|
return cs_block{
|
|
mmo(tagged, 0),
|
|
mmo(tagged, 1),
|
|
mmo(tagged, 2),
|
|
mmo(tagged, 3)};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
cs_block hash_node(std::size_t level, psnip_uint64_t x_bits,
|
|
simde__m128i seed) noexcept
|
|
{
|
|
// Low 16 bits carry the level (and optional domain tags such as
|
|
// `blocked::fold_spine_tag`). Native depths fit in 8 bits, so existing
|
|
// untagged levels keep the same digest as `level & 0xff`.
|
|
const simde__m128i tagged = simde_mm_xor_si128(seed,
|
|
simde_mm_set_epi64x(static_cast<psnip_int64_t>(0x5600 | (level & 0xffff)),
|
|
static_cast<psnip_int64_t>(x_bits)));
|
|
return cs_block{
|
|
mmo(tagged, 0),
|
|
mmo(tagged, 1),
|
|
mmo(tagged, 2),
|
|
mmo(tagged, 3)};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
cs_block make_cs(std::size_t level, psnip_uint64_t prefix_bits,
|
|
simde__m128i s0, simde__m128i s1) noexcept
|
|
{
|
|
const auto h0v = hash_node(level, prefix_bits, s0);
|
|
const auto h1v = hash_node(level, prefix_bits, s1);
|
|
return cs_block{
|
|
simde_mm_xor_si128(h0v[0], h1v[0]),
|
|
simde_mm_xor_si128(h0v[1], h1v[1]),
|
|
simde_mm_xor_si128(h0v[2], h1v[2]),
|
|
simde_mm_xor_si128(h0v[3], h1v[3])};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
cs_block correct(cs_block pi_tilde, const cs_block & cs,
|
|
bool t) noexcept
|
|
{
|
|
if (!t)
|
|
return pi_tilde;
|
|
return cs_block{
|
|
simde_mm_xor_si128(pi_tilde[0], cs[0]),
|
|
simde_mm_xor_si128(pi_tilde[1], cs[1]),
|
|
simde_mm_xor_si128(pi_tilde[2], cs[2]),
|
|
simde_mm_xor_si128(pi_tilde[3], cs[3])};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
proof_token h0(const cs_block & in) noexcept
|
|
{
|
|
const simde__m128i a = simde_mm_xor_si128(in[0], in[2]);
|
|
const simde__m128i b = simde_mm_xor_si128(in[1], in[3]);
|
|
return proof_token{mmo(a, 0x48), mmo(b, 0x48)};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_CONST
|
|
proof_token xor_proof(proof_token a, proof_token b) noexcept
|
|
{
|
|
return proof_token{
|
|
simde_mm_xor_si128(a[0], b[0]),
|
|
simde_mm_xor_si128(a[1], b[1])};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_CONST
|
|
proof_token zero_proof() noexcept
|
|
{
|
|
return proof_token{simde_mm_setzero_si128(), simde_mm_setzero_si128()};
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
void fold_node(proof_token & pi, std::size_t level,
|
|
psnip_uint64_t x_bits, simde__m128i seed, const cs_block & cs) noexcept
|
|
{
|
|
const bool t = static_cast<bool>(dpf::get_lo_bit(seed));
|
|
const cs_block tilde = hash_node(level, x_bits, seed);
|
|
const cs_block corrected = correct(tilde, cs, t);
|
|
cs_block mixed{
|
|
simde_mm_xor_si128(pi[0], corrected[0]),
|
|
simde_mm_xor_si128(pi[1], corrected[1]),
|
|
corrected[2],
|
|
corrected[3]};
|
|
pi = xor_proof(pi, h0(mixed));
|
|
}
|
|
|
|
/// @brief Mix public bytes into `pi` under domain tag `tag` (leaf / value CW).
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
void fold_bytes(proof_token & pi, std::size_t tag, const void * data,
|
|
std::size_t nbytes) noexcept
|
|
{
|
|
const auto * p = static_cast<const unsigned char *>(data);
|
|
// Build a 4-block digest the same shape as `hash_node`, then fold like
|
|
// `fold_node` with control bit 0 (no CS). Putting the same lane in
|
|
// mixed[0]/mixed[2] would cancel under `h0` when `pi` is still zero.
|
|
simde__m128i state = simde_mm_set_epi64x(
|
|
static_cast<psnip_int64_t>(0x4C00 | (tag & 0xffff)),
|
|
static_cast<psnip_int64_t>(nbytes));
|
|
for (std::size_t off = 0; off < nbytes; )
|
|
{
|
|
alignas(16) unsigned char block[16]{};
|
|
const std::size_t take = std::min(std::size_t{16}, nbytes - off);
|
|
std::memcpy(block, p + off, take);
|
|
simde__m128i chunk;
|
|
std::memcpy(&chunk, block, 16);
|
|
state = simde_mm_xor_si128(state, chunk);
|
|
state = mmo(state, static_cast<psnip_uint32_t>(0x4Cu + (off & 0xffu)));
|
|
off += take;
|
|
}
|
|
const cs_block digest{
|
|
mmo(state, 0), mmo(state, 1), mmo(state, 2), mmo(state, 3)};
|
|
cs_block mixed{
|
|
simde_mm_xor_si128(pi[0], digest[0]),
|
|
simde_mm_xor_si128(pi[1], digest[1]),
|
|
digest[2],
|
|
digest[3]};
|
|
pi = xor_proof(pi, h0(mixed));
|
|
}
|
|
|
|
template <typename KeyT, typename = void>
|
|
struct key_binds_cmp_values : std::false_type
|
|
{ };
|
|
template <typename KeyT>
|
|
struct key_binds_cmp_values<KeyT,
|
|
std::void_t<decltype(std::declval<const KeyT &>().has_cmp()),
|
|
decltype(std::declval<const KeyT &>().value_cw()),
|
|
decltype(std::declval<const KeyT &>().cw_last_word())>>
|
|
: std::true_type
|
|
{ };
|
|
|
|
/// @brief Fold the public leaf correction word(s) and comparison value words.
|
|
/// @details Binds the output share: a leaf or value-word tamper diverges `π`.
|
|
template <typename KeyT>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
void fold_output_binding(proof_token & pi, const KeyT & key) noexcept
|
|
{
|
|
if constexpr (KeyT::num_outputs > 0)
|
|
{
|
|
std::size_t slot = 0;
|
|
std::apply([&](const auto & ...leaf) {
|
|
((fold_bytes(pi, 0x4C00u | (slot++),
|
|
&leaf.get(), sizeof(leaf.get()))), ...);
|
|
}, key.leaf_nodes);
|
|
}
|
|
if constexpr (key_binds_cmp_values<KeyT>::value)
|
|
{
|
|
if (key.has_cmp())
|
|
{
|
|
const auto & vcw = key.value_cw();
|
|
if (vcw.size() > 0)
|
|
fold_bytes(pi, 0x56, vcw.data(),
|
|
sizeof(vcw[0]) * vcw.size());
|
|
const auto last = key.cw_last_word();
|
|
fold_bytes(pi, 0x57, &last, sizeof(last));
|
|
if constexpr (KeyT::cmp_block > 0)
|
|
{
|
|
const auto & tails = key.tail_cw();
|
|
if (tails.size() > 0)
|
|
fold_bytes(pi, 0x58, tails.data(),
|
|
sizeof(tails[0]) * tails.size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HEDLEY_NO_THROW
|
|
inline void leaf_xof(simde__m128i seed, simde__m128i * HEDLEY_RESTRICT out,
|
|
psnip_uint32_t count, psnip_uint32_t pos = 0) noexcept
|
|
{
|
|
const simde__m128i tagged = simde_mm_xor_si128(seed,
|
|
simde_mm_set_epi64x(0x45, 0));
|
|
for (psnip_uint32_t i = 0; i < count; ++i)
|
|
out[i] = mmo(tagged, pos + i);
|
|
}
|
|
|
|
/// Drop-in exterior PRG for leaf stretch under `dpf::extractable`.
|
|
template <typename BasePRG>
|
|
struct extractable_leaf_prg
|
|
{
|
|
using block_type = typename BasePRG::block_type;
|
|
HEDLEY_NO_THROW
|
|
static void eval(block_type seed, block_type * HEDLEY_RESTRICT out,
|
|
psnip_uint32_t count, psnip_uint32_t pos = 0) noexcept
|
|
{
|
|
leaf_xof(seed, out, count, pos);
|
|
}
|
|
};
|
|
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
bool proof_equal(const proof_token & a, const proof_token & b) noexcept
|
|
{
|
|
return std::memcmp(&a, &b, sizeof(proof_token)) == 0;
|
|
}
|
|
|
|
template <typename KeyT>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
void init_proof(proof_token & pi, const KeyT & /*key*/) noexcept
|
|
{
|
|
// Running proof starts at 0. Path folds and a final `fold_output_binding`
|
|
// (leaf / value words) are applied by the prove entry point.
|
|
pi = zero_proof();
|
|
}
|
|
|
|
} // namespace vdpf
|
|
} // namespace detail
|
|
|
|
/// @brief A proof token the caller owns, passed into evaluation.
|
|
struct prove_ref
|
|
{
|
|
/// @brief The token updated by the evaluation.
|
|
proof_token & token;
|
|
/// @brief Bind `t`.
|
|
/// @param t the token to update
|
|
HEDLEY_NO_THROW
|
|
explicit prove_ref(proof_token & t) noexcept : token{t} { }
|
|
};
|
|
|
|
/// @brief Bind `t` as the proof accumulator for one evaluation.
|
|
/// @param t the token to update
|
|
/// @return a `prove_ref` bound to `t`
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
prove_ref prove(proof_token & t) noexcept
|
|
{
|
|
return prove_ref{t};
|
|
}
|
|
|
|
/// @brief Whether two proof tokens are identical and non-zero.
|
|
/// @details The all-zero token is never accepted: a fresh proof that folded
|
|
/// no nodes would otherwise match another empty token.
|
|
/// @param a the first token
|
|
/// @param b the second token
|
|
/// @return `true` when every byte matches and the token is not all zeros
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
bool verify(const proof_token & a, const proof_token & b) noexcept
|
|
{
|
|
if (detail::vdpf::proof_equal(a, detail::vdpf::zero_proof())
|
|
|| detail::vdpf::proof_equal(b, detail::vdpf::zero_proof()))
|
|
return false;
|
|
return detail::vdpf::proof_equal(a, b);
|
|
}
|
|
|
|
/// @brief Fold two batches of proof tokens and compare them.
|
|
/// @tparam Range0 range of `proof_token` for party 0
|
|
/// @tparam Range1 range of `proof_token` for party 1
|
|
/// @param left party 0 tokens, in evaluation order
|
|
/// @param right party 1 tokens, in the same order
|
|
/// @return `false` when the ranges differ in length or the folded tokens differ
|
|
template <typename Range0, typename Range1>
|
|
bool verify_batch(Range0 && left, Range1 && right)
|
|
{
|
|
proof_token a = detail::vdpf::zero_proof();
|
|
proof_token b = detail::vdpf::zero_proof();
|
|
auto it0 = std::begin(left);
|
|
auto it1 = std::begin(right);
|
|
const auto end0 = std::end(left);
|
|
const auto end1 = std::end(right);
|
|
for (; it0 != end0 && it1 != end1; ++it0, ++it1)
|
|
{
|
|
a = detail::vdpf::xor_proof(a, *it0);
|
|
b = detail::vdpf::xor_proof(b, *it1);
|
|
a[0] = detail::vdpf::mmo(a[0], 1);
|
|
b[0] = detail::vdpf::mmo(b[0], 1);
|
|
a[1] = detail::vdpf::mmo(a[1], 2);
|
|
b[1] = detail::vdpf::mmo(b[1], 2);
|
|
}
|
|
if (it0 != end0 || it1 != end1)
|
|
return false;
|
|
return verify(a, b);
|
|
}
|
|
|
|
/// @brief Whether two keys publish the same correction words, advice, and hash.
|
|
/// @tparam KeyT0 key type of party 0
|
|
/// @tparam KeyT1 key type of party 1
|
|
/// @param k0 party 0 key
|
|
/// @param k1 party 1 key
|
|
/// @return `false` when a public field differs
|
|
template <typename KeyT0, typename KeyT1>
|
|
bool same_public_part(const KeyT0 & k0, const KeyT1 & k1)
|
|
{
|
|
static_assert(KeyT0::is_verifiable == KeyT1::is_verifiable,
|
|
"same_public_part: mismatched verifiable flags");
|
|
if (std::memcmp(k0.correction_words().data(), k1.correction_words().data(),
|
|
sizeof(typename KeyT0::correction_words_array)) != 0)
|
|
return false;
|
|
if (std::memcmp(k0.correction_advice().data(), k1.correction_advice().data(),
|
|
sizeof(typename KeyT0::correction_advice_array)) != 0)
|
|
return false;
|
|
if constexpr (KeyT0::is_verifiable)
|
|
{
|
|
if (std::memcmp(k0.correction_seeds().data(),
|
|
k1.correction_seeds().data(),
|
|
sizeof(typename KeyT0::correction_seeds_array)) != 0)
|
|
return false;
|
|
}
|
|
return std::memcmp(&k0.common_part_hash(), &k1.common_part_hash(),
|
|
sizeof(digest_type)) == 0;
|
|
}
|
|
|
|
struct sketch_share
|
|
{
|
|
fp61 z1{};
|
|
fp61 z2{};
|
|
fp61 z3{};
|
|
};
|
|
|
|
/// @brief Weight-1 subset sketch of payloads `ys` against challenges `rs`.
|
|
/// @tparam YRange range of integers convertible to `fp61`
|
|
/// @tparam RRange range of challenges, one per payload
|
|
/// @param ys the payloads
|
|
/// @param rs the challenges
|
|
/// @return the three folded moments. A short range stops at the shorter end
|
|
template <typename YRange, typename RRange>
|
|
sketch_share sketch_fold(YRange && ys, RRange && rs)
|
|
{
|
|
sketch_share out{};
|
|
auto iy = std::begin(ys);
|
|
auto ir = std::begin(rs);
|
|
const auto ey = std::end(ys);
|
|
const auto er = std::end(rs);
|
|
for (; iy != ey && ir != er; ++iy, ++ir)
|
|
{
|
|
const fp61 y{*iy};
|
|
const fp61 r{*ir};
|
|
const fp61 r2 = r * r;
|
|
out.z1 = out.z1 + y;
|
|
out.z2 = out.z2 + y * r;
|
|
out.z3 = out.z3 + y * r2;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/// @brief Whether `s0 - s1` is a weight-1 subset sketch.
|
|
/// @param s0 party 0's folded sketch
|
|
/// @param s1 party 1's folded sketch
|
|
/// @return `true` when `z2² = z1 · z3` after the shares are opened
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_CONST
|
|
bool sketch_verify(sketch_share s0, sketch_share s1) noexcept
|
|
{
|
|
const fp61 z1 = s0.z1 - s1.z1;
|
|
const fp61 z2 = s0.z2 - s1.z2;
|
|
const fp61 z3 = s0.z3 - s1.z3;
|
|
return (z2 * z2) == (z1 * z3);
|
|
}
|
|
|
|
/// @brief Fold payload shares into `out` when `KeyT` is extractable; no-op else.
|
|
/// @details Default eval never calls this. Party / sketch protocols opt in.
|
|
template <typename KeyT, typename YRange, typename RRange>
|
|
HEDLEY_ALWAYS_INLINE
|
|
void note_sketch(sketch_share & out, YRange && ys, RRange && rs) noexcept
|
|
{
|
|
if constexpr (KeyT::is_extractable)
|
|
out = sketch_fold(std::forward<YRange>(ys), std::forward<RRange>(rs));
|
|
else
|
|
(void)out, (void)ys, (void)rs;
|
|
}
|
|
|
|
/// @brief A sketch accumulator the caller owns, passed into evaluation.
|
|
/// @details Challenges `r` are chosen by the caller. Each written extractable
|
|
/// output consumes the next challenge, matching `prove(π)`.
|
|
struct sketch_ref
|
|
{
|
|
/// @brief Running sketch moments.
|
|
sketch_share & share;
|
|
/// @brief Challenge sequence, one per written output.
|
|
const fp61 * rs = nullptr;
|
|
/// @brief Number of challenges.
|
|
std::size_t n = 0;
|
|
/// @brief Next challenge index.
|
|
std::size_t i = 0;
|
|
|
|
/// @brief Bind `s` to challenges `[first, first + count)`.
|
|
HEDLEY_NO_THROW
|
|
sketch_ref(sketch_share & s, const fp61 * first, std::size_t count) noexcept
|
|
: share{s}, rs{first}, n{count}, i{0}
|
|
{ }
|
|
|
|
/// @brief Fold one payload into the running sketch.
|
|
/// @tparam Y integer convertible to `fp61` (extractable codomain)
|
|
/// @param y the payload share
|
|
template <typename Y>
|
|
HEDLEY_ALWAYS_INLINE
|
|
void absorb(Y y) noexcept
|
|
{
|
|
if (i >= n || rs == nullptr)
|
|
return;
|
|
const fp61 yy{y};
|
|
const fp61 r = rs[i++];
|
|
const fp61 r2 = r * r;
|
|
share.z1 = share.z1 + yy;
|
|
share.z2 = share.z2 + yy * r;
|
|
share.z3 = share.z3 + yy * r2;
|
|
}
|
|
};
|
|
|
|
/// @brief Bind `s` and challenge range `rs` as the sketch for one evaluation.
|
|
/// @tparam RRange contiguous range of `fp61` challenges
|
|
/// @param s the sketch to update
|
|
/// @param rs the challenges, one per written output
|
|
/// @return a `sketch_ref` bound to `s` and `rs`
|
|
template <typename RRange>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
sketch_ref sketch(sketch_share & s, RRange && rs) noexcept
|
|
{
|
|
const auto * first = std::data(rs);
|
|
const auto count = static_cast<std::size_t>(std::size(rs));
|
|
return sketch_ref{s, first, count};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Shark-style information-theoretic output MAC
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// @brief Phantom request tag: wrap the final group share in an output MAC.
|
|
/// @details Distinct from `verifiable` (path proof). An aggregate evaluation
|
|
/// must fold a path proof first; the MAC only binds the share that
|
|
/// proof covers. A MAC on a bare parity bit is not offered.
|
|
struct output_mac
|
|
{
|
|
static constexpr bool is_output_mac_tag = true;
|
|
};
|
|
|
|
template <typename T>
|
|
struct is_output_mac_tag : std::false_type
|
|
{ };
|
|
template <>
|
|
struct is_output_mac_tag<output_mac> : std::true_type
|
|
{ };
|
|
template <typename T>
|
|
inline constexpr bool is_output_mac_tag_v =
|
|
is_output_mac_tag<std::decay_t<T>>::value;
|
|
|
|
/// @brief Global MAC key `Δ`. Sampled by the dealer for the session.
|
|
/// @tparam Ring payload ring
|
|
template <typename Ring>
|
|
struct mac_key
|
|
{
|
|
Ring delta{};
|
|
};
|
|
|
|
/// @brief Additive share of `(y, y·Δ)`.
|
|
/// @tparam Ring payload ring
|
|
template <typename Ring>
|
|
struct mac_share
|
|
{
|
|
Ring value{};
|
|
Ring tag{};
|
|
};
|
|
|
|
/// @brief Sample a fresh MAC key.
|
|
/// @tparam Ring payload ring
|
|
/// @return a dealer key `Δ`
|
|
template <typename Ring>
|
|
HEDLEY_WARN_UNUSED_RESULT
|
|
mac_key<Ring> sample_mac_key()
|
|
{
|
|
return mac_key<Ring>{dpf::uniform_sample<Ring>()};
|
|
}
|
|
|
|
/// @brief Authenticate a cleartext `y` under `key`, returning party shares.
|
|
/// @tparam Ring payload ring
|
|
/// @param y the cleartext payload
|
|
/// @param key the session MAC key
|
|
/// @return additive shares of `(y, y·Δ)`
|
|
template <typename Ring>
|
|
HEDLEY_WARN_UNUSED_RESULT
|
|
std::pair<mac_share<Ring>, mac_share<Ring>> mac_share_value(
|
|
const Ring & y, const mac_key<Ring> & key)
|
|
{
|
|
const Ring y0 = dpf::uniform_sample<Ring>();
|
|
const Ring t0 = dpf::uniform_sample<Ring>();
|
|
const Ring y1 = static_cast<Ring>(y - y0);
|
|
const Ring t1 = static_cast<Ring>(y * key.delta - t0);
|
|
return {mac_share<Ring>{y0, t0}, mac_share<Ring>{y1, t1}};
|
|
}
|
|
|
|
/// @brief Authenticate existing additive shares under `key` (dealer knows both).
|
|
/// @tparam Ring payload ring
|
|
/// @param y0 party 0's share of the payload
|
|
/// @param y1 party 1's share of the payload
|
|
/// @param key the session MAC key
|
|
/// @return the same value shares, with fresh tag shares of `(y0+y1)·Δ`
|
|
template <typename Ring>
|
|
HEDLEY_WARN_UNUSED_RESULT
|
|
std::pair<mac_share<Ring>, mac_share<Ring>> mac_authenticate(
|
|
const Ring & y0, const Ring & y1, const mac_key<Ring> & key)
|
|
{
|
|
const Ring y = static_cast<Ring>(y0 + y1);
|
|
const Ring t0 = dpf::uniform_sample<Ring>();
|
|
const Ring t1 = static_cast<Ring>(y * key.delta - t0);
|
|
return {mac_share<Ring>{y0, t0}, mac_share<Ring>{y1, t1}};
|
|
}
|
|
|
|
/// @brief Local public scale of an authenticated share.
|
|
/// @tparam Ring payload ring
|
|
/// @param s the authenticated share
|
|
/// @param c the public coefficient
|
|
/// @return `(c·value, c·tag)`
|
|
template <typename Ring>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
mac_share<Ring> mac_scale(mac_share<Ring> s, const Ring & c) noexcept
|
|
{
|
|
return mac_share<Ring>{
|
|
static_cast<Ring>(s.value * c),
|
|
static_cast<Ring>(s.tag * c)};
|
|
}
|
|
|
|
/// @brief Local addition of authenticated shares.
|
|
/// @tparam Ring payload ring
|
|
/// @param a the first share
|
|
/// @param b the second share
|
|
/// @return the lane-wise sum of values and tags
|
|
template <typename Ring>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_CONST
|
|
mac_share<Ring> mac_add(mac_share<Ring> a, mac_share<Ring> b) noexcept
|
|
{
|
|
return mac_share<Ring>{
|
|
static_cast<Ring>(a.value + b.value),
|
|
static_cast<Ring>(a.tag + b.tag)};
|
|
}
|
|
|
|
/// @brief Whether the opened shares satisfy `tag = value · Δ`.
|
|
/// @details Algebraic check only. Beaver δ-MACs use this path via
|
|
/// `verify_delta` / `auth_split::verify`. DPF output MACs must call
|
|
/// the overload that also takes path-proof tokens.
|
|
/// @tparam Ring payload ring
|
|
/// @param s0 party 0's authenticated share
|
|
/// @param s1 party 1's authenticated share
|
|
/// @param key the session MAC key
|
|
/// @return `false` when the tag does not match the opened value
|
|
template <typename Ring>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
bool mac_verify(mac_share<Ring> s0, mac_share<Ring> s1,
|
|
const mac_key<Ring> & key) noexcept
|
|
{
|
|
const Ring y = static_cast<Ring>(s0.value + s1.value);
|
|
const Ring t = static_cast<Ring>(s0.tag + s1.tag);
|
|
return t == static_cast<Ring>(y * key.delta);
|
|
}
|
|
|
|
/// @brief Whether a DPF output MAC is valid under a verified path proof.
|
|
/// @details Rejects when either token is the all-zero proof or `verify(π0, π1)`
|
|
/// fails, then checks `tag = value · Δ`.
|
|
/// @tparam Ring payload ring
|
|
/// @param s0 party 0's authenticated share
|
|
/// @param s1 party 1's authenticated share
|
|
/// @param key the session MAC key
|
|
/// @param pi0 party 0's path-proof token from the same evaluation
|
|
/// @param pi1 party 1's path-proof token from the same evaluation
|
|
/// @return `false` when the proof or the tag check fails
|
|
template <typename Ring>
|
|
HEDLEY_NO_THROW
|
|
HEDLEY_ALWAYS_INLINE
|
|
HEDLEY_PURE
|
|
bool mac_verify(mac_share<Ring> s0, mac_share<Ring> s1,
|
|
const mac_key<Ring> & key, const proof_token & pi0,
|
|
const proof_token & pi1) noexcept
|
|
{
|
|
if (detail::vdpf::proof_equal(pi0, detail::vdpf::zero_proof())
|
|
|| detail::vdpf::proof_equal(pi1, detail::vdpf::zero_proof()))
|
|
return false;
|
|
if (!verify(pi0, pi1))
|
|
return false;
|
|
return mac_verify(s0, s1, key);
|
|
}
|
|
|
|
/// @brief Batch-check authenticated shares with public coefficients `coeffs`.
|
|
/// @details Forms `Σ c_i · share_i` locally and verifies the single MAC.
|
|
/// @tparam Ring payload ring
|
|
/// @tparam ShareRange0 range of `mac_share<Ring>` for party 0
|
|
/// @tparam ShareRange1 range of `mac_share<Ring>` for party 1
|
|
/// @tparam CoeffRange range of public `Ring` coefficients
|
|
/// @param left party 0 authenticated shares
|
|
/// @param right party 1 authenticated shares
|
|
/// @param coeffs public coefficients, one per share
|
|
/// @param key the session MAC key
|
|
/// @return `false` when the ranges differ in length or the folded MAC fails
|
|
template <typename Ring, typename ShareRange0, typename ShareRange1,
|
|
typename CoeffRange>
|
|
bool mac_verify_batch(ShareRange0 && left, ShareRange1 && right,
|
|
CoeffRange && coeffs, const mac_key<Ring> & key)
|
|
{
|
|
mac_share<Ring> a{};
|
|
mac_share<Ring> b{};
|
|
auto it0 = std::begin(left);
|
|
auto it1 = std::begin(right);
|
|
auto ic = std::begin(coeffs);
|
|
const auto end0 = std::end(left);
|
|
const auto end1 = std::end(right);
|
|
const auto endc = std::end(coeffs);
|
|
for (; it0 != end0 && it1 != end1 && ic != endc; ++it0, ++it1, ++ic)
|
|
{
|
|
a = mac_add(a, mac_scale(*it0, *ic));
|
|
b = mac_add(b, mac_scale(*it1, *ic));
|
|
}
|
|
if (it0 != end0 || it1 != end1 || ic != endc)
|
|
return false;
|
|
return mac_verify(a, b, key);
|
|
}
|
|
|
|
/// @brief Batch DPF output-MAC check under a verified path-proof batch.
|
|
/// @details Folds shares with `coeffs`, then requires a non-zero verified
|
|
/// proof batch before accepting the algebraic MAC.
|
|
/// @tparam Ring payload ring
|
|
/// @tparam ShareRange0 range of `mac_share<Ring>` for party 0
|
|
/// @tparam ShareRange1 range of `mac_share<Ring>` for party 1
|
|
/// @tparam CoeffRange range of public `Ring` coefficients
|
|
/// @tparam ProofRange0 range of `proof_token` for party 0
|
|
/// @tparam ProofRange1 range of `proof_token` for party 1
|
|
/// @param left party 0 authenticated shares
|
|
/// @param right party 1 authenticated shares
|
|
/// @param coeffs public coefficients, one per share
|
|
/// @param key the session MAC key
|
|
/// @param proofs0 party 0 path-proof tokens, same order as the shares
|
|
/// @param proofs1 party 1 path-proof tokens, same order as the shares
|
|
/// @return `false` when lengths differ, a proof is zero, proofs fail, or the MAC fails
|
|
template <typename Ring, typename ShareRange0, typename ShareRange1,
|
|
typename CoeffRange, typename ProofRange0, typename ProofRange1>
|
|
bool mac_verify_batch(ShareRange0 && left, ShareRange1 && right,
|
|
CoeffRange && coeffs, const mac_key<Ring> & key, ProofRange0 && proofs0,
|
|
ProofRange1 && proofs1)
|
|
{
|
|
for (const auto & p : proofs0)
|
|
{
|
|
if (detail::vdpf::proof_equal(p, detail::vdpf::zero_proof()))
|
|
return false;
|
|
}
|
|
for (const auto & p : proofs1)
|
|
{
|
|
if (detail::vdpf::proof_equal(p, detail::vdpf::zero_proof()))
|
|
return false;
|
|
}
|
|
if (!verify_batch(std::forward<ProofRange0>(proofs0),
|
|
std::forward<ProofRange1>(proofs1)))
|
|
return false;
|
|
return mac_verify_batch<Ring>(std::forward<ShareRange0>(left),
|
|
std::forward<ShareRange1>(right), std::forward<CoeffRange>(coeffs), key);
|
|
}
|
|
|
|
template <typename T, typename = void>
|
|
struct has_dpf_fp61 : std::false_type
|
|
{ };
|
|
template <typename T>
|
|
struct has_dpf_fp61<T, std::void_t<decltype(std::decay_t<T>::dpf_fp61)>>
|
|
: std::bool_constant<std::decay_t<T>::dpf_fp61>
|
|
{ };
|
|
|
|
template <typename T, typename = void>
|
|
struct extractable_codomain_ok
|
|
: std::bool_constant<has_dpf_fp61<T>::value>
|
|
{ };
|
|
template <typename T>
|
|
struct extractable_codomain_ok<xor_wrapper<T>, void>
|
|
: extractable_codomain_ok<T>
|
|
{ };
|
|
template <typename T>
|
|
inline constexpr bool extractable_codomain_ok_v =
|
|
extractable_codomain_ok<std::decay_t<T>>::value;
|
|
|
|
} // namespace dpf
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__
|