libdpf/test/tests/wildcard_test.cpp
Ryan Henry 0d22946a0e 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>
2026-09-28 05:59:19 -06:00

809 lines
32 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <tuple>
#include <cstring>
#include <cstdint>
#include "asio.hpp"
#define LIBDPF_HAS_ASIO
#include "dpf.hpp"
template <std::size_t I = 0,
typename DpfKey0,
typename DpfKey1,
typename ConcreteT>
void assign_leaf(DpfKey0 & dpf0, DpfKey1 & dpf1, const ConcreteT y_shr0, const ConcreteT y_shr1)
{
asio::io_context io_context;
std::thread server([&io_context, &dpf0, &y_shr0]()
{
asio::ip::tcp::acceptor acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 31337));
asio::ip::tcp::socket peer{io_context};
asio::ip::tcp::endpoint ep{};
acceptor.accept(peer, ep);
auto future = dpf0.template async_assign_leaf<I>(peer, y_shr0, asio::use_future);
io_context.run();
future.wait();
});
std::thread client([&io_context, &dpf1, &y_shr1]()
{
asio::ip::tcp::socket peer(io_context);
asio::ip::tcp::resolver resolver(io_context);
asio::connect(peer, resolver.resolve("localhost", "31337"));
auto future = dpf1.template async_assign_leaf<I>(peer, y_shr1, asio::use_future);
io_context.run();
future.wait();
});
client.join();
server.join();
}
TEST(WildcardTest, SingleLeafFailEvalBeforeVernalization)
{
using input_type = uint8_t;
using output_type = dpf::wildcard_value<uint32_t>;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type>;
input_type x = 0xAA;
output_type y;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
ASSERT_THROW(dpf::eval_point(dpf0, x), std::runtime_error);
ASSERT_THROW(dpf::eval_point(dpf1, x), std::runtime_error);
input_type from = 0x33, to = 0xCC;
ASSERT_THROW(dpf::eval_interval(dpf0, from, to), std::runtime_error);
ASSERT_THROW(dpf::eval_interval(dpf1, from, to), std::runtime_error);
ASSERT_THROW(dpf::eval_full(dpf0), std::runtime_error);
ASSERT_THROW(dpf::eval_full(dpf1), std::runtime_error);
std::array<input_type, 16> points{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
ASSERT_THROW(dpf::eval_sequence(dpf0, std::begin(points), std::end(points)), std::runtime_error);
ASSERT_THROW(dpf::eval_sequence(dpf1, std::begin(points), std::end(points)), std::runtime_error);
ASSERT_THROW(dpf::eval_sequence_breadth_first(dpf0, std::begin(points), std::end(points)), std::runtime_error);
ASSERT_THROW(dpf::eval_sequence_breadth_first(dpf1, std::begin(points), std::end(points)), std::runtime_error);
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(points), std::end(points));
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(points), std::end(points));
ASSERT_THROW(dpf::eval_sequence(dpf0, recipe0), std::runtime_error);
ASSERT_THROW(dpf::eval_sequence(dpf1, recipe1), std::runtime_error);
}
TEST(WildcardTest, MultiLeafFailEvalBeforeVernalization)
{
using input_type = uint8_t;
using output_type0 = uint32_t;
using output_type1 = dpf::wildcard_value<uint32_t>;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type0, output_type1>;
input_type x = 0xAA;
output_type0 y0 = 0xAAAAAAAA;
output_type1 y1;
auto [dpf0, dpf1] = dpf::make_dpf(x, y0, y1);
ASSERT_NO_THROW((dpf::eval_point<0>(dpf0, x)));
ASSERT_NO_THROW((dpf::eval_point<0>(dpf1, x)));
ASSERT_THROW((dpf::eval_point<1>(dpf0, x)), std::runtime_error);
ASSERT_THROW((dpf::eval_point<1>(dpf1, x)), std::runtime_error);
ASSERT_THROW((dpf::eval_point<0, 1>(dpf0, x)), std::runtime_error);
ASSERT_THROW((dpf::eval_point<0, 1>(dpf1, x)), std::runtime_error);
input_type from = 0x33, to = 0xCC;
ASSERT_NO_THROW((dpf::eval_interval<0>(dpf0, from, to)));
ASSERT_NO_THROW((dpf::eval_interval<0>(dpf1, from, to)));
ASSERT_THROW((dpf::eval_interval<1>(dpf0, from, to)), std::runtime_error);
ASSERT_THROW((dpf::eval_interval<1>(dpf1, from, to)), std::runtime_error);
ASSERT_THROW((dpf::eval_interval<0, 1>(dpf0, from, to)), std::runtime_error);
ASSERT_THROW((dpf::eval_interval<0, 1>(dpf1, from, to)), std::runtime_error);
ASSERT_NO_THROW((dpf::eval_full<0>(dpf0)));
ASSERT_NO_THROW((dpf::eval_full<0>(dpf1)));
ASSERT_THROW((dpf::eval_full<1>(dpf0)), std::runtime_error);
ASSERT_THROW((dpf::eval_full<1>(dpf1)), std::runtime_error);
ASSERT_THROW((dpf::eval_full<0, 1>(dpf0)), std::runtime_error);
ASSERT_THROW((dpf::eval_full<0, 1>(dpf1)), std::runtime_error);
std::array<input_type, 16> points{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
ASSERT_NO_THROW((dpf::eval_sequence<0>(dpf0, std::begin(points), std::end(points))));
ASSERT_NO_THROW((dpf::eval_sequence<0>(dpf1, std::begin(points), std::end(points))));
ASSERT_THROW((dpf::eval_sequence<1>(dpf0, std::begin(points), std::end(points))), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<1>(dpf1, std::begin(points), std::end(points))), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<0, 1>(dpf0, std::begin(points), std::end(points))), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<0, 1>(dpf1, std::begin(points), std::end(points))), std::runtime_error);
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(points), std::end(points));
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(points), std::end(points));
ASSERT_NO_THROW((dpf::eval_sequence<0>(dpf0, recipe0)));
ASSERT_NO_THROW((dpf::eval_sequence<0>(dpf1, recipe1)));
ASSERT_THROW((dpf::eval_sequence<1>(dpf0, recipe0)), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<1>(dpf1, recipe1)), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<0, 1>(dpf0, recipe0)), std::runtime_error);
ASSERT_THROW((dpf::eval_sequence<0, 1>(dpf1, recipe1)), std::runtime_error);
}
TEST(WildcardTest, SingleLeafSuccess)
{
using input_type = uint8_t;
using concrete_type = uint32_t;
using output_type = dpf::wildcard_value<concrete_type>;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type>;
input_type x = 0xAA, from = 0x33, to = 0xCC;
output_type y;
concrete_type y_exp = 0xAAAAAAAA,
y_shr0 = 0x12345678,
y_shr1 = y_exp - y_shr0,
zero_output = 0;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
assign_leaf(dpf0, dpf1, y_shr0, y_shr1);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i)
{
auto y0 = *dpf::eval_point(dpf0, i);
auto y1 = *dpf::eval_point(dpf1, i);
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(y0, y1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(y0, y1), zero_output);
}
}
auto [bufint0, iterint0] = dpf::eval_interval(dpf0, from, to);
auto [bufint1, iterint1] = dpf::eval_interval(dpf1, from, to);
auto itint0 = std::begin(iterint0);
auto itint1 = std::begin(iterint1);
for (std::size_t i = from; i <= to; ++i, ++itint0, ++itint1)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itint0, *itint1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itint0, *itint1), zero_output);
}
}
ASSERT_EQ(itint0, std::end(iterint0));
ASSERT_EQ(itint1, std::end(iterint1));
auto [bufful0, iterful0] = dpf::eval_full(dpf0);
auto [bufful1, iterful1] = dpf::eval_full(dpf1);
auto itful0 = std::begin(iterful0);
auto itful1 = std::begin(iterful1);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i, ++itful0, ++itful1)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itful0, *itful1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itful0, *itful1), zero_output);
}
}
ASSERT_EQ(itful0, std::end(iterful0));
ASSERT_EQ(itful1, std::end(iterful1));
std::array<input_type, 16> points{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
auto [bufseq0, iterseq0] = dpf::eval_sequence(dpf0, std::begin(points), std::end(points));
auto [bufseq1, iterseq1] = dpf::eval_sequence(dpf1, std::begin(points), std::end(points));
auto itseq0 = std::begin(iterseq0);
auto itseq1 = std::begin(iterseq1);
for (std::size_t i = 0; i < points.size(); ++i, ++itseq0, ++itseq1)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itseq0, *itseq1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itseq0, *itseq1), zero_output);
}
}
ASSERT_EQ(itseq0, std::end(iterseq0));
ASSERT_EQ(itseq1, std::end(iterseq1));
auto [bufbre0, iterbre0] = dpf::eval_sequence_breadth_first(dpf0, std::begin(points), std::end(points));
auto [bufbre1, iterbre1] = dpf::eval_sequence_breadth_first(dpf1, std::begin(points), std::end(points));
auto itbre0 = std::begin(iterbre0);
auto itbre1 = std::begin(iterbre1);
for (std::size_t i = 0; i < points.size(); ++i, ++itbre0, ++itbre1)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itbre0, *itbre1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itbre0, *itbre1), zero_output);
}
}
ASSERT_EQ(itbre0, std::end(iterbre0));
ASSERT_EQ(itbre1, std::end(iterbre1));
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(points), std::end(points));
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(points), std::end(points));
auto [bufrec0, iterrec0] = dpf::eval_sequence(dpf0, recipe0);
auto [bufrec1, iterrec1] = dpf::eval_sequence(dpf1, recipe1);
auto itrec0 = std::begin(iterrec0);
auto itrec1 = std::begin(iterrec1);
for (std::size_t i = 0; i < points.size(); ++i, ++itrec0, ++itrec1)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itrec0, *itrec1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itrec0, *itrec1), zero_output);
}
}
ASSERT_EQ(itrec0, std::end(iterrec0));
ASSERT_EQ(itrec1, std::end(iterrec1));
}
TEST(WildcardTest, MultiLeafSuccess)
{
using input_type = uint8_t;
using output_type0 = uint32_t;
using concrete_type = uint32_t;
using output_type1 = dpf::wildcard_value<concrete_type>;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type0, output_type1>;
input_type x = 0xAA, from = 0x33, to = 0xCC;
output_type0 y0 = 0x55555555,
zero_output0 = 0;
output_type1 y1;
concrete_type y_exp = 0xAAAAAAAA,
y_shr0 = 0x12345678,
y_shr1 = y_exp - y_shr0,
zero_output1 = 0;
auto [dpf0, dpf1] = dpf::make_dpf(x, y0, y1);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i)
{
auto ypoi0 = *dpf::eval_point<0>(dpf0, i);
auto ypoi1 = *dpf::eval_point<0>(dpf1, i);
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(ypoi0, ypoi1), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(ypoi0, ypoi1), zero_output0);
}
}
auto [bufint00, iterint00] = dpf::eval_interval<0>(dpf0, from, to);
auto [bufint01, iterint01] = dpf::eval_interval<0>(dpf1, from, to);
auto itint00 = std::begin(iterint00);
auto itint01 = std::begin(iterint01);
for (std::size_t i = from; i <= to; ++i, ++itint00, ++itint01)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itint00, *itint01), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itint00, *itint01), zero_output0);
}
}
ASSERT_EQ(itint00, std::end(iterint00));
ASSERT_EQ(itint01, std::end(iterint01));
auto [bufful00, iterful00] = dpf::eval_full<0>(dpf0);
auto [bufful01, iterful01] = dpf::eval_full<0>(dpf1);
auto itful00 = std::begin(iterful00);
auto itful01 = std::begin(iterful01);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i, ++itful00, ++itful01)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itful00, *itful01), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itful00, *itful01), zero_output0);
}
}
ASSERT_EQ(itful00, std::end(iterful00));
ASSERT_EQ(itful01, std::end(iterful01));
std::array<input_type, 16> points{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
auto [bufseq00, iterseq00] = dpf::eval_sequence<0>(dpf0, std::begin(points), std::end(points));
auto [bufseq01, iterseq01] = dpf::eval_sequence<0>(dpf1, std::begin(points), std::end(points));
auto itseq00 = std::begin(iterseq00);
auto itseq01 = std::begin(iterseq01);
for (std::size_t i = 0; i < points.size(); ++i, ++itseq00, ++itseq01)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itseq00, *itseq01), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itseq00, *itseq01), zero_output0);
}
}
ASSERT_EQ(itseq00, std::end(iterseq00));
ASSERT_EQ(itseq01, std::end(iterseq01));
auto [bufbre00, iterbre00] = dpf::eval_sequence_breadth_first<0>(dpf0, std::begin(points), std::end(points));
auto [bufbre01, iterbre01] = dpf::eval_sequence_breadth_first<0>(dpf1, std::begin(points), std::end(points));
auto itbre00 = std::begin(iterbre00);
auto itbre01 = std::begin(iterbre01);
for (std::size_t i = 0; i < points.size(); ++i, ++itbre00, ++itbre01)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itbre00, *itbre01), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itbre00, *itbre01), zero_output0);
}
}
ASSERT_EQ(itbre00, std::end(iterbre00));
ASSERT_EQ(itbre01, std::end(iterbre01));
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(points), std::end(points));
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(points), std::end(points));
auto [bufrec00, iterrec00] = dpf::eval_sequence<0>(dpf0, recipe0);
auto [bufrec01, iterrec01] = dpf::eval_sequence<0>(dpf1, recipe1);
auto itrec00 = std::begin(iterrec00);
auto itrec01 = std::begin(iterrec01);
for (std::size_t i = 0; i < points.size(); ++i, ++itrec00, ++itrec01)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itrec00, *itrec01), y0);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itrec00, *itrec01), zero_output0);
}
}
ASSERT_EQ(itrec00, std::end(iterrec00));
ASSERT_EQ(itrec01, std::end(iterrec01));
assign_leaf<1>(dpf0, dpf1, y_shr0, y_shr1);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i)
{
auto ypoi0 = *dpf::eval_point<1>(dpf0, i);
auto ypoi1 = *dpf::eval_point<1>(dpf1, i);
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(ypoi0, ypoi1), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(ypoi0, ypoi1), zero_output1);
}
}
auto [bufint10, iterint10] = dpf::eval_interval<1>(dpf0, from, to);
auto [bufint11, iterint11] = dpf::eval_interval<1>(dpf1, from, to);
auto itint10 = std::begin(iterint10);
auto itint11 = std::begin(iterint11);
for (std::size_t i = from; i <= to; ++i, ++itint10, ++itint11)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itint10, *itint11), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itint10, *itint11), zero_output1);
}
}
ASSERT_EQ(itint10, std::end(iterint10));
ASSERT_EQ(itint11, std::end(iterint11));
auto [bufful10, iterful10] = dpf::eval_full<1>(dpf0);
auto [bufful11, iterful11] = dpf::eval_full<1>(dpf1);
auto itful10 = std::begin(iterful10);
auto itful11 = std::begin(iterful11);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i, ++itful10, ++itful11)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(*itful10, *itful11), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itful10, *itful11), zero_output1);
}
}
ASSERT_EQ(itful10, std::end(iterful10));
ASSERT_EQ(itful11, std::end(iterful11));
auto [bufseq10, iterseq10] = dpf::eval_sequence<1>(dpf0, std::begin(points), std::end(points));
auto [bufseq11, iterseq11] = dpf::eval_sequence<1>(dpf1, std::begin(points), std::end(points));
auto itseq10 = std::begin(iterseq10);
auto itseq11 = std::begin(iterseq11);
for (std::size_t i = 0; i < points.size(); ++i, ++itseq10, ++itseq11)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itseq10, *itseq11), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itseq10, *itseq11), zero_output1);
}
}
ASSERT_EQ(itseq10, std::end(iterseq10));
ASSERT_EQ(itseq11, std::end(iterseq11));
auto [bufbre10, iterbre10] = dpf::eval_sequence_breadth_first<1>(dpf0, std::begin(points), std::end(points));
auto [bufbre11, iterbre11] = dpf::eval_sequence_breadth_first<1>(dpf1, std::begin(points), std::end(points));
auto itbre10 = std::begin(iterbre10);
auto itbre11 = std::begin(iterbre11);
for (std::size_t i = 0; i < points.size(); ++i, ++itbre10, ++itbre11)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itbre10, *itbre11), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itbre10, *itbre11), zero_output1);
}
}
ASSERT_EQ(itbre10, std::end(iterbre10));
ASSERT_EQ(itbre11, std::end(iterbre11));
auto [bufrec10, iterrec10] = dpf::eval_sequence<1>(dpf0, recipe0);
auto [bufrec11, iterrec11] = dpf::eval_sequence<1>(dpf1, recipe1);
auto itrec10 = std::begin(iterrec10);
auto itrec11 = std::begin(iterrec11);
for (std::size_t i = 0; i < points.size(); ++i, ++itrec10, ++itrec11)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(*itrec10, *itrec11), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(*itrec10, *itrec11), zero_output1);
}
}
ASSERT_EQ(itrec10, std::end(iterrec10));
ASSERT_EQ(itrec11, std::end(iterrec11));
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i)
{
auto ypoi0 = dpf::eval_point<0, 1>(dpf0, i);
auto ypoi1 = dpf::eval_point<0, 1>(dpf1, i);
if (i == x)
{
ASSERT_EQ(static_cast<output_type0>(dpf::reconstruct(std::get<0>(ypoi0), std::get<0>(ypoi1))), y0);
ASSERT_EQ(static_cast<concrete_type>(dpf::reconstruct(std::get<1>(ypoi0), std::get<1>(ypoi1))), y_exp);
}
else
{
ASSERT_EQ(static_cast<output_type0>(dpf::reconstruct(std::get<0>(ypoi0), std::get<0>(ypoi1))), zero_output0);
ASSERT_EQ(static_cast<concrete_type>(dpf::reconstruct(std::get<1>(ypoi0), std::get<1>(ypoi1))), zero_output1);
}
}
auto [bufintmul0, iterintmul0] = dpf::eval_interval<0, 1>(dpf0, from, to);
auto [bufintmul1, iterintmul1] = dpf::eval_interval<0, 1>(dpf1, from, to);
auto zipint0 = dpf::tuple_as_zip(iterintmul0);
auto zipint1 = dpf::tuple_as_zip(iterintmul1);
auto itintmul0 = std::begin(zipint0);
auto itintmul1 = std::begin(zipint1);
for (std::size_t i = from; i <= to; ++i, ++itintmul0, ++itintmul1)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itintmul0), std::get<0>(*itintmul1)), y0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itintmul0), std::get<1>(*itintmul1)), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itintmul0), std::get<0>(*itintmul1)), zero_output0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itintmul0), std::get<1>(*itintmul1)), zero_output1);
}
}
ASSERT_EQ(itintmul0, std::end(zipint0));
ASSERT_EQ(itintmul1, std::end(zipint1));
auto [buffulmul0, iterfulmul0] = dpf::eval_full<0, 1>(dpf0);
auto [buffulmul1, iterfulmul1] = dpf::eval_full<0, 1>(dpf1);
auto zipful0 = dpf::tuple_as_zip(iterfulmul0);
auto zipful1 = dpf::tuple_as_zip(iterfulmul1);
auto itfulmul0 = std::begin(zipful0);
auto itfulmul1 = std::begin(zipful1);
for (std::size_t i = std::numeric_limits<input_type>::min(); i <= std::numeric_limits<input_type>::max(); ++i, ++itfulmul0, ++itfulmul1)
{
if (i == x)
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itfulmul0), std::get<0>(*itfulmul1)), y0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itfulmul0), std::get<1>(*itfulmul1)), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itfulmul0), std::get<0>(*itfulmul1)), zero_output0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itfulmul0), std::get<1>(*itfulmul1)), zero_output1);
}
}
ASSERT_EQ(itfulmul0, std::end(zipful0));
ASSERT_EQ(itfulmul1, std::end(zipful1));
auto [bufseqmul0, iterseqmul0] = dpf::eval_sequence<0, 1>(dpf0, std::begin(points), std::end(points));
auto [bufseqmul1, iterseqmul1] = dpf::eval_sequence<0, 1>(dpf1, std::begin(points), std::end(points));
auto zipseq0 = dpf::tuple_as_zip(iterseqmul0);
auto zipseq1 = dpf::tuple_as_zip(iterseqmul1);
auto itseqmul0 = std::begin(zipseq0);
auto itseqmul1 = std::begin(zipseq1);
for (std::size_t i = 0; i < points.size(); ++i, ++itseqmul0, ++itseqmul1)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itseqmul0), std::get<0>(*itseqmul1)), y0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itseqmul0), std::get<1>(*itseqmul1)), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itseqmul0), std::get<0>(*itseqmul1)), zero_output0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itseqmul0), std::get<1>(*itseqmul1)), zero_output1);
}
}
ASSERT_EQ(itseqmul0, std::end(zipseq0));
ASSERT_EQ(itseqmul1, std::end(zipseq1));
auto [bufrecmul0, iterrecmul0] = dpf::eval_sequence<0, 1>(dpf0, recipe0);
auto [bufrecmul1, iterrecmul1] = dpf::eval_sequence<0, 1>(dpf1, recipe1);
auto ziprec0 = dpf::tuple_as_zip(iterrecmul0);
auto ziprec1 = dpf::tuple_as_zip(iterrecmul1);
auto itrecmul0 = std::begin(ziprec0);
auto itrecmul1 = std::begin(ziprec1);
for (std::size_t i = 0; i < points.size(); ++i, ++itrecmul0, ++itrecmul1)
{
if (points[i] == x)
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itrecmul0), std::get<0>(*itrecmul1)), y0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itrecmul0), std::get<1>(*itrecmul1)), y_exp);
}
else
{
ASSERT_EQ(dpf::reconstruct(std::get<0>(*itrecmul0), std::get<0>(*itrecmul1)), zero_output0);
ASSERT_EQ(dpf::reconstruct(std::get<1>(*itrecmul0), std::get<1>(*itrecmul1)), zero_output1);
}
}
ASSERT_EQ(itrecmul0, std::end(ziprec0));
ASSERT_EQ(itrecmul1, std::end(ziprec1));
}
TEST(WildcardTest, PackedSmallWildcardsAtNonTerminalAssignAll)
{
// Four packed uint8 wildcards at a non-terminal prefix, plus a concrete
// deepest output. Assign every wildcard leaf through the asio path.
using concrete_t = uint8_t;
dpf::wildcard_value<concrete_t> w;
uint16_t x = 0x55aa;
auto [dpf0, dpf1] = dpf::make_dpf(x,
dpf::at<10>(w, w, w, w),
uint16_t{99});
ASSERT_THROW(dpf::eval_point(dpf::out<0, 10>, dpf0, x), std::runtime_error);
ASSERT_EQ(static_cast<uint16_t>(
dpf::reconstruct(*dpf::eval_point(dpf::out<4>, dpf0, x), *dpf::eval_point(dpf::out<4>, dpf1, x))),
uint16_t{99});
const concrete_t want[4] = {1, 2, 3, 4};
const concrete_t shr0[4] = {10, 20, 30, 40};
assign_leaf<0>(dpf0, dpf1, shr0[0], static_cast<concrete_t>(want[0] - shr0[0]));
assign_leaf<1>(dpf0, dpf1, shr0[1], static_cast<concrete_t>(want[1] - shr0[1]));
assign_leaf<2>(dpf0, dpf1, shr0[2], static_cast<concrete_t>(want[2] - shr0[2]));
assign_leaf<3>(dpf0, dpf1, shr0[3], static_cast<concrete_t>(want[3] - shr0[3]));
ASSERT_EQ(static_cast<concrete_t>(
dpf::reconstruct(*dpf::eval_point(dpf::out<0, 10>, dpf0, x), *dpf::eval_point(dpf::out<0, 10>, dpf1, x))),
want[0]);
ASSERT_EQ(static_cast<concrete_t>(
dpf::reconstruct(*dpf::eval_point(dpf::out<3, 10>, dpf0, x), *dpf::eval_point(dpf::out<3, 10>, dpf1, x))),
want[3]);
ASSERT_EQ(static_cast<concrete_t>(
dpf::reconstruct(
*dpf::eval_point(dpf::out<0, 10>, dpf0,
static_cast<uint16_t>(x ^ (1u << 6))),
*dpf::eval_point(dpf::out<0, 10>, dpf1,
static_cast<uint16_t>(x ^ (1u << 6))))),
concrete_t{0});
}
namespace
{
template <typename Bits, typename T>
T bits_as(Bits bits)
{
T out{};
std::memcpy(&out, &bits, sizeof(T));
return out;
}
template <typename T>
auto bits_of(T v)
{
using bits_t = std::conditional_t<sizeof(T) == 4, std::uint32_t, std::uint64_t>;
bits_t bits{};
std::memcpy(&bits, &v, sizeof(T));
return bits;
}
/// In-process assign for a wildcard leaf (same messages as asio, no socket).
template <std::size_t I = 0, typename DpfKey0, typename DpfKey1, typename ShareT>
void assign_leaf_local(DpfKey0 & dpf0, DpfKey1 & dpf1, const ShareT & shr0,
const ShareT & shr1)
{
auto & w0 = std::get<I>(dpf0.leaf_nodes);
auto & w1 = std::get<I>(dpf1.leaf_nodes);
if (w0.is_ready())
w0.begin_update();
if (w1.is_ready())
w1.begin_update();
const auto b0 = w0.compute_and_get_blinded_output_share(shr0);
const auto b1 = w1.compute_and_get_blinded_output_share(shr1);
const auto l0 = w0.compute_and_get_leaf_share(b1);
const auto l1 = w1.compute_and_get_leaf_share(b0);
w0.reconstruct_correction_word(l1);
w1.reconstruct_correction_word(l0);
}
} // namespace
TEST(WildcardTest, FullWidthXorDoesNotRevealBeta)
{
using input_type = uint8_t;
using concrete_type = dpf::xints::xint128_t;
using output_type = dpf::wildcard_value<concrete_type>;
input_type x = 0x42;
output_type y;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
auto & w0 = std::get<0>(dpf0.leaf_nodes);
auto & w1 = std::get<0>(dpf1.leaf_nodes);
// Scale Beaver must be planted even for a single full-width XOR lane.
EXPECT_EQ((dpf::outputs_per_leaf_v<concrete_type,
typename std::decay_t<decltype(dpf0)>::exterior_node>), 1u);
EXPECT_TRUE(dpf::utils::has_characteristic_two_v<concrete_type>);
concrete_type y_exp = concrete_type{0x0123456789ABCDEFull};
concrete_type y_shr0 = concrete_type{0x1111111111111111ull};
concrete_type y_shr1 = y_exp + y_shr0; // XOR group
const auto blinded0 = w0.compute_and_get_blinded_output_share(y_shr0);
const auto blinded1 = w1.compute_and_get_blinded_output_share(y_shr1);
// With a non-trivial output blind, the exchanged value is not the share.
EXPECT_NE(blinded0, y_shr0);
EXPECT_NE(blinded1, y_shr1);
// Finish the assign after the privacy check above (state is already blinded).
const auto l0 = w0.compute_and_get_leaf_share(blinded1);
const auto l1 = w1.compute_and_get_leaf_share(blinded0);
w0.reconstruct_correction_word(l1);
w1.reconstruct_correction_word(l0);
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, x), *dpf::eval_point(dpf1, x)),
y_exp);
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, static_cast<input_type>(x ^ 1)),
*dpf::eval_point(dpf1, static_cast<input_type>(x ^ 1))),
concrete_type{});
}
TEST(WildcardTest, FloatWildcardRoundTrip)
{
using input_type = uint8_t;
using concrete_type = float;
using output_type = dpf::wildcard_value<concrete_type>;
input_type x = 0x55;
output_type y;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
const concrete_type y_exp = 3.14159265f;
const auto y_bits = bits_of(y_exp);
const std::uint32_t shr0_bits = 0xA5A5A5A5u;
const concrete_type y_shr0 = bits_as<std::uint32_t, concrete_type>(shr0_bits);
const concrete_type y_shr1 =
bits_as<std::uint32_t, concrete_type>(y_bits ^ shr0_bits);
assign_leaf_local(dpf0, dpf1, y_shr0, y_shr1);
const auto got = dpf::reconstruct(*dpf::eval_point(dpf0, x), *dpf::eval_point(dpf1, x));
EXPECT_EQ(bits_of(got), y_bits);
EXPECT_EQ(bits_of(dpf::reconstruct(
*dpf::eval_point(dpf0, static_cast<input_type>(x ^ 1)),
*dpf::eval_point(dpf1, static_cast<input_type>(x ^ 1)))),
0u);
}
TEST(WildcardTest, SecondAssignUpdatesPayload)
{
using input_type = uint8_t;
using concrete_type = uint32_t;
using output_type = dpf::wildcard_value<concrete_type>;
input_type x = 0x11;
auto [dpf0, dpf1] = dpf::make_dpf(x, output_type{});
const concrete_type beta = 0xAAAAAAAAu;
const concrete_type beta2 = 0xBBBBBBBBu;
const concrete_type s0 = 0x12345678u;
assign_leaf_local(dpf0, dpf1, s0, static_cast<concrete_type>(beta - s0));
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, x), *dpf::eval_point(dpf1, x)), beta);
// Second assign installs (beta2 - beta) on top of the ready leaf.
const concrete_type delta = static_cast<concrete_type>(beta2 - beta);
const concrete_type d0 = 0x01010101u;
assign_leaf_local(dpf0, dpf1, d0, static_cast<concrete_type>(delta - d0));
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, x), *dpf::eval_point(dpf1, x)),
beta2);
}
TEST(WildcardTest, AssignWildcardInputOpensPublicShiftNotAlpha)
{
using input_type = uint8_t;
using output_type = uint32_t;
// Wildcard domain: dealer plants a random mask; parties later open (mask - alpha).
auto [dpf0, dpf1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, output_type{7});
const input_type mask = static_cast<input_type>(
dpf0.offset_x.raw() + dpf1.offset_x.raw());
const input_type alpha = 0xAAu;
const input_type a0 = 0x12u;
const input_type a1 = static_cast<input_type>(alpha - a0);
const auto sh0 = dpf0.offset_x.compute_and_get_share(a0);
const auto sh1 = dpf1.offset_x.compute_and_get_share(a1);
const auto open0 = dpf0.offset_x.reconstruct(sh1);
const auto open1 = dpf1.offset_x.reconstruct(sh0);
EXPECT_EQ(open0, open1);
const input_type want_shift = static_cast<input_type>(mask - alpha);
EXPECT_EQ(open0, want_shift);
EXPECT_NE(open0, alpha); // public value is the shift, not alpha
EXPECT_EQ(static_cast<input_type>(open0 + alpha), mask);
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, alpha), *dpf::eval_point(dpf1, alpha)),
output_type{7});
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf0, static_cast<input_type>(alpha ^ 1)),
*dpf::eval_point(dpf1, static_cast<input_type>(alpha ^ 1))),
output_type{0});
}
TEST(WildcardTest, UpdatableTagAssignsThenRewrites)
{
const std::uint8_t alpha = 0x2a;
auto [k0, k1] = dpf::make_dpf(alpha, std::uint64_t{7}, dpf::updatable{});
EXPECT_TRUE(k0.is_wildcard(0));
EXPECT_TRUE(k1.is_wildcard(0));
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
std::uint64_t{7});
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, std::uint8_t{0}),
*dpf::eval_point(k1, std::uint8_t{0})),
std::uint64_t{0});
auto & w0 = std::get<0>(k0.leaf_nodes);
auto & w1 = std::get<0>(k1.leaf_nodes);
w0.begin_update();
w1.begin_update();
// A second assign installs the difference β' − β, not the new absolute payload.
const auto shares = dpf::additively_share(std::uint64_t{9} - std::uint64_t{7});
const auto b0 = w0.compute_and_get_blinded_output_share(shares.first.raw());
const auto b1 = w1.compute_and_get_blinded_output_share(shares.second.raw());
const auto l0 = w0.compute_and_get_leaf_share(b1);
const auto l1 = w1.compute_and_get_leaf_share(b0);
w0.reconstruct_correction_word(l1);
w1.reconstruct_correction_word(l0);
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
std::uint64_t{9});
}