/// @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 #include #include #include #include #include #include #include #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; /// 2λ = 32-byte proof token (two AES blocks). using proof_token = std::array; 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(0x56), static_cast(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(0x5600 | (level & 0xffff)), static_cast(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(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(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(0x4C00 | (tag & 0xffff)), static_cast(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(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 struct key_binds_cmp_values : std::false_type { }; template struct key_binds_cmp_values().has_cmp()), decltype(std::declval().value_cw()), decltype(std::declval().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 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::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 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 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 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 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 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 HEDLEY_ALWAYS_INLINE void note_sketch(sketch_share & out, YRange && ys, RRange && rs) noexcept { if constexpr (KeyT::is_extractable) out = sketch_fold(std::forward(ys), std::forward(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 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 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(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 struct is_output_mac_tag : std::false_type { }; template <> struct is_output_mac_tag : std::true_type { }; template inline constexpr bool is_output_mac_tag_v = is_output_mac_tag>::value; /// @brief Global MAC key `Δ`. Sampled by the dealer for the session. /// @tparam Ring payload ring template struct mac_key { Ring delta{}; }; /// @brief Additive share of `(y, y·Δ)`. /// @tparam Ring payload ring template struct mac_share { Ring value{}; Ring tag{}; }; /// @brief Sample a fresh MAC key. /// @tparam Ring payload ring /// @return a dealer key `Δ` template HEDLEY_WARN_UNUSED_RESULT mac_key sample_mac_key() { return mac_key{dpf::uniform_sample()}; } /// @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 HEDLEY_WARN_UNUSED_RESULT std::pair, mac_share> mac_share_value( const Ring & y, const mac_key & key) { const Ring y0 = dpf::uniform_sample(); const Ring t0 = dpf::uniform_sample(); const Ring y1 = static_cast(y - y0); const Ring t1 = static_cast(y * key.delta - t0); return {mac_share{y0, t0}, mac_share{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 HEDLEY_WARN_UNUSED_RESULT std::pair, mac_share> mac_authenticate( const Ring & y0, const Ring & y1, const mac_key & key) { const Ring y = static_cast(y0 + y1); const Ring t0 = dpf::uniform_sample(); const Ring t1 = static_cast(y * key.delta - t0); return {mac_share{y0, t0}, mac_share{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 HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE mac_share mac_scale(mac_share s, const Ring & c) noexcept { return mac_share{ static_cast(s.value * c), static_cast(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 HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_CONST mac_share mac_add(mac_share a, mac_share b) noexcept { return mac_share{ static_cast(a.value + b.value), static_cast(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 HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE bool mac_verify(mac_share s0, mac_share s1, const mac_key & key) noexcept { const Ring y = static_cast(s0.value + s1.value); const Ring t = static_cast(s0.tag + s1.tag); return t == static_cast(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 HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE bool mac_verify(mac_share s0, mac_share s1, const mac_key & 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` for party 0 /// @tparam ShareRange1 range of `mac_share` 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 bool mac_verify_batch(ShareRange0 && left, ShareRange1 && right, CoeffRange && coeffs, const mac_key & key) { mac_share a{}; mac_share 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` for party 0 /// @tparam ShareRange1 range of `mac_share` 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 bool mac_verify_batch(ShareRange0 && left, ShareRange1 && right, CoeffRange && coeffs, const mac_key & 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(proofs0), std::forward(proofs1))) return false; return mac_verify_batch(std::forward(left), std::forward(right), std::forward(coeffs), key); } template struct has_dpf_fp61 : std::false_type { }; template struct has_dpf_fp61::dpf_fp61)>> : std::bool_constant::dpf_fp61> { }; template struct extractable_codomain_ok : std::bool_constant::value> { }; template struct extractable_codomain_ok, void> : extractable_codomain_ok { }; template inline constexpr bool extractable_codomain_ok_v = extractable_codomain_ok>::value; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_VERIFIABLE_HPP__