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
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
int main()
|
||||
{
|
||||
system("./bin/dpf_key_test");
|
||||
system("./bin/wildcard_test");
|
||||
(void)std::system("./bin/dpf_key_test");
|
||||
(void)std::system("./bin/wildcard_test");
|
||||
|
||||
system("./bin/eval_point_test");
|
||||
system("./bin/eval_interval_test");
|
||||
system("./bin/eval_full_test");
|
||||
system("./bin/eval_sequence_test");
|
||||
(void)std::system("./bin/eval_point_test");
|
||||
(void)std::system("./bin/eval_interval_test");
|
||||
(void)std::system("./bin/eval_full_test");
|
||||
(void)std::system("./bin/eval_sequence_test");
|
||||
|
||||
system("./bin/eval_point_multi_test");
|
||||
system("./bin/eval_interval_multi_test");
|
||||
system("./bin/eval_full_multi_test");
|
||||
system("./bin/eval_sequence_multi_test");
|
||||
(void)std::system("./bin/eval_point_multi_test");
|
||||
(void)std::system("./bin/eval_interval_multi_test");
|
||||
(void)std::system("./bin/eval_full_multi_test");
|
||||
(void)std::system("./bin/eval_sequence_multi_test");
|
||||
|
||||
system("./bin/advice_bit_iterable_test");
|
||||
system("./bin/parallel_bit_iterable_test");
|
||||
system("./bin/setbit_index_iterable_test");
|
||||
system("./bin/keyword2_test");
|
||||
(void)std::system("./bin/advice_bit_iterable_test");
|
||||
(void)std::system("./bin/parallel_bit_iterable_test");
|
||||
(void)std::system("./bin/setbit_index_iterable_test");
|
||||
(void)std::system("./bin/keyword2_test");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
241
test/tests/arith_garble_test.cpp
Normal file
241
test/tests/arith_garble_test.cpp
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/arith_garble.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::arith_garble::circuit;
|
||||
using dpf::arith_garble::wire;
|
||||
|
||||
std::vector<std::uint16_t> plain_outs(const circuit & c,
|
||||
const std::uint16_t * in)
|
||||
{
|
||||
auto all = dpf::arith_garble::eval_plain(c, in);
|
||||
std::vector<std::uint16_t> out;
|
||||
for (auto id : c.outputs())
|
||||
out.push_back(all[id]);
|
||||
return out;
|
||||
}
|
||||
|
||||
void expect_match(const circuit & c, const std::vector<std::uint16_t> & in)
|
||||
{
|
||||
auto got = dpf::arith_garble::eval_pair(c, in.data());
|
||||
auto want = plain_outs(c, in.data());
|
||||
ASSERT_EQ(got.opened, want);
|
||||
ASSERT_EQ(got.mask.size(), want.size());
|
||||
for (std::size_t i = 0; i < want.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(got.opened[i],
|
||||
dpf::arith_garble::open_shares(got.modulus[i], got.mask[i], got.color[i]));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ArithGarble, ProjectionRowsAreModulusMinusOne)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(5);
|
||||
std::vector<std::uint16_t> sq{0, 1, 4, 4, 1};
|
||||
c.out(c.project(x, 5, sq));
|
||||
auto got = dpf::arith_garble::eval_pair(c, std::vector<std::uint16_t>{3}.data());
|
||||
EXPECT_EQ(got.ciphertext_rows, 4u);
|
||||
EXPECT_EQ(got.opened[0], 4u);
|
||||
}
|
||||
|
||||
TEST(ArithGarble, AddScaleAndPublicShift)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(7);
|
||||
auto y = c.input(7);
|
||||
auto s = c.add(x, y);
|
||||
auto t = c.scale(s, 3);
|
||||
c.out(c.add_const(t, 4));
|
||||
for (std::uint16_t a = 0; a < 7; ++a)
|
||||
for (std::uint16_t b = 0; b < 7; ++b)
|
||||
expect_match(c, {a, b});
|
||||
}
|
||||
|
||||
TEST(ArithGarble, ThresholdCostsFanInRows)
|
||||
{
|
||||
circuit c;
|
||||
std::vector<wire> bits;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
bits.push_back(c.input(4));
|
||||
c.out(c.threshold(bits, 3));
|
||||
auto got = dpf::arith_garble::eval_pair(
|
||||
c, std::vector<std::uint16_t>{1, 1, 1}.data());
|
||||
EXPECT_EQ(got.ciphertext_rows, 3u);
|
||||
EXPECT_EQ(got.opened[0], 1u);
|
||||
expect_match(c, {1, 1, 0});
|
||||
expect_match(c, {0, 0, 0});
|
||||
}
|
||||
|
||||
TEST(ArithGarble, FanInAndOfBits)
|
||||
{
|
||||
circuit c;
|
||||
std::vector<wire> bits;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
bits.push_back(c.input(2));
|
||||
c.out(c.fanin_and(bits));
|
||||
expect_match(c, {1, 1, 1, 1});
|
||||
expect_match(c, {1, 1, 0, 1});
|
||||
expect_match(c, {0, 0, 0, 0});
|
||||
}
|
||||
|
||||
TEST(ArithGarble, PrimeProduct)
|
||||
{
|
||||
for (std::uint16_t p : {2, 3, 5, 7})
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(p);
|
||||
auto y = c.input(p);
|
||||
c.out(c.mul(x, y));
|
||||
for (std::uint16_t a = 0; a < p; ++a)
|
||||
for (std::uint16_t b = 0; b < p; ++b)
|
||||
expect_match(c, {a, b});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ArithGarble, ChainedProduct)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(5);
|
||||
auto y = c.input(5);
|
||||
auto z = c.input(5);
|
||||
c.out(c.mul(c.add(x, y), z));
|
||||
expect_match(c, {2, 4, 3});
|
||||
expect_match(c, {0, 1, 4});
|
||||
expect_match(c, {4, 4, 0});
|
||||
}
|
||||
|
||||
TEST(ArithGarble, EveryResidueOfAProjectionAndAScale)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(11);
|
||||
std::vector<std::uint16_t> cube(11);
|
||||
for (std::uint16_t i = 0; i < 11; ++i)
|
||||
cube[i] = static_cast<std::uint16_t>((i * i * i) % 11);
|
||||
auto y = c.project(x, 11, cube);
|
||||
c.out(y);
|
||||
c.out(c.scale(y, 10));
|
||||
c.out(c.add_const(x, 18));
|
||||
for (std::uint16_t a = 0; a < 11; ++a)
|
||||
{
|
||||
expect_match(c, {a});
|
||||
auto got = dpf::arith_garble::eval_pair(c, &a);
|
||||
EXPECT_EQ(got.ciphertext_rows, 10u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ArithGarble, FreeGatesSendNoRows)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(9);
|
||||
auto y = c.input(9);
|
||||
c.out(c.add_const(c.scale(c.add(x, y), 5), 4));
|
||||
auto got = dpf::arith_garble::eval_pair(c, std::vector<std::uint16_t>{8, 8}.data());
|
||||
EXPECT_EQ(got.ciphertext_rows, 0u);
|
||||
EXPECT_EQ(got.opened[0], static_cast<std::uint16_t>((5 * (8 + 8) + 4) % 9));
|
||||
}
|
||||
|
||||
TEST(ArithGarble, ThresholdEveryWeight)
|
||||
{
|
||||
circuit c;
|
||||
std::vector<wire> bits;
|
||||
for (int i = 0; i < 5; ++i)
|
||||
bits.push_back(c.input(6));
|
||||
for (std::uint16_t t = 0; t <= 5; ++t)
|
||||
c.out(c.threshold(bits, t));
|
||||
auto one = dpf::arith_garble::eval_pair(
|
||||
c, std::vector<std::uint16_t>{1, 1, 1, 1, 1}.data());
|
||||
EXPECT_EQ(one.ciphertext_rows, 5u * 6u);
|
||||
for (unsigned mask = 0; mask < 32; ++mask)
|
||||
{
|
||||
std::vector<std::uint16_t> in(5);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
in[i] = static_cast<std::uint16_t>((mask >> i) & 1u);
|
||||
expect_match(c, in);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ArithGarble, FanInAndEveryPattern)
|
||||
{
|
||||
circuit c;
|
||||
std::vector<wire> bits;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
bits.push_back(c.input(2));
|
||||
c.out(c.fanin_and(bits));
|
||||
auto rows = dpf::arith_garble::eval_pair(
|
||||
c, std::vector<std::uint16_t>{1, 1, 1, 1}.data());
|
||||
EXPECT_EQ(rows.ciphertext_rows, 8u);
|
||||
for (unsigned mask = 0; mask < 16; ++mask)
|
||||
{
|
||||
std::vector<std::uint16_t> in(4);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
in[i] = static_cast<std::uint16_t>((mask >> i) & 1u);
|
||||
expect_match(c, in);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ArithGarble, PrimeElevenAndBitScale)
|
||||
{
|
||||
circuit mul;
|
||||
auto x = mul.input(11);
|
||||
auto y = mul.input(11);
|
||||
mul.out(mul.mul(x, y));
|
||||
for (std::uint16_t a = 0; a < 11; ++a)
|
||||
for (std::uint16_t b = 0; b < 11; ++b)
|
||||
expect_match(mul, {a, b});
|
||||
|
||||
circuit pass;
|
||||
auto word = pass.input(5);
|
||||
auto bit = pass.input(2);
|
||||
pass.out(pass.bit_scale(word, bit));
|
||||
for (std::uint16_t w = 0; w < 5; ++w)
|
||||
for (std::uint16_t b = 0; b < 2; ++b)
|
||||
expect_match(pass, {w, b});
|
||||
}
|
||||
|
||||
TEST(ArithGarble, TwoEvalutionsOpenTheSameValue)
|
||||
{
|
||||
circuit c;
|
||||
auto x = c.input(5);
|
||||
auto y = c.input(5);
|
||||
c.out(c.mul(x, y));
|
||||
const std::uint16_t in[2] = {3, 4};
|
||||
auto a = dpf::arith_garble::eval_pair(c, in);
|
||||
auto b = dpf::arith_garble::eval_pair(c, in);
|
||||
EXPECT_EQ(a.opened, b.opened);
|
||||
EXPECT_EQ(a.opened[0], static_cast<std::uint16_t>((3 * 4) % 5));
|
||||
}
|
||||
|
||||
TEST(ArithGarble, RejectsBadModuliTablesAndInputs)
|
||||
{
|
||||
circuit c;
|
||||
EXPECT_THROW(c.input(1), std::invalid_argument);
|
||||
EXPECT_THROW(c.input(129), std::invalid_argument);
|
||||
auto x = c.input(8);
|
||||
auto y = c.input(7);
|
||||
EXPECT_THROW(c.add(x, y), std::invalid_argument);
|
||||
EXPECT_THROW(c.scale(x, 2), std::invalid_argument);
|
||||
EXPECT_THROW(c.scale(x, 0), std::invalid_argument);
|
||||
EXPECT_THROW(c.project(x, 3, {0, 1}), std::invalid_argument);
|
||||
EXPECT_THROW(c.project(x, 3, std::vector<std::uint16_t>(8, 3)), std::invalid_argument);
|
||||
auto bit = c.input(2);
|
||||
EXPECT_THROW(c.bit_scale(x, x), std::invalid_argument);
|
||||
EXPECT_THROW(c.fanin_and({x}), std::invalid_argument);
|
||||
EXPECT_THROW(c.threshold({}, 0), std::invalid_argument);
|
||||
circuit bare;
|
||||
bare.input(3);
|
||||
EXPECT_THROW(dpf::arith_garble::eval_pair(bare, std::vector<std::uint16_t>{0}.data()),
|
||||
std::invalid_argument);
|
||||
circuit one;
|
||||
one.out(one.input(4));
|
||||
EXPECT_THROW(expect_match(one, {4}), std::invalid_argument);
|
||||
(void)bit;
|
||||
}
|
||||
87
test/tests/beaver_party_test.cpp
Normal file
87
test/tests/beaver_party_test.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cases.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "spawn.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class PartyFlowTest : public ::testing::TestWithParam<std::string>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(PartyFlowTest, Trio)
|
||||
{
|
||||
dpf::party::register_all_flows();
|
||||
const auto & name = GetParam();
|
||||
const auto * f = dpf::party::find_flow(name);
|
||||
ASSERT_NE(f, nullptr) << name;
|
||||
// IKNP replaces the dealer. Spawning p2 makes that role call a keygen
|
||||
// that refuses `role::p2` (`iknp point/comparison keygen has no dealer`).
|
||||
const bool pair = dpf::party::flow_is_pair(*f);
|
||||
auto r = pair ? dpf::party::spawn_pair_flow(name)
|
||||
: dpf::party::spawn_trio_flow(name);
|
||||
EXPECT_EQ(r.rc[0], 0) << "p0 failed for " << name;
|
||||
EXPECT_EQ(r.rc[1], 0) << "p1 failed for " << name;
|
||||
if (!pair)
|
||||
EXPECT_EQ(r.rc[2], 0) << "p2 failed for " << name;
|
||||
else
|
||||
EXPECT_EQ(r.rc[2], 0) << "pair spawn must not report a dealer";
|
||||
}
|
||||
|
||||
std::vector<std::string> all_flow_names()
|
||||
{
|
||||
dpf::party::register_all_flows();
|
||||
std::vector<std::string> names;
|
||||
for (const auto * f : dpf::party::select_flows({}))
|
||||
names.emplace_back(f->name);
|
||||
return names;
|
||||
}
|
||||
|
||||
TEST(PartyFlow, PairFlowsAreTheIknpOnes)
|
||||
{
|
||||
dpf::party::register_all_flows();
|
||||
int pairs = 0;
|
||||
int trios = 0;
|
||||
for (const auto * f : dpf::party::select_flows({}))
|
||||
{
|
||||
ASSERT_NE(f, nullptr);
|
||||
ASSERT_NE(f->name, nullptr);
|
||||
const std::string name = f->name;
|
||||
const bool iknp = name.rfind("iknp_", 0) == 0
|
||||
|| dpf::party::flow_has_tag(*f, "iknp");
|
||||
EXPECT_EQ(dpf::party::flow_is_pair(*f), iknp) << name;
|
||||
if (iknp)
|
||||
{
|
||||
++pairs;
|
||||
EXPECT_EQ(name.rfind("iknp_", 0), 0u) << name;
|
||||
}
|
||||
else
|
||||
{
|
||||
++trios;
|
||||
}
|
||||
}
|
||||
EXPECT_GE(pairs, 5);
|
||||
EXPECT_GT(trios, pairs);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
AllFlows,
|
||||
PartyFlowTest,
|
||||
::testing::ValuesIn(all_flow_names()),
|
||||
[](const auto & info) {
|
||||
std::string n = info.param;
|
||||
for (char & c : n)
|
||||
{
|
||||
if (!(std::isalnum(static_cast<unsigned char>(c)) || c == '_'))
|
||||
c = '_';
|
||||
}
|
||||
return n;
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
|
@ -5,8 +5,11 @@
|
|||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "dpf/beaver.hpp"
|
||||
#include "dpf/buffered_prg.hpp"
|
||||
#include "dpf/doerner_shelat.hpp"
|
||||
#include "dpf/modint.hpp"
|
||||
|
||||
namespace
|
||||
|
|
@ -303,6 +306,37 @@ TEST(Beaver, InnerProductMatchesTheSumAndIsOneCross)
|
|||
EXPECT_EQ(fused.open(again), expect);
|
||||
}
|
||||
|
||||
TEST(Beaver, ScheduleObjectiveRoundsKeepsOneRound)
|
||||
{
|
||||
session64 latency;
|
||||
latency.set_schedule_objective(dpf::beavers::schedule_objective::rounds);
|
||||
auto sgn = latency.input();
|
||||
auto x = latency.input();
|
||||
auto a0 = latency.input();
|
||||
auto a1 = latency.input();
|
||||
auto lin = latency(sgn * (a1 * x + a0));
|
||||
EXPECT_EQ(latency.round_of(lin), 1);
|
||||
|
||||
session64 prep;
|
||||
prep.set_schedule_objective(dpf::beavers::schedule_objective::prep);
|
||||
auto ps = prep.input();
|
||||
auto px = prep.input();
|
||||
auto pa0 = prep.input();
|
||||
auto pa1 = prep.input();
|
||||
auto plin = prep(ps * (pa1 * px + pa0));
|
||||
EXPECT_EQ(prep.round_of(plin), 2);
|
||||
EXPECT_LT(prep.preprocessing_count(), latency.preprocessing_count());
|
||||
|
||||
Counter rng;
|
||||
latency.sample(rng);
|
||||
latency.bind(sgn, u64{3}, rng);
|
||||
latency.bind(x, u64{2}, rng);
|
||||
latency.bind(a0, u64{4}, rng);
|
||||
latency.bind(a1, u64{5}, rng);
|
||||
latency.evaluate();
|
||||
EXPECT_EQ(latency.open(lin), 3u * (5u * 2u + 4u));
|
||||
}
|
||||
|
||||
TEST(Beaver, InnerProductPeelsASharedFactor)
|
||||
{
|
||||
session64 s;
|
||||
|
|
@ -632,16 +666,16 @@ TEST(Beaver, InnerProductRejectsABadShape)
|
|||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s.input();
|
||||
EXPECT_THROW((void)[&] {
|
||||
EXPECT_THROW([&] {
|
||||
return s.dot(std::initializer_list<wire64>{}, std::initializer_list<wire64>{});
|
||||
}(), std::invalid_argument);
|
||||
EXPECT_THROW((void)[&] { return s.dot({x}, {y, z}); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return s.dot({x}, {y, z}); }(), std::invalid_argument);
|
||||
session64 other;
|
||||
auto w = other.input();
|
||||
EXPECT_THROW((void)[&] { return s.dot({x}, {w}); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return s.dot({x}, {w}); }(), std::invalid_argument);
|
||||
auto dotted = s.dot({x}, {y});
|
||||
EXPECT_THROW((void)[&] { return s.dot_cross(dotted); }(), std::logic_error);
|
||||
EXPECT_THROW((void)[&] { return s.dot_cross(x); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return s.dot_cross(dotted); }(), std::logic_error);
|
||||
EXPECT_THROW([&] { return s.dot_cross(x); }(), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Beaver, ScaleSharesScalarBlind)
|
||||
|
|
@ -1531,7 +1565,7 @@ TEST(Beaver, FactoredProductIsVisibleAndTheSexticTermIsNot)
|
|||
EXPECT_EQ(s.preprocessing_count(), prep);
|
||||
EXPECT_EQ(s.monomial({{x, 1u}, {z, 1u}}).open(),
|
||||
s.lambda(x).open() * s.lambda(z).open());
|
||||
EXPECT_THROW((void)[&] { return s.monomial({{x, 2u}, {z, 2u}}); }(),
|
||||
EXPECT_THROW([&] { return s.monomial({{x, 2u}, {z, 2u}}); }(),
|
||||
std::logic_error);
|
||||
s.bind(x, u64{6}, rng);
|
||||
s.bind(z, u64{7}, rng);
|
||||
|
|
@ -1909,8 +1943,8 @@ TEST(Beaver, HighPowersAndTheExpansionLimit)
|
|||
|
||||
session64 s;
|
||||
auto x = s.input();
|
||||
EXPECT_THROW((void)[&] { return pow(x, 17u); }(), std::invalid_argument);
|
||||
EXPECT_THROW((void)[&] { return pow(x, 10) * pow(x, 7); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return pow(x, 17u); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return pow(x, 10) * pow(x, 7); }(), std::invalid_argument);
|
||||
|
||||
std::vector<wire64> wide;
|
||||
wide.reserve(12);
|
||||
|
|
@ -1929,7 +1963,7 @@ TEST(Beaver, HighPowersAndTheExpansionLimit)
|
|||
EXPECT_EQ(s.open(all), 1u);
|
||||
|
||||
auto thirteenth = s.input();
|
||||
EXPECT_THROW((void)[&] { return s(expr12 * thirteenth); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return s(expr12 * thirteenth); }(), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Beaver, ScheduledPolynomialRejectsEarlyUse)
|
||||
|
|
@ -1938,8 +1972,8 @@ TEST(Beaver, ScheduledPolynomialRejectsEarlyUse)
|
|||
session64 b;
|
||||
auto x = a.input();
|
||||
auto y = b.input();
|
||||
EXPECT_THROW((void)[&] { return x + y; }(), std::invalid_argument);
|
||||
EXPECT_THROW((void)[&] { return x * y; }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return x + y; }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return x * y; }(), std::invalid_argument);
|
||||
auto z = a(x + pow(x, 2));
|
||||
EXPECT_THROW(a.open(z), std::logic_error);
|
||||
EXPECT_THROW(a.evaluate(), std::logic_error);
|
||||
|
|
@ -1959,15 +1993,15 @@ TEST(Beaver, RejectsBadUse)
|
|||
dpf::beavers::session<u64> b;
|
||||
auto x = a.input();
|
||||
auto y = b.input();
|
||||
EXPECT_THROW((void)[&] { return a.product(x, y); }(), std::invalid_argument);
|
||||
EXPECT_THROW((void)[&] { return x * y; }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return a.product(x, y); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return x * y; }(), std::invalid_argument);
|
||||
auto bit = a.bit();
|
||||
EXPECT_THROW(a.bind(bit, u64{2}), std::invalid_argument);
|
||||
auto z = a.product(x, x);
|
||||
EXPECT_THROW(a.evaluate(), std::logic_error);
|
||||
a.sample();
|
||||
EXPECT_THROW(a.evaluate(), std::logic_error);
|
||||
EXPECT_THROW((void)[&] { return a.bit_mul(x, x); }(), std::invalid_argument);
|
||||
EXPECT_THROW([&] { return a.bit_mul(x, x); }(), std::invalid_argument);
|
||||
(void)z;
|
||||
}
|
||||
|
||||
|
|
@ -1993,3 +2027,178 @@ TEST(Beaver, ProductExtremes)
|
|||
EXPECT_EQ(s.open(z), want) << a << " * " << b;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BeaverAuth, AuthBeaver2HonestAndTamper)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
auto t = dpf::beavers::sample_auth_beaver2<u64>(key);
|
||||
EXPECT_TRUE(t.verify(key));
|
||||
EXPECT_EQ(t.ab.open(), t.a.open() * t.b.open());
|
||||
|
||||
auto bad = t;
|
||||
bad.ab.tag.p0 ^= 1ull;
|
||||
EXPECT_FALSE(bad.verify(key));
|
||||
}
|
||||
|
||||
TEST(BeaverAuth, AuthBeaverMulMatchesProduct)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
auto bev = dpf::beavers::sample_auth_beaver2<u64>(key);
|
||||
const u64 x = 7, y = 11;
|
||||
auto xs = dpf::beavers::auth_share(x, key);
|
||||
auto ys = dpf::beavers::auth_share(y, key);
|
||||
auto [z0, z1] = dpf::beavers::auth_beaver_mul(
|
||||
xs.party(0), xs.party(1), ys.party(0), ys.party(1), bev, key);
|
||||
EXPECT_EQ(z0.value + z1.value, x * y);
|
||||
EXPECT_TRUE(dpf::mac_verify(z0, z1, key));
|
||||
|
||||
z0.tag ^= 1ull;
|
||||
EXPECT_FALSE(dpf::mac_verify(z0, z1, key));
|
||||
}
|
||||
|
||||
TEST(BeaverAuth, SessionAby2ValuesVerify)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
session64 s;
|
||||
s.set_mac_key(key);
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s(x * y);
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
s.bind(x, u64{6}, rng);
|
||||
s.bind(y, u64{7}, rng);
|
||||
s.evaluate();
|
||||
EXPECT_EQ(s.open(z), 42u);
|
||||
EXPECT_TRUE(s.lambda_auth(x).verify(key));
|
||||
EXPECT_TRUE(s.value_auth(x).verify(key));
|
||||
EXPECT_TRUE(s.value_auth(z).verify(key));
|
||||
EXPECT_TRUE(s.verify_delta(x));
|
||||
EXPECT_TRUE(s.verify_delta(y));
|
||||
EXPECT_TRUE(s.verify_all());
|
||||
|
||||
auto tampered = s.value_auth(z);
|
||||
tampered.tag.p0 ^= 1ull;
|
||||
EXPECT_FALSE(tampered.verify(key));
|
||||
}
|
||||
|
||||
TEST(BeaverAuth, PartyTapeCarriesTagsAndOpeningsCheck)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
session64 dealer;
|
||||
dealer.set_mac_key(key);
|
||||
auto x = dealer.input();
|
||||
auto y = dealer.input();
|
||||
auto z = dealer(x * y);
|
||||
Counter rng;
|
||||
dealer.sample(rng);
|
||||
auto tape0 = dealer.export_party(0);
|
||||
auto tape1 = dealer.export_party(1);
|
||||
EXPECT_TRUE(tape0.has_mac);
|
||||
EXPECT_TRUE(tape1.has_mac);
|
||||
EXPECT_EQ(tape0.lambda_tag.size(), tape0.lambda.size());
|
||||
EXPECT_EQ(tape0.lambda[0] + tape1.lambda[0], dealer.lambda(x).open());
|
||||
EXPECT_EQ(tape0.lambda_tag[0] + tape1.lambda_tag[0],
|
||||
dealer.lambda_auth(x).tag.open());
|
||||
|
||||
dealer.bind(x, u64{3}, rng);
|
||||
dealer.bind(y, u64{5}, rng);
|
||||
dealer.evaluate();
|
||||
EXPECT_EQ(dealer.open(z), 15u);
|
||||
EXPECT_TRUE(dealer.verify_all());
|
||||
|
||||
auto honest = dealer.delta_auth(x);
|
||||
dpf::beavers::auth_opening<u64> a{honest.value.p0, honest.tag.p0};
|
||||
dpf::beavers::auth_opening<u64> b{honest.value.p1, honest.tag.p1};
|
||||
EXPECT_TRUE(dpf::beavers::verify_auth_opening(a, b, key));
|
||||
b.value ^= 1ull;
|
||||
EXPECT_FALSE(dpf::beavers::verify_auth_opening(a, b, key));
|
||||
|
||||
// Party views: authenticated input shares + λ tags reconstruct δ.
|
||||
auto xv = dealer.value_auth(x);
|
||||
dpf::beavers::auth_opening<u64> ox0{
|
||||
xv.party(0).value + tape0.lambda[0],
|
||||
xv.party(0).tag + tape0.lambda_tag[0]};
|
||||
dpf::beavers::auth_opening<u64> ox1{
|
||||
xv.party(1).value + tape1.lambda[0],
|
||||
xv.party(1).tag + tape1.lambda_tag[0]};
|
||||
EXPECT_TRUE(dpf::beavers::verify_auth_opening(ox0, ox1, key));
|
||||
EXPECT_EQ(ox0.value + ox1.value, dealer.delta(x));
|
||||
}
|
||||
|
||||
TEST(Beaver, OracleAuthTagsReplayFromTheSeed)
|
||||
{
|
||||
using block = dpf::prg::aes128::block_type;
|
||||
const block seed = simde_mm_set_epi64x(0x51, 0x52);
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
dpf::beavers::oracle<u64> left(seed, 4);
|
||||
dpf::beavers::oracle<u64> right(seed, 4);
|
||||
auto a = dpf::beavers::sample_auth_beaver2(key, left, 3);
|
||||
auto b = dpf::beavers::sample_auth_beaver2(key, right, 3);
|
||||
EXPECT_EQ(a.a, b.a);
|
||||
EXPECT_EQ(a.b, b.b);
|
||||
EXPECT_EQ(a.ab, b.ab);
|
||||
EXPECT_EQ(a.out, b.out);
|
||||
EXPECT_TRUE(a.verify(key));
|
||||
EXPECT_EQ(a.ab.open(), a.a.open() * a.b.open());
|
||||
|
||||
dpf::beavers::session<u64> session;
|
||||
session.set_mac_key(key);
|
||||
auto x = session.input();
|
||||
auto y = session.input();
|
||||
auto z = session(x * y);
|
||||
session.pin(z);
|
||||
session.sample_from(left, 3);
|
||||
EXPECT_TRUE(session.lambda_auth(x).verify(key));
|
||||
EXPECT_TRUE(session.monomial_auth({{x, 1u}, {y, 1u}}).verify(key));
|
||||
EXPECT_EQ(session.lambda(x), a.a.value);
|
||||
}
|
||||
|
||||
TEST(Beaver, OracleShapesOpen)
|
||||
{
|
||||
using block = dpf::prg::aes128::block_type;
|
||||
const block seed = simde_mm_set_epi64x(0x71, 0x72);
|
||||
dpf::beavers::oracle<u64> src(seed, 8);
|
||||
auto p3 = dpf::beavers::sample_beaver3(src, 1);
|
||||
EXPECT_EQ(p3.abc.open(), p3.a.open() * p3.b.open() * p3.c.open());
|
||||
auto sq = dpf::beavers::sample_square(src, 2);
|
||||
EXPECT_EQ(sq.x2.open(), sq.x.open() * sq.x.open());
|
||||
auto sc = dpf::beavers::sample_scale(2, src, 4);
|
||||
EXPECT_EQ(sc.cross[1].open(), sc.scalar.open() * sc.lanes[1].open());
|
||||
auto fresh = dpf::beavers::sample_fresh<3, u64>(src, 5);
|
||||
EXPECT_EQ(fresh.subset[(1u << 3) - 2].open(),
|
||||
fresh.in[0].open() * fresh.in[1].open() * fresh.in[2].open());
|
||||
}
|
||||
|
||||
TEST(Beaver, PrgPadReplaysDsGadgets)
|
||||
{
|
||||
using block = dpf::prg::aes128::block_type;
|
||||
const block seed = simde_mm_set_epi64x(0x81, 0x82);
|
||||
dpf::prg_pad_rng<> left(seed);
|
||||
dpf::prg_pad_rng<> right(seed);
|
||||
const auto a = dpf::detail::ds_sample_bit_and(left);
|
||||
const auto b = dpf::detail::ds_sample_bit_and(right);
|
||||
EXPECT_EQ(a.a0, b.a0);
|
||||
EXPECT_EQ(a.c1, b.c1);
|
||||
const auto abit = static_cast<std::uint8_t>(a.a0 ^ a.a1);
|
||||
const auto bbit = static_cast<std::uint8_t>(a.b0 ^ a.b1);
|
||||
const auto cbit = static_cast<std::uint8_t>(a.c0 ^ a.c1);
|
||||
EXPECT_EQ(cbit, static_cast<std::uint8_t>(abit & bbit));
|
||||
|
||||
dpf::prg_pad_rng<> cleft(seed);
|
||||
dpf::prg_pad_rng<> cright(seed);
|
||||
const auto cw0 = dpf::detail::ds_sample_cw(cleft);
|
||||
const auto cw1 = dpf::detail::ds_sample_cw(cright);
|
||||
EXPECT_EQ(std::memcmp(&cw0.p0.rand, &cw1.p0.rand, sizeof(cw0.p0.rand)), 0);
|
||||
EXPECT_EQ(std::memcmp(&cw0.p1.gamma, &cw1.p1.gamma, sizeof(cw0.p1.gamma)), 0);
|
||||
const auto prod = dpf::detail::ds_xor(
|
||||
(cw0.p1.bit & 1u) ? cw0.p0.rand : simde_mm_setzero_si128(),
|
||||
(cw0.p0.bit & 1u) ? cw0.p1.rand : simde_mm_setzero_si128());
|
||||
const auto got = dpf::detail::ds_xor(cw0.p0.gamma, cw0.p1.gamma);
|
||||
EXPECT_EQ(std::memcmp(&got, &prod, sizeof(got)), 0);
|
||||
|
||||
dpf::detail::urandom_pad_rng pad;
|
||||
const auto shares = dpf::beavers::sample_bit_arith(pad);
|
||||
EXPECT_EQ(shares.add0 + shares.add1,
|
||||
static_cast<std::uint64_t>(shares.xor0 ^ shares.xor1));
|
||||
}
|
||||
|
|
|
|||
45
test/tests/bench_cell_test.cpp
Normal file
45
test/tests/bench_cell_test.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/bench_cells.hpp"
|
||||
#include "dpf/party_runner.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void drive(const char * name, dpf::bench::kind id, int parties)
|
||||
{
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = dpf::net::transport::async_memory;
|
||||
cfg.n_lanes = 1;
|
||||
std::vector<dpf::protocol::plan> plans;
|
||||
for (int p = 0; p < parties; ++p)
|
||||
plans.push_back(dpf::bench::plan_for(static_cast<std::size_t>(p), id));
|
||||
ASSERT_GT(plans[0].rounds(), 0u);
|
||||
std::vector<dpf::app::party_values> values(plans.size());
|
||||
auto result = dpf::app::run_parties(plans, values, {}, cfg, nullptr,
|
||||
&dpf::bench::run_cell);
|
||||
EXPECT_GT(result.party0_wall_ns, 0u);
|
||||
ASSERT_FALSE(result.wire.empty());
|
||||
EXPECT_GT(result.wire[0].payload_out, 0u);
|
||||
(void)name;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BenchCells, ProjectionCrossesTheMesh)
|
||||
{
|
||||
drive("arith_proj_m5", dpf::bench::kind::proj5, 2);
|
||||
}
|
||||
|
||||
TEST(BenchCells, ShuffleCrossesTheRing)
|
||||
{
|
||||
drive("shuffle_n16", dpf::bench::kind::shuf16, 3);
|
||||
}
|
||||
|
||||
TEST(BenchCells, FluteAndStackCrossTheMesh)
|
||||
{
|
||||
drive("flute_d2", dpf::bench::kind::flute2, 2);
|
||||
drive("yao_if_4_2", dpf::bench::kind::yao_if4, 2);
|
||||
}
|
||||
393
test/tests/bitmore_mod_test.cpp
Normal file
393
test/tests/bitmore_mod_test.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <unsigned M, unsigned LaneBits = 8>
|
||||
struct scalar_acc
|
||||
{
|
||||
using mod = dpf::bitmore_mod<M, LaneBits>;
|
||||
unsigned acc = 0;
|
||||
unsigned bound = 0;
|
||||
|
||||
void add(unsigned x, unsigned addend_max)
|
||||
{
|
||||
x &= mod::slot_max;
|
||||
if (addend_max > mod::slot_max)
|
||||
addend_max = mod::slot_max;
|
||||
for (;;)
|
||||
{
|
||||
if (bound + addend_max <= mod::slot_max)
|
||||
break;
|
||||
if (bound > mod::partial_bound)
|
||||
{
|
||||
acc = mod::partial_reduce_slot(acc);
|
||||
bound = mod::partial_bound;
|
||||
continue;
|
||||
}
|
||||
if (addend_max > mod::partial_bound)
|
||||
{
|
||||
x = mod::partial_reduce_slot(x);
|
||||
addend_max = mod::partial_bound;
|
||||
continue;
|
||||
}
|
||||
if (bound > mod::stable_bound)
|
||||
{
|
||||
acc = mod::partial_reduce_slot(acc);
|
||||
bound = mod::stable_bound;
|
||||
continue;
|
||||
}
|
||||
if (addend_max > mod::stable_bound)
|
||||
{
|
||||
x = mod::partial_reduce_slot(x);
|
||||
addend_max = mod::stable_bound;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
acc += x;
|
||||
bound += addend_max;
|
||||
}
|
||||
|
||||
void insert(unsigned bit)
|
||||
{
|
||||
while (bound > (mod::slot_max >> 1))
|
||||
{
|
||||
acc = mod::partial_reduce_slot(acc);
|
||||
bound = bound > mod::partial_bound ? mod::partial_bound : mod::stable_bound;
|
||||
}
|
||||
acc = ((acc << 1) | (bit & 1u)) & mod::slot_max;
|
||||
bound = bound * 2u + 1u;
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned M, typename Reg>
|
||||
void expect_bytes()
|
||||
{
|
||||
using mod = dpf::bitmore_mod<M>;
|
||||
unsigned span = mod::resume_bound + 1u;
|
||||
unsigned k = 0;
|
||||
while ((span << 1) <= 256u)
|
||||
{
|
||||
span <<= 1;
|
||||
++k;
|
||||
}
|
||||
EXPECT_EQ(mod::shift_budget, k);
|
||||
EXPECT_EQ(mod::add_budget, 255u - mod::resume_bound);
|
||||
EXPECT_LE((mod::resume_bound + 1u) * (1u << k) - 1u, 255u);
|
||||
EXPECT_GT(span << 1, 256u);
|
||||
|
||||
constexpr int n = static_cast<int>(sizeof(Reg));
|
||||
for (unsigned base = 0; base < 256u; ++base)
|
||||
{
|
||||
alignas(32) unsigned char in[32]{};
|
||||
alignas(32) unsigned char out[32]{};
|
||||
for (int i = 0; i < n; ++i)
|
||||
in[i] = static_cast<unsigned char>((base + static_cast<unsigned>(i) * 13u) & 255u);
|
||||
Reg x;
|
||||
std::memcpy(&x, in, sizeof(Reg));
|
||||
|
||||
auto partial = mod::partial_reduce(x);
|
||||
std::memcpy(out, &partial, sizeof(Reg));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const unsigned expect = mod::partial_reduce_byte(in[i]);
|
||||
if (out[i] != expect || expect > mod::partial_bound || expect % M != in[i] % M)
|
||||
{
|
||||
ADD_FAILURE() << "partial M=" << M << " in=" << static_cast<int>(in[i])
|
||||
<< " got=" << static_cast<int>(out[i]) << " expect=" << expect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto full = mod::full_reduce(x);
|
||||
std::memcpy(out, &full, sizeof(Reg));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (out[i] != in[i] % M)
|
||||
{
|
||||
ADD_FAILURE() << "full M=" << M << " in=" << static_cast<int>(in[i])
|
||||
<< " got=" << static_cast<int>(out[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <unsigned M, typename Reg>
|
||||
void expect_accumulator()
|
||||
{
|
||||
constexpr int n = static_cast<int>(sizeof(Reg));
|
||||
dpf::bitmore_accumulator<M, Reg> acc;
|
||||
scalar_acc<M> slots[32]{};
|
||||
unsigned math[32]{};
|
||||
|
||||
for (int step = 0; step < 96; ++step)
|
||||
{
|
||||
alignas(32) unsigned char bytes[32]{};
|
||||
Reg packed;
|
||||
if (step % 3 == 0)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
bytes[i] = static_cast<unsigned char>(((step * 17 + i * 3) & 1u) | (i & 0xf0));
|
||||
slots[i].insert(bytes[i]);
|
||||
math[i] = (math[i] * 2u + (bytes[i] & 1u)) % M;
|
||||
}
|
||||
std::memcpy(&packed, bytes, sizeof(Reg));
|
||||
acc.insert_bit(packed);
|
||||
}
|
||||
else if (step % 3 == 1)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
bytes[i] = static_cast<unsigned char>((step + i) % M);
|
||||
slots[i].add(bytes[i], M - 1u);
|
||||
math[i] = (math[i] + bytes[i]) % M;
|
||||
}
|
||||
std::memcpy(&packed, bytes, sizeof(Reg));
|
||||
acc.add(packed, M - 1u);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
bytes[i] = static_cast<unsigned char>(200u + (i & 15u));
|
||||
slots[i].add(bytes[i], 255u);
|
||||
math[i] = (math[i] + bytes[i]) % M;
|
||||
}
|
||||
std::memcpy(&packed, bytes, sizeof(Reg));
|
||||
acc.add(packed, 255u);
|
||||
}
|
||||
|
||||
if (acc.bound() != slots[0].bound || acc.bound() > 255u)
|
||||
{
|
||||
ADD_FAILURE() << "bound M=" << M << " step=" << step
|
||||
<< " simd=" << acc.bound() << " scalar=" << slots[0].bound;
|
||||
return;
|
||||
}
|
||||
|
||||
alignas(32) unsigned char raw[32]{};
|
||||
const auto value = acc.value();
|
||||
std::memcpy(raw, &value, sizeof(Reg));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (raw[i] != slots[i].acc || raw[i] > acc.bound() || raw[i] % M != math[i])
|
||||
{
|
||||
ADD_FAILURE() << "lane M=" << M << " step=" << step << " i=" << i
|
||||
<< " got=" << static_cast<int>(raw[i])
|
||||
<< " expect=" << slots[i].acc
|
||||
<< " math=" << math[i] << " bound=" << acc.bound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alignas(32) unsigned char red[32]{};
|
||||
const auto reduced = acc.reduced();
|
||||
std::memcpy(red, &reduced, sizeof(Reg));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (red[i] != math[i])
|
||||
{
|
||||
ADD_FAILURE() << "reduced M=" << M << " i=" << i
|
||||
<< " got=" << static_cast<int>(red[i])
|
||||
<< " expect=" << math[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <unsigned M>
|
||||
void expect_all()
|
||||
{
|
||||
expect_bytes<M, simde__m128i>();
|
||||
expect_bytes<M, simde__m256i>();
|
||||
if constexpr (M <= 128u)
|
||||
{
|
||||
expect_accumulator<M, simde__m128i>();
|
||||
expect_accumulator<M, simde__m256i>();
|
||||
}
|
||||
if constexpr (M < 255u)
|
||||
expect_all<M + 1u>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <unsigned M, typename Reg>
|
||||
void expect_epi16_slots()
|
||||
{
|
||||
using mod = dpf::bitmore_mod<M, 16>;
|
||||
constexpr int lanes = static_cast<int>(sizeof(Reg) / 2u);
|
||||
unsigned span = mod::resume_bound + 1u;
|
||||
unsigned k = 0;
|
||||
const unsigned half = (mod::slot_max + 1u) >> 1;
|
||||
while (span <= half)
|
||||
{
|
||||
span *= 2u;
|
||||
++k;
|
||||
}
|
||||
EXPECT_EQ(mod::shift_budget, k);
|
||||
EXPECT_EQ(mod::add_budget, mod::slot_max - mod::resume_bound);
|
||||
|
||||
for (unsigned base = 0; base < 65536u; base += static_cast<unsigned>(lanes))
|
||||
{
|
||||
alignas(32) std::uint16_t in[16]{};
|
||||
alignas(32) std::uint16_t out[16]{};
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
in[i] = static_cast<std::uint16_t>(base + static_cast<unsigned>(i));
|
||||
Reg x;
|
||||
std::memcpy(&x, in, sizeof(Reg));
|
||||
auto partial = mod::partial_reduce(x);
|
||||
std::memcpy(out, &partial, sizeof(Reg));
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
const unsigned expect = mod::partial_reduce_slot(in[i]);
|
||||
if (out[i] != expect || expect > mod::partial_bound || expect % M != in[i] % M)
|
||||
{
|
||||
ADD_FAILURE() << "partial16 M=" << M << " in=" << in[i]
|
||||
<< " got=" << out[i] << " expect=" << expect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto full = mod::full_reduce(x);
|
||||
std::memcpy(out, &full, sizeof(Reg));
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
if (out[i] != in[i] % M)
|
||||
{
|
||||
ADD_FAILURE() << "full16 M=" << M << " in=" << in[i] << " got=" << out[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <unsigned M, typename Reg>
|
||||
void expect_epi16_accumulator()
|
||||
{
|
||||
constexpr int lanes = static_cast<int>(sizeof(Reg) / 2u);
|
||||
dpf::bitmore_accumulator<M, Reg, 16> acc;
|
||||
scalar_acc<M, 16> slots[16]{};
|
||||
unsigned math[16]{};
|
||||
|
||||
for (int step = 0; step < 64; ++step)
|
||||
{
|
||||
alignas(32) std::uint16_t words[16]{};
|
||||
Reg packed;
|
||||
if (step % 3 == 0)
|
||||
{
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
words[i] = static_cast<std::uint16_t>(((step * 17 + i * 3) & 1) | ((i * 0x1111) & 0xfff0));
|
||||
slots[i].insert(words[i]);
|
||||
math[i] = (math[i] * 2u + (words[i] & 1u)) % M;
|
||||
}
|
||||
std::memcpy(&packed, words, sizeof(Reg));
|
||||
acc.insert_bit(packed);
|
||||
}
|
||||
else if (step % 3 == 1)
|
||||
{
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
words[i] = static_cast<std::uint16_t>((step * 100 + i * 17) % M);
|
||||
slots[i].add(words[i], M - 1u);
|
||||
math[i] = (math[i] + words[i]) % M;
|
||||
}
|
||||
std::memcpy(&packed, words, sizeof(Reg));
|
||||
acc.add(packed, M - 1u);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
words[i] = static_cast<std::uint16_t>(40000u + static_cast<unsigned>(i) * 97u);
|
||||
slots[i].add(words[i], 65535u);
|
||||
math[i] = (math[i] + words[i]) % M;
|
||||
}
|
||||
std::memcpy(&packed, words, sizeof(Reg));
|
||||
acc.add(packed, 65535u);
|
||||
}
|
||||
|
||||
if (acc.bound() != slots[0].bound || acc.bound() > 65535u)
|
||||
{
|
||||
ADD_FAILURE() << "bound16 M=" << M << " step=" << step
|
||||
<< " simd=" << acc.bound() << " scalar=" << slots[0].bound;
|
||||
return;
|
||||
}
|
||||
|
||||
alignas(32) std::uint16_t raw[16]{};
|
||||
const auto value = acc.value();
|
||||
std::memcpy(raw, &value, sizeof(Reg));
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
if (raw[i] != slots[i].acc || raw[i] > acc.bound() || raw[i] % M != math[i])
|
||||
{
|
||||
ADD_FAILURE() << "lane16 M=" << M << " step=" << step << " i=" << i
|
||||
<< " got=" << raw[i] << " expect=" << slots[i].acc
|
||||
<< " math=" << math[i] << " bound=" << acc.bound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alignas(32) std::uint16_t red[16]{};
|
||||
const auto reduced = acc.reduced();
|
||||
std::memcpy(red, &reduced, sizeof(Reg));
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
{
|
||||
if (red[i] != math[i])
|
||||
{
|
||||
ADD_FAILURE() << "reduced16 M=" << M << " i=" << i
|
||||
<< " got=" << red[i] << " expect=" << math[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <unsigned M>
|
||||
void expect_wide()
|
||||
{
|
||||
expect_epi16_slots<M, simde__m128i>();
|
||||
expect_epi16_slots<M, simde__m256i>();
|
||||
if constexpr (M <= 32768u)
|
||||
{
|
||||
expect_epi16_accumulator<M, simde__m128i>();
|
||||
expect_epi16_accumulator<M, simde__m256i>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BitmoreMod, BytesAndBudget)
|
||||
{
|
||||
expect_all<2>();
|
||||
}
|
||||
|
||||
TEST(BitmoreMod, Epi16)
|
||||
{
|
||||
expect_wide<2>();
|
||||
expect_wide<3>();
|
||||
expect_wide<15>();
|
||||
expect_wide<127>();
|
||||
expect_wide<128>();
|
||||
expect_wide<255>();
|
||||
expect_wide<256>();
|
||||
expect_wide<257>();
|
||||
expect_wide<4095>();
|
||||
expect_wide<4096>();
|
||||
expect_wide<4097>();
|
||||
expect_wide<16384>();
|
||||
expect_wide<16385>();
|
||||
expect_wide<32765>();
|
||||
expect_wide<32767>();
|
||||
expect_wide<32768>();
|
||||
expect_wide<32769>();
|
||||
expect_wide<40000>();
|
||||
expect_wide<61440>();
|
||||
expect_wide<65535>();
|
||||
}
|
||||
109
test/tests/blob_leaf_test.cpp
Normal file
109
test/tests/blob_leaf_test.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <std::size_t N>
|
||||
dpf::blob<N> make_blob_pattern(unsigned char seed)
|
||||
{
|
||||
dpf::blob<N> b{};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
b.bytes[i] = static_cast<unsigned char>(seed + static_cast<unsigned char>(i));
|
||||
return b;
|
||||
}
|
||||
|
||||
template <std::size_t N, typename InputT>
|
||||
void expect_point_opens(InputT alpha, const dpf::blob<N> & beta)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto open = [&](InputT x) {
|
||||
return dpf::reconstruct(*dpf::eval_point(k0, x),
|
||||
*dpf::eval_point(k1, x));
|
||||
};
|
||||
|
||||
const auto s0 = *dpf::eval_point(k0, alpha);
|
||||
const auto s1 = *dpf::eval_point(k1, alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), beta);
|
||||
EXPECT_EQ(open(alpha), beta);
|
||||
|
||||
const dpf::blob<N> zero{};
|
||||
const std::vector<InputT> offs = {
|
||||
InputT{0},
|
||||
InputT{1},
|
||||
static_cast<InputT>(alpha + 1),
|
||||
static_cast<InputT>(alpha ^ InputT{1}),
|
||||
};
|
||||
for (InputT x : offs)
|
||||
{
|
||||
if (x == alpha)
|
||||
continue;
|
||||
EXPECT_EQ(open(x), zero) << "off-point";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BlobLeaf, PointEvalUint32VariousN)
|
||||
{
|
||||
const std::uint32_t alpha = 0x00ab12cdu;
|
||||
expect_point_opens<1>(alpha, make_blob_pattern<1>(0x11));
|
||||
expect_point_opens<16>(alpha, make_blob_pattern<16>(0x22));
|
||||
expect_point_opens<100>(alpha, make_blob_pattern<100>(0x33));
|
||||
expect_point_opens<1000>(alpha, make_blob_pattern<1000>(0x44));
|
||||
}
|
||||
|
||||
TEST(BlobLeaf, PointEvalUint128)
|
||||
{
|
||||
using input_t = simde_uint128;
|
||||
const input_t alpha = (input_t{1} << 100) | input_t{0x55aau};
|
||||
expect_point_opens<16>(alpha, make_blob_pattern<16>(0x7a));
|
||||
expect_point_opens<100>(alpha, make_blob_pattern<100>(0x8b));
|
||||
}
|
||||
|
||||
TEST(BlobLeaf, FullDomainMatchesPointShareForShare)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
using blob_t = dpf::blob<100>;
|
||||
const input_t alpha = 17;
|
||||
const blob_t beta = make_blob_pattern<100>(0x9c);
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto full0 = dpf::eval_full(k0);
|
||||
auto full1 = dpf::eval_full(k1);
|
||||
auto & it0 = full0.second;
|
||||
auto & it1 = full1.second;
|
||||
|
||||
auto a = std::begin(it0);
|
||||
auto b = std::begin(it1);
|
||||
for (std::size_t i = 0; a != std::end(it0) && b != std::end(it1);
|
||||
++a, ++b, ++i)
|
||||
{
|
||||
const auto p0 = *dpf::eval_point(k0, static_cast<input_t>(i));
|
||||
const auto p1 = *dpf::eval_point(k1, static_cast<input_t>(i));
|
||||
EXPECT_EQ(dpf::reconstruct(*a, *b), dpf::reconstruct(p0, p1)) << i;
|
||||
EXPECT_EQ(dpf::detail_walk::group_value(*a),
|
||||
dpf::detail_walk::group_value(p0)) << "party0 @" << i;
|
||||
EXPECT_EQ(dpf::detail_walk::group_value(*b),
|
||||
dpf::detail_walk::group_value(p1)) << "party1 @" << i;
|
||||
}
|
||||
EXPECT_EQ(std::size_t(std::distance(std::begin(it0), std::end(it0))), 256u);
|
||||
}
|
||||
|
||||
TEST(BlobLeaf, Domain128FullEvalThrows)
|
||||
{
|
||||
using input_t = simde_uint128;
|
||||
using blob_t = dpf::blob<16>;
|
||||
const input_t alpha = input_t{1} << 90;
|
||||
const blob_t beta = make_blob_pattern<16>(1);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
(void)k1;
|
||||
EXPECT_THROW((void)dpf::eval_full(k0), std::exception);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/blocked_dcf.hpp"
|
||||
|
|
@ -422,14 +423,18 @@ TEST(BlockedDcf, WideCheckpointFrontierMatchesDense)
|
|||
TEST(BlockedDcf, PathRecipesStayOnThePerLevelChannel)
|
||||
{
|
||||
const uint8_t alpha = 0x3C;
|
||||
EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<4>(dpf::lcp(uint64_t{1}))),
|
||||
EXPECT_THROW(
|
||||
dpf::make_dpf(alpha, dpf::block_width<4>(dpf::lcp(uint64_t{1}))),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<4>(dpf::break_bit(uint64_t{3}))),
|
||||
EXPECT_THROW(
|
||||
dpf::make_dpf(alpha, dpf::block_width<4>(dpf::break_bit(uint64_t{3}))),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_dpf(alpha,
|
||||
EXPECT_THROW(
|
||||
dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::prefix_with_length<4>(uint64_t{1}))),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_dpf(alpha, dpf::block_width<2>(dpf::path_paint(
|
||||
EXPECT_THROW(
|
||||
dpf::make_dpf(alpha, dpf::block_width<2>(dpf::path_paint(
|
||||
[](std::size_t matched, uint64_t, bool) {
|
||||
return static_cast<uint64_t>(matched);
|
||||
}))),
|
||||
|
|
@ -459,32 +464,34 @@ TEST(BlockedDcf, GrottoPrefixSegmentAndHorner)
|
|||
|
||||
const uint16_t center = 30;
|
||||
auto mat = grotto::make_offset_horner_keys<uint16_t, 1>(center);
|
||||
uint64_t payload[2];
|
||||
payload[0] = 1;
|
||||
payload[1] = center;
|
||||
using bare_pair = decltype(dpf::make_dpf(center, dpf::gt(uint64_t{0})));
|
||||
using pair = decltype(dpf::make_dpf(center,
|
||||
dpf::block_width<4>(dpf::gt(uint64_t{0}))));
|
||||
std::vector<bare_pair> bare_keys{
|
||||
dpf::make_dpf(center, dpf::gt(payload[0])),
|
||||
dpf::make_dpf(center, dpf::gt(payload[1]))};
|
||||
std::vector<pair> keys{
|
||||
dpf::make_dpf(center, dpf::block_width<4>(dpf::gt(payload[0]))),
|
||||
dpf::make_dpf(center, dpf::block_width<4>(dpf::gt(payload[1])))};
|
||||
std::vector<uint16_t> knots{0, 10, 40};
|
||||
std::vector<std::array<uint64_t, 2>> coeff{
|
||||
{1, 0},
|
||||
{2, 3},
|
||||
{4, 1}};
|
||||
const uint16_t eta = 0;
|
||||
const auto b0s = grotto::offset_horner_coefficient_share<0, 1>(
|
||||
bare_keys, mat.wrap_share, knots, coeff, eta);
|
||||
const auto b1s = grotto::offset_horner_coefficient_share<1, 1>(
|
||||
bare_keys, mat.wrap_share, knots, coeff, eta);
|
||||
const auto c0 = grotto::offset_horner_coefficient_share<0, 1>(
|
||||
keys, mat.wrap_share, knots, coeff, eta);
|
||||
const auto c1 = grotto::offset_horner_coefficient_share<1, 1>(
|
||||
keys, mat.wrap_share, knots, coeff, eta);
|
||||
for (std::size_t m = 0; m < 2; ++m)
|
||||
EXPECT_EQ(b0s[m] + b1s[m], c0[m] + c1[m]);
|
||||
const auto p0 = grotto::offset_horner_eval<0, 1>(mat, knots, coeff, eta);
|
||||
const auto p1 = grotto::offset_horner_eval<1, 1>(mat, knots, coeff, eta);
|
||||
EXPECT_EQ(p0 + p1, grotto::offset_horner_clear<1>(center, knots, coeff, eta));
|
||||
}
|
||||
|
||||
TEST(BlockedDcf, VerifiableUint8DomainMatchesThePredicate)
|
||||
{
|
||||
const uint8_t alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::lt(uint64_t{1})), dpf::verifiable{});
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto q = static_cast<uint8_t>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const uint64_t got = recon(dpf::eval_point(dpf::cmp, k0, q, dpf::prove(a)),
|
||||
dpf::eval_point(dpf::cmp, k1, q, dpf::prove(b)))
|
||||
& k0.cmp().mask;
|
||||
EXPECT_EQ(got, q < alpha ? 1u : 0u) << x;
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << x;
|
||||
const uint64_t swapped = recon(dpf::eval_point(dpf::cmp, k1, q),
|
||||
dpf::eval_point(dpf::cmp, k0, q))
|
||||
& k0.cmp().mask;
|
||||
EXPECT_EQ(swapped, got) << x;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
128
test/tests/caller_fold_test.cpp
Normal file
128
test/tests/caller_fold_test.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using input_t = std::uint8_t; // 256-point domain
|
||||
constexpr std::size_t kDomain = 256;
|
||||
|
||||
} // namespace
|
||||
|
||||
// eval_full_add_into(buf, key, fold): the fold sees every written share once,
|
||||
// in the same pass that adds it into the buffer (Express / Prio audit hook).
|
||||
TEST(CallerFold, FullAddIntoFoldSeesEveryShare)
|
||||
{
|
||||
const input_t alpha = 42;
|
||||
const std::uint64_t beta = 0xdeadbeefull;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
|
||||
std::vector<std::uint64_t> buf0(kDomain, 0), buf1(kDomain, 0);
|
||||
std::uint64_t fold0 = 0, fold1 = 0;
|
||||
std::size_t calls0 = 0, calls1 = 0;
|
||||
|
||||
dpf::eval_full_add_into(buf0, k0,
|
||||
[&](std::size_t, std::uint64_t g) { fold0 += g; ++calls0; });
|
||||
dpf::eval_full_add_into(buf1, k1,
|
||||
[&](std::size_t, std::uint64_t g) { fold1 += g; ++calls1; });
|
||||
|
||||
EXPECT_EQ(calls0, kDomain);
|
||||
EXPECT_EQ(calls1, kDomain);
|
||||
|
||||
// The buffer opens to a point function at alpha.
|
||||
for (std::size_t i = 0; i < kDomain; ++i)
|
||||
{
|
||||
const std::uint64_t got = buf0[i] - buf1[i];
|
||||
EXPECT_EQ(got, i == alpha ? beta : 0ull) << "i=" << i;
|
||||
}
|
||||
// The fold accumulated exactly the same shares: sum reconstructs to beta.
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(fold0 - fold1), beta);
|
||||
}
|
||||
|
||||
// eval_full_fold allocates its own buffer and folds each share.
|
||||
TEST(CallerFold, FullFoldReconstructsAudit)
|
||||
{
|
||||
const input_t alpha = 200;
|
||||
const std::uint64_t beta = 7;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
|
||||
// Weighted fold: sum_i (i+1) * share_i. Reconstructs to (alpha+1)*beta.
|
||||
std::uint64_t w0 = 0, w1 = 0;
|
||||
(void)dpf::eval_full_fold(k0,
|
||||
[&](std::size_t i, std::uint64_t g) { w0 += (i + 1) * g; });
|
||||
(void)dpf::eval_full_fold(k1,
|
||||
[&](std::size_t i, std::uint64_t g) { w1 += (i + 1) * g; });
|
||||
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(w0 - w1),
|
||||
static_cast<std::uint64_t>((alpha + 1) * beta));
|
||||
}
|
||||
|
||||
// eval_point_fold calls the fold exactly once with the written share.
|
||||
TEST(CallerFold, PointFoldCalledOnce)
|
||||
{
|
||||
const input_t alpha = 5;
|
||||
const std::uint64_t beta = 99;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
|
||||
std::uint64_t seen0 = 0, seen1 = 0;
|
||||
int calls0 = 0, calls1 = 0;
|
||||
auto o0 = dpf::eval_point_fold(k0, alpha,
|
||||
[&](std::size_t, std::uint64_t g) { seen0 = g; ++calls0; });
|
||||
auto o1 = dpf::eval_point_fold(k1, alpha,
|
||||
[&](std::size_t, std::uint64_t g) { seen1 = g; ++calls1; });
|
||||
|
||||
EXPECT_EQ(calls0, 1);
|
||||
EXPECT_EQ(calls1, 1);
|
||||
// What the fold saw equals what eval_point returned.
|
||||
EXPECT_EQ(seen0, static_cast<std::uint64_t>((*o0).raw()));
|
||||
EXPECT_EQ(seen1, static_cast<std::uint64_t>((*o1).raw()));
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(seen0 - seen1), beta);
|
||||
}
|
||||
|
||||
// The fold is generic over the leaf group: a blob<N> row folds by XOR.
|
||||
TEST(CallerFold, FoldOverBlobLeaf)
|
||||
{
|
||||
using blob_t = dpf::blob<24>;
|
||||
const input_t alpha = 17;
|
||||
blob_t beta{};
|
||||
for (std::size_t i = 0; i < blob_t::size; ++i)
|
||||
beta.bytes[i] = static_cast<unsigned char>(0x30 + i);
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
std::vector<blob_t> buf0(kDomain), buf1(kDomain);
|
||||
|
||||
blob_t x0{}, x1{};
|
||||
std::size_t calls = 0;
|
||||
dpf::eval_full_add_into(buf0, k0,
|
||||
[&](std::size_t, const blob_t & g) { x0 = x0 ^ g; ++calls; });
|
||||
dpf::eval_full_add_into(buf1, k1,
|
||||
[&](std::size_t, const blob_t & g) { x1 = x1 ^ g; });
|
||||
|
||||
EXPECT_EQ(calls, kDomain);
|
||||
// XOR of all shares reconstructs to beta (only alpha is nonzero).
|
||||
blob_t recon{};
|
||||
for (std::size_t i = 0; i < blob_t::size; ++i)
|
||||
recon.bytes[i] = static_cast<unsigned char>(x0.bytes[i] ^ x1.bytes[i]);
|
||||
EXPECT_EQ(recon, beta);
|
||||
}
|
||||
|
||||
// eval_sequence_xor: keyword PIR without materializing the bit vector.
|
||||
TEST(CallerFold, SequenceXorMatchesRecordAtAlpha)
|
||||
{
|
||||
const input_t alpha = 123;
|
||||
// Point function with a bit payload (1 at alpha).
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
|
||||
std::vector<std::uint64_t> records(kDomain);
|
||||
for (std::size_t i = 0; i < kDomain; ++i)
|
||||
records[i] = 0x1000ull * (i + 1) + 7;
|
||||
|
||||
const std::uint64_t acc0 = dpf::eval_sequence_xor(k0, records);
|
||||
const std::uint64_t acc1 = dpf::eval_sequence_xor(k1, records);
|
||||
|
||||
EXPECT_EQ(acc0 ^ acc1, records[alpha]);
|
||||
}
|
||||
597
test/tests/carry_test.cpp
Normal file
597
test/tests/carry_test.cpp
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/carry_plan.hpp"
|
||||
#include "grotto/carry.hpp"
|
||||
#include "grotto/prefix_parity.hpp"
|
||||
|
||||
using grotto::carry_mode;
|
||||
using grotto::sign_knowledge;
|
||||
|
||||
TEST(CarryPlan, TruncateReduceOnlyLowLt)
|
||||
{
|
||||
const auto r = grotto::plan_carry_in(8, 3);
|
||||
EXPECT_TRUE(r.use_low_lt);
|
||||
EXPECT_FALSE(r.use_msb_lt);
|
||||
EXPECT_FALSE(r.use_share_msb_and);
|
||||
EXPECT_FALSE(r.use_biased_wrap);
|
||||
EXPECT_EQ(r.out_n, 5u);
|
||||
}
|
||||
|
||||
TEST(CarryPlan, SameRingKnownSignIsAnd)
|
||||
{
|
||||
const auto pos = grotto::plan_carry_out(8, 2, sign_knowledge::nonnegative);
|
||||
EXPECT_TRUE(pos.use_share_msb_and);
|
||||
EXPECT_FALSE(pos.and_is_nor);
|
||||
EXPECT_FALSE(pos.use_msb_lt);
|
||||
EXPECT_EQ(pos.and_unit, std::int64_t{1} << 6);
|
||||
|
||||
const auto neg = grotto::plan_carry_out(8, 2, sign_knowledge::negative);
|
||||
EXPECT_TRUE(neg.use_share_msb_and);
|
||||
EXPECT_TRUE(neg.and_is_nor);
|
||||
EXPECT_EQ(neg.and_unit, -(std::int64_t{1} << 6));
|
||||
}
|
||||
|
||||
TEST(CarryPlan, SameRingSecretSignNeedsBoth)
|
||||
{
|
||||
const auto r = grotto::plan_carry_out(8, 2, sign_knowledge::unknown);
|
||||
EXPECT_TRUE(r.use_msb_lt);
|
||||
EXPECT_TRUE(r.use_share_msb_and);
|
||||
}
|
||||
|
||||
TEST(CarryPlan, FusedReducedRingDropsSign)
|
||||
{
|
||||
const auto r = grotto::plan_carry_fused(8, 3, 5, sign_knowledge::unknown);
|
||||
EXPECT_TRUE(r.use_low_lt);
|
||||
EXPECT_FALSE(r.use_msb_lt);
|
||||
EXPECT_FALSE(r.use_share_msb_and);
|
||||
}
|
||||
|
||||
TEST(CarryPlan, ExtendIsBiasedWrap)
|
||||
{
|
||||
const auto r = grotto::plan_carry(grotto::carry_request{
|
||||
8, 0, 16, carry_mode::extend, sign_knowledge::nonnegative});
|
||||
EXPECT_TRUE(r.use_biased_wrap);
|
||||
EXPECT_FALSE(r.use_share_msb_and);
|
||||
EXPECT_EQ(r.wrap_payload, std::int64_t{1} << 8);
|
||||
}
|
||||
|
||||
TEST(CarryPlan, WindowPublicCarryZeroDropsEq)
|
||||
{
|
||||
grotto::carry_request req{};
|
||||
req.n = 16;
|
||||
req.s = 4;
|
||||
req.out_n = 4;
|
||||
req.mode = carry_mode::window;
|
||||
req.incoming_carry_public = true;
|
||||
req.incoming_carry_value = 0;
|
||||
const auto r = grotto::plan_carry(req);
|
||||
EXPECT_TRUE(r.use_window_overflow);
|
||||
EXPECT_FALSE(r.use_window_eq);
|
||||
EXPECT_FALSE(r.use_window_product);
|
||||
}
|
||||
|
||||
TEST(CarryPlan, WindowSecretCarryNeedsProduct)
|
||||
{
|
||||
grotto::carry_request req{};
|
||||
req.n = 16;
|
||||
req.s = 4;
|
||||
req.out_n = 4;
|
||||
req.mode = carry_mode::window;
|
||||
req.incoming_carry_public = false;
|
||||
const auto r = grotto::plan_carry(req);
|
||||
EXPECT_TRUE(r.use_window_overflow);
|
||||
EXPECT_TRUE(r.use_window_eq);
|
||||
EXPECT_TRUE(r.use_window_product);
|
||||
}
|
||||
|
||||
TEST(CarryClear, TruncateReduceMatchesHighPlusCarry)
|
||||
{
|
||||
for (std::uint64_t x0 = 0; x0 < 64; ++x0)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 64; ++x1)
|
||||
{
|
||||
const auto got = grotto::carry_in_clear(x0, x1, 6, 2);
|
||||
const auto x = (x0 + x1) & 63u;
|
||||
EXPECT_EQ(got, x >> 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CarryClear, CarryOutKnownLeavesOnlySmallResidual)
|
||||
{
|
||||
constexpr unsigned n = 6;
|
||||
constexpr unsigned s = 2;
|
||||
std::size_t big = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < (1u << n); ++x0)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < (1u << n); ++x1)
|
||||
{
|
||||
const auto x = (x0 + x1) & grotto::carry_mask(n);
|
||||
const auto true_asr = grotto::carry_asr(x, n, s);
|
||||
const auto fixed = grotto::carry_out_clear(x0, x1, n, s,
|
||||
sign_knowledge::nonnegative);
|
||||
// Known nonnegative assumes msb(x)==0; skip negatives.
|
||||
if ((x >> (n - 1)) & 1u)
|
||||
continue;
|
||||
std::int64_t d = static_cast<std::int64_t>(true_asr)
|
||||
- static_cast<std::int64_t>(fixed);
|
||||
if (d > 32)
|
||||
d -= 64;
|
||||
if (d < -32)
|
||||
d += 64;
|
||||
if (std::abs(d) > 1)
|
||||
++big;
|
||||
EXPECT_LE(std::abs(d), 1);
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(big, 0u);
|
||||
}
|
||||
|
||||
TEST(CarryClear, AndDisagreesWithExtensionWrap)
|
||||
{
|
||||
// 4-bit shares: share-MSB AND is not the extension wrap bit.
|
||||
std::size_t mismatch = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < 16; ++x0)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 16; ++x1)
|
||||
{
|
||||
const auto wrap = ((x0 + x1) >= 16u) ? 1u : 0u;
|
||||
const auto s0 = (x0 >> 3) & 1u;
|
||||
const auto s1 = (x1 >> 3) & 1u;
|
||||
const auto x = (x0 + x1) & 15u;
|
||||
const auto m = (x >> 3) & 1u;
|
||||
const auto grotto = (m == 0) ? (s0 & s1) : ((1u - s0) & (1u - s1));
|
||||
if (grotto != wrap)
|
||||
++mismatch;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(mismatch, 112u);
|
||||
}
|
||||
|
||||
TEST(CarryClear, ExtendPreservesSignedValue)
|
||||
{
|
||||
EXPECT_EQ(grotto::carry_extend_clear(0x05, 0x00, 8, 16), 0x0005u);
|
||||
EXPECT_EQ(grotto::carry_extend_clear(0x80, 0x00, 8, 16), 0xff80u);
|
||||
// 0x7f + 0x01 = 0x80 in 8 bits, which is negative and sign-extends.
|
||||
EXPECT_EQ(grotto::carry_extend_clear(0x7f, 0x01, 8, 16), 0xff80u);
|
||||
EXPECT_EQ(grotto::carry_extend_clear(0x40, 0x01, 8, 16), 0x0041u);
|
||||
}
|
||||
|
||||
TEST(CarryClear, WindowOutgoingCarry)
|
||||
{
|
||||
const auto w = grotto::carry_window_clear(0x0f, 0x01, 4, 0);
|
||||
EXPECT_EQ(w.digit, 0u);
|
||||
EXPECT_EQ(w.carry_out, 1u);
|
||||
const auto w2 = grotto::carry_window_clear(0x0e, 0x00, 4, 1);
|
||||
EXPECT_EQ(w2.digit, 0x0fu);
|
||||
EXPECT_EQ(w2.carry_out, 0u);
|
||||
const auto w3 = grotto::carry_window_clear(0x0f, 0x00, 4, 1);
|
||||
EXPECT_EQ(w3.digit, 0u);
|
||||
EXPECT_EQ(w3.carry_out, 1u);
|
||||
}
|
||||
|
||||
TEST(CarryClear, FusedMatchesAsr)
|
||||
{
|
||||
for (std::uint64_t x0 = 0; x0 < 64; ++x0)
|
||||
for (std::uint64_t x1 = 0; x1 < 64; ++x1)
|
||||
EXPECT_EQ(grotto::carry_fused_clear(x0, x1, 6, 2),
|
||||
grotto::carry_asr((x0 + x1) & 63u, 6, 2));
|
||||
}
|
||||
|
||||
TEST(CarryKeys, TruncateReduceOnline)
|
||||
{
|
||||
auto keys = grotto::make_carry_in_keys(8, 3);
|
||||
for (int trial = 0; trial < 32; ++trial)
|
||||
{
|
||||
const std::uint64_t x0 = dpf::uniform_sample<std::uint8_t>();
|
||||
const std::uint64_t x1 = dpf::uniform_sample<std::uint8_t>();
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
const std::uint64_t opened = (x + keys.rin) & 0xffu;
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
const auto y0 = grotto::eval_carry_in(keys, 0, opened);
|
||||
const auto y1 = grotto::eval_carry_in(keys, 1, opened);
|
||||
const auto got = (y0.value + y1.value) & 0x1fu;
|
||||
EXPECT_EQ(got, grotto::carry_in_clear(x0, x1, 8, 3));
|
||||
(void)pi0;
|
||||
(void)pi1;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CarryKeys, TruncateReduceVerifiableProof)
|
||||
{
|
||||
grotto::carry_auth auth{};
|
||||
auth.verifiable = true;
|
||||
auto keys = grotto::make_carry_in_keys(8, 3, auth);
|
||||
const std::uint64_t x0 = 0x11;
|
||||
const std::uint64_t x1 = 0x22;
|
||||
const std::uint64_t opened = ((x0 + x1) + keys.rin) & 0xffu;
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
const auto y0 = grotto::eval_carry_in(keys, 0, opened, &pi0);
|
||||
const auto y1 = grotto::eval_carry_in(keys, 1, opened, &pi1);
|
||||
EXPECT_EQ((y0.value + y1.value) & 0x1fu, grotto::carry_in_clear(x0, x1, 8, 3));
|
||||
EXPECT_TRUE(dpf::verify(pi0, pi1));
|
||||
|
||||
// Flip every correction seed so some on-path level mixes the tamper.
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(keys.low_lt_v->first)>::correction_seeds_array &>(
|
||||
keys.low_lt_v->first.correction_seeds()))
|
||||
{
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
}
|
||||
dpf::proof_token q0{}, q1{};
|
||||
auto ignore0 = grotto::eval_carry_in(keys, 0, opened, &q0);
|
||||
auto ignore1 = grotto::eval_carry_in(keys, 1, opened, &q1);
|
||||
(void)ignore0;
|
||||
(void)ignore1;
|
||||
EXPECT_FALSE(dpf::verify(q0, q1));
|
||||
}
|
||||
|
||||
TEST(CarryKeys, ProveCarryKeysBatch)
|
||||
{
|
||||
grotto::carry_auth auth{};
|
||||
auth.verifiable = true;
|
||||
auto keys = grotto::make_carry_in_keys(8, 3, auth);
|
||||
const std::uint64_t opened = 0x3cu;
|
||||
dpf::proof_token t0[4]{}, t1[4]{};
|
||||
const auto n0 = grotto::prove_carry_keys(keys, 0, opened, t0, 4);
|
||||
const auto n1 = grotto::prove_carry_keys(keys, 1, opened, t1, 4);
|
||||
EXPECT_EQ(n0, n1);
|
||||
EXPECT_GE(n0, 1u);
|
||||
EXPECT_TRUE(dpf::verify_batch(
|
||||
std::vector<dpf::proof_token>(t0, t0 + n0),
|
||||
std::vector<dpf::proof_token>(t1, t1 + n1)));
|
||||
}
|
||||
|
||||
TEST(CarryKeys, OutputMacDetectsFlip)
|
||||
{
|
||||
grotto::carry_auth auth{};
|
||||
auth.verifiable = true;
|
||||
auth.output_mac = true;
|
||||
auto keys = grotto::make_carry_in_keys(8, 3, auth);
|
||||
ASSERT_TRUE(keys.has_mac);
|
||||
const std::uint64_t opened = 0x3cu;
|
||||
dpf::proof_token t0[4]{}, t1[4]{};
|
||||
const auto n0 = grotto::prove_carry_keys(keys, 0, opened, t0, 4);
|
||||
const auto n1 = grotto::prove_carry_keys(keys, 1, opened, t1, 4);
|
||||
ASSERT_EQ(n0, n1);
|
||||
ASSERT_GE(n0, 1u);
|
||||
const std::uint64_t y0 = 3;
|
||||
const std::uint64_t y1 = 4;
|
||||
auto [m0, m1] = grotto::mac_carry_result(keys, y0, y1);
|
||||
EXPECT_TRUE(dpf::mac_verify(m0, m1, keys.mac, t0[0], t1[0]));
|
||||
EXPECT_FALSE(dpf::mac_verify(m0, m1, keys.mac, dpf::detail::vdpf::zero_proof(),
|
||||
t1[0]));
|
||||
m0.value ^= 1u;
|
||||
EXPECT_FALSE(dpf::mac_verify(m0, m1, keys.mac, t0[0], t1[0]));
|
||||
}
|
||||
|
||||
TEST(CarryCmp, VerifiableComparisonPathProof)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{10}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
const auto y0 = dpf::eval_point(dpf::cmp, k0, Input{3}, dpf::prove(pi0));
|
||||
const auto y1 = dpf::eval_point(dpf::cmp, k1, Input{3}, dpf::prove(pi1));
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1) & 1u, 1u);
|
||||
EXPECT_TRUE(dpf::verify(pi0, pi1));
|
||||
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
|
||||
k0.correction_seeds()))
|
||||
{
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(0x5a));
|
||||
}
|
||||
dpf::proof_token q0{}, q1{};
|
||||
dpf::eval_point(dpf::cmp, k0, Input{3}, dpf::prove(q0));
|
||||
dpf::eval_point(dpf::cmp, k1, Input{3}, dpf::prove(q1));
|
||||
EXPECT_FALSE(dpf::verify(q0, q1));
|
||||
}
|
||||
|
||||
TEST(OutputMac, BatchVerify)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<std::uint64_t>();
|
||||
std::vector<dpf::mac_share<std::uint64_t>> left, right;
|
||||
std::vector<std::uint64_t> coeffs;
|
||||
for (std::uint64_t i = 0; i < 4; ++i)
|
||||
{
|
||||
auto [a, b] = dpf::mac_share_value(i * 3u + 1u, key);
|
||||
left.push_back(a);
|
||||
right.push_back(b);
|
||||
coeffs.push_back(i + 1u);
|
||||
}
|
||||
EXPECT_TRUE(dpf::mac_verify_batch(left, right, coeffs, key));
|
||||
left[1].tag ^= 1u;
|
||||
EXPECT_FALSE(dpf::mac_verify_batch(left, right, coeffs, key));
|
||||
}
|
||||
|
||||
TEST(CarryKeys, KnownSignCarryOutOnlineMatchesClear)
|
||||
{
|
||||
constexpr unsigned n = 8;
|
||||
constexpr unsigned s = 3;
|
||||
auto keys = grotto::make_carry_out_keys(n, s, sign_knowledge::nonnegative);
|
||||
ASSERT_TRUE(keys.has_and_beaver);
|
||||
ASSERT_TRUE(keys.recipe.use_share_msb_and);
|
||||
|
||||
std::size_t checked = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < 256; x0 += 17)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 256; x1 += 19)
|
||||
{
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
if ((x >> (n - 1)) & 1u)
|
||||
continue; // nonnegative assumption
|
||||
const std::uint64_t s0 = (x0 >> (n - 1)) & 1u;
|
||||
const std::uint64_t s1 = (x1 >> (n - 1)) & 1u;
|
||||
const auto y0 = grotto::eval_carry_out_known(keys, 0, x0, s0, s1);
|
||||
const auto y1 = grotto::eval_carry_out_known(keys, 1, x1, s1, s0);
|
||||
const auto got = (y0.value + y1.value) & 0xffu;
|
||||
const auto expect = grotto::carry_out_clear(x0, x1, n, s,
|
||||
sign_knowledge::nonnegative);
|
||||
EXPECT_EQ(got, expect) << "x0=" << x0 << " x1=" << x1;
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(checked, 50u);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, NegativeSignCarryOutOnlineMatchesClear)
|
||||
{
|
||||
constexpr unsigned n = 8;
|
||||
constexpr unsigned s = 3;
|
||||
auto keys = grotto::make_carry_out_keys(n, s, sign_knowledge::negative);
|
||||
ASSERT_TRUE(keys.recipe.and_is_nor);
|
||||
|
||||
std::size_t checked = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < 256; x0 += 17)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 256; x1 += 19)
|
||||
{
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
if (((x >> (n - 1)) & 1u) == 0u)
|
||||
continue; // negative assumption
|
||||
const std::uint64_t s0 = (x0 >> (n - 1)) & 1u;
|
||||
const std::uint64_t s1 = (x1 >> (n - 1)) & 1u;
|
||||
const auto y0 = grotto::eval_carry_out_known(keys, 0, x0, s0, s1);
|
||||
const auto y1 = grotto::eval_carry_out_known(keys, 1, x1, s1, s0);
|
||||
const auto got = (y0.value + y1.value) & 0xffu;
|
||||
const auto expect = grotto::carry_out_clear(x0, x1, n, s,
|
||||
sign_knowledge::negative);
|
||||
EXPECT_EQ(got, expect) << "x0=" << x0 << " x1=" << x1;
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(checked, 50u);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, WindowSecretBuildsEqAndOverflow)
|
||||
{
|
||||
grotto::carry_request req{};
|
||||
req.n = 16;
|
||||
req.s = 4;
|
||||
req.out_n = 4;
|
||||
req.mode = carry_mode::window;
|
||||
req.incoming_carry_public = false;
|
||||
auto keys = grotto::make_carry_keys(req);
|
||||
EXPECT_TRUE(keys.window_overflow.has_value() || keys.window_overflow_v.has_value());
|
||||
EXPECT_TRUE(keys.window_eq.has_value() || keys.window_eq_v.has_value());
|
||||
EXPECT_TRUE(keys.has_and_beaver);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, WindowPublicZeroOmitsEq)
|
||||
{
|
||||
grotto::carry_request req{};
|
||||
req.n = 16;
|
||||
req.s = 4;
|
||||
req.out_n = 4;
|
||||
req.mode = carry_mode::window;
|
||||
req.incoming_carry_public = true;
|
||||
req.incoming_carry_value = 0;
|
||||
auto keys = grotto::make_carry_keys(req);
|
||||
EXPECT_TRUE(keys.window_overflow.has_value() || keys.window_overflow_v.has_value());
|
||||
EXPECT_FALSE(keys.window_eq.has_value());
|
||||
EXPECT_FALSE(keys.window_eq_v.has_value());
|
||||
EXPECT_FALSE(keys.has_and_beaver);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, FusedSameRingPlansBothThenOnlineLowOnlyWhenReduced)
|
||||
{
|
||||
auto reduced = grotto::make_carry_fused_keys(8, 3, 5);
|
||||
EXPECT_TRUE(reduced.recipe.use_low_lt);
|
||||
EXPECT_FALSE(reduced.recipe.use_share_msb_and);
|
||||
EXPECT_TRUE(reduced.low_lt.has_value() || reduced.low_lt_v.has_value());
|
||||
|
||||
auto same = grotto::make_carry_fused_keys(8, 3, 8, sign_knowledge::nonnegative);
|
||||
EXPECT_TRUE(same.recipe.use_low_lt);
|
||||
EXPECT_TRUE(same.recipe.use_share_msb_and);
|
||||
EXPECT_TRUE(same.has_and_beaver);
|
||||
}
|
||||
|
||||
TEST(PrefixParity, VerifiableTokensMatchAndTamperRejects)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, dpf::bit{1}, dpf::verifiable{});
|
||||
// Exclusive of alpha; include alpha itself (must reconstruct to 0).
|
||||
const std::array<Input, 3> ends{Input{0x10}, Input{0x2a}, Input{0x40}};
|
||||
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
auto [p0, n0] = grotto::prefix_parities(k0, ends, dpf::prove(pi0));
|
||||
auto [p1, n1] = grotto::prefix_parities(k1, ends, dpf::prove(pi1));
|
||||
(void)n0;
|
||||
(void)n1;
|
||||
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||
EXPECT_EQ(p0[i] ^ p1[i], ends[i] > Input{0x2a});
|
||||
EXPECT_TRUE(dpf::verify(pi0, pi1));
|
||||
|
||||
pi0[0] = simde_mm_xor_si128(pi0[0], simde_mm_set1_epi8(0x3c));
|
||||
EXPECT_FALSE(dpf::verify(pi0, pi1));
|
||||
}
|
||||
|
||||
TEST(CarryClear, EvalCarryClearDispatchMatchesHelpers)
|
||||
{
|
||||
const auto tr = grotto::plan_carry_in(8, 3);
|
||||
EXPECT_EQ(grotto::eval_carry_clear(tr, 0xab, 0x11),
|
||||
grotto::carry_in_clear(0xab, 0x11, 8, 3));
|
||||
|
||||
const auto ext = grotto::plan_carry(grotto::carry_request{
|
||||
8, 0, 16, carry_mode::extend});
|
||||
EXPECT_EQ(grotto::eval_carry_clear(ext, 0x80, 0),
|
||||
grotto::carry_extend_clear(0x80, 0, 8, 16));
|
||||
|
||||
const auto fuse = grotto::plan_carry_fused(8, 2, 8);
|
||||
EXPECT_EQ(grotto::eval_carry_clear(fuse, 0x11, 0x22),
|
||||
grotto::carry_fused_clear(0x11, 0x22, 8, 2));
|
||||
}
|
||||
|
||||
TEST(CarryKeys, BeaverAndOfPrivateBitsIsOneWhenBothSet)
|
||||
{
|
||||
// Direct check of the AND helper via known-sign path with unit=1, s=0
|
||||
// is awkward; instead use n=2,s=1 so unit=2 and check the product bit.
|
||||
auto keys = grotto::make_carry_out_keys(4, 1, sign_knowledge::nonnegative);
|
||||
// Both MSBs set: x0=0b1xxx, x1=0b1xxx with nonnegative sum.
|
||||
// 0b1000 + 0b1000 = 0b0000 mod 16, msb of sum is 0 — OK for nonnegative.
|
||||
const std::uint64_t x0 = 0b1000;
|
||||
const std::uint64_t x1 = 0b1000;
|
||||
const auto y0 = grotto::eval_carry_out_known(keys, 0, x0, 1, 1);
|
||||
const auto y1 = grotto::eval_carry_out_known(keys, 1, x1, 1, 1);
|
||||
const auto got = (y0.value + y1.value) & 0xfu;
|
||||
const auto expect = grotto::carry_out_clear(x0, x1, 4, 1,
|
||||
sign_knowledge::nonnegative);
|
||||
EXPECT_EQ(got, expect);
|
||||
// AND must be 1, so correction is +2^{3}=+8 on top of local ASRs.
|
||||
const auto base = (grotto::carry_asr(x0, 4, 1) + grotto::carry_asr(x1, 4, 1)) & 0xfu;
|
||||
EXPECT_EQ(got, (base + 8u) & 0xfu);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, UnknownSignMatchesClear)
|
||||
{
|
||||
constexpr unsigned n = 8;
|
||||
constexpr unsigned s = 3;
|
||||
auto keys = grotto::make_carry_out_keys(n, s, sign_knowledge::unknown);
|
||||
ASSERT_TRUE(keys.recipe.use_msb_lt);
|
||||
ASSERT_TRUE(keys.recipe.use_share_msb_and);
|
||||
|
||||
std::size_t checked = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < 256; x0 += 17)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 256; x1 += 19)
|
||||
{
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
const std::uint64_t msb_high = (x >> (n - 1)) & 1u;
|
||||
const std::uint64_t s0 = (x0 >> (n - 1)) & 1u;
|
||||
const std::uint64_t s1 = (x1 >> (n - 1)) & 1u;
|
||||
const auto y0 = grotto::eval_carry_out_unknown(keys, 0, x0, s0, s1, msb_high);
|
||||
const auto y1 = grotto::eval_carry_out_unknown(keys, 1, x1, s1, s0, msb_high);
|
||||
const auto got = (y0.value + y1.value) & 0xffu;
|
||||
const auto expect = grotto::carry_out_clear(x0, x1, n, s,
|
||||
sign_knowledge::unknown);
|
||||
EXPECT_EQ(got, expect) << "x0=" << x0 << " x1=" << x1;
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(checked, 50u);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, ExtendMatchesClear)
|
||||
{
|
||||
auto keys = grotto::make_carry_keys(grotto::carry_request{
|
||||
8, 0, 16, carry_mode::extend});
|
||||
ASSERT_TRUE(keys.recipe.use_biased_wrap);
|
||||
for (std::uint64_t x0 = 0; x0 < 256; x0 += 13)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 256; x1 += 17)
|
||||
{
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
const std::uint64_t msb_high = (x >> 7) & 1u;
|
||||
const std::uint64_t opened = (x + keys.rin) & 0xffu;
|
||||
const auto y0 = grotto::eval_carry_extend(keys, 0, opened, msb_high);
|
||||
const auto y1 = grotto::eval_carry_extend(keys, 1, opened, msb_high);
|
||||
const auto got = (y0.value + y1.value) & 0xffffu;
|
||||
EXPECT_EQ(got, grotto::carry_extend_clear(x0, x1, 8, 16))
|
||||
<< "x0=" << x0 << " x1=" << x1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CarryKeys, WindowPublicMatchesClear)
|
||||
{
|
||||
grotto::carry_request req{};
|
||||
req.n = 16;
|
||||
req.s = 4;
|
||||
req.out_n = 4;
|
||||
req.mode = carry_mode::window;
|
||||
req.incoming_carry_public = true;
|
||||
req.incoming_carry_value = 1;
|
||||
auto keys = grotto::make_carry_keys(req);
|
||||
for (std::uint64_t p0 = 0; p0 < 16; ++p0)
|
||||
{
|
||||
for (std::uint64_t p1 = 0; p1 < 16; ++p1)
|
||||
{
|
||||
const std::uint64_t sum = p0 + p1 + 1u;
|
||||
const auto y0 = grotto::eval_carry_window(keys, 0, sum);
|
||||
const auto y1 = grotto::eval_carry_window(keys, 1, sum);
|
||||
const auto got = y0.value + y1.value;
|
||||
const auto w = grotto::carry_window_clear(p0, p1, 4, 1);
|
||||
EXPECT_EQ(got, (w.carry_out << 4) | w.digit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CarryKeys, FusedReducedMatchesTruncate)
|
||||
{
|
||||
auto keys = grotto::make_carry_fused_keys(8, 3, 5);
|
||||
ASSERT_TRUE(keys.recipe.use_low_lt);
|
||||
ASSERT_FALSE(keys.recipe.use_share_msb_and);
|
||||
for (int trial = 0; trial < 32; ++trial)
|
||||
{
|
||||
const std::uint64_t x0 = dpf::uniform_sample<std::uint8_t>();
|
||||
const std::uint64_t x1 = dpf::uniform_sample<std::uint8_t>();
|
||||
const std::uint64_t opened = ((x0 + x1) + keys.rin) & 0xffu;
|
||||
const auto y0 = grotto::eval_carry_fused(keys, 0, opened, x0);
|
||||
const auto y1 = grotto::eval_carry_fused(keys, 1, opened, x1);
|
||||
const auto got = (y0.value + y1.value) & 0x1fu;
|
||||
EXPECT_EQ(got, grotto::carry_in_clear(x0, x1, 8, 3));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CarryKeys, FusedSameRingKnownSignMatchesClear)
|
||||
{
|
||||
constexpr unsigned n = 8;
|
||||
constexpr unsigned s = 2;
|
||||
auto keys = grotto::make_carry_fused_keys(n, s, n, sign_knowledge::nonnegative);
|
||||
ASSERT_TRUE(keys.recipe.use_share_msb_and);
|
||||
std::size_t checked = 0;
|
||||
for (std::uint64_t x0 = 0; x0 < 256; x0 += 21)
|
||||
{
|
||||
for (std::uint64_t x1 = 0; x1 < 256; x1 += 23)
|
||||
{
|
||||
const std::uint64_t x = (x0 + x1) & 0xffu;
|
||||
if ((x >> (n - 1)) & 1u)
|
||||
continue;
|
||||
const std::uint64_t s0 = (x0 >> (n - 1)) & 1u;
|
||||
const std::uint64_t s1 = (x1 >> (n - 1)) & 1u;
|
||||
const std::uint64_t opened = (x + keys.rin) & 0xffu;
|
||||
const auto y0 = grotto::eval_carry_fused(keys, 0, opened, x0, s0, s1);
|
||||
const auto y1 = grotto::eval_carry_fused(keys, 1, opened, x1, s1, s0);
|
||||
const auto got = (y0.value + y1.value) & 0xffu;
|
||||
EXPECT_EQ(got, grotto::carry_out_clear(x0, x1, n, s,
|
||||
sign_knowledge::nonnegative))
|
||||
<< "x0=" << x0 << " x1=" << x1;
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(checked, 20u);
|
||||
}
|
||||
|
||||
TEST(CarryKeys, RecipeMakeCarryKeysFinalizesBlinds)
|
||||
{
|
||||
const auto recipe = grotto::plan_carry_in(8, 3);
|
||||
auto keys = grotto::make_carry_keys(recipe);
|
||||
const std::uint64_t high = grotto::carry_mask(5);
|
||||
const std::uint64_t y_hi = ((std::uint64_t{0} - keys.rin) >> 3) & high;
|
||||
EXPECT_EQ((keys.rout0 + keys.rout1) & high, y_hi);
|
||||
}
|
||||
535
test/tests/class_sweep_test.cpp
Normal file
535
test/tests/class_sweep_test.cpp
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/offset_horner.hpp"
|
||||
#include "grotto/lut_union.hpp"
|
||||
#include "grotto/offset_jet.hpp"
|
||||
#include "grotto/offset_repr.hpp"
|
||||
#include "grotto/offset_twist.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_alpha : std::false_type
|
||||
{ };
|
||||
template <typename T>
|
||||
struct has_member_alpha<T, std::void_t<decltype(std::declval<T>().alpha)>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_center : std::false_type
|
||||
{ };
|
||||
template <typename T>
|
||||
struct has_member_center<T, std::void_t<decltype(std::declval<T>().center)>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_beta : std::false_type
|
||||
{ };
|
||||
template <typename T>
|
||||
struct has_member_beta<T, std::void_t<decltype(std::declval<T>().beta)>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_both_dcf_halves : std::false_type
|
||||
{ };
|
||||
template <typename T>
|
||||
struct has_both_dcf_halves<T, std::void_t<
|
||||
decltype(std::declval<T>().key_a), decltype(std::declval<T>().key_b)>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
bool tokens_equal(const dpf::proof_token & a, const dpf::proof_token & b)
|
||||
{
|
||||
return std::memcmp(a.data(), b.data(), sizeof(dpf::proof_token)) == 0;
|
||||
}
|
||||
|
||||
void xor_first_byte(void * p)
|
||||
{
|
||||
auto * bytes = static_cast<unsigned char *>(p);
|
||||
bytes[0] = static_cast<unsigned char>(bytes[0] ^ 0x1u);
|
||||
}
|
||||
|
||||
template <typename Out>
|
||||
Out group_neg_one()
|
||||
{
|
||||
return -Out{1};
|
||||
}
|
||||
|
||||
template <typename Out>
|
||||
void expect_point_near_modulus()
|
||||
{
|
||||
const auto beta = group_neg_one<Out>();
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const Out on = dpf::reconstruct(*dpf::eval_point(k0, alpha),
|
||||
*dpf::eval_point(k1, alpha));
|
||||
EXPECT_EQ(on, beta);
|
||||
EXPECT_NE(on, Out{});
|
||||
const Out off = dpf::reconstruct(*dpf::eval_point(k0, std::uint8_t{0}),
|
||||
*dpf::eval_point(k1, std::uint8_t{0}));
|
||||
EXPECT_EQ(off, Out{});
|
||||
}
|
||||
|
||||
template <typename Out>
|
||||
void expect_comparison_keeps_negative_delta()
|
||||
{
|
||||
const auto beta = group_neg_one<Out>();
|
||||
const std::uint8_t alpha = 10;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(beta));
|
||||
const Out hot = dpf::reconstruct(
|
||||
dpf::eval_point<Out>(dpf::cmp, k0, std::uint8_t{0}),
|
||||
dpf::eval_point<Out>(dpf::cmp, k1, std::uint8_t{0}));
|
||||
EXPECT_EQ(hot, beta);
|
||||
EXPECT_NE(hot, Out{});
|
||||
const Out cold = dpf::reconstruct(
|
||||
dpf::eval_point<Out>(dpf::cmp, k0, std::uint8_t{11}),
|
||||
dpf::eval_point<Out>(dpf::cmp, k1, std::uint8_t{11}));
|
||||
EXPECT_EQ(cold, Out{});
|
||||
}
|
||||
|
||||
template <typename Out>
|
||||
void expect_proof_binds_leaf_and_warm_path()
|
||||
{
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
const Out beta = Out{9};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::verifiable{});
|
||||
using key_t = std::decay_t<decltype(k0)>;
|
||||
|
||||
dpf::proof_token cold0{};
|
||||
dpf::proof_token cold1{};
|
||||
(void)*dpf::eval_point(k0, alpha, dpf::prove(cold0));
|
||||
(void)*dpf::eval_point(k1, alpha, dpf::prove(cold1));
|
||||
EXPECT_TRUE(dpf::verify(cold0, cold1));
|
||||
EXPECT_FALSE(dpf::verify(dpf::proof_token{}, dpf::proof_token{}));
|
||||
|
||||
dpf::basic_path_memoizer<key_t> memo;
|
||||
(void)*dpf::eval_point(k0, alpha, memo);
|
||||
dpf::proof_token warm{};
|
||||
(void)*dpf::eval_point(k0, alpha, dpf::prove(warm), memo);
|
||||
EXPECT_TRUE(tokens_equal(warm, cold0));
|
||||
|
||||
auto & leaves = const_cast<std::decay_t<decltype(k0.leaves())> &>(k0.leaves());
|
||||
xor_first_byte(&std::get<0>(leaves).get());
|
||||
dpf::proof_token tampered{};
|
||||
(void)*dpf::eval_point(k0, alpha, dpf::prove(tampered), memo);
|
||||
EXPECT_FALSE(dpf::verify(tampered, cold1));
|
||||
EXPECT_FALSE(tokens_equal(tampered, cold0));
|
||||
}
|
||||
|
||||
template <typename Seeded>
|
||||
void expect_from_seed_reads_past_first_word()
|
||||
{
|
||||
unsigned char lo[16]{};
|
||||
unsigned char hi[16]{};
|
||||
hi[8] = 1;
|
||||
EXPECT_NE(Seeded::from_seed(lo, sizeof(lo)), Seeded::from_seed(hi, sizeof(hi)));
|
||||
}
|
||||
|
||||
template <typename Seeded>
|
||||
void expect_from_seed_reads_past_16_bytes()
|
||||
{
|
||||
unsigned char lo[32]{};
|
||||
unsigned char hi[32]{};
|
||||
hi[24] = 1;
|
||||
EXPECT_NE(Seeded::from_seed(lo, sizeof(lo)), Seeded::from_seed(hi, sizeof(hi)));
|
||||
}
|
||||
|
||||
bool scalar_below_order(const dpf::p256_scalar & s)
|
||||
{
|
||||
for (int i = 3; i >= 0; --i)
|
||||
{
|
||||
const auto limb = s.limb(static_cast<std::size_t>(i));
|
||||
const auto bound = dpf::p256_scalar::order[i];
|
||||
if (limb < bound)
|
||||
return true;
|
||||
if (limb > bound)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct IcPad
|
||||
{
|
||||
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
|
||||
std::uint8_t bit() { return 0; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ClassSweep, ModularLeafAndComparisonKeepTheField)
|
||||
{
|
||||
expect_point_near_modulus<dpf::fp61>();
|
||||
expect_point_near_modulus<dpf::field64>();
|
||||
expect_point_near_modulus<dpf::field128>();
|
||||
expect_point_near_modulus<dpf::p256_scalar>();
|
||||
expect_point_near_modulus<dpf::gf2>();
|
||||
expect_point_near_modulus<dpf::gf22>();
|
||||
expect_point_near_modulus<dpf::gf24>();
|
||||
expect_point_near_modulus<dpf::gf28>();
|
||||
expect_point_near_modulus<dpf::gf216>();
|
||||
expect_point_near_modulus<dpf::gf232>();
|
||||
expect_point_near_modulus<dpf::gf264>();
|
||||
|
||||
expect_comparison_keeps_negative_delta<dpf::fp61>();
|
||||
expect_comparison_keeps_negative_delta<dpf::field64>();
|
||||
expect_comparison_keeps_negative_delta<dpf::field128>();
|
||||
expect_comparison_keeps_negative_delta<dpf::p256_scalar>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf2>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf22>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf24>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf28>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf216>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf232>();
|
||||
expect_comparison_keeps_negative_delta<dpf::gf264>();
|
||||
}
|
||||
|
||||
TEST(ClassSweep, SimdLeafAddReducesInTheField)
|
||||
{
|
||||
alignas(16) std::uint64_t left[2] = {dpf::fp61_mod - 1, dpf::field64::mod - 3};
|
||||
alignas(16) std::uint64_t right[2] = {4, 5};
|
||||
simde__m128i a{};
|
||||
simde__m128i b{};
|
||||
std::memcpy(&a, left, sizeof(left));
|
||||
std::memcpy(&b, right, sizeof(right));
|
||||
|
||||
alignas(16) std::uint64_t fp_out[2]{};
|
||||
const auto fp_sum = dpf::leaf_arithmetic::add_t<dpf::fp61, simde__m128i>{}(a, b);
|
||||
std::memcpy(fp_out, &fp_sum, sizeof(fp_out));
|
||||
EXPECT_EQ(fp_out[0], dpf::fp61::reduce((dpf::fp61_mod - 1) + 4));
|
||||
EXPECT_EQ(fp_out[1], dpf::fp61::reduce((dpf::field64::mod - 3) + 5));
|
||||
|
||||
alignas(16) std::uint64_t f64_left[2] = {dpf::field64::mod - 1, dpf::field64::mod - 4};
|
||||
alignas(16) std::uint64_t f64_right[2] = {2, 6};
|
||||
std::memcpy(&a, f64_left, sizeof(f64_left));
|
||||
std::memcpy(&b, f64_right, sizeof(f64_right));
|
||||
alignas(16) std::uint64_t f64_out[2]{};
|
||||
const auto f64_sum = dpf::leaf_arithmetic::add_t<dpf::field64, simde__m128i>{}(a, b);
|
||||
std::memcpy(f64_out, &f64_sum, sizeof(f64_out));
|
||||
EXPECT_EQ(f64_out[0], 1u);
|
||||
EXPECT_EQ(f64_out[1], 2u);
|
||||
|
||||
const auto wide = (static_cast<unsigned __int128>(dpf::field128::mod_hi) << 64)
|
||||
| dpf::field128::mod_lo;
|
||||
const dpf::field128 near{wide - 1};
|
||||
const dpf::field128 step{3};
|
||||
simde__m128i na{};
|
||||
simde__m128i nb{};
|
||||
std::memcpy(&na, &near, sizeof(na));
|
||||
std::memcpy(&nb, &step, sizeof(nb));
|
||||
const auto f128_sum = dpf::leaf_arithmetic::add_t<dpf::field128, simde__m128i>{}(na, nb);
|
||||
dpf::field128 got{};
|
||||
std::memcpy(&got, &f128_sum, sizeof(got));
|
||||
EXPECT_EQ(got, near + step);
|
||||
EXPECT_EQ(got, dpf::field128{2});
|
||||
}
|
||||
|
||||
TEST(ClassSweep, FromSeedConsumesBytesPastTheOldWindow)
|
||||
{
|
||||
expect_from_seed_reads_past_first_word<dpf::fp61>();
|
||||
expect_from_seed_reads_past_first_word<dpf::field64>();
|
||||
expect_from_seed_reads_past_first_word<dpf::field128>();
|
||||
expect_from_seed_reads_past_first_word<dpf::p256_scalar>();
|
||||
expect_from_seed_reads_past_first_word<dpf::p256>();
|
||||
expect_from_seed_reads_past_16_bytes<dpf::p256_scalar>();
|
||||
expect_from_seed_reads_past_16_bytes<dpf::p256>();
|
||||
}
|
||||
|
||||
TEST(ClassSweep, RejectionSampleStaysInTheScalarField)
|
||||
{
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
EXPECT_TRUE(scalar_below_order(dpf::uniform_sample<dpf::p256_scalar>()));
|
||||
const auto s = -dpf::p256_scalar{1};
|
||||
EXPECT_EQ(-(-s), s);
|
||||
EXPECT_EQ(s, dpf::p256_scalar{1} - dpf::p256_scalar{2});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClassSweep, MalformedCurveEncodingsAreRejected)
|
||||
{
|
||||
for (unsigned prefix : {0x00u, 0x01u, 0x04u, 0x05u})
|
||||
{
|
||||
unsigned char bad[33]{};
|
||||
bad[0] = static_cast<unsigned char>(prefix);
|
||||
bad[32] = 1;
|
||||
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument) << prefix;
|
||||
}
|
||||
unsigned char off_curve[33]{};
|
||||
off_curve[0] = 0x02;
|
||||
off_curve[32] = 1;
|
||||
EXPECT_THROW(dpf::p256::from_compressed(off_curve), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(ClassSweep, ExtractableCodomainIsTheSketchGroupOnly)
|
||||
{
|
||||
static_assert(dpf::extractable_codomain_ok_v<dpf::fp61>);
|
||||
static_assert(dpf::extractable_codomain_ok_v<dpf::xor_wrapper<dpf::fp61>>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<dpf::field64>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<dpf::field128>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<dpf::p256>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<dpf::p256_scalar>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<std::uint64_t>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<float>);
|
||||
static_assert(!dpf::extractable_codomain_ok_v<double>);
|
||||
}
|
||||
|
||||
TEST(ClassSweep, FloatAndDoubleLeavesUseTheXorGroup)
|
||||
{
|
||||
const float xf = dpf::leaf_group_add(1.0f, 2.0f);
|
||||
const float ieee_f = 1.0f + 2.0f;
|
||||
EXPECT_NE(xf, ieee_f);
|
||||
std::uint32_t fb = 0;
|
||||
std::memcpy(&fb, &xf, sizeof(fb));
|
||||
EXPECT_EQ(fb, 0x3f800000u ^ 0x40000000u);
|
||||
|
||||
const double xd = dpf::leaf_group_add(1.0, 2.0);
|
||||
EXPECT_NE(xd, 1.0 + 2.0);
|
||||
std::uint64_t db = 0;
|
||||
std::memcpy(&db, &xd, sizeof(db));
|
||||
EXPECT_EQ(db, 0x3ff0000000000000ull ^ 0x4000000000000000ull);
|
||||
|
||||
simde__m128i a = simde_mm_set1_epi32(static_cast<int>(0x3f800000));
|
||||
simde__m128i b = simde_mm_set1_epi32(static_cast<int>(0x40000000));
|
||||
const auto packed = dpf::leaf_arithmetic::add_t<float, simde__m128i>{}(a, b);
|
||||
const auto expect = simde_mm_xor_si128(a, b);
|
||||
EXPECT_EQ(std::memcmp(&packed, &expect, sizeof(packed)), 0);
|
||||
}
|
||||
|
||||
TEST(ClassSweep, ProofsBindEveryEvalEntry)
|
||||
{
|
||||
expect_proof_binds_leaf_and_warm_path<std::uint32_t>();
|
||||
expect_proof_binds_leaf_and_warm_path<std::uint64_t>();
|
||||
expect_proof_binds_leaf_and_warm_path<dpf::fp61>();
|
||||
expect_proof_binds_leaf_and_warm_path<dpf::field64>();
|
||||
|
||||
const std::uint8_t alpha = 12;
|
||||
auto [c0, c1] = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{9}), dpf::verifiable{});
|
||||
dpf::proof_token before{};
|
||||
dpf::proof_token after{};
|
||||
(void)dpf::eval_point(dpf::cmp, c0, std::uint8_t{3}, dpf::prove(before));
|
||||
auto & words = const_cast<std::decay_t<decltype(c0.value_cw())> &>(c0.value_cw());
|
||||
xor_first_byte(&words[0]);
|
||||
(void)dpf::eval_point(dpf::cmp, c0, std::uint8_t{3}, dpf::prove(after));
|
||||
EXPECT_FALSE(tokens_equal(before, after));
|
||||
|
||||
dpf::proof_token other{};
|
||||
(void)dpf::eval_point(dpf::cmp, c1, std::uint8_t{3}, dpf::prove(other));
|
||||
EXPECT_FALSE(dpf::verify(after, other));
|
||||
|
||||
auto [p0, p1] = dpf::make_dpf(std::uint8_t{4}, std::uint32_t{3}, dpf::verifiable{});
|
||||
const std::uint8_t from = 1;
|
||||
const std::uint8_t to = 6;
|
||||
dpf::proof_token i0{};
|
||||
dpf::proof_token i1{};
|
||||
dpf::prove_interval(p0, from, to, dpf::prove(i0));
|
||||
dpf::prove_interval(p1, from, to, dpf::prove(i1));
|
||||
EXPECT_TRUE(dpf::verify(i0, i1));
|
||||
EXPECT_FALSE(dpf::verify(dpf::proof_token{}, dpf::proof_token{}));
|
||||
|
||||
dpf::proof_token full0{};
|
||||
dpf::proof_token full1{};
|
||||
dpf::prove_full(p0, dpf::prove(full0));
|
||||
dpf::prove_full(p1, dpf::prove(full1));
|
||||
EXPECT_TRUE(dpf::verify(full0, full1));
|
||||
|
||||
std::array<std::uint8_t, 4> seq{2, 3, 4, 9};
|
||||
dpf::proof_token s0{};
|
||||
dpf::proof_token s1{};
|
||||
dpf::prove_sequence(p0, seq.begin(), seq.end(), dpf::prove(s0));
|
||||
dpf::prove_sequence(p1, seq.begin(), seq.end(), dpf::prove(s1));
|
||||
EXPECT_TRUE(dpf::verify(s0, s1));
|
||||
dpf::proof_token s0_again{};
|
||||
dpf::prove_sequence(p0, seq.begin(), seq.end(), dpf::prove(s0_again));
|
||||
EXPECT_TRUE(tokens_equal(s0, s0_again));
|
||||
|
||||
// Packed leaves may touch slots next to [from, to]. The token does not use the weights.
|
||||
std::vector<std::uint32_t> weights(256, 1u);
|
||||
dpf::proof_token dot{};
|
||||
(void)dpf::eval_inner_product(p0, from, to, weights, dpf::prove(dot));
|
||||
EXPECT_TRUE(tokens_equal(dot, i0));
|
||||
|
||||
dpf::proof_token a0{};
|
||||
dpf::proof_token a1{};
|
||||
dpf::proof_token b0{};
|
||||
dpf::proof_token b1{};
|
||||
(void)*dpf::eval_point(p0, std::uint8_t{4}, dpf::prove(a0));
|
||||
(void)*dpf::eval_point(p1, std::uint8_t{4}, dpf::prove(b0));
|
||||
(void)*dpf::eval_point(p0, std::uint8_t{5}, dpf::prove(a1));
|
||||
(void)*dpf::eval_point(p1, std::uint8_t{5}, dpf::prove(b1));
|
||||
std::array<dpf::proof_token, 2> left{a0, a1};
|
||||
std::array<dpf::proof_token, 2> right{b0, b1};
|
||||
EXPECT_TRUE(dpf::verify_batch(left, right));
|
||||
// A swap of equal second halves is a no-op. Flip one byte of each half
|
||||
// so a batch that folds only token[0] still accepts the second-half flip.
|
||||
auto flipped_lo = left;
|
||||
xor_first_byte(&flipped_lo[0][0]);
|
||||
EXPECT_FALSE(dpf::verify_batch(flipped_lo, right));
|
||||
auto flipped_hi = left;
|
||||
xor_first_byte(&flipped_hi[1][1]);
|
||||
EXPECT_FALSE(dpf::verify_batch(flipped_hi, right));
|
||||
}
|
||||
|
||||
TEST(ClassSweep, OneComparisonHalfIsNotThePredicate)
|
||||
{
|
||||
const std::uint8_t thresh = 40;
|
||||
const std::uint64_t if_true = 19;
|
||||
auto keys = dpf::make_dpf3_cmp(thresh, if_true, std::uint64_t{0});
|
||||
auto & k1 = std::get<0>(keys);
|
||||
auto & k2 = std::get<1>(keys);
|
||||
auto & k3 = std::get<2>(keys);
|
||||
static_assert(!has_both_dcf_halves<std::decay_t<decltype(k1)>>::value);
|
||||
static_assert(!has_both_dcf_halves<std::decay_t<decltype(k2)>>::value);
|
||||
|
||||
const auto full1 = dpf::eval_full(k1);
|
||||
ASSERT_EQ(full1.size(), 256u);
|
||||
int hidden = 0;
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto q = static_cast<std::uint8_t>(x);
|
||||
dpf::basic_path_memoizer<std::decay_t<decltype(k1.dpf_key)>> memo;
|
||||
const auto s1 = dpf::eval_dpf3_cmp(k1, q, memo);
|
||||
const auto s1_again = dpf::eval_dpf3_cmp(k1, q, memo);
|
||||
const auto s2 = dpf::eval_dpf3_cmp(k2, q);
|
||||
const auto s3 = dpf::eval_dpf3_cmp(k3, q);
|
||||
EXPECT_EQ(s1, s1_again);
|
||||
EXPECT_EQ(s1, s3);
|
||||
EXPECT_EQ(s1, full1[x]);
|
||||
const auto opened = dpf::reconstruct_cmp_halves(s1, s2);
|
||||
const dpf::fp61 want = x < thresh ? dpf::fp61{if_true} : dpf::fp61{};
|
||||
EXPECT_EQ(opened, want) << x;
|
||||
if (dpf::fp61{s1} != want && dpf::fp61{s2} != want)
|
||||
++hidden;
|
||||
}
|
||||
EXPECT_GT(hidden, 200);
|
||||
}
|
||||
|
||||
TEST(ClassSweep, OpenedResultsDoNotCarryTheSecretPoint)
|
||||
{
|
||||
static_assert(!has_member_alpha<dpf::geneval_result<std::uint32_t, simde__m128i>>::value);
|
||||
static_assert(!has_member_beta<dpf::geneval_result<std::uint32_t, simde__m128i>>::value);
|
||||
static_assert(!has_member_alpha<dpf::geneval_cmp_result>::value);
|
||||
static_assert(!has_member_center<grotto::geneval_offset_horner_result<2, std::uint8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::geneval_lut_union_result<std::uint8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::geneval_offset_horner_result<3, std::int8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::offset_horner_keys<std::uint8_t, 2, false>>::value);
|
||||
static_assert(!has_member_center<grotto::offset_poly_keys<std::uint8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::offset_jet_keys<std::uint8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::offset_repr_keys<std::uint8_t>>::value);
|
||||
static_assert(!has_member_center<grotto::offset_twist_keys<std::uint8_t>>::value);
|
||||
|
||||
std::vector<std::uint8_t> none;
|
||||
auto empty = dpf::geneval_ic(std::uint8_t{1}, std::uint8_t{2},
|
||||
none.begin(), none.end(),
|
||||
dpf::ds_randomness<decltype(&dpf::uniform_sample<simde__m128i>), IcPad>{
|
||||
&dpf::uniform_sample<simde__m128i>, {}},
|
||||
dpf::ic(std::uint8_t{0}, std::uint8_t{4}, std::uint32_t{1}, std::uint32_t{0}));
|
||||
EXPECT_FALSE(dpf::verify(empty.proof0, empty.proof1));
|
||||
}
|
||||
|
||||
TEST(ClassSweep, ConstrainedComparisonAbortsUnlessAdjacent)
|
||||
{
|
||||
const std::uint64_t samples[][2] = {
|
||||
{0, 0}, {0, 2}, {5, 8}, {100, 0},
|
||||
{std::numeric_limits<std::uint64_t>::max(), 0},
|
||||
{20, 20},
|
||||
};
|
||||
for (const auto & pair : samples)
|
||||
EXPECT_THROW(dpf::local_ccmp(pair[0], pair[1]), std::invalid_argument)
|
||||
<< pair[0] << "," << pair[1];
|
||||
|
||||
EXPECT_NO_THROW(dpf::local_ccmp(4, 5));
|
||||
EXPECT_NO_THROW(dpf::local_ccmp(5, 4));
|
||||
EXPECT_NO_THROW(dpf::local_ccmp(0, 1));
|
||||
EXPECT_NO_THROW(dpf::local_ccmp(
|
||||
std::numeric_limits<std::uint64_t>::max(),
|
||||
std::numeric_limits<std::uint64_t>::max() - 1));
|
||||
}
|
||||
|
||||
TEST(ClassSweep, SignedJetMatchesClearForEveryDegreeAtLeastTwo)
|
||||
{
|
||||
const std::int8_t centers[] = {-40, -7, -1, 3};
|
||||
const int etas[] = {-12, 0, 5, 18};
|
||||
for (std::size_t degree = 2; degree <= 3; ++degree)
|
||||
{
|
||||
std::vector<std::uint64_t> coeff(degree + 1, 1);
|
||||
coeff[2] = 3;
|
||||
const std::vector<std::int8_t> knots{std::numeric_limits<std::int8_t>::min()};
|
||||
for (std::int8_t center : centers)
|
||||
{
|
||||
const auto mat = grotto::make_offset_jet_keys<std::int8_t>(center, degree);
|
||||
for (int eta : etas)
|
||||
{
|
||||
const auto e = static_cast<std::int8_t>(eta);
|
||||
const auto s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
|
||||
const auto s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
|
||||
const auto clear = grotto::offset_jet_clear<std::int8_t>(
|
||||
center, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear)
|
||||
<< "degree=" << degree << " center=" << int(center) << " eta=" << eta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClassSweep, DyadicShiftMatchesClearAcrossDegrees)
|
||||
{
|
||||
const std::uint8_t centers[] = {1, 4, 200};
|
||||
const int etas[] = {0, 3, 39, 100};
|
||||
for (std::size_t degree = 0; degree <= 2; ++degree)
|
||||
{
|
||||
std::vector<std::uint64_t> coeff(degree + 1, 1);
|
||||
if (degree >= 1)
|
||||
coeff[1] = 3;
|
||||
const std::vector<std::uint8_t> knots{0};
|
||||
for (std::uint8_t center : centers)
|
||||
{
|
||||
const auto mat = grotto::make_offset_twist_keys<std::uint8_t>(
|
||||
center, degree, grotto::twist_half);
|
||||
for (int eta : etas)
|
||||
{
|
||||
const auto e = static_cast<std::uint8_t>(eta);
|
||||
const auto s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, e);
|
||||
const auto s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, e);
|
||||
const auto clear = grotto::offset_twist_clear(
|
||||
center, grotto::twist_half, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear)
|
||||
<< "degree=" << degree << " center=" << int(center) << " eta=" << eta;
|
||||
EXPECT_NE(s0, clear);
|
||||
EXPECT_NE(s1, clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClassSweep, HornerSharesMatchClearAndHideTheCenter)
|
||||
{
|
||||
constexpr std::size_t D = 2;
|
||||
const std::uint8_t center = 9;
|
||||
const auto mat = grotto::make_offset_horner_keys<std::uint8_t, D>(center);
|
||||
const std::vector<std::uint8_t> knots{0, 40};
|
||||
const std::vector<std::array<std::uint64_t, D + 1>> coeff{
|
||||
{1, 2, 0},
|
||||
{4, 0, 1},
|
||||
};
|
||||
for (std::uint8_t eta : {std::uint8_t{0}, std::uint8_t{3}, std::uint8_t{70}})
|
||||
{
|
||||
const auto s0 = grotto::offset_horner_eval<0, D>(mat, knots, coeff, eta);
|
||||
const auto s1 = grotto::offset_horner_eval<1, D>(mat, knots, coeff, eta);
|
||||
const auto clear = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||||
EXPECT_EQ(s0 + s1, clear) << int(eta);
|
||||
}
|
||||
}
|
||||
151
test/tests/closed_form_test.cpp
Normal file
151
test/tests/closed_form_test.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/closed_form.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::int64_t raw_of(long double x, unsigned k)
|
||||
{
|
||||
return std::llround(std::ldexp(x, static_cast<int>(k)));
|
||||
}
|
||||
|
||||
long double truth(grotto::closed which, long double x)
|
||||
{
|
||||
switch (which)
|
||||
{
|
||||
case grotto::closed::atanh: return std::atanh(x);
|
||||
case grotto::closed::asinh: return std::asinh(x);
|
||||
case grotto::closed::acosh: return std::acosh(x);
|
||||
case grotto::closed::atan: return std::atan(x);
|
||||
case grotto::closed::acot: return 1.57079632679489661923L - std::atan(x);
|
||||
case grotto::closed::asec: return std::acos(1.0L / x);
|
||||
case grotto::closed::acsc: return std::asin(1.0L / x);
|
||||
case grotto::closed::asech: return std::acosh(1.0L / x);
|
||||
case grotto::closed::acsch: return std::asinh(1.0L / x);
|
||||
case grotto::closed::acoth: return std::atanh(1.0L / x);
|
||||
case grotto::closed::selu:
|
||||
return 1.0507009873554804934L * (x > 0 ? x : 1.6732632423543772848L * std::expm1(x));
|
||||
case grotto::closed::elu:
|
||||
case grotto::closed::celu:
|
||||
return x > 0 ? x : std::expm1(x);
|
||||
case grotto::closed::softsign: return x / (1.0L + std::fabsl(x));
|
||||
case grotto::closed::tanhshrink: return x - std::tanh(x);
|
||||
case grotto::closed::logistic: return std::log(x / (1.0L - x));
|
||||
case grotto::closed::exponential: return -std::log(1.0L - x);
|
||||
case grotto::closed::laplace:
|
||||
return x <= 0.5L ? std::log(2.0L * x) : -std::log(2.0L * (1.0L - x));
|
||||
case grotto::closed::cauchy: return std::tan(3.14159265358979323846L * (x - 0.5L));
|
||||
case grotto::closed::sinc: return x == 0 ? 1.0L : std::sin(x) / x;
|
||||
case grotto::closed::cbrt: return std::cbrt(x);
|
||||
case grotto::closed::qtrt: return std::sqrt(std::sqrt(x));
|
||||
case grotto::closed::icbrt: return 1.0L / std::cbrt(x);
|
||||
case grotto::closed::iqtrt: return 1.0L / std::sqrt(std::sqrt(x));
|
||||
case grotto::closed::pow_m01: return std::exp(-0.1L * std::log(x));
|
||||
case grotto::closed::pow_p15: return x * std::sqrt(x);
|
||||
case grotto::closed::pow_m3: return 1.0L / (x * x * x);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void expect_ulps(grotto::closed which, unsigned k, long double x, long double ulps)
|
||||
{
|
||||
const std::int64_t raw = raw_of(x, k);
|
||||
std::int64_t got = 0;
|
||||
ASSERT_NO_THROW(got = grotto::eval_closed(which, k, raw)) << static_cast<int>(which) << " x=" << static_cast<double>(x);
|
||||
const long double xr = std::ldexp(static_cast<long double>(raw), -static_cast<int>(k));
|
||||
const long double want = truth(which, xr) * std::ldexp(1.0L, static_cast<int>(k));
|
||||
EXPECT_LE(std::fabsl(static_cast<long double>(got) - want), ulps)
|
||||
<< static_cast<int>(which) << " k=" << k << " x=" << static_cast<double>(x)
|
||||
<< " got=" << got << " want=" << static_cast<double>(want);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ClosedForm, InverseHyperbolicsAndTrig)
|
||||
{
|
||||
for (unsigned k : {16u, 32u})
|
||||
{
|
||||
for (long double x : {-0.6L, -0.2L, 0.2L, 0.6L})
|
||||
expect_ulps(grotto::closed::atanh, k, x, 8.0L);
|
||||
for (long double x : {-2.0L, -0.5L, 0.3L, 1.5L, 4.0L})
|
||||
expect_ulps(grotto::closed::asinh, k, x, 16.0L);
|
||||
for (long double x : {1.0L, 1.5L, 3.0L})
|
||||
expect_ulps(grotto::closed::acosh, k, x, 16.0L);
|
||||
for (long double x : {-2.0L, -0.4L, 0.0L, 0.5L, 1.0L, 3.0L})
|
||||
expect_ulps(grotto::closed::atan, k, x, 8.0L);
|
||||
for (long double x : {-1.5L, 0.4L, 2.0L})
|
||||
expect_ulps(grotto::closed::acot, k, x, 8.0L);
|
||||
for (long double x : {-2.0L, -1.2L, 1.2L, 3.0L})
|
||||
{
|
||||
expect_ulps(grotto::closed::asec, k, x, 8.0L);
|
||||
expect_ulps(grotto::closed::acsc, k, x, 8.0L);
|
||||
}
|
||||
for (long double x : {0.2L, 0.5L, 1.0L})
|
||||
expect_ulps(grotto::closed::asech, k, x, 16.0L);
|
||||
for (long double x : {-1.5L, -0.4L, 0.4L, 2.0L})
|
||||
expect_ulps(grotto::closed::acsch, k, x, 16.0L);
|
||||
for (long double x : {-2.0L, 1.4L, 3.0L})
|
||||
expect_ulps(grotto::closed::acoth, k, x, 12.0L);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClosedForm, ActivationsQuantilesAndSinc)
|
||||
{
|
||||
for (unsigned k : {16u, 32u})
|
||||
{
|
||||
for (long double x : {-1.5L, -0.2L, 0.0L, 0.4L, 2.0L})
|
||||
{
|
||||
expect_ulps(grotto::closed::elu, k, x, 8.0L);
|
||||
expect_ulps(grotto::closed::celu, k, x, 8.0L);
|
||||
expect_ulps(grotto::closed::selu, k, x, 16.0L);
|
||||
expect_ulps(grotto::closed::softsign, k, x, 4.0L);
|
||||
expect_ulps(grotto::closed::tanhshrink, k, x, 8.0L);
|
||||
expect_ulps(grotto::closed::sinc, k, x, 1.0L);
|
||||
}
|
||||
for (long double p : {0.1L, 0.3L, 0.5L, 0.8L})
|
||||
{
|
||||
expect_ulps(grotto::closed::logistic, k, p, 12.0L);
|
||||
expect_ulps(grotto::closed::exponential, k, p, 12.0L);
|
||||
expect_ulps(grotto::closed::laplace, k, p, 12.0L);
|
||||
}
|
||||
for (long double p : {0.2L, 0.4L, 0.6L, 0.8L})
|
||||
expect_ulps(grotto::closed::cauchy, k, p, 16.0L);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClosedForm, ExtraPowers)
|
||||
{
|
||||
for (unsigned k : {16u, 32u})
|
||||
{
|
||||
for (long double x : {-8.0L, -1.0L, 0.5L, 1.0L, 2.0L, 8.0L})
|
||||
{
|
||||
expect_ulps(grotto::closed::cbrt, k, x, 24.0L);
|
||||
expect_ulps(grotto::closed::icbrt, k, x, 1.0L);
|
||||
expect_ulps(grotto::closed::pow_m3, k, x, 16.0L);
|
||||
}
|
||||
for (long double x : {0.25L, 0.5L, 1.0L, 2.0L, 9.0L, 81.0L})
|
||||
{
|
||||
expect_ulps(grotto::closed::qtrt, k, x, 24.0L);
|
||||
expect_ulps(grotto::closed::iqtrt, k, x, 32.0L);
|
||||
expect_ulps(grotto::closed::pow_m01, k, x, 24.0L);
|
||||
expect_ulps(grotto::closed::pow_p15, k, x, 1.0L);
|
||||
}
|
||||
expect_ulps(grotto::closed::icbrt, k, std::ldexp(1.0L, -8), 1.0L);
|
||||
expect_ulps(grotto::closed::icbrt, k, 100.0L, 1.0L);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClosedForm, RejectsPolesAndBadPrecision)
|
||||
{
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::atanh, 16, 1 << 16), std::domain_error);
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::acosh, 16, 0), std::domain_error);
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::qtrt, 16, -4), std::domain_error);
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::pow_m3, 16, 0), std::domain_error);
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::logistic, 16, 0), std::domain_error);
|
||||
EXPECT_THROW(grotto::eval_closed(grotto::closed::sinc, 7, 1), std::invalid_argument);
|
||||
}
|
||||
252
test/tests/cohort_test.cpp
Normal file
252
test/tests/cohort_test.cpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using in_type = std::uint8_t;
|
||||
using out_type = std::uint64_t;
|
||||
|
||||
template <typename T>
|
||||
out_type raw_of(const T & v)
|
||||
{
|
||||
if constexpr (dpf::is_secret_share_v<std::decay_t<T>>)
|
||||
return static_cast<out_type>(v.raw());
|
||||
else
|
||||
return static_cast<out_type>(v);
|
||||
}
|
||||
|
||||
out_type open(out_type a, out_type b)
|
||||
{
|
||||
return static_cast<out_type>(a - b);
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
std::vector<out_type> interval_alone(const Key & key, in_type from, in_type to)
|
||||
{
|
||||
auto buf = dpf::make_output_buffer_for_interval(key, from, to);
|
||||
dpf::eval_interval(key, from, to, buf);
|
||||
std::vector<out_type> v;
|
||||
v.reserve(buf.size());
|
||||
for (std::size_t i = 0; i < buf.size(); ++i)
|
||||
v.push_back(raw_of(buf[i]));
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Cohort, SamePointGenAndPointEval)
|
||||
{
|
||||
const in_type alpha = 7;
|
||||
const std::vector<out_type> beta{1, 2, 3, 4, 5};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort(alpha, beta.begin(), beta.end());
|
||||
ASSERT_EQ(c0.size(), beta.size());
|
||||
|
||||
std::vector<out_type> y0, y1;
|
||||
c0.eval_point(alpha, y0);
|
||||
c1.eval_point(alpha, y1);
|
||||
ASSERT_EQ(y0.size(), beta.size());
|
||||
for (std::size_t k = 0; k < beta.size(); ++k)
|
||||
{
|
||||
EXPECT_EQ(open(y0[k], y1[k]), beta[k]);
|
||||
EXPECT_EQ(y0[k], raw_of(*dpf::eval_point(c0.keys()[k], alpha)));
|
||||
EXPECT_EQ(y1[k], raw_of(*dpf::eval_point(c1.keys()[k], alpha)));
|
||||
}
|
||||
for (std::size_t k = 0; k < beta.size(); ++k)
|
||||
{
|
||||
const in_type other = static_cast<in_type>(alpha + 1 + k);
|
||||
std::vector<out_type> z0, z1;
|
||||
c0.eval_point(other, z0);
|
||||
c1.eval_point(other, z1);
|
||||
EXPECT_EQ(open(z0[k], z1[k]), 0u);
|
||||
EXPECT_EQ(z0[k], raw_of(*dpf::eval_point(c0.keys()[k], other)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Cohort, IntervalInterleavesLeaves)
|
||||
{
|
||||
const in_type alpha = 9;
|
||||
const std::vector<out_type> beta{4, 8, 15, 16};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort(alpha, beta.begin(), beta.end());
|
||||
const in_type from = 0, to = 15;
|
||||
std::vector<out_type> y0, y1;
|
||||
c0.eval_interval(from, to, y0);
|
||||
c1.eval_interval(from, to, y1);
|
||||
|
||||
using key0 = std::decay_t<decltype(c0.keys()[0])>;
|
||||
constexpr std::size_t opl = key0::outputs_per_leaf;
|
||||
const std::size_t n = beta.size();
|
||||
const auto alone0 = interval_alone(c0.keys()[0], from, to);
|
||||
ASSERT_EQ(y0.size(), alone0.size() * n);
|
||||
ASSERT_EQ(alone0.size() % opl, 0u);
|
||||
|
||||
for (std::size_t k = 0; k < n; ++k)
|
||||
{
|
||||
const auto a0 = interval_alone(c0.keys()[k], from, to);
|
||||
const auto a1 = interval_alone(c1.keys()[k], from, to);
|
||||
ASSERT_EQ(a0.size(), alone0.size());
|
||||
for (std::size_t i = 0; i < a0.size(); ++i)
|
||||
{
|
||||
const std::size_t node = i / opl;
|
||||
const std::size_t lane = i % opl;
|
||||
const std::size_t slot = (node * n + k) * opl + lane;
|
||||
EXPECT_EQ(y0[slot], a0[i]);
|
||||
EXPECT_EQ(y1[slot], a1[i]);
|
||||
const in_type x = static_cast<in_type>(from + i);
|
||||
const out_type opened = open(a0[i], a1[i]);
|
||||
if (x == alpha)
|
||||
EXPECT_EQ(opened, beta[k]);
|
||||
else
|
||||
EXPECT_EQ(opened, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Cohort, SequenceRecipeAndInnerProduct)
|
||||
{
|
||||
const in_type alpha = 5;
|
||||
const std::vector<out_type> beta{3, 9, 1};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort(alpha, beta.begin(), beta.end());
|
||||
const std::vector<in_type> points{1, 5, 5, 20, 40};
|
||||
const std::size_t n = beta.size();
|
||||
|
||||
std::vector<out_type> y0, y1;
|
||||
c0.eval_sequence(points.begin(), points.end(), y0);
|
||||
c1.eval_sequence(points.begin(), points.end(), y1);
|
||||
ASSERT_EQ(y0.size(), points.size() * n);
|
||||
|
||||
using key0 = std::decay_t<decltype(c0.keys()[0])>;
|
||||
auto recipe = dpf::make_sequence_recipe<key0>(
|
||||
points.begin(), points.end());
|
||||
std::vector<out_type> r0, r1;
|
||||
c0.eval_sequence(recipe, r0);
|
||||
c1.eval_sequence(recipe, r1);
|
||||
|
||||
for (std::size_t q = 0; q < points.size(); ++q)
|
||||
{
|
||||
for (std::size_t k = 0; k < n; ++k)
|
||||
{
|
||||
const std::size_t slot = dpf::cohort_index(q, k, n);
|
||||
const auto e0 = raw_of(*dpf::eval_point(c0.keys()[k], points[q]));
|
||||
const auto e1 = raw_of(*dpf::eval_point(c1.keys()[k], points[q]));
|
||||
EXPECT_EQ(y0[slot], e0);
|
||||
EXPECT_EQ(y1[slot], e1);
|
||||
EXPECT_EQ(r0[slot], e0);
|
||||
EXPECT_EQ(r1[slot], e1);
|
||||
if (points[q] == alpha)
|
||||
EXPECT_EQ(open(e0, e1), beta[k]);
|
||||
else
|
||||
EXPECT_EQ(open(e0, e1), 0u);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<out_type> w(points.size());
|
||||
for (std::size_t q = 0; q < w.size(); ++q)
|
||||
w[q] = q + 3;
|
||||
const auto s0 = c0.eval_sequence_inner_product(recipe, w);
|
||||
const auto s1 = c1.eval_sequence_inner_product(points.begin(), points.end(), w);
|
||||
ASSERT_EQ(s0.size(), n);
|
||||
for (std::size_t k = 0; k < n; ++k)
|
||||
{
|
||||
out_type acc0 = 0, acc1 = 0;
|
||||
for (std::size_t q = 0; q < points.size(); ++q)
|
||||
{
|
||||
acc0 = static_cast<out_type>(acc0
|
||||
+ y0[dpf::cohort_index(q, k, n)] * w[q]);
|
||||
acc1 = static_cast<out_type>(acc1
|
||||
+ y1[dpf::cohort_index(q, k, n)] * w[q]);
|
||||
}
|
||||
EXPECT_EQ(s0[k], acc0);
|
||||
EXPECT_EQ(s1[k], acc1);
|
||||
EXPECT_EQ(open(s0[k], s1[k]), beta[k] * (w[1] + w[2]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Cohort, IntervalInnerProduct)
|
||||
{
|
||||
const in_type alpha = 4;
|
||||
const std::vector<out_type> beta{6, 7};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort(alpha, beta.begin(), beta.end());
|
||||
const in_type from = 0, to = 15;
|
||||
const auto alone = interval_alone(c0.keys()[0], from, to);
|
||||
std::vector<out_type> w(alone.size());
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
w[i] = (i * 5u) + 1u;
|
||||
|
||||
const auto a0 = c0.eval_interval_inner_product(from, to, w);
|
||||
const auto a1 = c1.eval_interval_inner_product(from, to, w);
|
||||
for (std::size_t k = 0; k < beta.size(); ++k)
|
||||
{
|
||||
const auto b0 = interval_alone(c0.keys()[k], from, to);
|
||||
const auto b1 = interval_alone(c1.keys()[k], from, to);
|
||||
out_type e0 = 0;
|
||||
out_type e1 = 0;
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
{
|
||||
e0 = static_cast<out_type>(e0 + b0[i] * w[i]);
|
||||
e1 = static_cast<out_type>(e1 + b1[i] * w[i]);
|
||||
}
|
||||
EXPECT_EQ(a0[k], e0);
|
||||
EXPECT_EQ(a1[k], e1);
|
||||
|
||||
// Same weights against one-key batched leaf inner product.
|
||||
auto m0 = dpf::make_basic_interval_memoizer(c0.keys()[k], from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(c1.keys()[k], from, to);
|
||||
const auto ip0 = dpf::eval_inner_product(c0.keys()[k], from, to, w, m0);
|
||||
const auto ip1 = dpf::eval_inner_product(c1.keys()[k], from, to, w, m1);
|
||||
EXPECT_EQ(raw_of(ip0), a0[k]);
|
||||
EXPECT_EQ(raw_of(ip1), a1[k]);
|
||||
EXPECT_EQ(open(a0[k], a1[k]), beta[k] * w[alpha - from]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Cohort, InterleaveLeavesMatchesSequenceLayout)
|
||||
{
|
||||
// Sequence cohort layout is `out[q * n + k]`, the same order
|
||||
// `interleave_leaves` writes for whole leaf values.
|
||||
const in_type alpha = 6;
|
||||
const std::vector<out_type> beta{2, 3, 5};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort(alpha, beta.begin(), beta.end());
|
||||
(void)c1;
|
||||
const std::vector<in_type> pts{1, 6, 10, 20};
|
||||
const std::size_t n = beta.size();
|
||||
std::vector<out_type> cohort;
|
||||
c0.eval_sequence(pts.begin(), pts.end(), cohort);
|
||||
|
||||
std::vector<std::vector<out_type>> per_key(n);
|
||||
std::vector<const out_type *> ptrs(n);
|
||||
for (std::size_t k = 0; k < n; ++k)
|
||||
{
|
||||
per_key[k].resize(pts.size());
|
||||
for (std::size_t q = 0; q < pts.size(); ++q)
|
||||
per_key[k][q] = raw_of(*dpf::eval_point(c0.keys()[k], pts[q]));
|
||||
ptrs[k] = per_key[k].data();
|
||||
}
|
||||
std::vector<out_type> interleaved(pts.size() * n);
|
||||
dpf::interleave_leaves<out_type>(
|
||||
interleaved.data(), ptrs.data(), n, pts.size());
|
||||
ASSERT_EQ(interleaved.size(), cohort.size());
|
||||
for (std::size_t i = 0; i < interleaved.size(); ++i)
|
||||
EXPECT_EQ(interleaved[i], cohort[i]) << "slot " << i;
|
||||
}
|
||||
|
||||
TEST(Cohort, HalfTreeSamePoint)
|
||||
{
|
||||
using ht = dpf::prg::aes128_ccr;
|
||||
const in_type alpha = 3;
|
||||
const std::vector<out_type> beta{11, 13, 17, 19};
|
||||
auto [c0, c1] = dpf::make_dpf_cohort<ht, dpf::prg::aes128>(
|
||||
alpha, beta.begin(), beta.end());
|
||||
std::vector<out_type> y0, y1;
|
||||
c0.eval_point(alpha, y0);
|
||||
c1.eval_point(alpha, y1);
|
||||
for (std::size_t k = 0; k < beta.size(); ++k)
|
||||
{
|
||||
EXPECT_EQ(open(y0[k], y1[k]), beta[k]);
|
||||
EXPECT_EQ(y0[k], raw_of(*dpf::eval_point(c0.keys()[k], alpha)));
|
||||
}
|
||||
}
|
||||
1899
test/tests/compose_test.cpp
Normal file
1899
test/tests/compose_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/constant_lut.hpp"
|
||||
|
|
@ -258,10 +259,8 @@ bool dispatch_segments(const grotto::constant_lut<std::int8_t> & lut, std::int8_
|
|||
|
||||
TEST(ConstantLut, RejectsFractionalBitsPastTheRawWidth)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_exact_constant_lut<std::int8_t>(exact_constant::signum, 9),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 65),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_exact_constant_lut<std::int8_t>(exact_constant::signum, 9), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 65), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(ConstantLut, PaperPartCountsOnInt64With16FractionalBits)
|
||||
|
|
@ -714,7 +713,6 @@ TEST(ConstantLut, ClippedQuotientIntervalAndThreshold)
|
|||
|
||||
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int16_t>(0, -1, 1), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int16_t>(1, 4, -4), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int32_t>(1, -100000, 100000),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int32_t>(1, -100000, 100000), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_interval_lut<std::int8_t>(2, -2), std::invalid_argument);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/prefix_parity.hpp"
|
||||
|
|
|
|||
|
|
@ -109,10 +109,8 @@ TEST(CornerGaps, SaturatedUint64LeafCount)
|
|||
using key_t = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, in_t, out_t>;
|
||||
EXPECT_EQ((dpf::utils::get_nodes_in_interval<key_t>(in_t{1}, ~in_t{0})),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{0}, ~in_t{0})),
|
||||
std::length_error);
|
||||
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{5}, in_t{4})),
|
||||
std::length_error);
|
||||
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{0}, ~in_t{0})), std::length_error);
|
||||
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{5}, in_t{4})), std::length_error);
|
||||
}
|
||||
|
||||
TEST(CornerGaps, MemoizerRejectsALargerInterval)
|
||||
|
|
@ -121,8 +119,7 @@ TEST(CornerGaps, MemoizerRejectsALargerInterval)
|
|||
using key_t = std::decay_t<decltype(k0)>;
|
||||
auto memo = dpf::make_basic_interval_memoizer<key_t>(uint8_t{0}, uint8_t{10});
|
||||
auto buf = dpf::make_output_buffer_for_interval<key_t>(uint8_t{0}, uint8_t{100});
|
||||
EXPECT_THROW(dpf::eval_interval(k0, uint8_t{0}, uint8_t{100}, buf, memo),
|
||||
std::length_error);
|
||||
EXPECT_THROW(dpf::eval_interval(k0, uint8_t{0}, uint8_t{100}, buf, memo), std::length_error);
|
||||
(void)k1;
|
||||
}
|
||||
|
||||
|
|
@ -387,7 +384,6 @@ TEST(CornerGaps, PrgRejectsUint32Seam)
|
|||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
EXPECT_THROW((dpf::randomness::detail::lane_codec<dpf::prg::aes128, simde__m128i>::fill(
|
||||
seed, static_cast<std::uint64_t>(UINT32_MAX) - 1u, out, 4)),
|
||||
std::invalid_argument);
|
||||
seed, static_cast<std::uint64_t>(UINT32_MAX) - 1u, out, 4)), std::invalid_argument);
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
}
|
||||
|
|
|
|||
611
test/tests/defer_eval_test.cpp
Normal file
611
test/tests/defer_eval_test.cpp
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename A, typename B>
|
||||
auto recon(const A & a, const B & b)
|
||||
{
|
||||
if constexpr (dpf::is_secret_share_v<std::decay_t<A>>
|
||||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1, typename InputT>
|
||||
void assign_input_local(Key0 & k0, Key1 & k1, InputT alpha)
|
||||
{
|
||||
using input_type = InputT;
|
||||
const input_type a0 = static_cast<input_type>(0x12);
|
||||
const input_type a1 = static_cast<input_type>(alpha - a0);
|
||||
const auto sh0 = k0.offset_x.compute_and_get_share(a0);
|
||||
const auto sh1 = k1.offset_x.compute_and_get_share(a1);
|
||||
k0.offset_x.reconstruct(sh1);
|
||||
k1.offset_x.reconstruct(sh0);
|
||||
}
|
||||
|
||||
template <typename Def0, typename Def1, typename Eager0, typename Eager1>
|
||||
void expect_recon_equal(Def0 && deferred0, Def1 && deferred1,
|
||||
Eager0 && eager0, Eager1 && eager1)
|
||||
{
|
||||
auto it_a = std::begin(deferred0);
|
||||
auto it_b = std::begin(deferred1);
|
||||
auto it_c = std::begin(eager0);
|
||||
auto it_d = std::begin(eager1);
|
||||
const auto end_a = std::end(deferred0);
|
||||
std::size_t n = 0;
|
||||
while (it_a != end_a)
|
||||
{
|
||||
ASSERT_NE(it_b, std::end(deferred1)) << "at index " << n;
|
||||
ASSERT_NE(it_c, std::end(eager0)) << "at index " << n;
|
||||
ASSERT_NE(it_d, std::end(eager1)) << "at index " << n;
|
||||
EXPECT_EQ(recon(*it_a, *it_b), recon(*it_c, *it_d)) << "at index " << n;
|
||||
++it_a;
|
||||
++it_b;
|
||||
++it_c;
|
||||
++it_d;
|
||||
++n;
|
||||
}
|
||||
EXPECT_EQ(it_b, std::end(deferred1));
|
||||
EXPECT_EQ(it_c, std::end(eager0));
|
||||
EXPECT_EQ(it_d, std::end(eager1));
|
||||
EXPECT_GT(n, std::size_t{0});
|
||||
}
|
||||
|
||||
template <typename InputT>
|
||||
std::size_t inclusive_span(InputT from, InputT to)
|
||||
{
|
||||
constexpr auto bits = dpf::utils::bitlength_of_v<InputT>;
|
||||
constexpr auto to_int = dpf::utils::to_integral_type<InputT>{};
|
||||
auto span = to_int(to) - to_int(from);
|
||||
if constexpr (bits < dpf::utils::bitlength_of_v<decltype(span)>)
|
||||
span &= (decltype(span){1} << bits) - 1;
|
||||
return static_cast<std::size_t>(span) + 1;
|
||||
}
|
||||
|
||||
template <typename View0, typename View1, typename InputT, typename OutputT>
|
||||
void expect_point_mass(View0 && v0, View1 && v1, InputT from, InputT to,
|
||||
InputT alpha, OutputT beta)
|
||||
{
|
||||
auto it0 = std::begin(v0);
|
||||
auto it1 = std::begin(v1);
|
||||
bool saw = false;
|
||||
InputT x = from;
|
||||
for (std::size_t i = 0; i < inclusive_span(from, to); ++i)
|
||||
{
|
||||
ASSERT_NE(it0, std::end(v0));
|
||||
ASSERT_NE(it1, std::end(v1));
|
||||
const auto y = recon(*it0, *it1);
|
||||
if (x == alpha)
|
||||
{
|
||||
EXPECT_EQ(y, beta) << "x=" << +x;
|
||||
saw = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_EQ(y, OutputT{0}) << "x=" << +x;
|
||||
}
|
||||
++it0;
|
||||
++it1;
|
||||
++x;
|
||||
}
|
||||
EXPECT_EQ(it0, std::end(v0));
|
||||
EXPECT_EQ(it1, std::end(v1));
|
||||
bool expect_hit = false;
|
||||
if constexpr (std::is_unsigned_v<InputT>)
|
||||
{
|
||||
if (from <= to)
|
||||
expect_hit = (alpha >= from && alpha <= to);
|
||||
else
|
||||
expect_hit = (alpha >= from) || (alpha <= to);
|
||||
}
|
||||
else
|
||||
{
|
||||
expect_hit = (alpha >= from && alpha <= to);
|
||||
}
|
||||
EXPECT_EQ(saw, expect_hit);
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1, typename InputT, typename OutputT>
|
||||
void compare_deferred_interval(Key0 & d0, Key1 & d1, InputT from, InputT to,
|
||||
InputT alpha, OutputT beta)
|
||||
{
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_interval(d0, from, to, buf0);
|
||||
auto deferred1 = dpf::defer_eval_interval(d1, from, to, buf1);
|
||||
|
||||
assign_input_local(d0, d1, alpha);
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_interval(d0, from, to);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_interval(d1, from, to);
|
||||
auto eager_memo0 = dpf::make_basic_full_memoizer(d0);
|
||||
auto eager_memo1 = dpf::make_basic_full_memoizer(d1);
|
||||
auto eager0 = dpf::eval_interval(d0, from, to, eager_buf0, eager_memo0);
|
||||
auto eager1 = dpf::eval_interval(d1, from, to, eager_buf1, eager_memo1);
|
||||
|
||||
auto view0 = deferred0.get();
|
||||
auto view1 = deferred1.get();
|
||||
expect_recon_equal(view0, view1, eager0, eager1);
|
||||
expect_point_mass(view0, view1, from, to, alpha, beta);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Happy paths already covered lightly; keep regressions + expand corners.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, IntervalMatchesEagerAfterAssignUint8)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{7});
|
||||
compare_deferred_interval(d0, d1, input_type{0x10}, input_type{0x40},
|
||||
input_type{0x2A}, output_type{7});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, FullMatchesEagerAfterAssignUint8)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint64_t;
|
||||
constexpr output_type beta{0x1111};
|
||||
constexpr input_type alpha{0x33};
|
||||
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta);
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_full(d0, buf0);
|
||||
auto deferred1 = dpf::defer_eval_full(d1, buf1);
|
||||
assign_input_local(d0, d1, alpha);
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto eager0 = dpf::eval_full(d0, eager_buf0);
|
||||
auto eager1 = dpf::eval_full(d1, eager_buf1);
|
||||
auto view0 = deferred0.get();
|
||||
auto view1 = deferred1.get();
|
||||
expect_recon_equal(view0, view1, eager0, eager1);
|
||||
expect_point_mass(view0, view1,
|
||||
std::numeric_limits<input_type>::min(),
|
||||
std::numeric_limits<input_type>::max(), alpha, beta);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, IntervalMatchesEagerAfterAssignSigned)
|
||||
{
|
||||
using input_type = std::int8_t;
|
||||
using output_type = std::uint32_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{9});
|
||||
compare_deferred_interval(d0, d1, input_type{-40}, input_type{10},
|
||||
input_type{-20}, output_type{9});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, MultiOutputLeafMatchesEager)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint64_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{0xABCDEF0123456789ull});
|
||||
ASSERT_GT(decltype(d0)::outputs_per_leaf, std::size_t{1});
|
||||
compare_deferred_interval(d0, d1, input_type{0x70}, input_type{0x8F},
|
||||
input_type{0x7E}, output_type{0xABCDEF0123456789ull});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assert / misuse corners
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, GetBeforeAssignThrows)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
(void)d1;
|
||||
auto buf = dpf::make_output_buffer_for_full(d0);
|
||||
auto deferred = dpf::defer_eval_interval(d0, input_type{0}, input_type{3},
|
||||
buf);
|
||||
EXPECT_THROW(static_cast<void>(deferred.get()), std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(deferred.begin()), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, DeferAfterAssignThrows)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
assign_input_local(d0, d1, input_type{9});
|
||||
auto buf = dpf::make_output_buffer_for_full(d0);
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(dpf::defer_eval_interval(d0, input_type{0},
|
||||
input_type{3}, buf)),
|
||||
std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(dpf::defer_eval_full(d0, buf)),
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, EagerEvalBeforeAssignThrows)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
(void)d1;
|
||||
EXPECT_THROW(static_cast<void>(dpf::eval_point(d0, input_type{0})),
|
||||
std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(dpf::eval_interval(d0, input_type{0},
|
||||
input_type{1})),
|
||||
std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(dpf::eval_full(d0)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, DeferTraverseIntervalRequiresAssignedInput)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
auto memo = dpf::make_basic_full_memoizer(d0);
|
||||
EXPECT_THROW(
|
||||
dpf::defer_traverse_interval(d0, input_type{0}, input_type{10}, memo),
|
||||
std::runtime_error);
|
||||
assign_input_local(d0, d1, input_type{4});
|
||||
EXPECT_NO_THROW(
|
||||
dpf::defer_traverse_interval(d0, input_type{0}, input_type{10}, memo));
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, DeferTraverseFullAllowsUnassignedInput)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{3});
|
||||
auto memo0 = dpf::make_basic_full_memoizer(d0);
|
||||
auto memo1 = dpf::make_basic_full_memoizer(d1);
|
||||
EXPECT_NO_THROW(dpf::defer_traverse_full(d0, memo0));
|
||||
EXPECT_NO_THROW(dpf::defer_traverse_full(d1, memo1));
|
||||
assign_input_local(d0, d1, input_type{0x55});
|
||||
EXPECT_EQ(recon(*dpf::eval_point(d0, input_type{0x55}),
|
||||
*dpf::eval_point(d1, input_type{0x55})),
|
||||
std::uint32_t{3});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Boundary / length / caching
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, SinglePointInterval)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
constexpr input_type alpha{0x77};
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{42});
|
||||
compare_deferred_interval(d0, d1, alpha, alpha, alpha, output_type{42});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, SpikeAtFromAndToBoundaries)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{5});
|
||||
compare_deferred_interval(d0, d1, input_type{0x20}, input_type{0x30},
|
||||
input_type{0x20}, output_type{5});
|
||||
}
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{6});
|
||||
compare_deferred_interval(d0, d1, input_type{0x20}, input_type{0x30},
|
||||
input_type{0x30}, output_type{6});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, SpikeOutsideIntervalIsZero)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{99});
|
||||
compare_deferred_interval(d0, d1, input_type{0x10}, input_type{0x20},
|
||||
input_type{0x80}, output_type{99});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, ViewLengthMatchesInclusiveSpan)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
constexpr input_type from{5};
|
||||
constexpr input_type to{12};
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_interval(d0, from, to, buf0);
|
||||
auto deferred1 = dpf::defer_eval_interval(d1, from, to, buf1);
|
||||
assign_input_local(d0, d1, input_type{7});
|
||||
auto view0 = deferred0.get();
|
||||
auto view1 = deferred1.get();
|
||||
EXPECT_EQ(static_cast<std::size_t>(std::distance(std::begin(view0),
|
||||
std::end(view0))),
|
||||
inclusive_span(from, to));
|
||||
EXPECT_EQ(static_cast<std::size_t>(std::distance(std::begin(view1),
|
||||
std::end(view1))),
|
||||
inclusive_span(from, to));
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, GetCachesRotationSecondCallMatches)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{11});
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_interval(d0, input_type{1}, input_type{20},
|
||||
buf0);
|
||||
auto deferred1 = dpf::defer_eval_interval(d1, input_type{1}, input_type{20},
|
||||
buf1);
|
||||
assign_input_local(d0, d1, input_type{9});
|
||||
auto a0 = deferred0.get();
|
||||
auto a1 = deferred1.get();
|
||||
auto b0 = deferred0.get();
|
||||
auto b1 = deferred1.get();
|
||||
expect_recon_equal(a0, a1, b0, b1);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, BeginEndOnDeferredObject)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{2});
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_interval(d0, input_type{0}, input_type{4},
|
||||
buf0);
|
||||
auto deferred1 = dpf::defer_eval_interval(d1, input_type{0}, input_type{4},
|
||||
buf1);
|
||||
assign_input_local(d0, d1, input_type{2});
|
||||
std::size_t n = 0;
|
||||
auto it0 = deferred0.begin();
|
||||
auto it1 = deferred1.begin();
|
||||
for (; it0 != deferred0.end(); ++it0, ++it1, ++n)
|
||||
(void)recon(*it0, *it1);
|
||||
EXPECT_EQ(it1, deferred1.end());
|
||||
EXPECT_EQ(n, inclusive_span(input_type{0}, input_type{4}));
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, DeferEvalIntervalMinMaxMatchesDeferFull)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
constexpr output_type beta{13};
|
||||
constexpr input_type alpha{0x01};
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta);
|
||||
|
||||
auto buf_i0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf_i1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto buf_f0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf_f1 = dpf::make_output_buffer_for_full(d1);
|
||||
|
||||
auto as_interval0 = dpf::defer_eval_interval(d0,
|
||||
std::numeric_limits<input_type>::min(),
|
||||
std::numeric_limits<input_type>::max(), buf_i0);
|
||||
auto as_interval1 = dpf::defer_eval_interval(d1,
|
||||
std::numeric_limits<input_type>::min(),
|
||||
std::numeric_limits<input_type>::max(), buf_i1);
|
||||
auto as_full0 = dpf::defer_eval_full(d0, buf_f0);
|
||||
auto as_full1 = dpf::defer_eval_full(d1, buf_f1);
|
||||
|
||||
assign_input_local(d0, d1, alpha);
|
||||
expect_recon_equal(as_interval0.get(), as_interval1.get(),
|
||||
as_full0.get(), as_full1.get());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wrapping intervals and offset stress
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, WrappingLogicalIntervalUint8)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
// [200, 10] wraps across 0.
|
||||
constexpr input_type from{200};
|
||||
constexpr input_type to{10};
|
||||
ASSERT_GT(from, to);
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{8});
|
||||
compare_deferred_interval(d0, d1, from, to, input_type{250},
|
||||
output_type{8});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, WrappingIntervalSpikeInLowHalf)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{4});
|
||||
compare_deferred_interval(d0, d1, input_type{200}, input_type{10},
|
||||
input_type{5}, std::uint32_t{4});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, ManyRandomOffsetsMatchEager)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint32_t;
|
||||
constexpr input_type from{30};
|
||||
constexpr input_type to{90};
|
||||
constexpr output_type beta{77};
|
||||
|
||||
// Sweep alphas; each keygen draws a fresh mask so offsets differ.
|
||||
for (unsigned a = 0; a < 256; a += 17)
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta);
|
||||
compare_deferred_interval(d0, d1, from, to,
|
||||
static_cast<input_type>(a), beta);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, SignedFullDomain)
|
||||
{
|
||||
using input_type = std::int8_t;
|
||||
using output_type = std::uint32_t;
|
||||
constexpr output_type beta{21};
|
||||
constexpr input_type alpha{-128};
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta);
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto deferred0 = dpf::defer_eval_full(d0, buf0);
|
||||
auto deferred1 = dpf::defer_eval_full(d1, buf1);
|
||||
assign_input_local(d0, d1, alpha);
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto eager0 = dpf::eval_full(d0, eager_buf0);
|
||||
auto eager1 = dpf::eval_full(d1, eager_buf1);
|
||||
expect_recon_equal(deferred0.get(), deferred1.get(), eager0, eager1);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, SignedSpanCrossingZero)
|
||||
{
|
||||
using input_type = std::int8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{3});
|
||||
compare_deferred_interval(d0, d1, input_type{-5}, input_type{5},
|
||||
input_type{0}, std::uint32_t{3});
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, UnalignedMultiOutputLeafInterval)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using output_type = std::uint64_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
output_type{0x55});
|
||||
ASSERT_GT(decltype(d0)::outputs_per_leaf, std::size_t{1});
|
||||
// Odd endpoints: not leaf-aligned when opl == 2.
|
||||
compare_deferred_interval(d0, d1, input_type{0x11}, input_type{0x2A},
|
||||
input_type{0x1F}, output_type{0x55});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Multi-output keys
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, TwoOutputSlotsIndependent)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
using out0 = std::uint32_t;
|
||||
using out1 = std::uint32_t;
|
||||
constexpr input_type alpha{0x44};
|
||||
constexpr out0 beta0{100};
|
||||
constexpr out1 beta1{200};
|
||||
constexpr input_type from{0x40};
|
||||
constexpr input_type to{0x50};
|
||||
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta0,
|
||||
beta1);
|
||||
|
||||
auto bufs0 = dpf::make_output_buffer_for_full<0, 1>(d0);
|
||||
auto bufs1 = dpf::make_output_buffer_for_full<0, 1>(d1);
|
||||
auto def0 = dpf::defer_eval_interval<0, 1>(d0, from, to, bufs0);
|
||||
auto def1 = dpf::defer_eval_interval<0, 1>(d1, from, to, bufs1);
|
||||
|
||||
assign_input_local(d0, d1, alpha);
|
||||
|
||||
auto eager_bufs0 = dpf::make_output_buffer_for_interval<0, 1>(d0, from, to);
|
||||
auto eager_bufs1 = dpf::make_output_buffer_for_interval<0, 1>(d1, from, to);
|
||||
auto memo0 = dpf::make_basic_full_memoizer(d0);
|
||||
auto memo1 = dpf::make_basic_full_memoizer(d1);
|
||||
auto eager0 = dpf::eval_interval<0, 1>(d0, from, to, eager_bufs0, memo0);
|
||||
auto eager1 = dpf::eval_interval<0, 1>(d1, from, to, eager_bufs1, memo1);
|
||||
|
||||
expect_recon_equal(std::get<0>(def0).get(), std::get<0>(def1).get(),
|
||||
std::get<0>(eager0), std::get<0>(eager1));
|
||||
expect_recon_equal(std::get<1>(def0).get(), std::get<1>(def1).get(),
|
||||
std::get<1>(eager0), std::get<1>(eager1));
|
||||
|
||||
expect_point_mass(std::get<0>(def0).get(), std::get<0>(def1).get(),
|
||||
from, to, alpha, beta0);
|
||||
expect_point_mass(std::get<1>(def0).get(), std::get<1>(def1).get(),
|
||||
from, to, alpha, beta1);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, SelectSecondOutputOnly)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
constexpr input_type alpha{0x08};
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1}, std::uint32_t{99});
|
||||
|
||||
auto buf0 = dpf::make_output_buffer_for_full<1>(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full<1>(d1);
|
||||
auto def0 = dpf::defer_eval_interval<1>(d0, input_type{0}, input_type{15},
|
||||
buf0);
|
||||
auto def1 = dpf::defer_eval_interval<1>(d1, input_type{0}, input_type{15},
|
||||
buf1);
|
||||
assign_input_local(d0, d1, alpha);
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_interval<1>(d0, input_type{0},
|
||||
input_type{15});
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_interval<1>(d1, input_type{0},
|
||||
input_type{15});
|
||||
auto memo0 = dpf::make_basic_full_memoizer(d0);
|
||||
auto memo1 = dpf::make_basic_full_memoizer(d1);
|
||||
auto eager0 = dpf::eval_interval<1>(d0, input_type{0}, input_type{15},
|
||||
eager_buf0, memo0);
|
||||
auto eager1 = dpf::eval_interval<1>(d1, input_type{0}, input_type{15},
|
||||
eager_buf1, memo1);
|
||||
expect_recon_equal(def0.get(), def1.get(), eager0, eager1);
|
||||
expect_point_mass(def0.get(), def1.get(), input_type{0}, input_type{15},
|
||||
alpha, std::uint32_t{99});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata on the deferred object
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferEvalTest, DeferredStoresFromToAndKey)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
(void)d1;
|
||||
auto buf = dpf::make_output_buffer_for_full(d0);
|
||||
auto deferred = dpf::defer_eval_interval(d0, input_type{3}, input_type{9},
|
||||
buf);
|
||||
EXPECT_EQ(deferred.from(), input_type{3});
|
||||
EXPECT_EQ(deferred.to(), input_type{9});
|
||||
EXPECT_EQ(&deferred.dpf(), &d0);
|
||||
}
|
||||
|
||||
TEST(DeferEvalTest, DomainMinMaxSpike)
|
||||
{
|
||||
using input_type = std::uint8_t;
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{1});
|
||||
compare_deferred_interval(d0, d1, input_type{0}, input_type{255},
|
||||
input_type{0}, std::uint32_t{1});
|
||||
}
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<input_type>{},
|
||||
std::uint32_t{2});
|
||||
compare_deferred_interval(d0, d1, input_type{0}, input_type{255},
|
||||
input_type{255}, std::uint32_t{2});
|
||||
}
|
||||
}
|
||||
156
test/tests/domain128_point_test.cpp
Normal file
156
test/tests/domain128_point_test.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Full AES-128 block payload: Boyle packing leaves lg(outputs_per_leaf) = 0,
|
||||
// so a 128-bit domain walks depth 128 (Express `domainSize` = 128).
|
||||
using input_t = simde_uint128;
|
||||
using output_t = simde_uint128;
|
||||
using dpf_key_t = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
|
||||
input_t, output_t>;
|
||||
|
||||
output_t make_payload()
|
||||
{
|
||||
output_t y{};
|
||||
auto * bytes = reinterpret_cast<unsigned char *>(&y);
|
||||
for (std::size_t i = 0; i < sizeof(y); ++i)
|
||||
bytes[i] = static_cast<unsigned char>(0xa0 + i);
|
||||
return y;
|
||||
}
|
||||
|
||||
input_t make_alpha()
|
||||
{
|
||||
// High bit set, plus a distinctive low pattern.
|
||||
return (input_t{1} << 127) | (input_t{0x0123456789abcdefull} << 64)
|
||||
| input_t{0xfedcba9876543210ull};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Domain128Point, DepthIs128ForFullBlockPayload)
|
||||
{
|
||||
static_assert(dpf::utils::bitlength_of_v<input_t> == 128);
|
||||
static_assert(dpf_key_t::lg_outputs_per_leaf == 0);
|
||||
static_assert(dpf_key_t::outputs_per_leaf == 1);
|
||||
static_assert(dpf_key_t::depth == 128);
|
||||
EXPECT_EQ(dpf_key_t::depth, 128u);
|
||||
EXPECT_EQ(dpf::utils::bitlength_of_v<input_t>, 128u);
|
||||
}
|
||||
|
||||
TEST(Domain128Point, PointEvalShares)
|
||||
{
|
||||
const input_t alpha = make_alpha();
|
||||
const output_t beta = make_payload();
|
||||
const output_t zero{};
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
EXPECT_EQ(k0.correction_words().size(), 128u);
|
||||
EXPECT_EQ(k1.correction_words().size(), 128u);
|
||||
EXPECT_EQ(std::decay_t<decltype(k0)>::depth, 128u);
|
||||
|
||||
auto open = [&](input_t x) {
|
||||
return dpf::reconstruct(*dpf::eval_point(k0, x),
|
||||
*dpf::eval_point(k1, x));
|
||||
};
|
||||
|
||||
// Programmed point: both parties' shares open to the payload.
|
||||
{
|
||||
const auto s0 = *dpf::eval_point(k0, alpha);
|
||||
const auto s1 = *dpf::eval_point(k1, alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), beta);
|
||||
EXPECT_EQ(open(alpha), beta);
|
||||
}
|
||||
|
||||
const std::vector<input_t> off_points = {
|
||||
input_t{0},
|
||||
input_t{1},
|
||||
alpha + 1,
|
||||
alpha - 1,
|
||||
input_t{1} << 127,
|
||||
(input_t{1} << 127) | input_t{1},
|
||||
alpha ^ (input_t{1} << 64),
|
||||
alpha ^ input_t{1},
|
||||
~input_t{0},
|
||||
};
|
||||
|
||||
for (const input_t x : off_points)
|
||||
{
|
||||
if (x == alpha)
|
||||
continue;
|
||||
const auto s0 = *dpf::eval_point(k0, x);
|
||||
const auto s1 = *dpf::eval_point(k1, x);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), zero) << "off-point open";
|
||||
}
|
||||
|
||||
// Every individual bit of alpha must be on the path.
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
const input_t flipped = alpha ^ (input_t{1} << i);
|
||||
const auto s0 = *dpf::eval_point(k0, flipped);
|
||||
const auto s1 = *dpf::eval_point(k1, flipped);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), zero) << "bit " << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Domain128Point, Uint128ClassInput)
|
||||
{
|
||||
using in_t = uint128_t;
|
||||
using out_t = simde_uint128;
|
||||
using kt = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
|
||||
in_t, out_t>;
|
||||
static_assert(kt::depth == 128);
|
||||
static_assert(dpf::utils::bitlength_of_v<in_t> == 128);
|
||||
|
||||
// uint128_t(upper, lower): bit 127 set.
|
||||
const in_t alpha{std::uint64_t{1} << 63, 0};
|
||||
out_t beta{};
|
||||
std::memset(&beta, 0x5c, sizeof(beta));
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
EXPECT_EQ(k0.correction_words().size(), 128u);
|
||||
|
||||
const auto s0 = *dpf::eval_point(k0, alpha);
|
||||
const auto s1 = *dpf::eval_point(k1, alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), beta);
|
||||
|
||||
const in_t neigh = alpha + in_t{1};
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, neigh),
|
||||
*dpf::eval_point(k1, neigh)), out_t{});
|
||||
}
|
||||
|
||||
TEST(Domain128Point, FullDomainExpansionThrows)
|
||||
{
|
||||
const input_t alpha = make_alpha();
|
||||
const output_t beta = make_payload();
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
(void)k1;
|
||||
|
||||
EXPECT_THROW(
|
||||
(void)dpf::make_output_buffer_for_full(k0),
|
||||
std::length_error);
|
||||
|
||||
EXPECT_THROW(
|
||||
(void)dpf::eval_full(k0),
|
||||
std::length_error);
|
||||
|
||||
EXPECT_THROW(
|
||||
(void)dpf::eval_interval(k0,
|
||||
std::numeric_limits<input_t>::min(),
|
||||
std::numeric_limits<input_t>::max()),
|
||||
std::length_error);
|
||||
|
||||
EXPECT_THROW(
|
||||
(void)dpf::make_basic_full_memoizer(k0),
|
||||
std::length_error);
|
||||
}
|
||||
757
test/tests/dpf3_test.cpp
Normal file
757
test/tests/dpf3_test.cpp
Normal file
|
|
@ -0,0 +1,757 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/dpf3_ds.hpp"
|
||||
#include "dpf/prg_aes_ccr.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::fp61;
|
||||
|
||||
fp61 open3(fp61 a, fp61 b, fp61 c)
|
||||
{
|
||||
return dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{1, a}, dpf::shamir3::share{2, b},
|
||||
dpf::shamir3::share{3, c});
|
||||
}
|
||||
|
||||
/// F_DPF3CMP: complementary DCF halves (party 1 = k0, party 2 = k1).
|
||||
fp61 open_cmp(std::uint64_t k0_half, std::uint64_t k1_half)
|
||||
{
|
||||
return dpf::reconstruct_cmp_halves(k0_half, k1_half);
|
||||
}
|
||||
|
||||
template <typename K1, typename K2, typename K3, typename Input>
|
||||
void expect_point(const K1 & k1, const K2 & k2, const K3 & k3, Input alpha,
|
||||
fp61 beta)
|
||||
{
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const fp61 got = open3(dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)),
|
||||
dpf::eval_point(k3, static_cast<Input>(x)));
|
||||
if (static_cast<Input>(x) == alpha)
|
||||
EXPECT_EQ(got, beta) << "x=" << x;
|
||||
else
|
||||
EXPECT_EQ(got.raw(), 0u) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Shamir3, ShareAndReconstruct)
|
||||
{
|
||||
const fp61 secret{123456789u};
|
||||
const auto s = dpf::shamir3::share_secret(secret);
|
||||
EXPECT_EQ(dpf::shamir3::reconstruct(s[0], s[1]), secret);
|
||||
EXPECT_EQ(dpf::shamir3::reconstruct(s[0], s[2]), secret);
|
||||
EXPECT_EQ(dpf::shamir3::reconstruct(s[1], s[2]), secret);
|
||||
EXPECT_EQ(dpf::shamir3::reconstruct(s[0], s[1], s[2]), secret);
|
||||
}
|
||||
|
||||
TEST(Shamir3, PartyScaleRoundTrip)
|
||||
{
|
||||
const fp61 v{0x123456789abcdefull & ((1ull << 61) - 1)};
|
||||
const auto s = dpf::shamir3::share_secret(v);
|
||||
for (const auto & sh : s)
|
||||
{
|
||||
const fp61 uns = dpf::shamir3::unscale(sh);
|
||||
const fp61 back = dpf::shamir3::party_scale(
|
||||
dpf::shamir3::share{sh.party, uns});
|
||||
EXPECT_EQ(back, sh.value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, PointFullDomain)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 42;
|
||||
const fp61 beta{99};
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, beta);
|
||||
expect_point(k1, k2, k3, alpha, beta);
|
||||
// Single key is not the clear point function.
|
||||
EXPECT_NE(dpf::eval_point(k1, alpha), beta);
|
||||
EXPECT_NE(dpf::eval_point(k2, alpha), beta);
|
||||
EXPECT_NE(dpf::eval_point(k3, alpha), beta);
|
||||
}
|
||||
|
||||
TEST(Dpf3, HalfTree)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
using Interior = dpf::prg::aes128_ccr;
|
||||
using Exterior = dpf::prg::aes128;
|
||||
const Input alpha = 7;
|
||||
const fp61 beta{5};
|
||||
auto [k1, k2, k3] = dpf::make_dpf3<Interior, Exterior>(alpha, beta);
|
||||
expect_point(k1, k2, k3, alpha, beta);
|
||||
}
|
||||
|
||||
TEST(Dpf3, VerifiableProof)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 11;
|
||||
const fp61 beta{3};
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, beta, dpf::verifiable{});
|
||||
auto p1 = dpf::prove_dpf3(k1, alpha);
|
||||
auto p2 = dpf::prove_dpf3(k2, alpha);
|
||||
auto p3 = dpf::prove_dpf3(k3, alpha);
|
||||
EXPECT_TRUE(dpf::verify_dpf3(p1, p2, p3));
|
||||
using arr = typename std::decay_t<decltype(k1.a.dpf_key)>::correction_seeds_array;
|
||||
for (auto & cs : const_cast<arr &>(k1.a.dpf_key.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
auto bad = dpf::prove_dpf3(k1, alpha);
|
||||
EXPECT_FALSE(dpf::verify_dpf3(bad, p2, p3));
|
||||
}
|
||||
|
||||
TEST(Dpf3, ExtractableWeightOne)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 20;
|
||||
const fp61 beta{7};
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, beta, dpf::extractable{});
|
||||
EXPECT_TRUE(k1.extractable);
|
||||
std::vector<fp61> s1(256), s2(256), s3(256), rs(256);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
s1[x] = dpf::eval_point(k1, static_cast<Input>(x));
|
||||
s2[x] = dpf::eval_point(k2, static_cast<Input>(x));
|
||||
s3[x] = dpf::eval_point(k3, static_cast<Input>(x));
|
||||
rs[x] = dpf::uniform_sample<fp61>();
|
||||
}
|
||||
EXPECT_TRUE(dpf::sketch_verify3(k1, k2, k3, s1, s2, s3, rs));
|
||||
s1[21] = s1[21] + beta;
|
||||
s2[21] = s2[21] + beta;
|
||||
s3[21] = s3[21] + beta;
|
||||
EXPECT_FALSE(dpf::sketch_verify3(k1, k2, k3, s1, s2, s3, rs));
|
||||
}
|
||||
|
||||
TEST(Dpf3, ExtractableGateRejectsPlainKeys)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(Input{1}, fp61{1});
|
||||
std::vector<fp61> z(1, fp61{});
|
||||
std::vector<fp61> rs(1, fp61{1});
|
||||
EXPECT_THROW(dpf::sketch_verify3(k1, k2, k3, z, z, z, rs),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Dpf3, VerifiableExtractableProofAndSketch)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 33;
|
||||
const fp61 beta{6};
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3(alpha, beta, dpf::verifiable{}, dpf::extractable{});
|
||||
std::vector<fp61> s1(256), s2(256), s3(256), rs(256);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
s1[x] = dpf::eval_point(k1, static_cast<Input>(x));
|
||||
s2[x] = dpf::eval_point(k2, static_cast<Input>(x));
|
||||
s3[x] = dpf::eval_point(k3, static_cast<Input>(x));
|
||||
rs[x] = dpf::uniform_sample<fp61>();
|
||||
}
|
||||
EXPECT_TRUE(dpf::verify_dpf3(k1, k2, k3, dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha), s1, s2, s3,
|
||||
rs));
|
||||
}
|
||||
|
||||
TEST(Dpf3, UpdatableInPlaceKeepsAlpha)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 9;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, fp61{5}, dpf::updatable{});
|
||||
EXPECT_TRUE(k1.updatable && k2.updatable && k3.updatable);
|
||||
expect_point(k1, k2, k3, alpha, fp61{5});
|
||||
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{8});
|
||||
expect_point(k1, k2, k3, alpha, fp61{8});
|
||||
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{0});
|
||||
expect_point(k1, k2, k3, alpha, fp61{0});
|
||||
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{100});
|
||||
expect_point(k1, k2, k3, alpha, fp61{100});
|
||||
}
|
||||
|
||||
TEST(Dpf3, NonUpdatableRejectsUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(Input{3}, fp61{1});
|
||||
EXPECT_FALSE(k1.updatable);
|
||||
EXPECT_THROW(dpf::update_payload(k1, k2, k3, Input{3}, fp61{2}), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Dpf3, RemakeFreshTrees)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 15;
|
||||
auto [k1, k2, k3] = dpf::remake_dpf3(alpha, fp61{44});
|
||||
expect_point(k1, k2, k3, alpha, fp61{44});
|
||||
auto [v1, v2, v3] = dpf::remake_dpf3(alpha, fp61{2}, dpf::verifiable{});
|
||||
EXPECT_TRUE(v1.verifiable);
|
||||
expect_point(v1, v2, v3, alpha, fp61{2});
|
||||
}
|
||||
|
||||
TEST(Dpf3, VerifiableUpdatableProofSurvivesUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 33;
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3(alpha, fp61{6}, dpf::verifiable{}, dpf::updatable{});
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{90});
|
||||
expect_point(k1, k2, k3, alpha, fp61{90});
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointFullDomain)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{1, 2, 9, 40};
|
||||
const std::vector<fp61> betas{fp61{7}, fp61{11}, fp61{3}, fp61{4}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
fp61 want{};
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
if (alphas[i] == static_cast<Input>(x))
|
||||
want = betas[i];
|
||||
const fp61 got = open3(
|
||||
dpf::eval_multipoint(k1, static_cast<Input>(x)),
|
||||
dpf::eval_multipoint(k2, static_cast<Input>(x)),
|
||||
dpf::eval_multipoint(k3, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got, want) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointUpdatableInPlace)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{1, 2, 9, 40};
|
||||
const std::vector<fp61> betas{fp61{7}, fp61{11}, fp61{3}, fp61{4}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas, dpf::updatable{});
|
||||
EXPECT_TRUE(k1.updatable);
|
||||
const auto sigma = k1.sigma;
|
||||
const auto m = k1.bucket_count;
|
||||
const std::vector<fp61> betas2{fp61{1}, fp61{2}, fp61{3}, fp61{4}};
|
||||
dpf::update_payload(k1, k2, k3, alphas, betas2);
|
||||
EXPECT_EQ(0, std::memcmp(&k1.sigma, &sigma, sizeof(sigma)));
|
||||
EXPECT_EQ(k1.bucket_count, m);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
fp61 want{};
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
if (alphas[i] == static_cast<Input>(x))
|
||||
want = betas2[i];
|
||||
const fp61 got = open3(
|
||||
dpf::eval_multipoint(k1, static_cast<Input>(x)),
|
||||
dpf::eval_multipoint(k2, static_cast<Input>(x)),
|
||||
dpf::eval_multipoint(k3, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got, want) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointNonUpdatableRejects)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{3, 5};
|
||||
const std::vector<fp61> betas{fp61{1}, fp61{1}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas);
|
||||
EXPECT_THROW(dpf::update_payload(k1, k2, k3, alphas, betas), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointSeedFlip)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{3, 5, 7};
|
||||
const std::vector<fp61> betas{fp61{1}, fp61{1}, fp61{1}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas, dpf::verifiable{});
|
||||
ASSERT_FALSE(k1.buckets.empty());
|
||||
auto flip = [](auto & plus) {
|
||||
using arr =
|
||||
typename std::decay_t<decltype(plus.dpf_key)>::correction_seeds_array;
|
||||
for (auto & cs : const_cast<arr &>(plus.dpf_key.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
};
|
||||
for (auto & bucket : k1.buckets)
|
||||
{
|
||||
flip(bucket.a);
|
||||
flip(bucket.b);
|
||||
}
|
||||
bool rejected = false;
|
||||
for (std::size_t i = 0; i < k1.buckets.size(); ++i)
|
||||
{
|
||||
auto p1 = dpf::prove_dpf3(k1.buckets[i], Input{0});
|
||||
auto p2 = dpf::prove_dpf3(k2.buckets[i], Input{0});
|
||||
auto p3 = dpf::prove_dpf3(k3.buckets[i], Input{0});
|
||||
if (!dpf::verify_dpf3(p1, p2, p3))
|
||||
rejected = true;
|
||||
}
|
||||
EXPECT_TRUE(rejected);
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonFullDomain)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 100;
|
||||
const uint64_t beta = 17;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_cmp(thresh, beta);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const bool hot = static_cast<Input>(x) < thresh;
|
||||
const auto s1 = dpf::eval_point(k1, static_cast<Input>(x));
|
||||
const auto s2 = dpf::eval_point(k2, static_cast<Input>(x));
|
||||
const auto s3 = dpf::eval_point(k3, static_cast<Input>(x));
|
||||
EXPECT_EQ(open_cmp(s1, s2).raw(), hot ? beta : 0u) << "x=" << x;
|
||||
EXPECT_EQ(open_cmp(s3, s2).raw(), hot ? beta : 0u) << "x=" << x;
|
||||
EXPECT_EQ(s1, s3) << "x=" << x; // both hold k0
|
||||
if (hot)
|
||||
EXPECT_NE(s1, beta);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, BlockedComparison)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_cmp_blocked<4>(Input{50}, 9u);
|
||||
EXPECT_EQ(open_cmp(dpf::eval_point(k1, Input{10}), dpf::eval_point(k2, Input{10}))
|
||||
.raw(),
|
||||
9u);
|
||||
EXPECT_EQ(open_cmp(dpf::eval_point(k1, Input{200}),
|
||||
dpf::eval_point(k2, Input{200}))
|
||||
.raw(),
|
||||
0u);
|
||||
}
|
||||
|
||||
TEST(Dpf3, IntervalFullDomain)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input r = 10, p = 20, q = 40;
|
||||
const uint64_t beta = 5;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_ic(r, p, q, beta);
|
||||
auto two = dpf::make_dpf(r, dpf::ic(p, q, beta));
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto want = dpf::reconstruct(
|
||||
dpf::eval_point(dpf::ic, two.first, static_cast<Input>(x)),
|
||||
dpf::eval_point(dpf::ic, two.second, static_cast<Input>(x)));
|
||||
const fp61 got = open_cmp(
|
||||
dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), static_cast<uint64_t>(want)) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonPayloadUpdateInPlace)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 80;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_cmp(thresh, 3u);
|
||||
dpf::update_payload_cmp(k1, k2, k3, 3u, 11u);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const bool hot = static_cast<Input>(x) < thresh;
|
||||
const fp61 got = open_cmp(
|
||||
dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), hot ? 11u : 0u) << "x=" << x;
|
||||
}
|
||||
dpf::update_payload_cmp(k1, k2, k3, 11u, 0u);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const fp61 got = open_cmp(
|
||||
dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), 0u) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonRemake)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k1, k2, k3] = dpf::remake_dpf3_cmp(Input{50}, 7u, 0u);
|
||||
EXPECT_EQ(open_cmp(dpf::eval_point(k1, Input{10}), dpf::eval_point(k2, Input{10}))
|
||||
.raw(),
|
||||
7u);
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatMatchesDealer)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x2a;
|
||||
const Input x0 = 0x13;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const fp61 beta{77};
|
||||
auto [d1, d2, d3] = dpf::make_dpf3(alpha, beta);
|
||||
auto [s1, s2, s3] = dpf::make_dpf3_doerner_shelat(x0, x1, beta);
|
||||
expect_point(d1, d2, d3, alpha, beta);
|
||||
expect_point(s1, s2, s3, alpha, beta);
|
||||
EXPECT_EQ(dpf::detail::dpf3_impl::open_xor_point(x0, x1), alpha);
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatVerifiable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 19;
|
||||
const Input x0 = 7;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3_doerner_shelat(x0, x1, fp61{4}, dpf::verifiable{});
|
||||
EXPECT_TRUE(k1.verifiable);
|
||||
expect_point(k1, k2, k3, alpha, fp61{4});
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatUpdatable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 8;
|
||||
const Input x0 = 1;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3_doerner_shelat(x0, x1, fp61{2}, dpf::updatable{});
|
||||
EXPECT_TRUE(k1.updatable);
|
||||
EXPECT_FALSE(k1.verifiable);
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{55});
|
||||
expect_point(k1, k2, k3, alpha, fp61{55});
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatVerifiableUpdatableTagParity)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 14;
|
||||
const Input x0 = 2;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_doerner_shelat(x0, x1, fp61{3},
|
||||
dpf::verifiable{}, dpf::updatable{});
|
||||
EXPECT_TRUE(k1.verifiable && k1.updatable);
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{11});
|
||||
expect_point(k1, k2, k3, alpha, fp61{11});
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatExtractable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 12;
|
||||
const Input x0 = 3;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3_doerner_shelat(x0, x1, fp61{1}, dpf::extractable{});
|
||||
EXPECT_TRUE(k1.extractable && k2.extractable && k3.extractable);
|
||||
expect_point(k1, k2, k3, alpha, fp61{1});
|
||||
std::vector<fp61> s1(256), s2(256), s3(256), rs(256);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
s1[x] = dpf::eval_point(k1, static_cast<Input>(x));
|
||||
s2[x] = dpf::eval_point(k2, static_cast<Input>(x));
|
||||
s3[x] = dpf::eval_point(k3, static_cast<Input>(x));
|
||||
rs[x] = dpf::uniform_sample<fp61>();
|
||||
}
|
||||
EXPECT_TRUE(dpf::sketch_verify3(k1, k2, k3, s1, s2, s3, rs));
|
||||
}
|
||||
|
||||
TEST(Dpf3, RemakeTagParity)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [u1, u2, u3] = dpf::remake_dpf3(Input{1}, fp61{2}, dpf::updatable{});
|
||||
EXPECT_TRUE(u1.updatable);
|
||||
expect_point(u1, u2, u3, Input{1}, fp61{2});
|
||||
auto [e1, e2, e3] =
|
||||
dpf::remake_dpf3(Input{2}, fp61{3}, dpf::verifiable{}, dpf::extractable{});
|
||||
EXPECT_TRUE(e1.verifiable && e1.extractable);
|
||||
expect_point(e1, e2, e3, Input{2}, fp61{3});
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointVerifiableUpdatable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{4, 8, 16};
|
||||
const std::vector<fp61> betas{fp61{1}, fp61{2}, fp61{3}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas, dpf::verifiable{},
|
||||
dpf::updatable{});
|
||||
EXPECT_TRUE(k1.verifiable && k1.updatable);
|
||||
const std::vector<fp61> betas2{fp61{9}, fp61{8}, fp61{7}};
|
||||
dpf::update_payload(k1, k2, k3, alphas, betas2);
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
{
|
||||
const fp61 got = open3(dpf::eval_multipoint(k1, alphas[i]),
|
||||
dpf::eval_multipoint(k2, alphas[i]),
|
||||
dpf::eval_multipoint(k3, alphas[i]));
|
||||
EXPECT_EQ(got, betas2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, AsSharePartyIndex)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(Input{0}, fp61{1});
|
||||
const fp61 y{};
|
||||
EXPECT_EQ(dpf::as_share(k1, y).party, 1);
|
||||
EXPECT_EQ(dpf::as_share(k2, y).party, 2);
|
||||
EXPECT_EQ(dpf::as_share(k3, y).party, 3);
|
||||
}
|
||||
|
||||
TEST(Shamir3, InconsistentSharesThrow)
|
||||
{
|
||||
const auto s = dpf::shamir3::share_secret(fp61{42});
|
||||
auto bad = s[2];
|
||||
bad.value = bad.value + fp61{1};
|
||||
EXPECT_THROW(dpf::shamir3::reconstruct(s[0], s[1], bad), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonPredicatesLeqGtGeq)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 100;
|
||||
const uint64_t beta = 4;
|
||||
auto check = [&](auto keys, auto pred) {
|
||||
auto [k1, k2, k3] = keys;
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const bool hot = pred(static_cast<Input>(x));
|
||||
const fp61 got = open_cmp(dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), hot ? beta : 0u) << "x=" << x;
|
||||
}
|
||||
};
|
||||
check(dpf::make_dpf3_cmp(thresh, dpf::leq(beta)),
|
||||
[&](Input x) { return x <= thresh; });
|
||||
check(dpf::make_dpf3_cmp(thresh, dpf::gt(beta)),
|
||||
[&](Input x) { return x > thresh; });
|
||||
check(dpf::make_dpf3_cmp(thresh, dpf::geq(beta)),
|
||||
[&](Input x) { return x >= thresh; });
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonUpdateFp61NotUint64Wrap)
|
||||
{
|
||||
// Historical bug: `(0 - 11) & ~0ull` is not −11 mod p. Updating 11 → 0
|
||||
// must clear the hot lane, not leave a wraparound residue.
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 60;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_cmp(thresh, 11u);
|
||||
dpf::update_payload_cmp(k1, k2, k3, 11u, 0u);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const fp61 got = open_cmp(dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), 0u) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, PublicPiIdenticalAfterUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 21;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, fp61{3}, dpf::updatable{});
|
||||
EXPECT_EQ(k1.a.offset, k2.a.offset);
|
||||
EXPECT_EQ(k2.a.offset, k3.a.offset);
|
||||
EXPECT_EQ(k1.b.offset, k2.b.offset);
|
||||
EXPECT_EQ(k2.b.offset, k3.b.offset);
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{70});
|
||||
EXPECT_EQ(k1.a.offset, k2.a.offset);
|
||||
EXPECT_EQ(k2.a.offset, k3.a.offset);
|
||||
EXPECT_EQ(k1.b.offset, k2.b.offset);
|
||||
EXPECT_EQ(k2.b.offset, k3.b.offset);
|
||||
expect_point(k1, k2, k3, alpha, fp61{70});
|
||||
}
|
||||
|
||||
TEST(Dpf3, ProveRejectsClearedVerifiableFlag)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 5;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, fp61{1}, dpf::verifiable{});
|
||||
k1.verifiable = false; // outer flag gate (inners remain V)
|
||||
EXPECT_THROW(dpf::prove_dpf3(k1, alpha), std::invalid_argument);
|
||||
(void)k2;
|
||||
(void)k3;
|
||||
}
|
||||
|
||||
TEST(Dpf3, VerifyExtractableRejectsNonExtractable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 8;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, fp61{2}, dpf::verifiable{});
|
||||
std::vector<fp61> s1(8), s2(8), s3(8), rs(8);
|
||||
for (unsigned x = 0; x < 8; ++x)
|
||||
{
|
||||
s1[x] = dpf::eval_point(k1, static_cast<Input>(x));
|
||||
s2[x] = dpf::eval_point(k2, static_cast<Input>(x));
|
||||
s3[x] = dpf::eval_point(k3, static_cast<Input>(x));
|
||||
rs[x] = fp61{1};
|
||||
}
|
||||
EXPECT_THROW(
|
||||
dpf::verify_dpf3(k1, k2, k3, dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha), s1, s2, s3,
|
||||
rs),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Dpf3, IntervalBoundaryRelativeToR)
|
||||
{
|
||||
// Interval is relative to public shift `r`; x=r+p and x=r+q−1 are the
|
||||
// classic edges that mis-scored in the IC example (absolute vs relative).
|
||||
using Input = std::uint8_t;
|
||||
const Input r = 10, p = 20, q = 40;
|
||||
const uint64_t beta = 5;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_ic(r, p, q, beta);
|
||||
auto two = dpf::make_dpf(r, dpf::ic(p, q, beta));
|
||||
for (Input x : {Input{29}, Input{30}, Input{35}, Input{49}, Input{50}})
|
||||
{
|
||||
const auto want = dpf::reconstruct(
|
||||
dpf::eval_point(dpf::ic, two.first, x),
|
||||
dpf::eval_point(dpf::ic, two.second, x));
|
||||
const fp61 got = open_cmp(dpf::eval_point(k1, x), dpf::eval_point(k2, x));
|
||||
EXPECT_EQ(got.raw(), static_cast<uint64_t>(want)) << "x=" << unsigned(x);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointRemakeFreshSigma)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{2, 4, 8};
|
||||
const std::vector<fp61> betas{fp61{1}, fp61{2}, fp61{3}};
|
||||
auto [a1, a2, a3] = dpf::make_multipoint3(alphas, betas, dpf::updatable{});
|
||||
const auto sigma0 = a1.sigma;
|
||||
auto [b1, b2, b3] = dpf::remake_multipoint3(alphas, betas);
|
||||
EXPECT_NE(0, std::memcmp(&b1.sigma, &sigma0, sizeof(sigma0)));
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(open3(dpf::eval_multipoint(b1, alphas[i]),
|
||||
dpf::eval_multipoint(b2, alphas[i]),
|
||||
dpf::eval_multipoint(b3, alphas[i])),
|
||||
betas[i]);
|
||||
}
|
||||
(void)a2;
|
||||
(void)a3;
|
||||
}
|
||||
|
||||
TEST(Dpf3, HalfTreeUpdatableUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
using Interior = dpf::prg::aes128_ccr;
|
||||
using Exterior = dpf::prg::aes128;
|
||||
const Input alpha = 55;
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3<Interior, Exterior>(alpha, fp61{8}, dpf::updatable{});
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{12});
|
||||
expect_point(k1, k2, k3, alpha, fp61{12});
|
||||
}
|
||||
|
||||
TEST(Dpf3, UpdatePreservesCorrectionSeeds)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 17;
|
||||
auto [k1, k2, k3] =
|
||||
dpf::make_dpf3(alpha, fp61{4}, dpf::verifiable{}, dpf::updatable{});
|
||||
const auto seeds = k1.a.dpf_key.correction_seeds();
|
||||
const auto root = k1.a.dpf_key.root();
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{19});
|
||||
EXPECT_EQ(0, std::memcmp(seeds.data(), k1.a.dpf_key.correction_seeds().data(),
|
||||
sizeof(seeds)));
|
||||
EXPECT_EQ(0, std::memcmp(&root, &k1.a.dpf_key.root(), sizeof(root)));
|
||||
expect_point(k1, k2, k3, alpha, fp61{19});
|
||||
auto [r1, r2, r3] =
|
||||
dpf::remake_dpf3(alpha, fp61{19}, dpf::verifiable{}, dpf::updatable{});
|
||||
EXPECT_NE(0, std::memcmp(seeds.data(), r1.a.dpf_key.correction_seeds().data(),
|
||||
sizeof(seeds)));
|
||||
(void)r2;
|
||||
(void)r3;
|
||||
}
|
||||
|
||||
TEST(Dpf3, DoernerShelatVuProofAfterUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 22;
|
||||
const Input x0 = 6;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_doerner_shelat(x0, x1, fp61{5},
|
||||
dpf::verifiable{}, dpf::updatable{});
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{41});
|
||||
expect_point(k1, k2, k3, alpha, fp61{41});
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1, alpha),
|
||||
dpf::prove_dpf3(k2, alpha), dpf::prove_dpf3(k3, alpha)));
|
||||
}
|
||||
|
||||
TEST(Dpf3, MultipointVuProofAfterUpdate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{4, 8, 16};
|
||||
const std::vector<fp61> betas{fp61{1}, fp61{2}, fp61{3}};
|
||||
auto [k1, k2, k3] = dpf::make_multipoint3(alphas, betas, dpf::verifiable{},
|
||||
dpf::updatable{});
|
||||
const auto sigma = k1.sigma;
|
||||
const std::vector<fp61> betas2{fp61{9}, fp61{8}, fp61{7}};
|
||||
dpf::update_payload(k1, k2, k3, alphas, betas2);
|
||||
EXPECT_EQ(0, std::memcmp(&k1.sigma, &sigma, sizeof(sigma)));
|
||||
bool any = false;
|
||||
for (std::size_t i = 0; i < k1.buckets.size(); ++i)
|
||||
{
|
||||
any = true;
|
||||
EXPECT_TRUE(dpf::verify_dpf3(dpf::prove_dpf3(k1.buckets[i], Input{0}),
|
||||
dpf::prove_dpf3(k2.buckets[i], Input{0}),
|
||||
dpf::prove_dpf3(k3.buckets[i], Input{0})));
|
||||
}
|
||||
EXPECT_TRUE(any);
|
||||
}
|
||||
|
||||
TEST(Dpf3, BlockedComparisonFullDomain)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 50;
|
||||
const uint64_t beta = 9;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3_cmp_blocked<4>(thresh, beta);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const bool hot = static_cast<Input>(x) < thresh;
|
||||
const fp61 got = open_cmp(dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), hot ? beta : 0u) << "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Dpf3, TagsAnyOrderIncludingExtractableUpdatable)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 17;
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, fp61{4},
|
||||
dpf::extractable{}, dpf::updatable{}, dpf::verifiable{});
|
||||
EXPECT_TRUE(k1.verifiable && k1.extractable && k1.updatable);
|
||||
EXPECT_TRUE(k2.extractable && k3.updatable);
|
||||
expect_point(k1, k2, k3, alpha, fp61{4});
|
||||
dpf::update_payload(k1, k2, k3, alpha, fp61{11});
|
||||
expect_point(k1, k2, k3, alpha, fp61{11});
|
||||
}
|
||||
|
||||
TEST(Dpf3, ComparisonSpecWrappersMatchPlainPredicate)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input thresh = 40;
|
||||
const uint64_t beta = 3;
|
||||
auto check = [&](auto keys) {
|
||||
auto [k1, k2, k3] = keys;
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const bool hot = static_cast<Input>(x) > thresh;
|
||||
const fp61 got = open_cmp(dpf::eval_point(k1, static_cast<Input>(x)),
|
||||
dpf::eval_point(k2, static_cast<Input>(x)));
|
||||
EXPECT_EQ(got.raw(), hot ? beta : 0u) << "x=" << x;
|
||||
}
|
||||
};
|
||||
check(dpf::make_dpf3_cmp(thresh, dpf::idcf(dpf::gt(beta))));
|
||||
check(dpf::make_dpf3_cmp(thresh, dpf::block_width<4>(dpf::gt(beta))));
|
||||
}
|
||||
|
||||
107
test/tests/dwt_lut_test.cpp
Normal file
107
test/tests/dwt_lut_test.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "grotto/dwt_lut.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
grotto::dwt_lut make(grotto::dwt_family family, const std::vector<double> & samples,
|
||||
unsigned fractional_bits, unsigned depth)
|
||||
{
|
||||
if (family == grotto::dwt_family::haar)
|
||||
return grotto::make_haar_dwt_lut(samples, fractional_bits, depth);
|
||||
return grotto::make_bior53_dwt_lut(samples, fractional_bits, depth);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DwtLut, HaarIsQuantizedBlockMean)
|
||||
{
|
||||
const std::vector<double> samples{
|
||||
0.0, 0.5, -1.25, 3.0, 4.0, 0.25, -0.5, 2.0};
|
||||
const auto lut = grotto::make_haar_dwt_lut(samples, 8, 1);
|
||||
ASSERT_EQ(lut.coeff.size(), 4u);
|
||||
EXPECT_EQ(lut.coeff[0], 64);
|
||||
EXPECT_EQ(lut.coeff[1], 224);
|
||||
EXPECT_EQ(lut.coeff[2], 544);
|
||||
EXPECT_EQ(lut.coeff[3], 192);
|
||||
EXPECT_EQ(lut(0), 64);
|
||||
EXPECT_EQ(lut(1), 64);
|
||||
EXPECT_EQ(lut(7), 192);
|
||||
}
|
||||
|
||||
TEST(DwtLut, HaarNegativeFloor)
|
||||
{
|
||||
const std::vector<double> samples{-1.1, 0.2, -0.3, -4.0};
|
||||
const auto lut = grotto::make_haar_dwt_lut(samples, 10, 1);
|
||||
ASSERT_EQ(lut.coeff.size(), 2u);
|
||||
EXPECT_EQ(lut.coeff[0], -461);
|
||||
EXPECT_EQ(lut.coeff[1], -2202);
|
||||
EXPECT_EQ(lut(0), -461);
|
||||
EXPECT_EQ(lut(3), -2202);
|
||||
}
|
||||
|
||||
TEST(DwtLut, Bior53MatchesSmoothExtension)
|
||||
{
|
||||
const std::vector<double> samples{
|
||||
0.0, 0.5, -1.25, 3.0, 4.0, 0.25, -0.5, 2.0};
|
||||
const auto one = make(grotto::dwt_family::bior53, samples, 8, 1);
|
||||
const std::vector<std::int64_t> expect1{-513, 144, -288, 2064, -449, 2304};
|
||||
EXPECT_EQ(one.coeff, expect1);
|
||||
|
||||
const auto two = make(grotto::dwt_family::bior53, samples, 8, 2);
|
||||
const std::vector<std::int64_t> expect2{-3649, -753, 912, 319, 10112};
|
||||
EXPECT_EQ(two.coeff, expect2);
|
||||
const std::int64_t at[8] = {228, 190, 153, 116, 79, 691, 1303, 1915};
|
||||
for (std::uint64_t raw = 0; raw < 8; ++raw)
|
||||
EXPECT_EQ(two(raw), at[raw]) << raw;
|
||||
}
|
||||
|
||||
TEST(DwtLut, Bior53FloorDivOnNegatives)
|
||||
{
|
||||
const std::vector<double> samples{-1.1, 0.2, -0.3, -4.0};
|
||||
const auto lut = grotto::make_bior53_dwt_lut(samples, 10, 1);
|
||||
const std::vector<std::int64_t> expect{-7578, -1793, -154, -15770};
|
||||
EXPECT_EQ(lut.coeff, expect);
|
||||
EXPECT_EQ(lut(0), -77);
|
||||
EXPECT_EQ(lut(1), -3981);
|
||||
EXPECT_EQ(lut(2), -7885);
|
||||
EXPECT_EQ(lut(3), -5837);
|
||||
}
|
||||
|
||||
TEST(DwtLut, SigmoidGridAgreesWithHaarMean)
|
||||
{
|
||||
auto samples = grotto::sample_dwt_signal(6, 4, [](double x) {
|
||||
return 1.0 / (1.0 + std::exp(-(x - 2.0)));
|
||||
});
|
||||
const auto haar = grotto::make_haar_dwt_lut(samples, 4, 2);
|
||||
ASSERT_EQ(haar.coeff.size(), 16u);
|
||||
for (std::uint64_t bin = 0; bin < 16; ++bin)
|
||||
{
|
||||
double sum = 0;
|
||||
for (unsigned k = 0; k < 4; ++k)
|
||||
sum += samples[bin * 4 + k];
|
||||
const auto q = static_cast<std::int64_t>(std::floor(sum / 4.0 * 16.0));
|
||||
EXPECT_EQ(haar.coeff[bin], q) << bin;
|
||||
EXPECT_EQ(haar(bin * 4), q);
|
||||
}
|
||||
const auto bior = grotto::make_bior53_dwt_lut(samples, 4, 2);
|
||||
EXPECT_EQ(bior(32), 8);
|
||||
EXPECT_EQ(bior(48), 12);
|
||||
EXPECT_EQ(haar(32), 8);
|
||||
}
|
||||
|
||||
TEST(DwtLut, RejectsBadShape)
|
||||
{
|
||||
const std::vector<double> samples{0.0, 1.0, 2.0};
|
||||
EXPECT_THROW(grotto::make_haar_dwt_lut(samples, 4, 1), std::invalid_argument);
|
||||
const std::vector<double> four{0.0, 1.0, 2.0, 3.0};
|
||||
EXPECT_THROW(grotto::make_haar_dwt_lut(four, 4, 0), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_bior53_dwt_lut(four, 4, 3), std::invalid_argument);
|
||||
auto lut = grotto::make_haar_dwt_lut(four, 4, 1);
|
||||
EXPECT_THROW(lut(4), std::out_of_range);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/dyadic_lut.hpp"
|
||||
|
||||
|
|
@ -181,8 +182,7 @@ TEST(DyadicLut, MostSignificantBits)
|
|||
EXPECT_EQ(lut(x), bit) << raw << " i=" << i;
|
||||
}
|
||||
}
|
||||
EXPECT_THROW(grotto::make_msb_lut<std::int16_t>(grotto::msb_bit_limit),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_msb_lut<std::int16_t>(grotto::msb_bit_limit), std::invalid_argument);
|
||||
const auto scaled = grotto::make_msb_lut<std::int16_t>(0, 4);
|
||||
EXPECT_EQ(scaled(std::int16_t{-1}), 16);
|
||||
EXPECT_EQ(scaled(std::int16_t{1}), 0);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ TEST(EasyLut, FewPiecesAtEveryPrecision)
|
|||
EXPECT_EQ((grotto::make_squared_relu_lut<std::int16_t>(0).parts()), 2u);
|
||||
EXPECT_EQ((grotto::make_leaky_relu_lut<std::int16_t>(0).parts()), 1u);
|
||||
EXPECT_EQ((grotto::make_leaky_relu_lut<std::int16_t>(3).parts()), 2u);
|
||||
EXPECT_EQ((grotto::make_leaky_relu_hundredth_lut<std::int16_t>().parts()), 2u);
|
||||
|
||||
for (unsigned fractional_bits : {0u, 2u, 4u})
|
||||
{
|
||||
|
|
@ -144,6 +145,10 @@ TEST(EasyLut, ExactLinesMatchOnInt16)
|
|||
return raw >= 0 ? raw : round_div(raw, 4);
|
||||
});
|
||||
}
|
||||
const auto hundredth = grotto::make_leaky_relu_hundredth_lut<std::int16_t>();
|
||||
expect_all<std::int16_t>(hundredth, [](std::int64_t raw) {
|
||||
return raw >= 0 ? raw : round_div(raw, 100);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(EasyLut, ClipIsTheGeneralProgram)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -222,6 +228,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -199,6 +205,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
695
test/tests/eval_inner_product_test.cpp
Normal file
695
test/tests/eval_inner_product_test.cpp
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using In = std::uint8_t;
|
||||
|
||||
template <typename A, typename B>
|
||||
auto open(const A & a, const B & b)
|
||||
{
|
||||
if constexpr (dpf::is_secret_share_v<A>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else if constexpr (dpf::utils::is_xor_wrapper_v<A>)
|
||||
return static_cast<A>(a ^ b);
|
||||
else
|
||||
return static_cast<A>(a - b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(InnerProduct, ScalarIntervalMatchesPoints)
|
||||
{
|
||||
const In alpha = 42;
|
||||
const std::uint64_t beta = 7;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const In from = 40, to = 50;
|
||||
std::vector<std::uint64_t> w(to - from + 1);
|
||||
std::uint64_t expect = 0;
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
{
|
||||
w[i] = i + 1;
|
||||
const In x = static_cast<In>(from + i);
|
||||
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||||
expect += static_cast<std::uint64_t>(y) * w[i];
|
||||
}
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
EXPECT_EQ(expect, beta * w[alpha - from]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, FullDomainOnlyAlpha)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{7}, std::uint64_t{9});
|
||||
std::vector<std::uint64_t> w(256);
|
||||
for (unsigned i = 0; i < w.size(); ++i)
|
||||
w[i] = (i * 3u) & 15u;
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_full_inner_product(dpf::paired, k0, w),
|
||||
dpf::eval_full_inner_product(dpf::paired, k1, w)),
|
||||
std::uint64_t{9} * w[7]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, TwoOutputsSameLeaf)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{9}, std::uint32_t{3}, std::uint32_t{5});
|
||||
std::vector<std::array<std::uint32_t, 2>> rows;
|
||||
std::uint64_t expect = 0;
|
||||
for (In x = 8;; ++x)
|
||||
{
|
||||
const std::uint32_t w0 = 1, w1 = x;
|
||||
rows.push_back({w0, w1});
|
||||
const auto y0 = open(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x));
|
||||
const auto y1 = open(*dpf::eval_point<1>(k0, x), *dpf::eval_point<1>(k1, x));
|
||||
expect += static_cast<std::uint64_t>(y0) * w0
|
||||
+ static_cast<std::uint64_t>(y1) * w1;
|
||||
if (x == 10)
|
||||
break;
|
||||
}
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product<0, 1>(dpf::paired, k0, In{8}, In{10}, rows),
|
||||
dpf::eval_inner_product<0, 1>(dpf::paired, k1, In{8}, In{10}, rows)),
|
||||
expect);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, TupleRowMatchesArray)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{2}, std::uint16_t{6});
|
||||
const std::vector<In> pts{1, 4, 8};
|
||||
std::vector<std::tuple<std::uint32_t, std::uint32_t>> tuples{
|
||||
{1u, 1u}, {3u, 5u}, {7u, 9u}};
|
||||
std::vector<std::array<std::uint32_t, 2>> arrays{{1u, 1u}, {3u, 5u}, {7u, 9u}};
|
||||
const auto t0 = dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), tuples);
|
||||
const auto t1 = dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), tuples);
|
||||
const auto a0 = dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), arrays);
|
||||
const auto a1 = dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), arrays);
|
||||
EXPECT_EQ(open(t0, t1), open(a0, a1));
|
||||
}
|
||||
|
||||
TEST(InnerProduct, AncestorAndLeaf)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{0x2a}, dpf::at<4>(std::uint8_t{5}), std::uint8_t{9});
|
||||
const std::vector<In> pts{0x10, 0x2a, 0x2b, 0x30};
|
||||
const std::vector<std::array<std::uint32_t, 2>> rows{{1, 0}, {1, 1}, {2, 4}, {8, 1}};
|
||||
std::uint64_t expect = 0;
|
||||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||||
{
|
||||
const auto y0 = open(*dpf::eval_point(dpf::out<0>, k0, pts[i]),
|
||||
*dpf::eval_point(dpf::out<0>, k1, pts[i]));
|
||||
const auto y1 = open(*dpf::eval_point(dpf::out<1>, k0, pts[i]),
|
||||
*dpf::eval_point(dpf::out<1>, k1, pts[i]));
|
||||
expect += static_cast<std::uint64_t>(y0) * rows[i][0]
|
||||
+ static_cast<std::uint64_t>(y1) * rows[i][1];
|
||||
if (pts[i] == In{0x2a} || pts[i] == In{0x2b})
|
||||
EXPECT_EQ(y0, std::uint8_t{5});
|
||||
else
|
||||
EXPECT_EQ(y0, std::uint8_t{0});
|
||||
}
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), rows),
|
||||
dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), rows)),
|
||||
expect);
|
||||
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_sequence_inner_product<0, 1>(k0, recipe, pts.begin(), pts.end(), rows),
|
||||
dpf::eval_sequence_inner_product<0, 1>(k1, recipe, pts.begin(), pts.end(), rows)),
|
||||
expect);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, WrapInterval)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{255}, std::uint32_t{4});
|
||||
const In from = 250, to = 2;
|
||||
std::vector<std::uint32_t> w;
|
||||
std::uint64_t expect = 0;
|
||||
auto push = [&](In x) {
|
||||
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||||
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||||
expect += static_cast<std::uint64_t>(y) * w.back();
|
||||
};
|
||||
for (unsigned x = from; x < 256; ++x)
|
||||
push(static_cast<In>(x));
|
||||
for (unsigned x = 0; x <= to; ++x)
|
||||
push(static_cast<In>(x));
|
||||
EXPECT_EQ(w.size(), 9u);
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
EXPECT_EQ(expect, std::uint64_t{4} * w[5]); // 255 is the 6th point, index 5
|
||||
}
|
||||
|
||||
TEST(InnerProduct, XorWeights)
|
||||
{
|
||||
using X = dpf::xor_wrapper<std::uint32_t>;
|
||||
auto [k0, k1] = dpf::make_dpf(In{3}, X{0x0fu});
|
||||
const std::vector<In> pts{1, 3, 4};
|
||||
const std::vector<X> w{X{0xffu}, X{0xf0u}, X{0x0fu}};
|
||||
// Only x=3 is hot: 0x0f AND 0xf0.
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||||
dpf::eval_sequence_inner_product<0>(k1, pts.begin(), pts.end(), w)),
|
||||
X{0x0fu & 0xf0u});
|
||||
}
|
||||
|
||||
TEST(InnerProduct, EmptySequenceIsZero)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||||
const std::vector<In> pts;
|
||||
const std::vector<std::uint64_t> w;
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||||
dpf::eval_sequence_inner_product<0>(k1, pts.begin(), pts.end(), w)),
|
||||
std::uint64_t{0});
|
||||
}
|
||||
|
||||
TEST(InnerProduct, UnsortedSequenceThrows)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||||
(void)k1;
|
||||
const std::vector<In> pts{3, 1};
|
||||
const std::vector<std::uint64_t> w{1, 1};
|
||||
EXPECT_THROW(
|
||||
dpf::eval_sequence_inner_product<0>(k0, pts.begin(), pts.end(), w),
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsThreeMomentsOneWalk)
|
||||
{
|
||||
const In alpha = 42;
|
||||
const std::uint64_t beta = 7;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const std::vector<In> pts{40, 41, 42, 50};
|
||||
std::vector<std::uint64_t> r(pts.size());
|
||||
std::vector<std::uint64_t> r2(pts.size());
|
||||
std::vector<std::uint64_t> ones(pts.size(), 1);
|
||||
std::uint64_t expect_sum = 0, expect_dot = 0, expect_sq = 0;
|
||||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||||
{
|
||||
r[i] = 3 + i * 5;
|
||||
r2[i] = r[i] * r[i];
|
||||
const auto y = open(*dpf::eval_point(k0, pts[i]), *dpf::eval_point(k1, pts[i]));
|
||||
expect_sum += static_cast<std::uint64_t>(y);
|
||||
expect_dot += static_cast<std::uint64_t>(y) * r[i];
|
||||
expect_sq += static_cast<std::uint64_t>(y) * r2[i];
|
||||
}
|
||||
const auto streams = std::tie(ones, r, r2);
|
||||
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), streams);
|
||||
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k1, pts.begin(), pts.end(), streams);
|
||||
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), expect_sum);
|
||||
EXPECT_EQ(open(std::get<1>(s0), std::get<1>(s1)), expect_dot);
|
||||
EXPECT_EQ(open(std::get<2>(s0), std::get<2>(s1)), expect_sq);
|
||||
EXPECT_EQ(expect_sum, beta);
|
||||
EXPECT_EQ(expect_dot, beta * r[2]);
|
||||
EXPECT_EQ(expect_sq, beta * r2[2]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsCallableStreamsAndSideVisit)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{9}, std::uint32_t{4});
|
||||
const std::vector<In> pts{1, 9, 12};
|
||||
auto ones = [](std::size_t) { return std::uint32_t{1}; };
|
||||
auto scale = [](std::size_t i) { return std::uint32_t(i + 2); };
|
||||
std::size_t visits = 0;
|
||||
std::uint32_t seen = 0;
|
||||
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), std::tie(ones, scale),
|
||||
dpf::project([](auto share) { return share + share; }),
|
||||
dpf::also([&](std::size_t, In x, auto share) {
|
||||
++visits;
|
||||
if (x == In{9})
|
||||
seen = share.raw();
|
||||
}));
|
||||
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k1, pts.begin(), pts.end(), std::tie(ones, scale),
|
||||
dpf::project([](auto share) { return share + share; }));
|
||||
EXPECT_EQ(visits, pts.size());
|
||||
const auto hot = *dpf::eval_point(k0, In{9});
|
||||
EXPECT_EQ(seen, hot.raw());
|
||||
// project doubles the share, so the opened moments are 2 * beta * weight.
|
||||
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), std::uint64_t{8});
|
||||
EXPECT_EQ(open(std::get<1>(s0), std::get<1>(s1)), std::uint64_t{8} * 3u);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsIntervalMatchesSequence)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{6});
|
||||
const In from = 2, to = 6;
|
||||
std::vector<std::uint16_t> w;
|
||||
for (In x = from;; ++x)
|
||||
{
|
||||
w.push_back(static_cast<std::uint16_t>(x));
|
||||
if (x == to)
|
||||
break;
|
||||
}
|
||||
const auto a0 = dpf::eval_inner_product<0>(dpf::columns, k0, from, to, std::tie(w));
|
||||
const auto a1 = dpf::eval_inner_product<0>(dpf::columns, k1, from, to, std::tie(w));
|
||||
std::vector<In> pts;
|
||||
for (In x = from;; ++x)
|
||||
{
|
||||
pts.push_back(x);
|
||||
if (x == to)
|
||||
break;
|
||||
}
|
||||
const auto b0 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), std::tie(w));
|
||||
const auto b1 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k1, pts.begin(), pts.end(), std::tie(w));
|
||||
EXPECT_EQ(open(std::get<0>(a0), std::get<0>(a1)),
|
||||
open(std::get<0>(b0), std::get<0>(b1)));
|
||||
EXPECT_EQ(open(std::get<0>(a0), std::get<0>(a1)), std::uint16_t{6} * 4);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, RecipeLengthMismatchThrows)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||||
(void)k1;
|
||||
const std::vector<In> pts{1, 2};
|
||||
const std::vector<In> shorter{1};
|
||||
const std::vector<std::uint64_t> w{1, 1};
|
||||
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||||
EXPECT_THROW(
|
||||
dpf::eval_sequence_inner_product<0>(k0, recipe, shorter.begin(), shorter.end(), w),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsUnsortedAndRecipeMismatchThrow)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||||
(void)k1;
|
||||
const std::vector<In> unsorted{3, 1};
|
||||
const std::vector<std::uint64_t> w{1, 1};
|
||||
EXPECT_THROW(
|
||||
dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, unsorted.begin(), unsorted.end(), std::tie(w)),
|
||||
std::runtime_error);
|
||||
|
||||
const std::vector<In> pts{1, 2};
|
||||
const std::vector<In> shorter{1};
|
||||
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||||
EXPECT_THROW(
|
||||
dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, recipe, shorter.begin(), shorter.end(), std::tie(w)),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, BatchedLeafWalkMatchesPaired)
|
||||
{
|
||||
// Plain `eval_inner_product` (memoized leaf walk) and `dpf::paired` both
|
||||
// compute sum_x DPF(x)*w[x] when there is one output and `[from, to]` is
|
||||
// leaf-aligned, so the covering-leaf weight layout matches the clipped
|
||||
// domain points.
|
||||
const In alpha = 36;
|
||||
const std::uint64_t beta = 11;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const In from = 30, to = 45; // even..odd => full covering leaves
|
||||
ASSERT_EQ(decltype(k0)::outputs_per_leaf, 2u);
|
||||
std::vector<std::uint64_t> w(to - from + 1);
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
w[i] = (i * 7u) + 3u;
|
||||
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
const auto leaf0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||||
const auto leaf1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||||
const auto pair0 = dpf::eval_inner_product(dpf::paired, k0, from, to, w);
|
||||
const auto pair1 = dpf::eval_inner_product(dpf::paired, k1, from, to, w);
|
||||
EXPECT_EQ(open(leaf0, leaf1), open(pair0, pair1));
|
||||
EXPECT_EQ(open(leaf0, leaf1), beta * w[alpha - from]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsOneStreamMatchesPaired)
|
||||
{
|
||||
// Transposed (`columns`) with a single weight stream is the same scalar
|
||||
// product as `paired` on that stream.
|
||||
auto [k0, k1] = dpf::make_dpf(In{12}, std::uint32_t{5});
|
||||
const In from = 8, to = 20;
|
||||
std::vector<std::uint32_t> w;
|
||||
for (In x = from;; ++x)
|
||||
{
|
||||
w.push_back(static_cast<std::uint32_t>(x + 1));
|
||||
if (x == to)
|
||||
break;
|
||||
}
|
||||
const auto p0 = dpf::eval_inner_product(dpf::paired, k0, from, to, w);
|
||||
const auto p1 = dpf::eval_inner_product(dpf::paired, k1, from, to, w);
|
||||
const auto c0 = dpf::eval_inner_product<0>(dpf::columns, k0, from, to, std::tie(w));
|
||||
const auto c1 = dpf::eval_inner_product<0>(dpf::columns, k1, from, to, std::tie(w));
|
||||
EXPECT_EQ(open(p0, p1), open(std::get<0>(c0), std::get<0>(c1)));
|
||||
EXPECT_EQ(open(p0, p1), std::uint32_t{5} * w[12 - 8]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, LengthOneOddAndUnaligned)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{41}, std::uint64_t{9});
|
||||
|
||||
// Single domain point.
|
||||
{
|
||||
const In x = 41;
|
||||
std::vector<std::uint64_t> w{4};
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, x, x, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, x, x, w)),
|
||||
std::uint64_t{9} * 4u);
|
||||
const auto c0 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k0, x, x, std::tie(w));
|
||||
const auto c1 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k1, x, x, std::tie(w));
|
||||
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), std::uint64_t{9} * 4u);
|
||||
}
|
||||
|
||||
// Odd length, not leaf-aligned (uint64 packs two lanes per leaf).
|
||||
// `paired` / `columns` weight by clipped domain points; the batched leaf
|
||||
// walk weights the covering leaves (see file brief / cohort docs).
|
||||
{
|
||||
const In from = 39, to = 45; // 7 points; covering leaf also holds 38
|
||||
std::vector<std::uint64_t> w(to - from + 1);
|
||||
std::uint64_t expect = 0;
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
{
|
||||
w[i] = i + 2;
|
||||
const In x = static_cast<In>(from + i);
|
||||
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||||
expect += static_cast<std::uint64_t>(y) * w[i];
|
||||
}
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
const auto c0 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k0, from, to, std::tie(w));
|
||||
const auto c1 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k1, from, to, std::tie(w));
|
||||
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), expect);
|
||||
EXPECT_EQ(expect, std::uint64_t{9} * w[41 - 39]);
|
||||
|
||||
// Covering-leaf weights: pad the missing start lane (x=38) with 0 so
|
||||
// the batched walk agrees with the clipped paired result.
|
||||
constexpr std::size_t opl = decltype(k0)::outputs_per_leaf;
|
||||
ASSERT_EQ(opl, 2u);
|
||||
std::vector<std::uint64_t> cover(w.size() + 1, 0);
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
cover[i + 1] = w[i];
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(k0, from, to, cover, m0),
|
||||
dpf::eval_inner_product(k1, from, to, cover, m1)),
|
||||
expect);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(InnerProduct, EmptyIntervalWeightsStillZero)
|
||||
{
|
||||
// Empty point list already covered; empty closed interval is impossible,
|
||||
// but a sequence of length 0 for columns must stay zero.
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint64_t{3});
|
||||
const std::vector<In> pts;
|
||||
const std::vector<std::uint64_t> w;
|
||||
const auto c0 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), std::tie(w));
|
||||
const auto c1 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k1, pts.begin(), pts.end(), std::tie(w));
|
||||
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), std::uint64_t{0});
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ColumnsFullAndAlsoThenProject)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{7}, std::uint16_t{4});
|
||||
std::vector<std::uint16_t> ones(256, 1);
|
||||
std::vector<std::uint16_t> r(256);
|
||||
for (unsigned i = 0; i < 256; ++i)
|
||||
r[i] = static_cast<std::uint16_t>((i * 3u) & 15u);
|
||||
|
||||
const auto f0 = dpf::eval_full_inner_product<0>(
|
||||
dpf::columns, k0, std::tie(ones, r));
|
||||
const auto f1 = dpf::eval_full_inner_product<0>(
|
||||
dpf::columns, k1, std::tie(ones, r));
|
||||
EXPECT_EQ(open(std::get<0>(f0), std::get<0>(f1)), std::uint16_t{4});
|
||||
EXPECT_EQ(open(std::get<1>(f0), std::get<1>(f1)), std::uint16_t{4} * r[7]);
|
||||
|
||||
const std::vector<In> pts{3, 7, 9};
|
||||
std::size_t visits = 0;
|
||||
const auto s0 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), std::tie(ones),
|
||||
dpf::also([&](std::size_t, In, auto) { ++visits; }),
|
||||
dpf::project([](auto share) { return share; }));
|
||||
const auto s1 = dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k1, pts.begin(), pts.end(), std::tie(ones));
|
||||
EXPECT_EQ(visits, pts.size());
|
||||
EXPECT_EQ(open(std::get<0>(s0), std::get<0>(s1)), std::uint16_t{4});
|
||||
}
|
||||
|
||||
TEST(InnerProduct, PrepareMemoizerThenInnerProduct)
|
||||
{
|
||||
// uint64 packs two lanes per leaf. [10, 31] fills those leaves, so a
|
||||
// weight per domain point is also a weight per covering lane.
|
||||
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||||
const In from = 10, to = 31;
|
||||
ASSERT_EQ(decltype(k0)::outputs_per_leaf, 2u);
|
||||
ASSERT_EQ(static_cast<unsigned>(to - from + 1) % 2u, 0u);
|
||||
std::vector<std::uint64_t> w(to - from + 1, 2);
|
||||
auto cold0 = dpf::eval_inner_product(k0, from, to, w,
|
||||
dpf::make_basic_interval_memoizer(k0, from, to));
|
||||
auto cold1 = dpf::eval_inner_product(k1, from, to, w,
|
||||
dpf::make_basic_interval_memoizer(k1, from, to));
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
dpf::eval_prepare_interval(k0, from, to, m0);
|
||||
dpf::eval_prepare_interval(k1, from, to, m1);
|
||||
const auto warm0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||||
const auto warm1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||||
EXPECT_EQ(warm0, cold0);
|
||||
EXPECT_EQ(warm1, cold1);
|
||||
EXPECT_EQ(open(warm0, warm1), std::uint64_t{6} * 2u);
|
||||
// A second pass on the warm memoizer must not rebuild a different share.
|
||||
EXPECT_EQ(dpf::eval_inner_product(k0, from, to, w, m0), warm0);
|
||||
EXPECT_EQ(dpf::eval_inner_product(k1, from, to, w, m1), warm1);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, IntervalMatchesPointReconstruction)
|
||||
{
|
||||
// Long interval: reopen against point-by-point reconstruction for both
|
||||
// paired and columns (two streams).
|
||||
const In alpha = 100;
|
||||
const std::uint64_t beta = 13;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const In from = 80, to = 140;
|
||||
const std::size_t n = static_cast<std::size_t>(to - from + 1);
|
||||
std::vector<std::uint64_t> ones(n, 1);
|
||||
std::vector<std::uint64_t> scale(n);
|
||||
std::uint64_t expect_sum = 0, expect_dot = 0;
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
scale[i] = i + 1;
|
||||
const In x = static_cast<In>(from + i);
|
||||
const auto y = open(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x));
|
||||
expect_sum += static_cast<std::uint64_t>(y);
|
||||
expect_dot += static_cast<std::uint64_t>(y) * scale[i];
|
||||
}
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, scale),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, scale)),
|
||||
expect_dot);
|
||||
const auto c0 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k0, from, to, std::tie(ones, scale));
|
||||
const auto c1 = dpf::eval_inner_product<0>(
|
||||
dpf::columns, k1, from, to, std::tie(ones, scale));
|
||||
EXPECT_EQ(open(std::get<0>(c0), std::get<0>(c1)), expect_sum);
|
||||
EXPECT_EQ(open(std::get<1>(c0), std::get<1>(c1)), expect_dot);
|
||||
EXPECT_EQ(expect_sum, beta);
|
||||
EXPECT_EQ(expect_dot, beta * scale[alpha - from]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, ShortCoveringWeightsThrow)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||||
(void)k1;
|
||||
// [10, 30] is 21 points and 22 covering lanes (the leaf of 30 also holds 31).
|
||||
const In from = 10, to = 30;
|
||||
std::vector<std::uint64_t> clipped(to - from + 1, 2);
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, clipped, m0),
|
||||
std::invalid_argument);
|
||||
dpf::eval_prepare_interval(k0, from, to, m0);
|
||||
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, clipped, m0),
|
||||
std::invalid_argument);
|
||||
|
||||
std::vector<std::uint64_t> cover(clipped.size() + 1, 2);
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(k0, from, to, cover, m0),
|
||||
dpf::eval_inner_product(k1, from, to, cover,
|
||||
dpf::make_basic_interval_memoizer(k1, from, to))),
|
||||
std::uint64_t{6} * 2u);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, OneLaneShortStillThrows)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint64_t{1});
|
||||
(void)k1;
|
||||
const In from = 0, to = 2; // 3 points, 4 covering lanes
|
||||
std::vector<std::uint64_t> almost(3, 1);
|
||||
auto memo = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
EXPECT_THROW(dpf::eval_inner_product(k0, from, to, almost, memo),
|
||||
std::invalid_argument);
|
||||
almost.push_back(0);
|
||||
EXPECT_NO_THROW(dpf::eval_inner_product(k0, from, to, almost, memo));
|
||||
}
|
||||
|
||||
TEST(InnerProduct, MemoizerSmallerThanIntervalThrows)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{8}, std::uint64_t{3});
|
||||
(void)k1;
|
||||
auto small = dpf::make_basic_interval_memoizer(k0, In{0}, In{1});
|
||||
std::vector<std::uint64_t> w(64, 1);
|
||||
EXPECT_THROW(dpf::eval_inner_product(k0, In{0}, In{30}, w, small),
|
||||
std::length_error);
|
||||
EXPECT_THROW(dpf::eval_prepare_interval(k0, In{0}, In{30}, small),
|
||||
std::length_error);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, PairedAndColumnsRejectShortWeights)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{5}, std::uint64_t{2});
|
||||
(void)k1;
|
||||
const In from = 1, to = 8;
|
||||
std::vector<std::uint64_t> short_w(3, 1);
|
||||
EXPECT_THROW(dpf::eval_inner_product(dpf::paired, k0, from, to, short_w),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::eval_inner_product<0>(dpf::columns, k0, from, to,
|
||||
std::tie(short_w)), std::invalid_argument);
|
||||
|
||||
const std::vector<In> pts{1, 5, 8};
|
||||
const std::vector<std::uint64_t> one_row{1};
|
||||
EXPECT_THROW(dpf::eval_sequence_inner_product<0>(
|
||||
k0, pts.begin(), pts.end(), one_row), std::invalid_argument);
|
||||
const std::vector<std::uint64_t> one{1};
|
||||
EXPECT_THROW(dpf::eval_sequence_inner_product<0>(
|
||||
dpf::columns, k0, pts.begin(), pts.end(), std::tie(one)),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, PrepareOnAWrapStillMatchesPoints)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint32_t{9});
|
||||
const In from = 250, to = 4;
|
||||
std::vector<std::uint32_t> w;
|
||||
std::uint64_t expect = 0;
|
||||
for (unsigned x = from; x < 256; ++x)
|
||||
{
|
||||
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||||
const auto y = open(*dpf::eval_point(k0, static_cast<In>(x)),
|
||||
*dpf::eval_point(k1, static_cast<In>(x)));
|
||||
expect += static_cast<std::uint64_t>(y) * w.back();
|
||||
}
|
||||
for (unsigned x = 0; x <= to; ++x)
|
||||
{
|
||||
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||||
const auto y = open(*dpf::eval_point(k0, static_cast<In>(x)),
|
||||
*dpf::eval_point(k1, static_cast<In>(x)));
|
||||
expect += static_cast<std::uint64_t>(y) * w.back();
|
||||
}
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
dpf::eval_prepare_interval(k0, from, to, m0);
|
||||
dpf::eval_prepare_interval(k1, from, to, m1);
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
// A wrap is two leaf segments. The memoizer keeps one, so prepare must
|
||||
// leave the paired product unchanged and must not throw.
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
std::vector<std::uint32_t> too_short(w.size() - 1, 1);
|
||||
EXPECT_THROW(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, too_short),
|
||||
std::invalid_argument);
|
||||
(void)m0;
|
||||
(void)m1;
|
||||
}
|
||||
|
||||
TEST(InnerProduct, BatchedWalkMatchesIntervalBuffer)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{20}, std::uint64_t{6});
|
||||
const In from = 10, to = 30;
|
||||
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
|
||||
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
|
||||
(void)it0;
|
||||
(void)it1;
|
||||
ASSERT_GT(buf0.size(), static_cast<std::size_t>(to - from + 1));
|
||||
std::vector<std::uint64_t> w(buf0.size());
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
w[i] = (i * 3u) + 1u;
|
||||
std::uint64_t e0 = 0, e1 = 0, opened = 0;
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
{
|
||||
e0 += static_cast<std::uint64_t>(buf0[i].value) * w[i];
|
||||
e1 += static_cast<std::uint64_t>(buf1[i].value) * w[i];
|
||||
opened += static_cast<std::uint64_t>(open(buf0[i], buf1[i])) * w[i];
|
||||
}
|
||||
auto m0 = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto m1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
dpf::eval_prepare_interval(k0, from, to, m0);
|
||||
dpf::eval_prepare_interval(k1, from, to, m1);
|
||||
const auto ip0 = dpf::eval_inner_product(k0, from, to, w, m0);
|
||||
const auto ip1 = dpf::eval_inner_product(k1, from, to, w, m1);
|
||||
EXPECT_EQ(ip0, e0);
|
||||
EXPECT_EQ(ip1, e1);
|
||||
EXPECT_EQ(open(ip0, ip1), opened);
|
||||
std::size_t hot = w.size();
|
||||
for (std::size_t i = 0; i < w.size(); ++i)
|
||||
{
|
||||
if (open(buf0[i], buf1[i]) != 0)
|
||||
{
|
||||
EXPECT_EQ(hot, w.size());
|
||||
hot = i;
|
||||
}
|
||||
}
|
||||
ASSERT_LT(hot, w.size());
|
||||
EXPECT_EQ(opened, std::uint64_t{6} * w[hot]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, FullDomainBatchedRejectsAShortVector)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{3}, std::uint64_t{1});
|
||||
(void)k1;
|
||||
std::vector<std::uint64_t> w(255, 1);
|
||||
auto memo = dpf::make_basic_full_memoizer(k0);
|
||||
EXPECT_THROW(dpf::eval_full_inner_product(k0, w, memo),
|
||||
std::invalid_argument);
|
||||
w.push_back(1);
|
||||
EXPECT_EQ(open(
|
||||
dpf::eval_full_inner_product(k0, w, memo),
|
||||
dpf::eval_full_inner_product(k1, w,
|
||||
dpf::make_basic_full_memoizer(k1))),
|
||||
std::uint64_t{1} * w[3]);
|
||||
}
|
||||
|
||||
TEST(InnerProduct, TwoOutputRowsRejectAShortList)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(In{4}, std::uint16_t{2}, std::uint16_t{6});
|
||||
(void)k1;
|
||||
const std::vector<In> pts{1, 4, 8};
|
||||
std::vector<std::array<std::uint32_t, 2>> rows{{1u, 1u}};
|
||||
EXPECT_THROW((dpf::eval_sequence_inner_product<0, 1>(
|
||||
k0, pts.begin(), pts.end(), rows)), std::invalid_argument);
|
||||
rows.push_back({3u, 5u});
|
||||
rows.push_back({7u, 9u});
|
||||
EXPECT_NO_THROW((dpf::eval_sequence_inner_product<0, 1>(
|
||||
k0, pts.begin(), pts.end(), rows)));
|
||||
}
|
||||
|
|
@ -16,7 +16,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -289,6 +295,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -265,6 +271,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -128,7 +134,11 @@ TYPED_TEST_P(EvalPointMultiTest, Basic)
|
|||
|
||||
for (auto [x, y0, y1, y2, y3] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
auto _keys = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
|
||||
this->assert_wrapper(x, y0, y1, y2, y3,
|
||||
[&dpf0](input_type cur)
|
||||
|
|
@ -152,7 +162,11 @@ TYPED_TEST_P(EvalPointMultiTest, BasicPathMemoizer)
|
|||
|
||||
for (auto [x, y0, y1, y2, y3] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
auto _keys = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
|
||||
this->assert_wrapper(x, y0, y1, y2, y3,
|
||||
[&dpf0, &memo0](input_type cur)
|
||||
|
|
@ -176,7 +190,11 @@ TYPED_TEST_P(EvalPointMultiTest, NonmemoizingPathMemoizer)
|
|||
|
||||
for (auto [x, y0, y1, y2, y3] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
auto _keys = dpf::make_dpf(x, y0, y1, y2, y3);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
|
||||
this->assert_wrapper(x, y0, y1, y2, y3,
|
||||
[&dpf0, &memo0](input_type cur)
|
||||
|
|
@ -222,6 +240,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -110,7 +116,11 @@ TYPED_TEST_P(EvalPointTest, Basic)
|
|||
|
||||
for (auto [x, y] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
|
||||
auto _keys = dpf::make_dpf(x, y);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
|
||||
this->assert_wrapper(x, y,
|
||||
[&dpf0](input_type cur)
|
||||
|
|
@ -131,7 +141,11 @@ TYPED_TEST_P(EvalPointTest, BasicPathMemoizer)
|
|||
|
||||
for (auto [x, y] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
|
||||
auto _keys = dpf::make_dpf(x, y);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
using key0_t = std::decay_t<decltype(dpf0)>;
|
||||
using key1_t = std::decay_t<decltype(dpf1)>;
|
||||
auto memo0 = dpf::make_basic_path_memoizer<key0_t>(),
|
||||
|
|
@ -156,7 +170,11 @@ TYPED_TEST_P(EvalPointTest, NonmemoizingPathMemoizer)
|
|||
|
||||
for (auto [x, y] : this->params)
|
||||
{
|
||||
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
|
||||
auto _keys = dpf::make_dpf(x, y);
|
||||
|
||||
auto & dpf0 = std::get<0>(_keys);
|
||||
|
||||
auto & dpf1 = std::get<1>(_keys);
|
||||
using key0_t = std::decay_t<decltype(dpf0)>;
|
||||
using key1_t = std::decay_t<decltype(dpf1)>;
|
||||
auto memo0 = dpf::make_nonmemoizing_path_memoizer<key0_t>(),
|
||||
|
|
@ -206,6 +224,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -702,6 +708,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,13 @@ auto recon(const A & a, const B & b)
|
|||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
return a - b;
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -711,6 +717,13 @@ using Types = testing::Types
|
|||
test_type<uint16_t, dpf::bitstring<150>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
test_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
test_type<uint16_t, dpf::gf2>,
|
||||
test_type<uint16_t, dpf::gf22>,
|
||||
test_type<uint16_t, dpf::gf24>,
|
||||
test_type<uint16_t, dpf::gf28>,
|
||||
test_type<uint16_t, dpf::gf216>,
|
||||
test_type<uint16_t, dpf::gf232>,
|
||||
test_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
test_type<custom_input_type, uint64_t>,
|
||||
|
|
|
|||
138
test/tests/eval_until_test.cpp
Normal file
138
test/tests/eval_until_test.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
auto make_unit16(std::uint16_t alpha)
|
||||
{
|
||||
return dpf::make_dpf(alpha, dpf::idpf_ones<16>());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EvalUntil, SpineMatchesEvalPoint)
|
||||
{
|
||||
const std::uint16_t alpha = 0xBEEF;
|
||||
auto [k0, k1] = make_unit16(alpha);
|
||||
|
||||
dpf::idpf_eval_ctx ctx0(k0);
|
||||
dpf::idpf_eval_ctx ctx1(k1);
|
||||
EXPECT_EQ(ctx0.level(), 0u);
|
||||
EXPECT_EQ(ctx0.node_count(), 1u);
|
||||
|
||||
// Empty prefixes on a fresh context leave the root in place.
|
||||
auto empty = dpf::eval_until(ctx0, 0, std::vector<std::uint16_t>{});
|
||||
EXPECT_TRUE(empty.empty());
|
||||
EXPECT_EQ(ctx0.node_count(), 1u);
|
||||
|
||||
std::uint16_t prefix = 0;
|
||||
for (std::size_t level = 1; level <= 16; ++level)
|
||||
{
|
||||
const auto bit = static_cast<std::uint16_t>(
|
||||
(alpha >> (16 - level)) & 1u);
|
||||
prefix = static_cast<std::uint16_t>((prefix << 1) | bit);
|
||||
auto s0 = dpf::eval_until(ctx0, level,
|
||||
std::vector<std::uint16_t>{prefix});
|
||||
auto s1 = dpf::eval_until(ctx1, level,
|
||||
std::vector<std::uint16_t>{prefix});
|
||||
ASSERT_EQ(s0.size(), 1u);
|
||||
ASSERT_EQ(s1.size(), 1u);
|
||||
EXPECT_EQ(ctx0.node_count(), 1u);
|
||||
EXPECT_LE(ctx0.node_count(), level);
|
||||
|
||||
const auto domain = static_cast<std::uint16_t>(
|
||||
prefix << (16 - level));
|
||||
// Dispatch eval_point through a level switch for the matching out<I,N>.
|
||||
std::uint64_t ref = 0;
|
||||
switch (level)
|
||||
{
|
||||
#define LIBDPF_CHECK_LEVEL(L) \
|
||||
case L: \
|
||||
ref = dpf::reconstruct( \
|
||||
*dpf::eval_point(dpf::out<L - 1, L>, k0, domain), \
|
||||
*dpf::eval_point(dpf::out<L - 1, L>, k1, domain)); \
|
||||
break
|
||||
LIBDPF_CHECK_LEVEL(1); LIBDPF_CHECK_LEVEL(2); LIBDPF_CHECK_LEVEL(3);
|
||||
LIBDPF_CHECK_LEVEL(4); LIBDPF_CHECK_LEVEL(5); LIBDPF_CHECK_LEVEL(6);
|
||||
LIBDPF_CHECK_LEVEL(7); LIBDPF_CHECK_LEVEL(8); LIBDPF_CHECK_LEVEL(9);
|
||||
LIBDPF_CHECK_LEVEL(10); LIBDPF_CHECK_LEVEL(11); LIBDPF_CHECK_LEVEL(12);
|
||||
LIBDPF_CHECK_LEVEL(13); LIBDPF_CHECK_LEVEL(14); LIBDPF_CHECK_LEVEL(15);
|
||||
LIBDPF_CHECK_LEVEL(16);
|
||||
#undef LIBDPF_CHECK_LEVEL
|
||||
default:
|
||||
FAIL() << "bad level";
|
||||
}
|
||||
EXPECT_EQ(dpf::reconstruct(s0[0], s1[0]), ref);
|
||||
EXPECT_EQ(ref, 1u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EvalUntil, RejectsNonExtendingPrefix)
|
||||
{
|
||||
auto [k0, k1] = make_unit16(0x0001);
|
||||
(void)k1;
|
||||
dpf::idpf_eval_ctx ctx(k0);
|
||||
dpf::eval_until(ctx, 1, std::vector<std::uint16_t>{0});
|
||||
EXPECT_THROW(
|
||||
dpf::eval_until(ctx, 2, std::vector<std::uint16_t>{3}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(EvalUntil, BothChildrenStayLinear)
|
||||
{
|
||||
auto [k0, k1] = make_unit16(0xF00D);
|
||||
dpf::idpf_eval_ctx ctx0(k0);
|
||||
dpf::idpf_eval_ctx ctx1(k1);
|
||||
auto s0 = dpf::eval_until(ctx0, 1, std::vector<std::uint16_t>{0, 1});
|
||||
auto s1 = dpf::eval_until(ctx1, 1, std::vector<std::uint16_t>{0, 1});
|
||||
EXPECT_EQ(ctx0.node_count(), 2u);
|
||||
EXPECT_EQ(dpf::reconstruct(s0[0], s1[0]), 0u);
|
||||
EXPECT_EQ(dpf::reconstruct(s0[1], s1[1]), 1u);
|
||||
}
|
||||
|
||||
// Seam with the older full-prefix walk: at depth 1, eval_until on {0,1}
|
||||
// opens the same counts as eval_prefixes(out<0,1>).
|
||||
TEST(EvalUntil, MatchesEvalPrefixesAtDepthOne)
|
||||
{
|
||||
const std::uint16_t alpha = 0x8000;
|
||||
auto [k0, k1] = make_unit16(alpha);
|
||||
|
||||
dpf::idpf_eval_ctx ctx0(k0);
|
||||
dpf::idpf_eval_ctx ctx1(k1);
|
||||
auto u0 = dpf::eval_until(ctx0, 1, std::vector<std::uint16_t>{0, 1});
|
||||
auto u1 = dpf::eval_until(ctx1, 1, std::vector<std::uint16_t>{0, 1});
|
||||
|
||||
auto [b0, it0] = dpf::eval_prefixes(dpf::out<0, 1>, k0);
|
||||
auto [b1, it1] = dpf::eval_prefixes(dpf::out<0, 1>, k1);
|
||||
(void)b0;
|
||||
(void)b1;
|
||||
auto p0 = std::begin(it0);
|
||||
auto p1 = std::begin(it1);
|
||||
EXPECT_EQ(dpf::reconstruct(u0[0], u1[0]), dpf::reconstruct(*p0, *p1));
|
||||
++p0;
|
||||
++p1;
|
||||
EXPECT_EQ(dpf::reconstruct(u0[1], u1[1]), dpf::reconstruct(*p0, *p1));
|
||||
}
|
||||
|
||||
TEST(EvalUntil, RetainThenContinue)
|
||||
{
|
||||
auto [k0, k1] = make_unit16(0xC000);
|
||||
dpf::idpf_eval_ctx ctx0(k0);
|
||||
dpf::idpf_eval_ctx ctx1(k1);
|
||||
dpf::eval_until(ctx0, 1, std::vector<std::uint16_t>{0, 1});
|
||||
dpf::eval_until(ctx1, 1, std::vector<std::uint16_t>{0, 1});
|
||||
ctx0.retain(1);
|
||||
ctx1.retain(1);
|
||||
EXPECT_EQ(ctx0.node_count(), 1u);
|
||||
auto s0 = dpf::eval_until(ctx0, 2, std::vector<std::uint16_t>{2, 3});
|
||||
auto s1 = dpf::eval_until(ctx1, 2, std::vector<std::uint16_t>{2, 3});
|
||||
// 0xC000 = 1100… so length-2 prefix is 3 (bits 11).
|
||||
EXPECT_EQ(dpf::reconstruct(s0[0], s1[0]), 0u);
|
||||
EXPECT_EQ(dpf::reconstruct(s0[1], s1[1]), 1u);
|
||||
}
|
||||
164
test/tests/exact_steps_test.cpp
Normal file
164
test/tests/exact_steps_test.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/exact_steps.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int ref_bit_width(std::uint16_t bits)
|
||||
{
|
||||
if (bits == 0)
|
||||
return 0;
|
||||
return 32 - __builtin_clz(static_cast<unsigned>(bits));
|
||||
}
|
||||
|
||||
int ref_countl_one(std::uint16_t bits)
|
||||
{
|
||||
int n = 0;
|
||||
for (int b = 15; b >= 0; --b)
|
||||
{
|
||||
if (((bits >> b) & 1u) == 0)
|
||||
break;
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
std::uint16_t ref_bit_floor(std::uint16_t bits)
|
||||
{
|
||||
if (bits == 0)
|
||||
return 0;
|
||||
return std::uint16_t{1} << (ref_bit_width(bits) - 1);
|
||||
}
|
||||
|
||||
std::uint32_t ref_bit_ceil(std::uint16_t bits)
|
||||
{
|
||||
if (bits <= 1)
|
||||
return 1;
|
||||
if ((bits & (bits - 1u)) == 0)
|
||||
return bits;
|
||||
return std::uint32_t{1} << ref_bit_width(bits);
|
||||
}
|
||||
|
||||
int ref_ceil_log(std::int16_t raw, unsigned k, int group)
|
||||
{
|
||||
const long double x = std::ldexp(static_cast<long double>(raw < 0 ? -raw : raw), -static_cast<int>(k));
|
||||
return static_cast<int>(std::ceil(std::log2(static_cast<double>(x)) / group - 1e-15));
|
||||
}
|
||||
|
||||
int ref_logstar(std::int16_t raw, unsigned k)
|
||||
{
|
||||
long double x = std::ldexp(static_cast<long double>(raw), -static_cast<int>(k));
|
||||
int n = 0;
|
||||
while (x > 1.0L)
|
||||
{
|
||||
x = std::log2(static_cast<double>(x));
|
||||
++n;
|
||||
if (n > 8)
|
||||
break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int ref_length(std::int16_t raw, unsigned k, int base)
|
||||
{
|
||||
const long double x = std::ldexp(static_cast<long double>(raw < 0 ? -raw : raw), -static_cast<int>(k));
|
||||
const auto n = static_cast<std::int64_t>(std::floor(static_cast<double>(x)));
|
||||
if (n <= 0)
|
||||
return 1;
|
||||
int digits = 0;
|
||||
auto m = n;
|
||||
while (m > 0)
|
||||
{
|
||||
m /= base;
|
||||
++digits;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ExactSteps, WordBitsMatchTheUnsignedPattern)
|
||||
{
|
||||
for (int raw = -32768; raw <= 32767; ++raw)
|
||||
{
|
||||
const auto bits = static_cast<std::uint16_t>(raw);
|
||||
const auto value = static_cast<std::int64_t>(static_cast<std::int16_t>(raw));
|
||||
EXPECT_EQ(grotto::eval_bit_width<std::int16_t>(value, 0), ref_bit_width(bits));
|
||||
EXPECT_EQ(grotto::eval_countl_one<std::int16_t>(value, 0), ref_countl_one(bits));
|
||||
EXPECT_EQ(grotto::eval_has_single_bit<std::int16_t>(value, 0),
|
||||
((bits != 0 && (bits & (bits - 1u)) == 0) ? 1 : 0));
|
||||
EXPECT_EQ(grotto::eval_bit_floor<std::int16_t>(value), ref_bit_floor(bits));
|
||||
EXPECT_EQ(grotto::eval_bit_ceil<std::int16_t>(value), ref_bit_ceil(bits));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExactSteps, IntegerLogsAndLogstar)
|
||||
{
|
||||
for (unsigned k : {0u, 4u})
|
||||
{
|
||||
for (int raw = 1; raw <= 32767; raw += (k == 0 ? 17 : 3))
|
||||
{
|
||||
const auto value = static_cast<std::int64_t>(raw);
|
||||
EXPECT_EQ(grotto::eval_ilog16<std::int16_t>(value, k) >> k, ref_ceil_log(static_cast<std::int16_t>(raw), k, 4))
|
||||
<< raw << " k=" << k;
|
||||
EXPECT_EQ(grotto::eval_ilog256<std::int16_t>(value, k) >> k, ref_ceil_log(static_cast<std::int16_t>(raw), k, 8))
|
||||
<< raw;
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(value, k) >> k, ref_logstar(static_cast<std::int16_t>(raw), k))
|
||||
<< raw;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(grotto::eval_ilog16<std::int16_t>(0, 0), grotto::ilog_of_zero);
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(-4, 0), grotto::ilog_of_zero);
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(1, 0), 0);
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(2, 0), 1);
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(4, 0), 2);
|
||||
EXPECT_EQ(grotto::eval_logstar<std::int16_t>(16, 0), 3);
|
||||
}
|
||||
|
||||
TEST(ExactSteps, DecimalFloorCeilAndLengths)
|
||||
{
|
||||
for (unsigned k : {0u, 4u})
|
||||
{
|
||||
for (int raw = -4000; raw <= 4000; raw += 3)
|
||||
{
|
||||
const long double x = std::ldexp(static_cast<long double>(raw), -static_cast<int>(k));
|
||||
const auto floor_raw = static_cast<std::int64_t>(std::floor(static_cast<double>(x)) * std::ldexp(1.0, static_cast<int>(k)));
|
||||
const auto ceil_raw = static_cast<std::int64_t>(std::ceil(static_cast<double>(x)) * std::ldexp(1.0, static_cast<int>(k)));
|
||||
EXPECT_EQ(grotto::eval_dec_floor(raw, k), floor_raw) << raw;
|
||||
EXPECT_EQ(grotto::eval_dec_ceil(raw, k), ceil_raw) << raw;
|
||||
const auto width = static_cast<std::int16_t>(raw);
|
||||
EXPECT_EQ(grotto::eval_dec_width(raw, k) >> k, ref_length(width, k, 10));
|
||||
EXPECT_EQ(grotto::eval_oct_width(raw, k) >> k, ref_length(width, k, 8));
|
||||
EXPECT_EQ(grotto::eval_b64_width(raw, k) >> k, ref_length(width, k, 64));
|
||||
const auto digits = std::llabs(static_cast<long long>(std::floor(std::fabs(static_cast<double>(x)))));
|
||||
EXPECT_EQ(grotto::eval_has_single_digit(raw, k) >> k, digits <= 9 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExactSteps, AngleScaleIsTheLinearFactor)
|
||||
{
|
||||
const unsigned k = 16;
|
||||
const std::int64_t deg = 180ll << k;
|
||||
const auto rad = grotto::eval_deg2rad(deg, k);
|
||||
const long double want = 180.0L * 3.14159265358979323846L / 180.0L * std::ldexp(1.0L, k);
|
||||
EXPECT_LE(std::fabsl(static_cast<long double>(rad) - want), 64.0L);
|
||||
const auto back = grotto::eval_rad2deg(rad, k);
|
||||
EXPECT_LE(std::llabs(back - deg), 4096);
|
||||
}
|
||||
|
||||
TEST(ExactSteps, StepLutAgreesWithTheScalarOnInt8)
|
||||
{
|
||||
const auto lut = grotto::make_exact_step_lut<std::int8_t>(0, [](std::int64_t raw) {
|
||||
return grotto::eval_bit_width<std::int8_t>(raw, 0);
|
||||
});
|
||||
for (int raw = -128; raw <= 127; ++raw)
|
||||
EXPECT_EQ(lut(static_cast<std::int8_t>(raw)), grotto::eval_bit_width<std::int8_t>(raw, 0));
|
||||
EXPECT_THROW(grotto::make_exact_step_lut<std::int32_t>(0, [](std::int64_t) { return 0; }), std::invalid_argument);
|
||||
}
|
||||
299
test/tests/experiment_test.cpp
Normal file
299
test/tests/experiment_test.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/app_flow.hpp"
|
||||
#include "dpf/app_plans.hpp"
|
||||
#include "dpf/beaver.hpp"
|
||||
#include "dpf/buffered_prg.hpp"
|
||||
#include "dpf/doerner_shelat.hpp"
|
||||
#include "dpf/experiment.hpp"
|
||||
#include "dpf/prg_aes.hpp"
|
||||
#include "dpf/random.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string tmp_dir(const char * tag)
|
||||
{
|
||||
const std::string dir =
|
||||
std::string("/tmp/libdpf_ex_") + tag + "_" + std::to_string(::getpid());
|
||||
(void)::system(("rm -rf " + dir + " && mkdir -p " + dir).c_str());
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::size_t count_lines(const std::string & path)
|
||||
{
|
||||
std::ifstream in(path);
|
||||
std::size_t n = 0;
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
++n;
|
||||
return n;
|
||||
}
|
||||
|
||||
TEST(Experiment, ReplayMatchesMakeDpfRoots)
|
||||
{
|
||||
dpf::experiment::master_seed master{};
|
||||
std::uint8_t alpha = 7;
|
||||
using node_t = dpf::prg::aes128::block_type;
|
||||
node_t root0a{}, root0b{}, root1a{}, root1b{};
|
||||
{
|
||||
dpf::experiment ex("dpf");
|
||||
master = ex.seed();
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, std::uint64_t{1});
|
||||
root0a = k0.root();
|
||||
root1a = k1.root();
|
||||
}
|
||||
{
|
||||
auto ex = dpf::experiment::replay("dpf", master);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, std::uint64_t{1});
|
||||
root0b = k0.root();
|
||||
root1b = k1.root();
|
||||
}
|
||||
EXPECT_EQ(std::memcmp(&root0a, &root0b, sizeof(root0a)), 0);
|
||||
EXPECT_EQ(std::memcmp(&root1a, &root1b, sizeof(root1a)), 0);
|
||||
}
|
||||
|
||||
TEST(Experiment, NestedRaiiRestoresOuterHook)
|
||||
{
|
||||
dpf::experiment outer("outer");
|
||||
const auto outer_seed = outer.seed();
|
||||
const auto a = dpf::uniform_sample<std::uint64_t>();
|
||||
{
|
||||
dpf::experiment inner("inner");
|
||||
EXPECT_NE(inner.seed(), outer_seed);
|
||||
(void)dpf::uniform_sample<std::uint64_t>();
|
||||
}
|
||||
// Outer stream continues where it left off (inner restored the hook).
|
||||
const auto b = dpf::uniform_sample<std::uint64_t>();
|
||||
auto again = dpf::experiment::replay("outer", outer_seed);
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), a);
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), b);
|
||||
}
|
||||
|
||||
TEST(Experiment, NoteSeedFromConstructors)
|
||||
{
|
||||
dpf::experiment ex("ctors");
|
||||
EXPECT_TRUE(ex.has_seed_named("master"));
|
||||
{
|
||||
dpf::beavers::oracle<std::uint64_t> o(16);
|
||||
EXPECT_TRUE(ex.has_seed_named("beavers::oracle"));
|
||||
EXPECT_TRUE(ex.has_seed_named("lane_table"));
|
||||
(void)o;
|
||||
}
|
||||
{
|
||||
dpf::randomness::buffered_prg<dpf::prg::aes128, std::uint64_t> prg(8);
|
||||
EXPECT_TRUE(ex.has_seed_named("buffered_prg"));
|
||||
(void)prg;
|
||||
}
|
||||
{
|
||||
dpf::prg_pad_rng<> pad;
|
||||
EXPECT_TRUE(ex.has_seed_named("prg_pad_rng"));
|
||||
(void)pad;
|
||||
}
|
||||
{
|
||||
dpf::pseudorandom_root_sampler<dpf::prg::aes128> roots;
|
||||
EXPECT_TRUE(ex.has_seed_named("pseudorandom_root_sampler"));
|
||||
(void)roots;
|
||||
}
|
||||
ex.note_seed("custom", std::uint32_t{0xdeadbeefu});
|
||||
EXPECT_TRUE(ex.has_seed_named("custom"));
|
||||
EXPECT_GE(ex.seed_count(), 6u);
|
||||
}
|
||||
|
||||
TEST(Experiment, RandomBytesCounterTracksUniformFill)
|
||||
{
|
||||
dpf::reset_random_bytes_count();
|
||||
EXPECT_EQ(dpf::random_bytes_count(), 0u);
|
||||
dpf::experiment ex("bytes");
|
||||
// ctor resets after drawing the master.
|
||||
EXPECT_EQ(dpf::random_bytes_count(), 0u);
|
||||
(void)dpf::uniform_sample<std::uint64_t>();
|
||||
EXPECT_EQ(dpf::random_bytes_count(), 8u);
|
||||
(void)dpf::uniform_sample<std::uint32_t>();
|
||||
EXPECT_EQ(dpf::random_bytes_count(), 12u);
|
||||
}
|
||||
|
||||
TEST(Experiment, IngestPlanCriticalPathAndEdges)
|
||||
{
|
||||
dpf::experiment ex("plan");
|
||||
auto p = dpf::protocol::fss_point_plan(0, 8, 16);
|
||||
ex.ingest_plan(p);
|
||||
EXPECT_EQ(ex.interactive_rounds(), 8u);
|
||||
EXPECT_EQ(ex.dag_depth(), p.waves());
|
||||
EXPECT_EQ(ex.plan_bytes_out(), 128u);
|
||||
EXPECT_EQ(ex.plan_edge_bytes(dpf::protocol::edge_channel::peer), 128u);
|
||||
EXPECT_EQ(ex.plan_edge_bytes(dpf::protocol::edge_channel::dealer), 0u);
|
||||
EXPECT_GT(ex.critical_path_length(), 0u);
|
||||
}
|
||||
|
||||
TEST(Experiment, MeasurePlanProbeAndTiming)
|
||||
{
|
||||
auto plan = dpf::protocol::mailbox_write_fused_plan(0);
|
||||
auto ex = dpf::app::measure_plan("mailbox", plan);
|
||||
EXPECT_EQ(ex.interactive_rounds(), 8u);
|
||||
EXPECT_EQ(ex.rounds().size(), 8u);
|
||||
std::size_t sum_out = 0, sum_in = 0;
|
||||
for (const auto & r : ex.rounds())
|
||||
{
|
||||
EXPECT_EQ(r.channel, dpf::protocol::edge_channel::peer);
|
||||
sum_out += r.bytes_out;
|
||||
sum_in += r.bytes_in;
|
||||
}
|
||||
EXPECT_EQ(sum_out, ex.bytes_out());
|
||||
EXPECT_EQ(sum_in, ex.bytes_in());
|
||||
EXPECT_EQ(ex.edge_bytes_out(dpf::protocol::edge_channel::peer),
|
||||
ex.bytes_out());
|
||||
EXPECT_GT(ex.wall_ns(), 0u);
|
||||
EXPECT_GT(ex.prg_evals(), 0u); // walk kernels expand
|
||||
EXPECT_TRUE(ex.has_seed_named("master"));
|
||||
}
|
||||
|
||||
TEST(Experiment, MeasureClientServersTwoRounds)
|
||||
{
|
||||
auto plan = dpf::protocol::n_server_pir_plan(0, 2, 64, 8);
|
||||
const auto slots = plan.slot_bytes_all();
|
||||
ASSERT_EQ(slots.size(), 2u);
|
||||
auto ex = dpf::app::measure_plan("pir2", plan);
|
||||
EXPECT_EQ(ex.interactive_rounds(), 2u);
|
||||
ASSERT_EQ(ex.rounds().size(), 2u);
|
||||
EXPECT_EQ(ex.rounds()[0].bytes_out, slots[0]);
|
||||
EXPECT_EQ(ex.rounds()[1].bytes_out, slots[1]);
|
||||
EXPECT_EQ(ex.plan_bytes_out(), slots[0] + slots[1]);
|
||||
}
|
||||
|
||||
TEST(Experiment, WriteCsvAllFilesAndAppend)
|
||||
{
|
||||
const auto dir = tmp_dir("csv");
|
||||
auto plan = dpf::protocol::fss_cmp_plan(0);
|
||||
auto ex = dpf::app::measure_plan("cmp", plan);
|
||||
ex.set_run_id(1);
|
||||
ex.write_csv(dir);
|
||||
EXPECT_EQ(count_lines(dir + "/summary.csv"), 2u); // header + row
|
||||
EXPECT_GE(count_lines(dir + "/rounds.csv"), 2u);
|
||||
EXPECT_GE(count_lines(dir + "/edges.csv"), 2u);
|
||||
EXPECT_GE(count_lines(dir + "/seeds.csv"), 2u);
|
||||
EXPECT_GE(count_lines(dir + "/critical_path.csv"), 2u);
|
||||
|
||||
ex.set_run_id(2);
|
||||
ex.write_csv(dir);
|
||||
EXPECT_EQ(count_lines(dir + "/summary.csv"), 3u); // append
|
||||
|
||||
std::ifstream seeds(dir + "/seeds.csv");
|
||||
std::string line;
|
||||
bool saw_master = false;
|
||||
while (std::getline(seeds, line))
|
||||
if (line.find("master") != std::string::npos)
|
||||
saw_master = true;
|
||||
EXPECT_TRUE(saw_master);
|
||||
(void)::system(("rm -rf " + dir).c_str());
|
||||
}
|
||||
|
||||
TEST(Experiment, RunMeasuredEnvCsv)
|
||||
{
|
||||
const auto dir = tmp_dir("env");
|
||||
ASSERT_EQ(::setenv("DPF_EXPERIMENT_DIR", dir.c_str(), 1), 0);
|
||||
const int rc = dpf::app::run_measured("env_csv",
|
||||
dpf::protocol::keyword_pir_compose_plan(0, 8), 2);
|
||||
EXPECT_EQ(rc, 0);
|
||||
EXPECT_GE(count_lines(dir + "/summary.csv"), 2u);
|
||||
ASSERT_EQ(::unsetenv("DPF_EXPERIMENT_DIR"), 0);
|
||||
(void)::system(("rm -rf " + dir).c_str());
|
||||
}
|
||||
|
||||
TEST(Experiment, ProbeAbsentDriveStillWorks)
|
||||
{
|
||||
// exercise_plan without experiment must not require clocks/probes.
|
||||
auto plan = dpf::protocol::range_count_plan(0);
|
||||
const auto cost = dpf::app::exercise_plan(plan);
|
||||
EXPECT_EQ(cost.rounds, 8u);
|
||||
EXPECT_EQ(cost.bytes, 128u);
|
||||
}
|
||||
|
||||
TEST(Experiment, UninstallRestoresSystemEntropy)
|
||||
{
|
||||
dpf::experiment::master_seed master{};
|
||||
{
|
||||
dpf::experiment ex("tmp");
|
||||
master = ex.seed();
|
||||
(void)dpf::uniform_sample<std::uint64_t>();
|
||||
}
|
||||
// After destroy, draws are fresh system entropy (not the replay stream).
|
||||
// Just ensure we can sample without a hook and without throwing.
|
||||
const auto x = dpf::uniform_sample<std::uint64_t>();
|
||||
const auto y = dpf::uniform_sample<std::uint64_t>();
|
||||
(void)x;
|
||||
(void)y;
|
||||
auto replayed = dpf::experiment::replay("tmp", master);
|
||||
// Replay still works after a window of system entropy.
|
||||
(void)dpf::uniform_sample<std::uint64_t>();
|
||||
}
|
||||
|
||||
TEST(Experiment, ThreadsDoNotCrossStreams)
|
||||
{
|
||||
constexpr int n = 32;
|
||||
std::vector<std::uint64_t> a(n), b(n);
|
||||
dpf::experiment::master_seed sa{}, sb{};
|
||||
std::thread t0([&] {
|
||||
dpf::experiment ex("A", "p0");
|
||||
sa = ex.seed();
|
||||
for (int i = 0; i < n; ++i)
|
||||
a[i] = dpf::uniform_sample<std::uint64_t>();
|
||||
});
|
||||
std::thread t1([&] {
|
||||
dpf::experiment ex("B", "p1");
|
||||
sb = ex.seed();
|
||||
for (int i = 0; i < n; ++i)
|
||||
b[i] = dpf::uniform_sample<std::uint64_t>();
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
EXPECT_NE(sa, sb);
|
||||
auto ra = dpf::experiment::replay("A", sa);
|
||||
for (int i = 0; i < n; ++i)
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), a[i]);
|
||||
auto rb = dpf::experiment::replay("B", sb);
|
||||
for (int i = 0; i < n; ++i)
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), b[i]);
|
||||
}
|
||||
|
||||
TEST(Experiment, MoveTransfersActiveContext)
|
||||
{
|
||||
dpf::experiment::master_seed master{};
|
||||
std::uint64_t first = 0;
|
||||
{
|
||||
dpf::experiment ex("move");
|
||||
master = ex.seed();
|
||||
first = dpf::uniform_sample<std::uint64_t>();
|
||||
dpf::experiment moved = std::move(ex);
|
||||
EXPECT_EQ(moved.seed(), master);
|
||||
const auto second = dpf::uniform_sample<std::uint64_t>();
|
||||
auto again = dpf::experiment::replay("move", master);
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), first);
|
||||
EXPECT_EQ(dpf::uniform_sample<std::uint64_t>(), second);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Experiment, BeginEndTimingWithoutProbe)
|
||||
{
|
||||
dpf::experiment ex("timing");
|
||||
ex.begin_timing();
|
||||
volatile std::uint64_t sink = 0;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
sink += dpf::uniform_sample<std::uint64_t>();
|
||||
(void)sink;
|
||||
ex.end_timing();
|
||||
EXPECT_GT(ex.wall_ns(), 0u);
|
||||
EXPECT_GE(ex.random_bytes(), 8000u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
62
test/tests/extractable_full_test.cpp
Normal file
62
test/tests/extractable_full_test.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
// Extractable full-domain expansion must match point eval share-for-share,
|
||||
// including both lanes of the packed leaf that holds the programmed point.
|
||||
TEST(ExtractableFull, Fp61SharesMatchPointEval)
|
||||
{
|
||||
constexpr std::uint8_t alpha = 9;
|
||||
const dpf::fp61 message{42};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, message, dpf::extractable{});
|
||||
|
||||
auto [buf0, it0] = dpf::eval_full(k0);
|
||||
auto [buf1, it1] = dpf::eval_full(k1);
|
||||
auto a0 = std::begin(it0);
|
||||
auto a1 = std::begin(it1);
|
||||
|
||||
for (unsigned i = 0; i < 256; ++i, ++a0, ++a1)
|
||||
{
|
||||
const auto x = static_cast<std::uint8_t>(i);
|
||||
const auto p0 = (*dpf::eval_point(k0, x)).raw();
|
||||
const auto p1 = (*dpf::eval_point(k1, x)).raw();
|
||||
EXPECT_EQ((*a0).raw().raw(), p0.raw()) << "party0 at " << i;
|
||||
EXPECT_EQ((*a1).raw().raw(), p1.raw()) << "party1 at " << i;
|
||||
}
|
||||
EXPECT_EQ(a0, std::end(it0));
|
||||
EXPECT_EQ(a1, std::end(it1));
|
||||
|
||||
EXPECT_EQ(dpf::reconstruct(*a0 = *std::begin(it0), *std::begin(it1)),
|
||||
dpf::fp61{0}); // silence unused if reconstruct needs the type
|
||||
(void)buf0;
|
||||
(void)buf1;
|
||||
}
|
||||
|
||||
TEST(ExtractableFull, Fp61OpenedMailbox)
|
||||
{
|
||||
constexpr std::uint8_t alpha = 9;
|
||||
const dpf::fp61 message{42};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, message, dpf::extractable{});
|
||||
auto [buf0, it0] = dpf::eval_full(k0);
|
||||
auto [buf1, it1] = dpf::eval_full(k1);
|
||||
|
||||
std::vector<dpf::fp61> y0, y1;
|
||||
for (auto it = std::begin(it0); it != std::end(it0); ++it)
|
||||
y0.push_back((*it).raw());
|
||||
for (auto it = std::begin(it1); it != std::end(it1); ++it)
|
||||
y1.push_back((*it).raw());
|
||||
|
||||
EXPECT_EQ(y0[alpha] - y1[alpha], message);
|
||||
EXPECT_EQ((y0[0] - y1[0]).raw(), 0u);
|
||||
EXPECT_EQ((y0[8] - y1[8]).raw(), 0u);
|
||||
EXPECT_EQ((y0[10] - y1[10]).raw(), 0u);
|
||||
|
||||
std::vector<dpf::fp61> challenge(256);
|
||||
for (std::size_t i = 0; i < challenge.size(); ++i)
|
||||
challenge[i] = dpf::fp61{static_cast<std::uint64_t>(i + 1)};
|
||||
EXPECT_TRUE(dpf::sketch_verify(dpf::sketch_fold(y0, challenge),
|
||||
dpf::sketch_fold(y1, challenge)));
|
||||
}
|
||||
215
test/tests/field_output_test.cpp
Normal file
215
test/tests/field_output_test.cpp
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename Key0, typename Key1, typename In>
|
||||
auto open_at(const Key0 &k0, const Key1 &k1, In x)
|
||||
{
|
||||
const auto y0 = *dpf::eval_point(k0, x);
|
||||
const auto y1 = *dpf::eval_point(k1, x);
|
||||
return dpf::reconstruct(y0, y1);
|
||||
}
|
||||
|
||||
template <typename Out, typename In>
|
||||
void expect_point_payload(In alpha, Out beta)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const In last = static_cast<In>(16);
|
||||
for (In x = 0; x < last; ++x)
|
||||
{
|
||||
const Out got = open_at(k0, k1, x);
|
||||
EXPECT_EQ(got, x == alpha ? beta : Out{}) << static_cast<unsigned>(x);
|
||||
}
|
||||
const Out on = open_at(k0, k1, alpha);
|
||||
EXPECT_EQ(on, beta);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Field64, ArithmeticMatchesThePrime)
|
||||
{
|
||||
constexpr auto p = dpf::field64::mod;
|
||||
EXPECT_EQ((dpf::field64{p - 1} + dpf::field64{p - 1}).raw(), p - 2);
|
||||
EXPECT_EQ((dpf::field64{p - 1} + dpf::field64{1}).raw(), 0u);
|
||||
EXPECT_EQ((dpf::field64{1} - dpf::field64{2}).raw(), p - 1);
|
||||
EXPECT_EQ((-dpf::field64{0}).raw(), 0u);
|
||||
EXPECT_EQ((-dpf::field64{1}).raw(), p - 1);
|
||||
EXPECT_EQ(dpf::field64{-1}.raw(), p - 1);
|
||||
EXPECT_EQ((dpf::field64{p - 1} * dpf::field64{p - 1}).raw(), 1u);
|
||||
EXPECT_EQ((dpf::field64{0x0123456789abcdefull} * dpf::field64{0xfedcba9876543210ull}).raw(),
|
||||
0xcfaeafd136c7bbaeull);
|
||||
EXPECT_EQ((dpf::field64{std::uint64_t{1} << 32} * dpf::field64{std::uint64_t{1} << 32}).raw(),
|
||||
0xffffffffull);
|
||||
const dpf::field64 a{1000};
|
||||
const dpf::field64 b{p - 5};
|
||||
const dpf::field64 c{17};
|
||||
EXPECT_EQ((a + b) * c, a * c + b * c);
|
||||
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::field64>);
|
||||
EXPECT_TRUE(std::is_standard_layout_v<dpf::field64>);
|
||||
}
|
||||
|
||||
TEST(Field128, ArithmeticMatchesThePrime)
|
||||
{
|
||||
const dpf::field128 almost{static_cast<unsigned __int128>(
|
||||
(static_cast<unsigned __int128>(dpf::field128::mod_hi) << 64)
|
||||
| dpf::field128::mod_lo) - 1};
|
||||
EXPECT_EQ(almost + dpf::field128{1}, dpf::field128{0});
|
||||
EXPECT_EQ(almost * almost, dpf::field128{1});
|
||||
EXPECT_EQ(-almost, dpf::field128{1});
|
||||
EXPECT_EQ(dpf::field128{-1}, almost);
|
||||
EXPECT_EQ(dpf::field128{1} - dpf::field128{2}, almost);
|
||||
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::field128>);
|
||||
EXPECT_TRUE(std::is_standard_layout_v<dpf::field128>);
|
||||
}
|
||||
|
||||
TEST(Field128, WideProduct)
|
||||
{
|
||||
const unsigned __int128 a =
|
||||
(static_cast<unsigned __int128>(0x123456789abcdef0ull) << 64)
|
||||
| 0x123456789abcdefull;
|
||||
const unsigned __int128 b =
|
||||
(static_cast<unsigned __int128>(0xfedcba9876543210ull) << 64)
|
||||
| 0xfedcba9876543210ull;
|
||||
const auto got = dpf::field128{a} * dpf::field128{b};
|
||||
EXPECT_EQ(got.hi(), 0xb0a8b1cbf73350c9ull);
|
||||
EXPECT_EQ(got.lo(), 0xf0123456789abe97ull);
|
||||
}
|
||||
|
||||
TEST(P256, GeneratorAndDoubling)
|
||||
{
|
||||
const auto g = dpf::p256::generator();
|
||||
EXPECT_EQ(dpf::p256{1}, g);
|
||||
EXPECT_EQ(dpf::p256{0}, dpf::p256{});
|
||||
EXPECT_TRUE(dpf::p256{}.is_identity());
|
||||
EXPECT_EQ(g + dpf::p256{}, g);
|
||||
EXPECT_EQ(g - g, dpf::p256{});
|
||||
EXPECT_EQ(-dpf::p256{1}, dpf::p256{-1});
|
||||
const auto two = g + g;
|
||||
EXPECT_EQ(two, dpf::p256{2});
|
||||
EXPECT_EQ(two - g, g);
|
||||
const unsigned char expect[] = {
|
||||
0x03,
|
||||
0x7c, 0xf2, 0x7b, 0x18, 0x8d, 0x03, 0x4f, 0x7e,
|
||||
0x8a, 0x52, 0x38, 0x03, 0x04, 0xb5, 0x1a, 0xc3,
|
||||
0xc0, 0x89, 0x69, 0xe2, 0x77, 0xf2, 0x1b, 0x35,
|
||||
0xa6, 0x0b, 0x48, 0xfc, 0x47, 0x66, 0x99, 0x78,
|
||||
};
|
||||
EXPECT_EQ(std::memcmp(two.bytes(), expect, 33), 0);
|
||||
EXPECT_EQ(dpf::p256::from_compressed(expect), two);
|
||||
const unsigned char bad[33] = {0x04};
|
||||
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument);
|
||||
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::p256>);
|
||||
EXPECT_TRUE(std::is_standard_layout_v<dpf::p256>);
|
||||
}
|
||||
|
||||
template <typename Out, typename Spec>
|
||||
void expect_cmp(std::uint8_t alpha, Out beta, Spec spec, bool leq)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, spec);
|
||||
for (int x = 0; x < 24; ++x)
|
||||
{
|
||||
const auto q = static_cast<std::uint8_t>(x);
|
||||
const auto y0 = dpf::eval_point<Out>(dpf::cmp, k0, q);
|
||||
const auto y1 = dpf::eval_point<Out>(dpf::cmp, k1, q);
|
||||
const bool hot = leq ? x <= static_cast<int>(alpha)
|
||||
: x < static_cast<int>(alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), hot ? beta : Out{}) << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FieldOutput, ComparisonPayload)
|
||||
{
|
||||
const auto f = dpf::field64{5};
|
||||
expect_cmp(std::uint8_t{10}, f, dpf::lt(f), false);
|
||||
expect_cmp(std::uint8_t{10}, f, dpf::leq(f), true);
|
||||
const auto w = dpf::field128{9};
|
||||
expect_cmp(std::uint8_t{10}, w, dpf::lt(w), false);
|
||||
expect_cmp(std::uint8_t{4}, dpf::p256{1}, dpf::lt(dpf::p256{1}), false);
|
||||
}
|
||||
|
||||
TEST(FieldOutput, IdcfPrefixUsesTheSameGroup)
|
||||
{
|
||||
const auto beta = dpf::field64{3};
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::idcf(dpf::lt(beta)));
|
||||
auto [a0, a1] = dpf::make_dpf(alpha, dpf::lt_at<4>(beta));
|
||||
for (int x = 0; x < 32; ++x)
|
||||
{
|
||||
const auto q = static_cast<std::uint8_t>(x);
|
||||
const auto full0 = dpf::eval_point<dpf::field64>(dpf::cmp, k0, q);
|
||||
const auto full1 = dpf::eval_point<dpf::field64>(dpf::cmp, k1, q);
|
||||
EXPECT_EQ(dpf::reconstruct(full0, full1),
|
||||
x < static_cast<int>(alpha) ? beta : dpf::field64{});
|
||||
const auto p0 = dpf::eval_point<4, dpf::field64>(dpf::cmp_prefix<4>, k0, q);
|
||||
const auto p1 = dpf::eval_point<4, dpf::field64>(dpf::cmp_prefix<4>, k1, q);
|
||||
const auto n0 = dpf::eval_point<dpf::field64>(dpf::cmp, a0, q);
|
||||
const auto n1 = dpf::eval_point<dpf::field64>(dpf::cmp, a1, q);
|
||||
EXPECT_EQ(dpf::reconstruct(p0, p1), dpf::reconstruct(n0, n1)) << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FieldOutput, PointPayloadRoundTrip)
|
||||
{
|
||||
expect_point_payload<dpf::field64>(std::uint8_t{0x2a}, dpf::field64{99});
|
||||
expect_point_payload<dpf::field128>(std::uint8_t{0x11}, dpf::field128{7});
|
||||
expect_point_payload<dpf::p256>(std::uint8_t{0x2a}, dpf::p256{1});
|
||||
}
|
||||
|
||||
TEST(FieldOutput, Fp61PayloadNearModulus)
|
||||
{
|
||||
const dpf::fp61 beta{dpf::fp61_mod - 1};
|
||||
expect_point_payload<dpf::fp61>(std::uint8_t{0x2a}, beta);
|
||||
expect_point_payload<dpf::fp61>(std::uint8_t{0x05}, dpf::fp61{dpf::fp61_mod - 7});
|
||||
}
|
||||
|
||||
TEST(FieldOutput, Fp61NegativeComparisonDelta)
|
||||
{
|
||||
// δ = −1 ≡ p−1 must survive comparison (not collapse to 0 via a 61-bit mask).
|
||||
expect_cmp(std::uint8_t{10}, dpf::fp61{dpf::fp61_mod - 1},
|
||||
dpf::lt(dpf::fp61{dpf::fp61_mod - 1}), false);
|
||||
expect_cmp(std::uint8_t{10}, dpf::fp61{dpf::fp61_mod - 1},
|
||||
dpf::leq(dpf::fp61{dpf::fp61_mod - 1}), true);
|
||||
}
|
||||
|
||||
TEST(FieldOutput, P256MalformedCompressedRejected)
|
||||
{
|
||||
unsigned char bad[33] = {0x02};
|
||||
bad[32] = 1; // x = 1 is not on P-256
|
||||
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument);
|
||||
|
||||
dpf::p256 bogus{};
|
||||
unsigned char raw[sizeof(dpf::p256)]{};
|
||||
std::memcpy(raw, &bogus, sizeof(raw));
|
||||
std::memcpy(raw, bad, 33);
|
||||
std::memcpy(&bogus, raw, sizeof(raw));
|
||||
EXPECT_THROW(bogus + dpf::p256{}, std::invalid_argument);
|
||||
EXPECT_THROW(-bogus, std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(FieldOutput, P256ScalarWideComparisonPayload)
|
||||
{
|
||||
const dpf::p256_scalar beta{17};
|
||||
expect_cmp(std::uint8_t{10}, beta, dpf::lt(beta), false);
|
||||
expect_cmp(std::uint8_t{10}, beta, dpf::leq(beta), true);
|
||||
}
|
||||
|
||||
TEST(FieldOutput, Field128NoncanonicalNegationAndEquality)
|
||||
{
|
||||
dpf::field128 a{};
|
||||
const std::uint64_t w[2] = {
|
||||
dpf::field128::mod_lo, dpf::field128::mod_hi};
|
||||
std::memcpy(&a, w, sizeof(w));
|
||||
EXPECT_EQ(dpf::field128::canonicalize(a), dpf::field128{});
|
||||
EXPECT_EQ(-a, dpf::field128{});
|
||||
EXPECT_EQ(a, dpf::field128{});
|
||||
}
|
||||
|
||||
310
test/tests/fixedpoint_beaver_test.cpp
Normal file
310
test/tests/fixedpoint_beaver_test.cpp
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "grotto.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct bus
|
||||
{
|
||||
std::mutex mu;
|
||||
std::condition_variable cv;
|
||||
std::uint64_t word[2]{};
|
||||
std::uint64_t deliver[2]{};
|
||||
int epoch = 0;
|
||||
int arrived = 0;
|
||||
int sent0 = 0;
|
||||
|
||||
std::uint64_t exchange(int me, std::uint64_t mine)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu);
|
||||
const int seen = epoch;
|
||||
word[me] = mine;
|
||||
if (me == 0)
|
||||
++sent0;
|
||||
if (++arrived == 2)
|
||||
{
|
||||
deliver[0] = word[1];
|
||||
deliver[1] = word[0];
|
||||
arrived = 0;
|
||||
++epoch;
|
||||
cv.notify_all();
|
||||
}
|
||||
else
|
||||
cv.wait(lock, [&] { return epoch != seen; });
|
||||
return deliver[me];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::pair<T, T> split_word(T secret)
|
||||
{
|
||||
using U = std::make_unsigned_t<T>;
|
||||
const U mask = static_cast<U>(~U{0});
|
||||
const U bits = static_cast<U>(secret);
|
||||
const U share = static_cast<U>(dpf::uniform_sample<U>() & mask);
|
||||
const U other = static_cast<U>(bits - share);
|
||||
return {static_cast<T>(share), static_cast<T>(other)};
|
||||
}
|
||||
|
||||
template <typename Fixed>
|
||||
simde_uint128 raw_bits(Fixed value)
|
||||
{
|
||||
std::uint64_t limb[4] = {};
|
||||
grotto::detail::store_raw_limbs(value.integral_representation(), limb);
|
||||
return simde_uint128{limb[0]} | (simde_uint128{limb[1]} << 64);
|
||||
}
|
||||
|
||||
template <unsigned I, unsigned F, unsigned Lf, typename L, unsigned Rf, typename R>
|
||||
void expect_product(std::pair<L, L> lhs, std::pair<R, R> rhs)
|
||||
{
|
||||
using shape = grotto::fixed_mul_beaver_shape<I, F, Lf, L, Rf, R>;
|
||||
ASSERT_TRUE(shape::fits);
|
||||
static const auto prep = grotto::make_fixed_mul_beaver_prep<I, F, Lf, L, Rf, R>();
|
||||
const auto triple = grotto::sample_fixed_mul_beaver_triple(shape::plan::multiply_bits);
|
||||
bus link;
|
||||
using result = typename shape::plan::result_type;
|
||||
result y0{};
|
||||
result y1{};
|
||||
std::exception_ptr e0;
|
||||
std::exception_ptr e1;
|
||||
std::thread t0([&] {
|
||||
try
|
||||
{
|
||||
y0 = grotto::eval_fixed_mul_beaver<I, F, Lf, L, Rf, R>(
|
||||
prep, triple, 0, lhs.first, rhs.first,
|
||||
[&](std::uint64_t mine) { return link.exchange(0, mine); });
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
e0 = std::current_exception();
|
||||
}
|
||||
});
|
||||
std::thread t1([&] {
|
||||
try
|
||||
{
|
||||
y1 = grotto::eval_fixed_mul_beaver<I, F, Lf, L, Rf, R>(
|
||||
prep, triple, 1, lhs.second, rhs.second,
|
||||
[&](std::uint64_t mine) { return link.exchange(1, mine); });
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
e1 = std::current_exception();
|
||||
}
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
if (e0)
|
||||
std::rethrow_exception(e0);
|
||||
if (e1)
|
||||
std::rethrow_exception(e1);
|
||||
EXPECT_EQ(link.sent0, static_cast<int>(shape::messages));
|
||||
|
||||
const auto clear = grotto::fixed_mul<I, F>(
|
||||
grotto::make_fixed_from_integral_type<Lf, L>(
|
||||
static_cast<L>(static_cast<std::make_unsigned_t<L>>(lhs.first)
|
||||
+ static_cast<std::make_unsigned_t<L>>(lhs.second))),
|
||||
grotto::make_fixed_from_integral_type<Rf, R>(
|
||||
static_cast<R>(static_cast<std::make_unsigned_t<R>>(rhs.first)
|
||||
+ static_cast<std::make_unsigned_t<R>>(rhs.second))));
|
||||
const unsigned storage = shape::storage_bits;
|
||||
const simde_uint128 mask = storage >= 128u
|
||||
? ~simde_uint128{0}
|
||||
: (simde_uint128{1} << storage) - 1;
|
||||
const auto got = (raw_bits(y0) + raw_bits(y1)) & mask;
|
||||
const auto want = raw_bits(clear) & mask;
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(got), static_cast<std::uint64_t>(want));
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(got >> 64),
|
||||
static_cast<std::uint64_t>(want >> 64));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T reconstruct(std::pair<T, T> shares)
|
||||
{
|
||||
using U = std::make_unsigned_t<T>;
|
||||
return static_cast<T>(static_cast<U>(shares.first) + static_cast<U>(shares.second));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(FixedMulBeaver, EmptyWindowIsZeroAndSilent)
|
||||
{
|
||||
using shape = grotto::fixed_mul_beaver_shape<0, 4, 0, std::int32_t, 0, std::int32_t>;
|
||||
EXPECT_TRUE(shape::fits);
|
||||
EXPECT_FALSE(shape::active);
|
||||
EXPECT_EQ(shape::messages, 0u);
|
||||
auto prep = grotto::make_fixed_mul_beaver_prep<0, 4, 0, std::int32_t, 0, std::int32_t>();
|
||||
grotto::fixed_mul_beaver_triple triple;
|
||||
int calls = 0;
|
||||
auto y = grotto::eval_fixed_mul_beaver<0, 4, 0, std::int32_t, 0, std::int32_t>(
|
||||
prep, triple, 0, 7, 9, [&](std::uint64_t) {
|
||||
++calls;
|
||||
return std::uint64_t{0};
|
||||
});
|
||||
EXPECT_EQ(calls, 0);
|
||||
EXPECT_EQ(y.integral_representation(), 0);
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, WideSignedWindowDoesNotFit)
|
||||
{
|
||||
using shape = grotto::fixed_mul_beaver_shape<16, 64, 16, std::int32_t, 16, std::int32_t>;
|
||||
EXPECT_FALSE(shape::fits);
|
||||
EXPECT_TRUE(shape::result_lift);
|
||||
EXPECT_GT(shape::plan::out_bits, 64u);
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, TruncationIsOnlyTheBeaverOpening)
|
||||
{
|
||||
using L = std::uint32_t;
|
||||
using shape = grotto::fixed_mul_beaver_shape<16, 0, 0, L, 0, L>;
|
||||
EXPECT_TRUE(shape::fits);
|
||||
EXPECT_FALSE(shape::lhs_lift);
|
||||
EXPECT_FALSE(shape::shift_right);
|
||||
EXPECT_EQ(shape::messages, 2u);
|
||||
auto prep = grotto::make_fixed_mul_beaver_prep<16, 0, 0, L, 0, L>();
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const auto lhs = split_word(i == 0 ? L{7} : dpf::uniform_sample<L>());
|
||||
const auto rhs = split_word(i == 0 ? L{9} : dpf::uniform_sample<L>());
|
||||
const auto triple = grotto::sample_fixed_mul_beaver_triple(16u);
|
||||
bus link;
|
||||
auto y0 = grotto::fixedpoint<0, std::uint16_t>{};
|
||||
auto y1 = y0;
|
||||
std::thread t0([&] {
|
||||
y0 = grotto::eval_fixed_mul_beaver<16, 0, 0, L, 0, L>(
|
||||
prep, triple, 0, lhs.first, rhs.first,
|
||||
[&](std::uint64_t mine) { return link.exchange(0, mine); });
|
||||
});
|
||||
std::thread t1([&] {
|
||||
y1 = grotto::eval_fixed_mul_beaver<16, 0, 0, L, 0, L>(
|
||||
prep, triple, 1, lhs.second, rhs.second,
|
||||
[&](std::uint64_t mine) { return link.exchange(1, mine); });
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
EXPECT_EQ(link.sent0, 2);
|
||||
const auto clear = grotto::fixed_mul<16, 0>(
|
||||
grotto::make_fixed_from_integral_type<0, L>(reconstruct(lhs)),
|
||||
grotto::make_fixed_from_integral_type<0, L>(reconstruct(rhs)));
|
||||
const auto got = static_cast<std::uint16_t>(
|
||||
static_cast<std::uint16_t>(y0.integral_representation())
|
||||
+ static_cast<std::uint16_t>(y1.integral_representation()));
|
||||
EXPECT_EQ(got, clear.integral_representation());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, NegativeProductFloorsIntoTheWindow)
|
||||
{
|
||||
using Q = std::int32_t;
|
||||
const auto lhs = split_word(Q{-3});
|
||||
const auto rhs = split_word(Q{1});
|
||||
expect_product<8, 4, 4, Q, 4, Q>(lhs, rhs);
|
||||
const auto neg = split_word(Q{-2});
|
||||
expect_product<8, 4, 4, Q, 4, Q>(lhs, neg);
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, SignedLiftAndRightShift)
|
||||
{
|
||||
using Q = std::int32_t;
|
||||
using shape = grotto::fixed_mul_beaver_shape<16, 16, 16, Q, 16, Q>;
|
||||
EXPECT_TRUE(shape::lhs_lift);
|
||||
EXPECT_TRUE(shape::shift_right);
|
||||
EXPECT_FALSE(shape::product_lift);
|
||||
EXPECT_EQ(shape::plan::multiply_bits, 48u);
|
||||
EXPECT_EQ(shape::messages, 5u);
|
||||
auto prep = grotto::make_fixed_mul_beaver_prep<16, 16, 16, Q, 16, Q>();
|
||||
const Q samples[][2] = {
|
||||
{0, 0},
|
||||
{1 << 16, 2 << 16},
|
||||
{-(3 << 16), 1 << 16},
|
||||
{-(3 << 16), -(2 << 16)},
|
||||
{std::numeric_limits<Q>::max(), -1},
|
||||
{std::numeric_limits<Q>::min(), 1},
|
||||
};
|
||||
for (const auto & sample : samples)
|
||||
{
|
||||
const auto triple = grotto::sample_fixed_mul_beaver_triple(48u);
|
||||
const auto lhs = split_word(sample[0]);
|
||||
const auto rhs = split_word(sample[1]);
|
||||
bus link;
|
||||
using result = typename shape::plan::result_type;
|
||||
result y0{};
|
||||
result y1{};
|
||||
std::thread t0([&] {
|
||||
y0 = grotto::eval_fixed_mul_beaver<16, 16, 16, Q, 16, Q>(
|
||||
prep, triple, 0, lhs.first, rhs.first,
|
||||
[&](std::uint64_t mine) { return link.exchange(0, mine); });
|
||||
});
|
||||
std::thread t1([&] {
|
||||
y1 = grotto::eval_fixed_mul_beaver<16, 16, 16, Q, 16, Q>(
|
||||
prep, triple, 1, lhs.second, rhs.second,
|
||||
[&](std::uint64_t mine) { return link.exchange(1, mine); });
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
EXPECT_EQ(link.sent0, 5);
|
||||
const auto clear = grotto::fixed_mul<16, 16>(
|
||||
grotto::make_fixed_from_integral_type<16, Q>(reconstruct(lhs)),
|
||||
grotto::make_fixed_from_integral_type<16, Q>(reconstruct(rhs)));
|
||||
const auto got = static_cast<std::uint32_t>(y0.integral_representation())
|
||||
+ static_cast<std::uint32_t>(y1.integral_representation());
|
||||
EXPECT_EQ(got, static_cast<std::uint32_t>(clear.integral_representation()))
|
||||
<< "lhs=" << sample[0] << " rhs=" << sample[1];
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, LeftShiftAndUnsignedLift)
|
||||
{
|
||||
using L = std::uint16_t;
|
||||
using shape = grotto::fixed_mul_beaver_shape<4, 8, 0, L, 0, L>;
|
||||
EXPECT_TRUE(shape::shift_left);
|
||||
EXPECT_TRUE(shape::fits);
|
||||
const auto lhs = split_word(L{3});
|
||||
const auto rhs = split_word(L{5});
|
||||
expect_product<4, 8, 0, L, 0, L>(lhs, rhs);
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, NarrowProductIsSignExtendedBeforeTheWindow)
|
||||
{
|
||||
using Q = std::int8_t;
|
||||
using shape = grotto::fixed_mul_beaver_shape<16, 8, 4, Q, 4, Q>;
|
||||
EXPECT_TRUE(shape::fits);
|
||||
EXPECT_TRUE(shape::lhs_lift);
|
||||
EXPECT_TRUE(shape::product_lift);
|
||||
EXPECT_TRUE(shape::result_lift);
|
||||
EXPECT_FALSE(shape::shift_right);
|
||||
EXPECT_EQ(shape::messages, 6u);
|
||||
const Q samples[] = {0, 1, -1, -3, 7, -8, 12, std::numeric_limits<Q>::max(),
|
||||
std::numeric_limits<Q>::min()};
|
||||
for (Q a : samples)
|
||||
for (Q b : samples)
|
||||
expect_product<16, 8, 4, Q, 4, Q>(split_word(a), split_word(b));
|
||||
}
|
||||
|
||||
TEST(FixedMulBeaver, Int64WindowUsesA96BitProduct)
|
||||
{
|
||||
using Q = std::int64_t;
|
||||
using shape = grotto::fixed_mul_beaver_shape<32, 32, 32, Q, 32, Q>;
|
||||
ASSERT_TRUE(shape::fits);
|
||||
EXPECT_EQ(shape::plan::multiply_bits, 96u);
|
||||
EXPECT_EQ(shape::plan::modulus_bits, 96u);
|
||||
EXPECT_TRUE(shape::lhs_lift);
|
||||
EXPECT_EQ(shape::messages, 8u);
|
||||
const Q samples[][2] = {
|
||||
{0, 0},
|
||||
{Q{3} << 32, Q{5} << 32},
|
||||
{-(Q{3} << 32), Q{1} << 32},
|
||||
{-(Q{4} << 32), -(Q{2} << 32)},
|
||||
};
|
||||
for (const auto & sample : samples)
|
||||
expect_product<32, 32, 32, Q, 32, Q>(split_word(sample[0]), split_word(sample[1]));
|
||||
}
|
||||
180
test/tests/flute_test.cpp
Normal file
180
test/tests/flute_test.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/flute.hpp"
|
||||
|
||||
TEST(Flute, EveryTwoBitFunction)
|
||||
{
|
||||
const unsigned delta = 2;
|
||||
std::uint8_t rows[4];
|
||||
for (unsigned fn = 0; fn < 16; ++fn)
|
||||
{
|
||||
for (unsigned j = 0; j < 4; ++j)
|
||||
rows[j] = static_cast<std::uint8_t>((fn >> j) & 1u);
|
||||
for (unsigned x = 0; x < 4; ++x)
|
||||
{
|
||||
std::uint8_t bits[2] = {
|
||||
static_cast<std::uint8_t>(x & 1u),
|
||||
static_cast<std::uint8_t>((x >> 1) & 1u),
|
||||
};
|
||||
auto want = dpf::flute::eval_plain(delta, 1, rows, bits);
|
||||
auto pair = dpf::flute::eval_pair(delta, 1, rows, bits);
|
||||
auto trio = dpf::flute::eval_trio(delta, 1, rows, bits);
|
||||
EXPECT_EQ(pair.opened, want);
|
||||
EXPECT_EQ(trio.opened, want);
|
||||
EXPECT_EQ(pair.online_bits, 2u);
|
||||
EXPECT_EQ(trio.online_bits, 3u);
|
||||
EXPECT_EQ(pair.mask.size(), 2u);
|
||||
EXPECT_EQ(trio.mask.size(), 3u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Flute, TwoOutputBits)
|
||||
{
|
||||
// LSB of the 3-bit index, and its majority with the other two bits.
|
||||
const unsigned delta = 3;
|
||||
const unsigned rows = 8;
|
||||
std::vector<std::uint8_t> columns(2 * rows);
|
||||
for (unsigned j = 0; j < rows; ++j)
|
||||
{
|
||||
columns[j] = static_cast<std::uint8_t>(j & 1u);
|
||||
const unsigned ones = (j & 1u) + ((j >> 1) & 1u) + ((j >> 2) & 1u);
|
||||
columns[rows + j] = static_cast<std::uint8_t>(ones >= 2);
|
||||
}
|
||||
for (unsigned x = 0; x < rows; ++x)
|
||||
{
|
||||
std::uint8_t bits[3] = {
|
||||
static_cast<std::uint8_t>(x & 1u),
|
||||
static_cast<std::uint8_t>((x >> 1) & 1u),
|
||||
static_cast<std::uint8_t>((x >> 2) & 1u),
|
||||
};
|
||||
auto want = dpf::flute::eval_plain(delta, 2, columns.data(), bits);
|
||||
auto got = dpf::flute::eval_pair(delta, 2, columns.data(), bits);
|
||||
EXPECT_EQ(got.opened, want);
|
||||
EXPECT_EQ(got.online_bits, 4u);
|
||||
std::uint8_t lam0 = 0;
|
||||
std::uint8_t lam1 = 0;
|
||||
lam0 = static_cast<std::uint8_t>(got.mask[0][0] ^ got.mask[0][1]);
|
||||
lam1 = static_cast<std::uint8_t>(got.mask[1][0] ^ got.mask[1][1]);
|
||||
(void)lam0;
|
||||
(void)lam1;
|
||||
for (unsigned w = 0; w < 2; ++w)
|
||||
{
|
||||
const std::uint8_t lam = static_cast<std::uint8_t>(
|
||||
got.mask[0][w] ^ got.mask[1][w]);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.masked[w] ^ lam), want[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void expect_lut(unsigned delta, unsigned n_out, const std::vector<std::uint8_t> & columns,
|
||||
const std::uint8_t * bits)
|
||||
{
|
||||
auto want = dpf::flute::eval_plain(delta, n_out, columns.data(), bits);
|
||||
auto pair = dpf::flute::eval_pair(delta, n_out, columns.data(), bits);
|
||||
auto trio = dpf::flute::eval_trio(delta, n_out, columns.data(), bits);
|
||||
EXPECT_EQ(pair.opened, want);
|
||||
EXPECT_EQ(trio.opened, want);
|
||||
EXPECT_EQ(pair.online_bits, static_cast<std::size_t>(2 * n_out));
|
||||
EXPECT_EQ(trio.online_bits, static_cast<std::size_t>(3 * n_out));
|
||||
for (unsigned w = 0; w < n_out; ++w)
|
||||
{
|
||||
const std::uint8_t two = static_cast<std::uint8_t>(
|
||||
pair.masked[w] ^ pair.mask[0][w] ^ pair.mask[1][w]);
|
||||
const std::uint8_t three = static_cast<std::uint8_t>(
|
||||
trio.masked[w] ^ trio.mask[0][w] ^ trio.mask[1][w] ^ trio.mask[2][w]);
|
||||
EXPECT_EQ(two, want[w]);
|
||||
EXPECT_EQ(three, want[w]);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t xorshift(std::uint32_t & s)
|
||||
{
|
||||
s ^= s << 13;
|
||||
s ^= s >> 17;
|
||||
s ^= s << 5;
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Flute, EveryFunctionThroughThreeBits)
|
||||
{
|
||||
for (unsigned delta = 1; delta <= 3; ++delta)
|
||||
{
|
||||
const unsigned rows = 1u << delta;
|
||||
const unsigned nfn = 1u << rows;
|
||||
std::vector<std::uint8_t> column(rows);
|
||||
std::vector<std::uint8_t> bits(delta);
|
||||
for (unsigned fn = 0; fn < nfn; ++fn)
|
||||
{
|
||||
for (unsigned j = 0; j < rows; ++j)
|
||||
column[j] = static_cast<std::uint8_t>((fn >> j) & 1u);
|
||||
for (unsigned x = 0; x < rows; ++x)
|
||||
{
|
||||
for (unsigned i = 0; i < delta; ++i)
|
||||
bits[i] = static_cast<std::uint8_t>((x >> i) & 1u);
|
||||
expect_lut(delta, 1, column, bits.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Flute, FourBitSampleAndEightBitSpecials)
|
||||
{
|
||||
const unsigned delta = 4;
|
||||
const unsigned rows = 16;
|
||||
std::uint32_t rng = 0xC0FFEEu;
|
||||
std::vector<std::uint8_t> columns(2 * rows);
|
||||
std::vector<std::uint8_t> bits(delta);
|
||||
for (unsigned sample = 0; sample < 24; ++sample)
|
||||
{
|
||||
for (auto & cell : columns)
|
||||
cell = static_cast<std::uint8_t>(xorshift(rng) & 1u);
|
||||
for (unsigned x = 0; x < rows; ++x)
|
||||
{
|
||||
for (unsigned i = 0; i < delta; ++i)
|
||||
bits[i] = static_cast<std::uint8_t>((x >> i) & 1u);
|
||||
expect_lut(delta, 2, columns, bits.data());
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned wide = 8;
|
||||
const unsigned wrows = 256;
|
||||
std::vector<std::uint8_t> table(4 * wrows, 0);
|
||||
for (unsigned j = 0; j < wrows; ++j)
|
||||
{
|
||||
unsigned ones = 0;
|
||||
for (unsigned i = 0; i < wide; ++i)
|
||||
ones += (j >> i) & 1u;
|
||||
table[j] = static_cast<std::uint8_t>(ones & 1u);
|
||||
table[wrows + j] = static_cast<std::uint8_t>(ones == wide);
|
||||
table[2 * wrows + j] = static_cast<std::uint8_t>((j >> 7) & 1u);
|
||||
table[3 * wrows + j] = static_cast<std::uint8_t>(j == 0 || j == 255);
|
||||
}
|
||||
const unsigned points[] = {0u, 1u, 2u, 17u, 128u, 170u, 254u, 255u};
|
||||
std::vector<std::uint8_t> wbits(wide);
|
||||
for (unsigned x : points)
|
||||
{
|
||||
for (unsigned i = 0; i < wide; ++i)
|
||||
wbits[i] = static_cast<std::uint8_t>((x >> i) & 1u);
|
||||
expect_lut(wide, 4, table, wbits.data());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Flute, RejectsABadIndex)
|
||||
{
|
||||
std::uint8_t column[2] = {0, 1};
|
||||
std::uint8_t bit = 1;
|
||||
EXPECT_THROW(dpf::flute::eval_plain(0, 1, column, &bit), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::flute::eval_pair(9, 1, column, &bit), std::invalid_argument);
|
||||
bit = 2;
|
||||
EXPECT_THROW(dpf::flute::eval_trio(1, 1, column, &bit), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::flute::eval_pair(1, 0, column, &bit), std::invalid_argument);
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
#include "dpf/fp61.hpp"
|
||||
#include "dpf/leaf_arithmetic.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -55,3 +59,26 @@ TEST(Fp61, StreamPrintsTheReducedValue)
|
|||
os << fp61{fp61_mod + 4};
|
||||
EXPECT_EQ(os.str(), "4");
|
||||
}
|
||||
|
||||
TEST(Fp61, FromSeedMersenneFoldsWideWords)
|
||||
{
|
||||
std::uint64_t words[2] = {fp61_mod - 1, 1};
|
||||
const auto a = fp61::from_seed(words, sizeof(words));
|
||||
// (p-1) + 8·1 (since 2^64 ≡ 8), then reduce — not a 61-bit mask of the lo word.
|
||||
EXPECT_NE(a.raw(), 0u);
|
||||
EXPECT_EQ(a.raw(), fp61::reduce((fp61_mod - 1) + 8));
|
||||
}
|
||||
|
||||
TEST(Fp61, LeafAddReducesNearModulus)
|
||||
{
|
||||
alignas(16) std::uint64_t left[2] = {fp61_mod - 1, fp61_mod - 2};
|
||||
alignas(16) std::uint64_t right[2] = {3, 5};
|
||||
alignas(16) std::uint64_t out[2]{};
|
||||
simde__m128i a{}, b{};
|
||||
std::memcpy(&a, left, sizeof(left));
|
||||
std::memcpy(&b, right, sizeof(right));
|
||||
const auto sum = dpf::leaf_arithmetic::add_t<fp61, simde__m128i>{}(a, b);
|
||||
std::memcpy(out, &sum, sizeof(out));
|
||||
EXPECT_EQ(out[0], 2u);
|
||||
EXPECT_EQ(out[1], 3u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
|
|
@ -349,6 +350,10 @@ TEST(Geneval, FullDomainUint8)
|
|||
}
|
||||
EXPECT_EQ(recon(g.party0[alpha], g.party1[alpha]), y);
|
||||
EXPECT_EQ(recon(g.party0[0], g.party1[0]), out_t{0});
|
||||
EXPECT_TRUE(dpf::verify(g.proof0, g.proof1));
|
||||
EXPECT_NE(recon(g.party1[alpha], g.party0[alpha]), y);
|
||||
g.proof0[0] = simde_mm_xor_si128(g.proof0[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(g.proof0, g.proof1));
|
||||
}
|
||||
|
||||
TEST(Geneval, EmptySequence)
|
||||
|
|
@ -796,10 +801,10 @@ TEST(Geneval, FullMatchesWholeIntervalAndRejectsHugeDomain)
|
|||
EXPECT_EQ(recon(full.party0[255], full.party1[255]), y);
|
||||
EXPECT_EQ(recon(full.party0[0], full.party1[0]), out_t{0});
|
||||
|
||||
EXPECT_THROW((dpf::geneval_full(uint32_t{1}, uint32_t{2}, rng<uint32_t>(),
|
||||
uint32_t{1})), std::length_error);
|
||||
EXPECT_THROW((dpf::geneval_interval(in_t{5}, in_t{1}, in_t{4}, in_t{3},
|
||||
rng<in_t>(), y)), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::geneval_full(uint32_t{1}, uint32_t{2}, rng<uint32_t>(),
|
||||
uint32_t{1}), std::length_error);
|
||||
EXPECT_THROW(dpf::geneval_interval(in_t{5}, in_t{1}, in_t{4}, in_t{3},
|
||||
rng<in_t>(), y), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Geneval, SequencePermutationKeepsWordsAndDuplicates)
|
||||
|
|
@ -981,8 +986,8 @@ TEST(Geneval, ArithShareOverflowAndWrappingInterval)
|
|||
|
||||
in_t from = 250;
|
||||
in_t to = 10;
|
||||
EXPECT_THROW((dpf::geneval_interval(dpf::arith_input, x0, x1, from, to,
|
||||
rng<in_t>(), y)), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::geneval_interval(dpf::arith_input, x0, x1, from, to,
|
||||
rng<in_t>(), y), std::invalid_argument);
|
||||
|
||||
from = 250;
|
||||
to = 255;
|
||||
|
|
@ -1043,8 +1048,8 @@ TEST(Geneval, SignedRegressionsFromTheCornerPass)
|
|||
out_t y = -7;
|
||||
|
||||
// An inverted signed range must not wrap the long way around.
|
||||
EXPECT_THROW((dpf::geneval_interval(in_t{2}, in_t{1}, in_t{4}, in_t{-3},
|
||||
rng<in_t>(), y)), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::geneval_interval(in_t{2}, in_t{1}, in_t{4}, in_t{-3},
|
||||
rng<in_t>(), y), std::invalid_argument);
|
||||
|
||||
// [INT_MIN, INT_MAX] is numeric order; full is bit-pattern order.
|
||||
// The words are the same trie. Each input's share matches either way.
|
||||
|
|
@ -1402,3 +1407,37 @@ TEST(Geneval, ArithDoernerShelatAndCmpMatchDealer)
|
|||
EXPECT_EQ(opened, ends[i] > secret ? beta : 0u) << int(ends[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Geneval, ArithCarryLeavesXorShares)
|
||||
{
|
||||
// 250 + 13 = 7 (mod 256). The carry must not collapse the sharing to (7, 0).
|
||||
auto split_u8 = [](std::uint64_t seed) {
|
||||
Pad pad;
|
||||
pad.n = seed;
|
||||
dpf::detail::local_cw_protocol<Pad> proto{pad};
|
||||
std::uint8_t x0 = 250;
|
||||
std::uint8_t x1 = 13;
|
||||
proto.encode_walk_shares(x0, x1, true);
|
||||
// Seven carries. Each is one bit-Beaver from `sample_beaver2` on
|
||||
// `xor_wrapper<uint8_t>`: `sample_fresh<2>` draws 7 ring elements
|
||||
// and each element is 8 pad bits.
|
||||
EXPECT_EQ(pad.n, seed + 7u * 7u * 8u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(x0 ^ x1), 7);
|
||||
EXPECT_NE(x1, 0);
|
||||
EXPECT_NE(x0, 7);
|
||||
};
|
||||
split_u8(1);
|
||||
split_u8(50);
|
||||
|
||||
Pad pad;
|
||||
dpf::detail::local_cw_protocol<Pad> proto{pad};
|
||||
const std::int8_t secret = -20;
|
||||
std::int8_t a0 = 100;
|
||||
std::int8_t a1 = static_cast<std::int8_t>(secret - a0);
|
||||
std::int8_t encoded = secret;
|
||||
dpf::utils::flip_msb_if_signed_integral(encoded);
|
||||
proto.encode_walk_shares(a0, a1, true);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(a0) ^ static_cast<std::uint8_t>(a1),
|
||||
static_cast<std::uint8_t>(encoded));
|
||||
EXPECT_NE(a1, 0);
|
||||
}
|
||||
|
|
|
|||
536
test/tests/gf2_adversarial_test.cpp
Normal file
536
test/tests/gf2_adversarial_test.cpp
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
/// @file gf2_adversarial_test.cpp
|
||||
/// @brief Adversarial checks for GF(2^k) output types.
|
||||
/// @details Full domains, lane boundaries, poisoned high bits, independent
|
||||
/// polynomial inverses, leaf scaling, shares, multi-output keys,
|
||||
/// comparisons, and proof tampering.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using u128 = unsigned __int128;
|
||||
|
||||
u128 poly_mul(u128 a, u128 b)
|
||||
{
|
||||
unsigned __int128 p = 0;
|
||||
for (int i = 0; i < 128 && b != 0; ++i)
|
||||
{
|
||||
if ((b & 1) != 0)
|
||||
p ^= a;
|
||||
a <<= 1;
|
||||
b >>= 1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
u128 poly_quot_rem(u128 & rem, u128 den, int)
|
||||
{
|
||||
unsigned __int128 q = 0;
|
||||
int den_bit = -1;
|
||||
for (int i = 127; i >= 0; --i)
|
||||
{
|
||||
if (((den >> i) & 1) != 0)
|
||||
{
|
||||
den_bit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (den_bit < 0)
|
||||
return 0;
|
||||
for (;;)
|
||||
{
|
||||
int bit = -1;
|
||||
for (int i = 127; i >= 0; --i)
|
||||
{
|
||||
if (((rem >> i) & 1) != 0)
|
||||
{
|
||||
bit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bit < den_bit)
|
||||
break;
|
||||
const int sh = bit - den_bit;
|
||||
q ^= u128{1} << sh;
|
||||
rem ^= den << sh;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
u128 poly_inv(u128 a, u128 mod)
|
||||
{
|
||||
u128 r0 = mod;
|
||||
u128 r1 = a;
|
||||
u128 s0 = 0;
|
||||
u128 s1 = 1;
|
||||
while (r1 != 0)
|
||||
{
|
||||
const u128 q = poly_quot_rem(r0, r1, 0);
|
||||
const u128 nr = r0;
|
||||
u128 ns = s0 ^ poly_mul(q, s1);
|
||||
int mod_bit = -1;
|
||||
for (int i = 127; i >= 0; --i)
|
||||
{
|
||||
if (((mod >> i) & 1) != 0)
|
||||
{
|
||||
mod_bit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (mod_bit >= 0)
|
||||
{
|
||||
int bit = -1;
|
||||
for (int i = 127; i >= 0; --i)
|
||||
{
|
||||
if (((ns >> i) & 1) != 0)
|
||||
{
|
||||
bit = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bit < mod_bit)
|
||||
break;
|
||||
ns ^= mod << (bit - mod_bit);
|
||||
}
|
||||
r0 = r1;
|
||||
s0 = s1;
|
||||
r1 = nr;
|
||||
s1 = ns;
|
||||
}
|
||||
return r0 == 1 ? s0 : 0;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
u128 modulus_of()
|
||||
{
|
||||
constexpr unsigned bits = F::bits;
|
||||
if constexpr (bits == 1)
|
||||
return 0x3;
|
||||
else if constexpr (bits == 2)
|
||||
return 0x7;
|
||||
else if constexpr (bits == 4)
|
||||
return 0x13;
|
||||
else if constexpr (bits == 8)
|
||||
return 0x11b;
|
||||
else if constexpr (bits == 16)
|
||||
return 0x1002d;
|
||||
else if constexpr (bits == 32)
|
||||
return 0x190200001ull;
|
||||
else
|
||||
return (u128{1} << 64) | (u128{1} << 63)
|
||||
| (u128{1} << 62) | (u128{1} << 53) | 1;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
F mask_elem(u128 v)
|
||||
{
|
||||
using word = typename F::integral_type;
|
||||
if constexpr (F::bits >= sizeof(word) * 8u)
|
||||
return F{static_cast<word>(v)};
|
||||
else
|
||||
{
|
||||
const word m = static_cast<word>((word{1} << F::bits) - word{1});
|
||||
return F{static_cast<word>(static_cast<word>(v) & m)};
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t rng_state = 0x123456789abcdefull;
|
||||
|
||||
std::uint64_t next_rng()
|
||||
{
|
||||
rng_state = rng_state * 6364136223846793005ull + 1ull;
|
||||
return rng_state;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
F random_elem()
|
||||
{
|
||||
return mask_elem<F>(next_rng());
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1, typename In, typename F>
|
||||
F open_at(const Key0 & k0, const Key1 & k1, In x)
|
||||
{
|
||||
const auto y0 = *dpf::eval_point(k0, x);
|
||||
const auto y1 = *dpf::eval_point(k1, x);
|
||||
const F fwd = dpf::reconstruct(y0, y1);
|
||||
const F rev = dpf::reconstruct(y1, y0);
|
||||
EXPECT_EQ(fwd, rev);
|
||||
return fwd;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_full_domain(F beta)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
for (int alpha_i : {0, 1, 31, 32, 127, 128, 254, 255})
|
||||
{
|
||||
const In alpha = static_cast<In>(alpha_i);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const In q = static_cast<In>(x);
|
||||
const F got = open_at<decltype(k0), decltype(k1), In, F>(k0, k1, q);
|
||||
EXPECT_EQ(got, q == alpha ? beta : F{}) << +q << " alpha " << +alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F, typename It0, typename It1>
|
||||
F open_written(It0 it0, It1 it1)
|
||||
{
|
||||
using V0 = typename std::iterator_traits<It0>::value_type;
|
||||
using V1 = typename std::iterator_traits<It1>::value_type;
|
||||
V0 a = *it0;
|
||||
V1 b = *it1;
|
||||
if constexpr (dpf::is_secret_share_v<V0>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
{
|
||||
const auto lane = [](auto v) {
|
||||
return F{static_cast<typename F::integral_type>(static_cast<unsigned>(v))};
|
||||
};
|
||||
return lane(a) + lane(b);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_interval_and_sequence(F beta)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
const In alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto [b0, i0] = dpf::eval_interval(k0, In{0}, In{255});
|
||||
auto [b1, i1] = dpf::eval_interval(k1, In{0}, In{255});
|
||||
auto p0 = std::begin(i0);
|
||||
auto p1 = std::begin(i1);
|
||||
for (int x = 0; x < 256; ++x, ++p0, ++p1)
|
||||
{
|
||||
EXPECT_EQ(open_written<F>(p0, p1),
|
||||
static_cast<In>(x) == alpha ? beta : F{}) << x;
|
||||
}
|
||||
|
||||
const In pts[] = {0, 1, 41, 42, 43, 127, 128, 255};
|
||||
auto [s0, is0] = dpf::eval_sequence(k0, std::begin(pts), std::end(pts));
|
||||
auto [s1, is1] = dpf::eval_sequence(k1, std::begin(pts), std::end(pts));
|
||||
auto q0 = std::begin(is0);
|
||||
auto q1 = std::begin(is1);
|
||||
for (In q : pts)
|
||||
{
|
||||
EXPECT_EQ(open_written<F>(q0, q1),
|
||||
q == alpha ? beta : F{}) << +q;
|
||||
++q0;
|
||||
++q1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_comparison(F beta)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
const In alpha = 10;
|
||||
auto [lt0, lt1] = dpf::make_dpf(alpha, dpf::lt(beta));
|
||||
auto [le0, le1] = dpf::make_dpf(alpha, dpf::leq(beta));
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const In q = static_cast<In>(x);
|
||||
const auto a0 = dpf::eval_point<F>(dpf::cmp, lt0, q);
|
||||
const auto a1 = dpf::eval_point<F>(dpf::cmp, lt1, q);
|
||||
const auto b0 = dpf::eval_point<F>(dpf::cmp, le0, q);
|
||||
const auto b1 = dpf::eval_point<F>(dpf::cmp, le1, q);
|
||||
EXPECT_EQ(dpf::reconstruct(a0, a1), x < 10 ? beta : F{}) << x;
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), x <= 10 ? beta : F{}) << x;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Node, typename F>
|
||||
void expect_scale(Node node, F k)
|
||||
{
|
||||
const auto scaled = dpf::multiply_leaf(node, k);
|
||||
unsigned char src[sizeof(Node)];
|
||||
unsigned char got[sizeof(Node)];
|
||||
std::memcpy(src, &node, sizeof(src));
|
||||
std::memcpy(got, &scaled, sizeof(got));
|
||||
if constexpr (F::bits >= 8)
|
||||
{
|
||||
constexpr std::size_t lanes = sizeof(Node) / sizeof(typename F::integral_type);
|
||||
for (std::size_t i = 0; i < lanes; ++i)
|
||||
{
|
||||
typename F::integral_type lane{};
|
||||
std::memcpy(&lane, src + i * sizeof(lane), sizeof(lane));
|
||||
typename F::integral_type out{};
|
||||
std::memcpy(&out, got + i * sizeof(out), sizeof(out));
|
||||
EXPECT_EQ(F{out}, F{lane} * k);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr unsigned w = F::bits;
|
||||
constexpr unsigned mask = (1u << w) - 1u;
|
||||
constexpr unsigned per = 8u / w;
|
||||
for (std::size_t i = 0; i < sizeof(Node); ++i)
|
||||
{
|
||||
unsigned expect = 0;
|
||||
for (unsigned lane = 0; lane < per; ++lane)
|
||||
{
|
||||
const auto a = F{(src[i] >> (lane * w)) & mask};
|
||||
expect |= static_cast<unsigned>((a * k).raw()) << (lane * w);
|
||||
}
|
||||
EXPECT_EQ(got[i], static_cast<unsigned char>(expect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_algebra()
|
||||
{
|
||||
const auto mod = modulus_of<F>();
|
||||
const F one{1};
|
||||
EXPECT_EQ(one + one, F{});
|
||||
EXPECT_EQ(-one, one);
|
||||
EXPECT_EQ(F{-7}, F{7});
|
||||
EXPECT_EQ(one * one, one);
|
||||
EXPECT_EQ(F{} * random_elem<F>(), F{});
|
||||
|
||||
const unsigned lim = F::bits <= 8 ? (1u << F::bits) : 0u;
|
||||
if (lim != 0)
|
||||
{
|
||||
for (unsigned a = 1; a < lim; ++a)
|
||||
{
|
||||
const auto inv = poly_inv(a, mod);
|
||||
ASSERT_NE(inv, 0u) << a;
|
||||
EXPECT_EQ(F{static_cast<typename F::integral_type>(a)}
|
||||
* mask_elem<F>(inv), one) << a;
|
||||
}
|
||||
for (unsigned a = 0; a < lim; ++a)
|
||||
{
|
||||
for (unsigned b = 0; b < lim; ++b)
|
||||
{
|
||||
const F prod = F{static_cast<typename F::integral_type>(a)}
|
||||
* F{static_cast<typename F::integral_type>(b)};
|
||||
if (a != 0 && b != 0)
|
||||
EXPECT_NE(prod, F{}) << a << " " << b;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int n = 0; n < 48; ++n)
|
||||
{
|
||||
const F a = random_elem<F>();
|
||||
if (a == F{})
|
||||
continue;
|
||||
const auto inv = poly_inv(a.raw(), mod);
|
||||
ASSERT_NE(inv, 0u);
|
||||
EXPECT_EQ(a * mask_elem<F>(inv), one);
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < 32; ++n)
|
||||
{
|
||||
const F a = random_elem<F>();
|
||||
const F b = random_elem<F>();
|
||||
const F c = random_elem<F>();
|
||||
EXPECT_EQ((a + b) * c, a * c + b * c);
|
||||
EXPECT_EQ((a * b) * c, a * (b * c));
|
||||
EXPECT_EQ(a + a, F{});
|
||||
}
|
||||
|
||||
unsigned char poison[sizeof(F)];
|
||||
std::memset(poison, 0xff, sizeof(poison));
|
||||
F poisoned{};
|
||||
std::memcpy(&poisoned, poison, sizeof(poisoned));
|
||||
EXPECT_EQ(poisoned.raw(), mask_elem<F>(~u128{0}).raw());
|
||||
EXPECT_EQ(poisoned + F{}, mask_elem<F>(~u128{0}));
|
||||
|
||||
unsigned char lo[16]{};
|
||||
unsigned char hi[16]{};
|
||||
lo[0] = 0x15;
|
||||
hi[0] = 0x15;
|
||||
if (sizeof(typename F::integral_type) < 16)
|
||||
hi[sizeof(typename F::integral_type)] = 0x5a;
|
||||
EXPECT_EQ(F::from_seed(lo, sizeof(lo)), F::from_seed(hi, sizeof(hi)));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_leaf_ops()
|
||||
{
|
||||
alignas(32) unsigned char bytes[32];
|
||||
for (int i = 0; i < 32; ++i)
|
||||
bytes[i] = static_cast<unsigned char>(0xA5 ^ i);
|
||||
simde__m128i n128;
|
||||
simde__m256i n256;
|
||||
std::memcpy(&n128, bytes, sizeof(n128));
|
||||
std::memcpy(&n256, bytes, sizeof(n256));
|
||||
const F k = F::bits == 1 ? F{1} : F{2};
|
||||
expect_scale(n128, k);
|
||||
expect_scale(n128, F{});
|
||||
expect_scale(n128, F{1});
|
||||
expect_scale(n256, k);
|
||||
|
||||
const auto sum = dpf::add_leaf<F>(n128, n128);
|
||||
unsigned char z[sizeof(n128)];
|
||||
std::memcpy(z, &sum, sizeof(z));
|
||||
for (unsigned char b : z)
|
||||
EXPECT_EQ(b, 0);
|
||||
unsigned char d[sizeof(n128)];
|
||||
const auto sub = dpf::subtract_leaf<F>(n128, n128);
|
||||
std::memcpy(d, &sub, sizeof(d));
|
||||
for (unsigned char b : d)
|
||||
EXPECT_EQ(b, 0);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_shares_and_prefix(F beta)
|
||||
{
|
||||
const auto add = dpf::make_additive_shares(beta);
|
||||
EXPECT_EQ(dpf::reconstruct(add.first, add.second), beta);
|
||||
EXPECT_EQ(dpf::reconstruct(add.second, add.first), beta);
|
||||
const auto sub = dpf::make_subtractive_shares(beta);
|
||||
EXPECT_EQ(dpf::reconstruct(sub.first, sub.second), beta);
|
||||
EXPECT_EQ(dpf::reconstruct(sub.second, sub.first), beta);
|
||||
|
||||
auto drawn = dpf::additively_share(beta);
|
||||
EXPECT_EQ(dpf::reconstruct(drawn.first, drawn.second), beta);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_verifiable(F beta)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
const In alpha = 0x11;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::verifiable{});
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const In q = static_cast<In>(x);
|
||||
dpf::proof_token a{};
|
||||
dpf::proof_token b{};
|
||||
const auto y0 = *dpf::eval_point(k0, q, dpf::prove(a));
|
||||
const auto y1 = *dpf::eval_point(k1, q, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << +q;
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), q == alpha ? beta : F{});
|
||||
}
|
||||
auto & leaves = const_cast<std::decay_t<decltype(k0.leaves())> &>(k0.leaves());
|
||||
auto * raw = reinterpret_cast<unsigned char *>(&std::get<0>(leaves).get());
|
||||
raw[0] = static_cast<unsigned char>(raw[0] ^ 0x1u);
|
||||
dpf::proof_token tampered{};
|
||||
dpf::proof_token honest{};
|
||||
(void)*dpf::eval_point(k0, alpha, dpf::prove(tampered));
|
||||
(void)*dpf::eval_point(k1, alpha, dpf::prove(honest));
|
||||
EXPECT_FALSE(dpf::verify(tampered, honest));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void run_all(F beta)
|
||||
{
|
||||
expect_algebra<F>();
|
||||
expect_full_domain(F{});
|
||||
expect_full_domain(F{1});
|
||||
expect_full_domain(beta);
|
||||
expect_interval_and_sequence(beta);
|
||||
expect_comparison(beta);
|
||||
expect_leaf_ops<F>();
|
||||
expect_shares_and_prefix(beta);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Gf2Adversarial, AlgebraDomainAndLeaves)
|
||||
{
|
||||
run_all(dpf::gf2{1});
|
||||
run_all(dpf::gf22{3});
|
||||
run_all(dpf::gf24{0xa});
|
||||
run_all(dpf::gf28{0x1b});
|
||||
run_all(dpf::gf216{0x2d});
|
||||
run_all(dpf::gf232{0x90200001u});
|
||||
run_all(dpf::gf264{0x11});
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, ProofsRejectAFlippedLeaf)
|
||||
{
|
||||
expect_verifiable(dpf::gf2{1});
|
||||
expect_verifiable(dpf::gf24{0xf});
|
||||
expect_verifiable(dpf::gf28{0xff});
|
||||
expect_verifiable(dpf::gf264{~std::uint64_t{0}});
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, PrefixLongerThanTheLane)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
const In alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::at<8>(dpf::gf28{0x1b}), dpf::gf28{0x5a});
|
||||
const auto p0 = *dpf::eval_point<0>(k0, alpha);
|
||||
const auto p1 = *dpf::eval_point<0>(k1, alpha);
|
||||
const auto l0 = *dpf::eval_point<1>(k0, alpha);
|
||||
const auto l1 = *dpf::eval_point<1>(k1, alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(p0, p1), dpf::gf28{0x1b});
|
||||
EXPECT_EQ(dpf::reconstruct(l0, l1), dpf::gf28{0x5a});
|
||||
const auto off0 = *dpf::eval_point<1>(k0, In{0});
|
||||
const auto off1 = *dpf::eval_point<1>(k1, In{0});
|
||||
EXPECT_EQ(dpf::reconstruct(off0, off1), dpf::gf28{});
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, MultiOutputDoesNotBleed)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
const In alpha = 0x2a;
|
||||
const dpf::gf28 lane{0x1b};
|
||||
const std::uint8_t wide = 9;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, lane, wide);
|
||||
for (int x = 0; x < 64; ++x)
|
||||
{
|
||||
const In q = static_cast<In>(x);
|
||||
const auto a0 = *dpf::eval_point<0>(k0, q);
|
||||
const auto a1 = *dpf::eval_point<0>(k1, q);
|
||||
const auto b0 = *dpf::eval_point<1>(k0, q);
|
||||
const auto b1 = *dpf::eval_point<1>(k1, q);
|
||||
EXPECT_EQ(dpf::reconstruct(a0, a1), q == alpha ? lane : dpf::gf28{});
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), q == alpha ? wide : std::uint8_t{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, NakedLeafMasksHighBits)
|
||||
{
|
||||
const auto leaf = dpf::make_naked_leaf<simde__m128i>(std::uint8_t{3}, dpf::gf24{0xF5});
|
||||
EXPECT_EQ((dpf::extract_leaf<simde__m128i, dpf::gf24>(leaf, std::uint8_t{3})),
|
||||
dpf::gf24{0x5});
|
||||
EXPECT_EQ((dpf::extract_leaf<simde__m128i, dpf::gf24>(leaf, std::uint8_t{2})),
|
||||
dpf::gf24{});
|
||||
EXPECT_EQ((dpf::extract_leaf<simde__m128i, dpf::gf24>(leaf, std::uint8_t{4})),
|
||||
dpf::gf24{});
|
||||
EXPECT_EQ((dpf::extract_leaf<simde__m128i, dpf::gf24>(leaf, std::uint8_t{31})),
|
||||
dpf::gf24{});
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, UnassignedWildcardThrows)
|
||||
{
|
||||
auto [w0, w1] = dpf::make_dpf(std::uint8_t{3}, dpf::wildcard_value<dpf::gf28>{});
|
||||
EXPECT_THROW((void)*dpf::eval_point(w0, std::uint8_t{3}), std::runtime_error);
|
||||
EXPECT_THROW((void)*dpf::eval_point(w1, std::uint8_t{3}), std::runtime_error);
|
||||
auto [p0, p1] = dpf::make_dpf(std::uint8_t{1}, dpf::wildcard_value<dpf::gf2>{});
|
||||
EXPECT_THROW((void)*dpf::eval_point(p0, std::uint8_t{1}), std::runtime_error);
|
||||
(void)p1;
|
||||
}
|
||||
|
||||
TEST(Gf2Adversarial, FromSeedDropsBytesPastTheWord)
|
||||
{
|
||||
unsigned char narrow[16]{};
|
||||
unsigned char wide[16];
|
||||
std::memset(wide, 0xa5, sizeof(wide));
|
||||
narrow[0] = 0x15;
|
||||
wide[0] = 0x15;
|
||||
EXPECT_EQ(dpf::gf24::from_seed(narrow, 16), dpf::gf24{0x5});
|
||||
EXPECT_EQ(dpf::gf24::from_seed(wide, 16), dpf::gf24{0x5});
|
||||
narrow[0] = 0xab;
|
||||
wide[0] = 0xab;
|
||||
EXPECT_EQ(dpf::gf28::from_seed(narrow, 1), dpf::gf28::from_seed(wide, 16));
|
||||
}
|
||||
306
test/tests/gf2_test.cpp
Normal file
306
test/tests/gf2_test.cpp
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename F>
|
||||
F pow_elem(F base, unsigned long long exp)
|
||||
{
|
||||
F r{1};
|
||||
while (exp != 0)
|
||||
{
|
||||
if ((exp & 1ull) != 0)
|
||||
r = r * base;
|
||||
exp >>= 1;
|
||||
if (exp != 0)
|
||||
base = base * base;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_field_laws()
|
||||
{
|
||||
constexpr unsigned bits = F::bits;
|
||||
EXPECT_EQ(F{0} + F{1}, F{1});
|
||||
EXPECT_EQ(F{1} + F{1}, F{0});
|
||||
EXPECT_EQ(-F{1}, F{1});
|
||||
EXPECT_EQ(F{-1}, F{1});
|
||||
EXPECT_EQ(F{1} * F{1}, F{1});
|
||||
EXPECT_EQ(F{0} * F{5}, F{0});
|
||||
EXPECT_TRUE(std::is_trivially_copyable_v<F>);
|
||||
EXPECT_TRUE(std::is_standard_layout_v<F>);
|
||||
EXPECT_TRUE(dpf::utils::has_characteristic_two_v<F>);
|
||||
EXPECT_EQ(dpf::utils::bitlength_of_v<F>, bits);
|
||||
EXPECT_EQ(dpf::utils::make_default_v<F>, F{1});
|
||||
|
||||
if constexpr (bits > 1)
|
||||
{
|
||||
const F x{2};
|
||||
F xpow{1};
|
||||
for (unsigned i = 0; i < bits; ++i)
|
||||
xpow = xpow * x;
|
||||
if constexpr (bits == 2 || bits == 4)
|
||||
EXPECT_EQ(xpow, F{0x3});
|
||||
else if constexpr (bits == 8)
|
||||
EXPECT_EQ(xpow, F{0x1b});
|
||||
else if constexpr (bits == 16)
|
||||
EXPECT_EQ(xpow, F{0x2d});
|
||||
else if constexpr (bits == 32)
|
||||
EXPECT_EQ(xpow.raw(), 0x90200001u);
|
||||
else
|
||||
{
|
||||
const auto tail = static_cast<typename F::integral_type>(
|
||||
(typename F::integral_type{1} << 63)
|
||||
| (typename F::integral_type{1} << 62)
|
||||
| (typename F::integral_type{1} << 53)
|
||||
| typename F::integral_type{1});
|
||||
EXPECT_EQ(xpow.raw(), tail);
|
||||
}
|
||||
}
|
||||
|
||||
const F a{0x13};
|
||||
const F b{0x2a};
|
||||
const F c{0x7};
|
||||
EXPECT_EQ((a + b) * c, a * c + b * c);
|
||||
EXPECT_EQ((a * b) * c, a * (b * c));
|
||||
if constexpr (bits <= 32)
|
||||
{
|
||||
if (a != F{0})
|
||||
EXPECT_EQ(a * pow_elem(a, (1ull << bits) - 2ull), F{1});
|
||||
}
|
||||
|
||||
if constexpr (bits <= 8)
|
||||
{
|
||||
const unsigned lim = 1u << bits;
|
||||
for (unsigned i = 1; i < lim; ++i)
|
||||
EXPECT_EQ(F{i} * pow_elem(F{i}, (1ull << bits) - 2ull), F{1}) << i;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1, typename In>
|
||||
auto open_at(const Key0 & k0, const Key1 & k1, In x)
|
||||
{
|
||||
const auto y0 = *dpf::eval_point(k0, x);
|
||||
const auto y1 = *dpf::eval_point(k1, x);
|
||||
return dpf::reconstruct(y0, y1);
|
||||
}
|
||||
|
||||
template <typename Out, typename In>
|
||||
void expect_point_payload(In alpha, Out beta)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
for (int x = 0; x < 32; ++x)
|
||||
{
|
||||
const In q = static_cast<In>(x);
|
||||
const Out got = open_at(k0, k1, q);
|
||||
EXPECT_EQ(got, q == alpha ? beta : Out{}) << static_cast<unsigned>(x);
|
||||
}
|
||||
EXPECT_EQ(open_at(k0, k1, alpha), beta);
|
||||
}
|
||||
|
||||
template <typename Out, typename Spec>
|
||||
void expect_cmp(std::uint8_t alpha, Out beta, Spec spec, bool leq)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, spec);
|
||||
for (int x = 0; x < 24; ++x)
|
||||
{
|
||||
const auto q = static_cast<std::uint8_t>(x);
|
||||
const auto y0 = dpf::eval_point<Out>(dpf::cmp, k0, q);
|
||||
const auto y1 = dpf::eval_point<Out>(dpf::cmp, k1, q);
|
||||
const bool hot = leq ? x <= static_cast<int>(alpha)
|
||||
: x < static_cast<int>(alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), hot ? beta : Out{}) << x;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Gf2, FieldLaws)
|
||||
{
|
||||
expect_field_laws<dpf::gf2>();
|
||||
expect_field_laws<dpf::gf22>();
|
||||
expect_field_laws<dpf::gf24>();
|
||||
expect_field_laws<dpf::gf28>();
|
||||
expect_field_laws<dpf::gf216>();
|
||||
expect_field_laws<dpf::gf232>();
|
||||
expect_field_laws<dpf::gf264>();
|
||||
}
|
||||
|
||||
TEST(Gf2, SubByteAdditionIsXor)
|
||||
{
|
||||
EXPECT_EQ(dpf::gf22{3} + dpf::gf22{1}, dpf::gf22{2});
|
||||
EXPECT_EQ(dpf::gf24{0xf} + dpf::gf24{1}, dpf::gf24{0xe});
|
||||
EXPECT_EQ(dpf::gf24{2} * dpf::gf24{2}, dpf::gf24{4});
|
||||
EXPECT_EQ(dpf::gf22{2} * dpf::gf22{2}, dpf::gf22{3});
|
||||
}
|
||||
|
||||
TEST(Gf2, LeafScaleIsPerLane)
|
||||
{
|
||||
alignas(16) unsigned char bytes[16];
|
||||
for (int i = 0; i < 16; ++i)
|
||||
bytes[i] = 0xe4;
|
||||
simde__m128i node;
|
||||
std::memcpy(&node, bytes, sizeof(node));
|
||||
const auto scaled = dpf::multiply_leaf(node, dpf::gf24{2});
|
||||
unsigned char out[16];
|
||||
std::memcpy(out, &scaled, sizeof(out));
|
||||
const auto lo = static_cast<unsigned>((dpf::gf24{0x4} * dpf::gf24{2}).raw());
|
||||
const auto hi = static_cast<unsigned>((dpf::gf24{0xe} * dpf::gf24{2}).raw());
|
||||
const auto expect = static_cast<unsigned char>(lo | (hi << 4));
|
||||
for (unsigned char b : out)
|
||||
EXPECT_EQ(b, expect);
|
||||
|
||||
const auto summed = dpf::add_leaf<dpf::gf24>(node, node);
|
||||
unsigned char z[16];
|
||||
std::memcpy(z, &summed, sizeof(z));
|
||||
for (unsigned char b : z)
|
||||
EXPECT_EQ(b, 0);
|
||||
}
|
||||
|
||||
TEST(Gf2, ShufbScalarVectorMatchesFieldMul)
|
||||
{
|
||||
alignas(32) unsigned char bytes[33];
|
||||
for (int i = 0; i < 33; ++i)
|
||||
bytes[i] = static_cast<unsigned char>(i * 17 + 3);
|
||||
const unsigned scalars[] = {0u, 1u, 2u, 0x1bu, 0x80u, 0xffu, 0x2du, 0x8000u, 0xffffu};
|
||||
|
||||
for (unsigned s : scalars)
|
||||
{
|
||||
if (s > 0xffu)
|
||||
continue;
|
||||
simde__m128i n128;
|
||||
simde__m256i n256;
|
||||
std::memcpy(&n128, bytes, 16);
|
||||
std::memcpy(&n256, bytes, 32);
|
||||
const auto a128 = dpf::multiply_leaf(n128, dpf::gf28{s});
|
||||
const auto a256 = dpf::multiply_leaf(n256, dpf::gf28{s});
|
||||
unsigned char o128[16];
|
||||
unsigned char o256[32];
|
||||
std::memcpy(o128, &a128, 16);
|
||||
std::memcpy(o256, &a256, 32);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
EXPECT_EQ(o128[i], (dpf::gf28{bytes[i]} * dpf::gf28{s}).raw()) << s << " " << i;
|
||||
for (int i = 0; i < 32; ++i)
|
||||
EXPECT_EQ(o256[i], (dpf::gf28{bytes[i]} * dpf::gf28{s}).raw()) << s << " " << i;
|
||||
|
||||
unsigned char tail[33];
|
||||
std::memcpy(tail, bytes, 33);
|
||||
dpf::gf2_detail::scale_gf28(tail, bytes, 33, static_cast<std::uint8_t>(s));
|
||||
for (int i = 0; i < 33; ++i)
|
||||
EXPECT_EQ(tail[i], (dpf::gf28{bytes[i]} * dpf::gf28{s}).raw()) << "tail " << i;
|
||||
}
|
||||
|
||||
alignas(32) unsigned char lanes[32];
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
const auto lane = static_cast<std::uint16_t>(i * 19 + 1);
|
||||
lanes[2 * i] = static_cast<unsigned char>(lane);
|
||||
lanes[2 * i + 1] = static_cast<unsigned char>(lane >> 8);
|
||||
}
|
||||
for (unsigned s : scalars)
|
||||
{
|
||||
simde__m256i node;
|
||||
std::memcpy(&node, lanes, 32);
|
||||
const auto scaled = dpf::multiply_leaf(node, dpf::gf216{s});
|
||||
unsigned char got[32];
|
||||
std::memcpy(got, &scaled, 32);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
const auto lane = static_cast<std::uint16_t>(lanes[2 * i] | (lanes[2 * i + 1] << 8));
|
||||
const auto prod = (dpf::gf216{lane} * dpf::gf216{s}).raw();
|
||||
EXPECT_EQ(got[2 * i], static_cast<unsigned char>(prod)) << s << " " << i;
|
||||
EXPECT_EQ(got[2 * i + 1], static_cast<unsigned char>(prod >> 8)) << s << " " << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Gf2, PointPayload)
|
||||
{
|
||||
expect_point_payload<dpf::gf2>(std::uint8_t{0x2a}, dpf::gf2{1});
|
||||
expect_point_payload<dpf::gf22>(std::uint8_t{0x11}, dpf::gf22{3});
|
||||
expect_point_payload<dpf::gf24>(std::uint8_t{0x05}, dpf::gf24{0xa});
|
||||
expect_point_payload<dpf::gf28>(std::uint8_t{0x2a}, dpf::gf28{0x1b});
|
||||
expect_point_payload<dpf::gf216>(std::uint8_t{0x07}, dpf::gf216{0x2d});
|
||||
expect_point_payload<dpf::gf232>(std::uint8_t{0x13}, dpf::gf232{0x90200001u});
|
||||
expect_point_payload<dpf::gf264>(std::uint8_t{0x04}, dpf::gf264{0x11});
|
||||
}
|
||||
|
||||
TEST(Gf2, ComparisonPayload)
|
||||
{
|
||||
expect_cmp(std::uint8_t{10}, dpf::gf24{0x7}, dpf::lt(dpf::gf24{0x7}), false);
|
||||
expect_cmp(std::uint8_t{10}, dpf::gf28{0x1b}, dpf::leq(dpf::gf28{0x1b}), true);
|
||||
expect_cmp(std::uint8_t{4}, dpf::gf264{0x53}, dpf::lt(dpf::gf264{0x53}), false);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_inv_exhaustive()
|
||||
{
|
||||
EXPECT_THROW(dpf::detail::shamir_field<F>::inv(F{0}), std::invalid_argument);
|
||||
const unsigned long long n = 1ull << F::bits;
|
||||
for (unsigned long long i = 1; i < n; ++i)
|
||||
{
|
||||
const F a{static_cast<typename F::integral_type>(i)};
|
||||
const F b = dpf::detail::shamir_field<F>::inv(a);
|
||||
EXPECT_EQ(a * b, F{1}) << i;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void expect_shamir23(F secret, F slope)
|
||||
{
|
||||
const auto shares = dpf::shamir::deal<F, 2, 3>(secret, std::array<F, 1>{{slope}});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<0>(shares), std::get<1>(shares))), secret);
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<1>(shares), std::get<2>(shares))), secret);
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<2>(shares), std::get<0>(shares))), secret);
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(
|
||||
std::get<0>(shares), std::get<1>(shares), std::get<2>(shares))), secret);
|
||||
}
|
||||
|
||||
TEST(Gf2, InverseAndShamir)
|
||||
{
|
||||
expect_inv_exhaustive<dpf::gf2>();
|
||||
expect_inv_exhaustive<dpf::gf22>();
|
||||
expect_inv_exhaustive<dpf::gf24>();
|
||||
expect_inv_exhaustive<dpf::gf28>();
|
||||
|
||||
const dpf::gf216 wide[] = {dpf::gf216{1}, dpf::gf216{2}, dpf::gf216{0x2d},
|
||||
dpf::gf216{0xffff}};
|
||||
for (auto a : wide)
|
||||
EXPECT_EQ(a * dpf::detail::shamir_field<dpf::gf216>::inv(a), dpf::gf216{1});
|
||||
const dpf::gf232 mid[] = {dpf::gf232{1}, dpf::gf232{2}, dpf::gf232{0x190200001u}};
|
||||
for (auto a : mid)
|
||||
EXPECT_EQ(a * dpf::detail::shamir_field<dpf::gf232>::inv(a), dpf::gf232{1});
|
||||
const dpf::gf264 big[] = {dpf::gf264{1}, dpf::gf264{2}, dpf::gf264{~std::uint64_t{0}}};
|
||||
for (auto a : big)
|
||||
EXPECT_EQ(a * dpf::detail::shamir_field<dpf::gf264>::inv(a), dpf::gf264{1});
|
||||
|
||||
expect_shamir23(dpf::gf22{1}, dpf::gf22{2});
|
||||
expect_shamir23(dpf::gf28{0x1b}, dpf::gf28{0x5a});
|
||||
expect_shamir23(dpf::gf264{0x11}, dpf::gf264{0x53});
|
||||
|
||||
const auto shares = dpf::shamir::deal<dpf::gf28, 3, 5>(
|
||||
dpf::gf28{0x1b}, std::array<dpf::gf28, 2>{{dpf::gf28{2}, dpf::gf28{9}}});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(
|
||||
std::get<0>(shares), std::get<2>(shares), std::get<4>(shares))),
|
||||
dpf::gf28{0x1b});
|
||||
|
||||
// Point 2 is 0 in GF(2), so that party would hold the secret.
|
||||
const auto leaked = dpf::shamir::deal<dpf::gf2, 2, 3>(
|
||||
dpf::gf2{1}, std::array<dpf::gf2, 1>{{dpf::gf2{1}}});
|
||||
EXPECT_EQ(std::get<1>(leaked).raw(), dpf::gf2{1}.raw());
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(leaked), std::get<1>(leaked))),
|
||||
std::invalid_argument);
|
||||
|
||||
const auto one = dpf::shamir::deal<dpf::gf2, 1, 1>(dpf::gf2{1}, std::array<dpf::gf2, 0>{});
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(std::get<0>(one)), dpf::gf2{1});
|
||||
}
|
||||
487
test/tests/growing_idpf_test.cpp
Normal file
487
test/tests/growing_idpf_test.cpp
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using block_t = dpf::prg::aes128::block_type;
|
||||
constexpr std::size_t kBits = 8;
|
||||
|
||||
static block_t g_roots[2];
|
||||
static int g_ri = 0;
|
||||
block_t take_root()
|
||||
{
|
||||
return g_roots[g_ri++];
|
||||
}
|
||||
|
||||
void reset_roots(block_t r0, block_t r1)
|
||||
{
|
||||
g_roots[0] = r0;
|
||||
g_roots[1] = r1;
|
||||
g_ri = 0;
|
||||
}
|
||||
|
||||
std::uint8_t sibling_at(std::uint8_t alpha, std::size_t level)
|
||||
{
|
||||
return static_cast<std::uint8_t>(alpha ^ (1u << (kBits - 1 - level)));
|
||||
}
|
||||
|
||||
template <std::size_t I, std::size_t N, typename Keys>
|
||||
auto grow_rest(Keys keys, std::uint8_t alpha,
|
||||
const std::array<std::uint64_t, N> & betas)
|
||||
{
|
||||
if constexpr (I >= N)
|
||||
return keys;
|
||||
else
|
||||
{
|
||||
auto next = dpf::extend(keys.first, keys.second, alpha,
|
||||
dpf::at<I + 1>(betas[I]));
|
||||
return grow_rest<I + 1, N>(std::move(next), alpha, betas);
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
auto grow_incrementally(std::uint8_t alpha,
|
||||
const std::array<std::uint64_t, N> & betas, block_t r0, block_t r1)
|
||||
{
|
||||
reset_roots(r0, r1);
|
||||
auto keys = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<1>(betas[0]));
|
||||
return grow_rest<1, N>(std::move(keys), alpha, betas);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GrowingIdpf, PrefixesOpenToOwnPayload)
|
||||
{
|
||||
const std::uint8_t alpha = 0xB2;
|
||||
const std::array<std::uint64_t, kBits> betas = {
|
||||
0x11ull, 0x2222ull, 0x333333ull, 0x44444444ull,
|
||||
0x5555555555ull, 0x666666666666ull, 0x77777777777777ull,
|
||||
0x8888888888888888ull};
|
||||
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
auto [k0, k1] = grow_incrementally(alpha, betas, r0, r1);
|
||||
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
EXPECT_EQ(KT::depth, kBits - 1);
|
||||
EXPECT_EQ(KT::num_outputs, kBits);
|
||||
|
||||
auto open = [&](auto out_ic, std::uint8_t x) {
|
||||
return dpf::reconstruct(
|
||||
*dpf::eval_point(out_ic, k0, x),
|
||||
*dpf::eval_point(out_ic, k1, x));
|
||||
};
|
||||
|
||||
[&]<std::size_t... L>(std::index_sequence<L...>) {
|
||||
(([&] {
|
||||
EXPECT_EQ(open(dpf::out<L>, alpha), betas[L]) << "prefix " << L;
|
||||
EXPECT_EQ(open(dpf::out<L>, sibling_at(alpha, L)), 0ull)
|
||||
<< "sibling at prefix " << L;
|
||||
}()), ...);
|
||||
}(std::make_index_sequence<kBits>{});
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ExtendMatchesFullySpecified)
|
||||
{
|
||||
const std::uint8_t alpha = 0x6D;
|
||||
const std::array<std::uint64_t, kBits> betas = {
|
||||
1001, 2002, 3003, 4004, 5005, 6006, 7007, 8008};
|
||||
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
|
||||
auto [k0, k1] = grow_incrementally(alpha, betas, r0, r1);
|
||||
|
||||
reset_roots(r0, r1);
|
||||
auto [ref0, ref1] = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<1>(betas[0]), dpf::at<2>(betas[1]), dpf::at<3>(betas[2]),
|
||||
dpf::at<4>(betas[3]), dpf::at<5>(betas[4]), dpf::at<6>(betas[5]),
|
||||
dpf::at<7>(betas[6]), dpf::at<8>(betas[7]));
|
||||
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
static_assert(std::is_same_v<KT, std::decay_t<decltype(ref0)>>);
|
||||
EXPECT_EQ(std::memcmp(k0.correction_words().data(),
|
||||
ref0.correction_words().data(),
|
||||
sizeof(typename KT::correction_words_array)), 0);
|
||||
EXPECT_EQ(std::memcmp(k0.correction_advice().data(),
|
||||
ref0.correction_advice().data(),
|
||||
sizeof(typename KT::correction_advice_array)), 0);
|
||||
EXPECT_EQ(std::memcmp(&k0.root(), &ref0.root(), sizeof(block_t)), 0);
|
||||
EXPECT_EQ(std::memcmp(&k1.root(), &ref1.root(), sizeof(block_t)), 0);
|
||||
|
||||
[&]<std::size_t... L>(std::index_sequence<L...>) {
|
||||
(([&] {
|
||||
EXPECT_EQ(std::memcmp(&k0.template leaf<L>(),
|
||||
&ref0.template leaf<L>(), sizeof(k0.template leaf<L>())), 0)
|
||||
<< "leaf0 " << L;
|
||||
EXPECT_EQ(std::memcmp(&k1.template leaf<L>(),
|
||||
&ref1.template leaf<L>(), sizeof(k1.template leaf<L>())), 0)
|
||||
<< "leaf1 " << L;
|
||||
EXPECT_EQ(dpf::reconstruct(
|
||||
*dpf::eval_point(dpf::out<L>, k0, alpha),
|
||||
*dpf::eval_point(dpf::out<L>, k1, alpha)),
|
||||
betas[L])
|
||||
<< "open " << L;
|
||||
}()), ...);
|
||||
}(std::make_index_sequence<kBits>{});
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, AddOutputWildcardOnExistingLevel)
|
||||
{
|
||||
const std::uint8_t alpha = 0xA5;
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
reset_roots(r0, r1);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<2>(std::uint64_t{42}));
|
||||
// at<2>(u64) → level 1. at<3>(u32) → level 1 (lg_opl=2), new group.
|
||||
auto [a0, a1] = dpf::add_output(k0, k1, alpha,
|
||||
dpf::at<3>(dpf::wildcard_value<std::uint32_t>{}));
|
||||
using AT = std::decay_t<decltype(a0)>;
|
||||
EXPECT_EQ(AT::depth, 1u);
|
||||
EXPECT_EQ(AT::num_outputs, 2u);
|
||||
EXPECT_TRUE(AT::wildcard_bits[1]);
|
||||
|
||||
auto o0 = dpf::eval_point(dpf::out<0>, a0, alpha);
|
||||
auto o1 = dpf::eval_point(dpf::out<0>, a1, alpha);
|
||||
EXPECT_EQ(dpf::reconstruct(*o0, *o1), 42ull);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, DeepestPrefixMatchesClassicDpf)
|
||||
{
|
||||
const std::uint8_t alpha = 0xC3;
|
||||
const std::array<std::uint64_t, kBits> betas = {
|
||||
1, 2, 4, 8, 16, 32, 64, 128};
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
auto [k0, k1] = grow_incrementally(alpha, betas, r0, r1);
|
||||
|
||||
auto recon = [&](std::uint8_t x) {
|
||||
return dpf::reconstruct(
|
||||
*dpf::eval_point(dpf::out<kBits - 1>, k0, x),
|
||||
*dpf::eval_point(dpf::out<kBits - 1>, k1, x));
|
||||
};
|
||||
|
||||
auto [c0, c1] = dpf::make_dpf(alpha, betas.back());
|
||||
for (std::uint32_t x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto want = dpf::reconstruct(
|
||||
*dpf::eval_point(c0, static_cast<std::uint8_t>(x)),
|
||||
*dpf::eval_point(c1, static_cast<std::uint8_t>(x)));
|
||||
EXPECT_EQ(recon(static_cast<std::uint8_t>(x)),
|
||||
static_cast<std::uint64_t>(want))
|
||||
<< "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ChaChaPrgOpensThePrefix)
|
||||
{
|
||||
using prg = dpf::prg::chacha20;
|
||||
using block = prg::block_type;
|
||||
static block roots[2];
|
||||
static int ri = 0;
|
||||
auto take = +[]() -> block { return roots[ri++]; };
|
||||
const std::uint8_t alpha = 0x3C;
|
||||
const std::uint64_t beta = 0x1234;
|
||||
roots[0] = dpf::uniform_sample<block>();
|
||||
roots[1] = dpf::uniform_sample<block>();
|
||||
ri = 0;
|
||||
auto [k0, k1] = dpf::make_dpf<prg, prg>(alpha,
|
||||
dpf::root_sampler_t<prg>{take}, dpf::at<1>(beta));
|
||||
const auto opened = dpf::reconstruct(
|
||||
*dpf::eval_point(dpf::out<0>, k0, alpha),
|
||||
*dpf::eval_point(dpf::out<0>, k1, alpha));
|
||||
EXPECT_EQ(opened, beta);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct GrowPad
|
||||
{
|
||||
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
|
||||
std::uint8_t bit()
|
||||
{
|
||||
return static_cast<std::uint8_t>(
|
||||
dpf::uniform_sample<unsigned char>() & 1u);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GrowingIdpf, MemoizerExtendMatchesRewalk)
|
||||
{
|
||||
const std::uint8_t alpha = 0xB2;
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
reset_roots(r0, r1);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<1>(std::uint64_t{7}));
|
||||
|
||||
auto [d0, d1] = dpf::extend(k0, k1, alpha, dpf::at<2>(std::uint64_t{9}));
|
||||
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
auto [m_k0, m_k1] =
|
||||
dpf::extend(k0, k1, m0, m1, alpha, dpf::at<2>(std::uint64_t{9}));
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(d0.correction_words().data(),
|
||||
m_k0.correction_words().data(),
|
||||
sizeof(d0.correction_words())));
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<0>, m_k0, alpha),
|
||||
*dpf::eval_point(dpf::out<0>, m_k1, alpha)),
|
||||
7ull);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<1>, m_k0, alpha),
|
||||
*dpf::eval_point(dpf::out<1>, m_k1, alpha)),
|
||||
9ull);
|
||||
(void)d1;
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ExtendDsMatchesDealer)
|
||||
{
|
||||
const std::uint8_t alpha = 0xB2;
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
reset_roots(r0, r1);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<1>(std::uint64_t{7}));
|
||||
auto [d0, d1] = dpf::extend(k0, k1, alpha, dpf::at<2>(std::uint64_t{9}));
|
||||
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
GrowPad pads{};
|
||||
dpf::local_cw_protocol<GrowPad> proto{pads};
|
||||
auto [s0, s1] = dpf::extend_ds(k0, k1, m0, m1, alpha, std::uint8_t{0}, proto,
|
||||
dpf::at<2>(std::uint64_t{9}));
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(d0.correction_words().data(),
|
||||
s0.correction_words().data(),
|
||||
sizeof(d0.correction_words())));
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<1>, s0, alpha),
|
||||
*dpf::eval_point(dpf::out<1>, s1, alpha)),
|
||||
9ull);
|
||||
(void)d1;
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, AddOutputDsWildcard)
|
||||
{
|
||||
const std::uint8_t alpha = 0xA5;
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
reset_roots(r0, r1);
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||||
dpf::at<2>(std::uint64_t{42}));
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
GrowPad pads{};
|
||||
dpf::local_cw_protocol<GrowPad> proto{pads};
|
||||
auto [a0, a1] = dpf::add_output_ds(k0, k1, m0, m1, alpha, std::uint8_t{0},
|
||||
proto, dpf::at<3>(dpf::wildcard_value<std::uint32_t>{}));
|
||||
EXPECT_EQ(std::decay_t<decltype(a0)>::num_outputs, 2u);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<0>, a0, alpha),
|
||||
*dpf::eval_point(dpf::out<0>, a1, alpha)),
|
||||
42ull);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ShallowMemoizerThrows)
|
||||
{
|
||||
const std::uint8_t alpha = 0x10;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<2>(std::uint64_t{1}));
|
||||
using bare = dpf::unwrap_party_key_t<std::decay_t<decltype(k0)>>;
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
const bare & b0 = k0;
|
||||
const bare & b1 = k1;
|
||||
dpf::detail::ensure_level(b0, alpha, m0, 0);
|
||||
dpf::detail::ensure_level(b1, alpha, m1, 0);
|
||||
EXPECT_THROW(
|
||||
(void)dpf::extend(k0, k1, m0, m1, alpha, dpf::at<3>(std::uint64_t{2})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpfDeath, MismatchedKeyPairThrows)
|
||||
{
|
||||
const std::uint8_t alpha = 0x55;
|
||||
// depth >= 1 so correction-word memcmp runs
|
||||
auto [k0, _] = dpf::make_dpf(alpha, dpf::at<2>(std::uint64_t{1}));
|
||||
auto [__, k1] = dpf::make_dpf(alpha, dpf::at<2>(std::uint64_t{1}));
|
||||
EXPECT_THROW(
|
||||
(void)dpf::extend(k0, k1, alpha, dpf::at<3>(std::uint64_t{2})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpfDeath, WrongPathBitThrows)
|
||||
{
|
||||
const std::uint8_t alpha = 0x80; // MSB = 1
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<1>(std::uint64_t{1}));
|
||||
// depth 0; programmed bit is 1; flip it
|
||||
EXPECT_THROW(
|
||||
(void)dpf::extend(k0, k1, /*bit=*/false, alpha,
|
||||
dpf::at<2>(std::uint64_t{2})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpfDeath, SpecNotOnNewDepthThrows)
|
||||
{
|
||||
const std::uint8_t alpha = 0x11;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<1>(std::uint64_t{1}));
|
||||
// Deepens by one via at<2>, but also plants at<1> on the old level.
|
||||
EXPECT_THROW(
|
||||
(void)dpf::extend(k0, k1, alpha, dpf::at<2>(std::uint64_t{9}),
|
||||
dpf::at<1>(std::uint64_t{8})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ExtendSaturatesEightBitDomain)
|
||||
{
|
||||
const std::uint8_t alpha = 0x01;
|
||||
const std::array<std::uint64_t, 8> betas = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
block_t r0 = dpf::uniform_sample<block_t>();
|
||||
block_t r1 = dpf::uniform_sample<block_t>();
|
||||
auto [k0, k1] = grow_incrementally(alpha, betas, r0, r1);
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
EXPECT_EQ(KT::depth, 7u);
|
||||
EXPECT_EQ(KT::depth + 1, dpf::utils::bitlength_of_v<std::uint8_t>);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<7>, k0, alpha),
|
||||
*dpf::eval_point(dpf::out<7>, k1, alpha)),
|
||||
8ull);
|
||||
}
|
||||
|
||||
TEST(GrowingIdpfDeath, MemoizerOnSiblingCorruptsNewSlot)
|
||||
{
|
||||
const std::uint8_t alpha = 0xB2;
|
||||
const std::uint8_t sibling = static_cast<std::uint8_t>(alpha ^ 0x80);
|
||||
// depth 1 so the frontier is past the root
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<2>(std::uint64_t{7}));
|
||||
auto [good0, good1] =
|
||||
dpf::extend(k0, k1, alpha, dpf::at<3>(std::uint64_t{9}));
|
||||
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, sibling, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, sibling, m1);
|
||||
auto [bad0, bad1] =
|
||||
dpf::extend(k0, k1, m0, m1, alpha, dpf::at<3>(std::uint64_t{9}));
|
||||
|
||||
EXPECT_NE(0, std::memcmp(good0.correction_words().data(),
|
||||
bad0.correction_words().data(),
|
||||
sizeof(good0.correction_words())));
|
||||
const auto got = dpf::reconstruct(*dpf::eval_point(dpf::out<1>, bad0, alpha),
|
||||
*dpf::eval_point(dpf::out<1>, bad1, alpha));
|
||||
EXPECT_NE(got, 9ull);
|
||||
(void)good1;
|
||||
}
|
||||
|
||||
TEST(GrowingIdpfDeath, DsWrongSharesDisagreeWithDealer)
|
||||
{
|
||||
const std::uint8_t alpha = 0xB2;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<2>(std::uint64_t{7}));
|
||||
auto [good0, good1] =
|
||||
dpf::extend(k0, k1, alpha, dpf::at<3>(std::uint64_t{9}));
|
||||
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
GrowPad pads{};
|
||||
dpf::local_cw_protocol<GrowPad> proto{pads};
|
||||
// Flip the bit used at the extend level (depth 1 → second MSB).
|
||||
const std::uint8_t wrong = static_cast<std::uint8_t>(alpha ^ 0x40);
|
||||
auto [bad0, bad1] = dpf::extend_ds(k0, k1, m0, m1, wrong, std::uint8_t{0},
|
||||
proto, dpf::at<3>(std::uint64_t{9}));
|
||||
|
||||
EXPECT_NE(0, std::memcmp(good0.correction_words().data(),
|
||||
bad0.correction_words().data(),
|
||||
sizeof(good0.correction_words())));
|
||||
(void)good1;
|
||||
(void)bad1;
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, ExhaustiveDomainAfterMemoGrow)
|
||||
{
|
||||
const std::uint8_t alpha = 0x5A;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<1>(std::uint64_t{3}));
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
auto [g0, g1] =
|
||||
dpf::extend(k0, k1, m0, m1, alpha, dpf::at<2>(std::uint64_t{11}));
|
||||
auto [d0, d1] =
|
||||
dpf::extend(k0, k1, alpha, dpf::at<2>(std::uint64_t{11}));
|
||||
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xa = static_cast<std::uint8_t>(x);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<0>, g0, xa),
|
||||
*dpf::eval_point(dpf::out<0>, g1, xa)),
|
||||
dpf::reconstruct(*dpf::eval_point(dpf::out<0>, d0, xa),
|
||||
*dpf::eval_point(dpf::out<0>, d1, xa)))
|
||||
<< "out0 x=" << x;
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<1>, g0, xa),
|
||||
*dpf::eval_point(dpf::out<1>, g1, xa)),
|
||||
dpf::reconstruct(*dpf::eval_point(dpf::out<1>, d0, xa),
|
||||
*dpf::eval_point(dpf::out<1>, d1, xa)))
|
||||
<< "out1 x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, DsAndDealerAgreeFullDomain)
|
||||
{
|
||||
const std::uint8_t alpha = 0x3C;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<1>(std::uint64_t{4}));
|
||||
auto [d0, d1] =
|
||||
dpf::extend(k0, k1, alpha, dpf::at<2>(std::uint64_t{8}));
|
||||
auto m0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto m1 = dpf::make_basic_path_memoizer(k1);
|
||||
(void)dpf::eval_point(dpf::out<0>, k0, alpha, m0);
|
||||
(void)dpf::eval_point(dpf::out<0>, k1, alpha, m1);
|
||||
GrowPad pads{};
|
||||
dpf::local_cw_protocol<GrowPad> proto{pads};
|
||||
auto [s0, s1] = dpf::extend_ds(k0, k1, m0, m1, alpha, std::uint8_t{0}, proto,
|
||||
dpf::at<2>(std::uint64_t{8}));
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(d0.correction_words().data(),
|
||||
s0.correction_words().data(),
|
||||
sizeof(d0.correction_words())));
|
||||
EXPECT_EQ(0, std::memcmp(d0.correction_advice().data(),
|
||||
s0.correction_advice().data(),
|
||||
sizeof(d0.correction_advice())));
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xa = static_cast<std::uint8_t>(x);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(dpf::out<1>, s0, xa),
|
||||
*dpf::eval_point(dpf::out<1>, s1, xa)),
|
||||
dpf::reconstruct(*dpf::eval_point(dpf::out<1>, d0, xa),
|
||||
*dpf::eval_point(dpf::out<1>, d1, xa)))
|
||||
<< "x=" << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GrowingIdpf, EmptyAddOutputThrows)
|
||||
{
|
||||
const std::uint8_t alpha = 0x01;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<1>(std::uint64_t{1}));
|
||||
EXPECT_THROW((void)dpf::add_output(k0, k1, alpha), std::invalid_argument);
|
||||
}
|
||||
|
|
@ -320,3 +320,36 @@ TEST(HalfTree, IncrementalPlacementSmoke)
|
|||
EXPECT_EQ(got, q == alpha ? uint8_t{7} : uint8_t{0}) << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(HalfTree, VerifiableFullDomainDetectsSeedTamper)
|
||||
{
|
||||
using in_t = std::uint8_t;
|
||||
using out_t = std::uint16_t;
|
||||
const in_t alpha = 0;
|
||||
const out_t beta = 0x1111;
|
||||
auto [k0, k1] = dpf::make_dpf<ht_prg, leaf_prg>(alpha, beta, dpf::verifiable{});
|
||||
EXPECT_TRUE(std::decay_t<decltype(k0)>::tree::is_half_tree);
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
const in_t q = static_cast<in_t>(i);
|
||||
dpf::proof_token a{}, b{};
|
||||
const out_t got = recon(*dpf::eval_point(k0, q, dpf::prove(a)),
|
||||
*dpf::eval_point(k1, q, dpf::prove(b)));
|
||||
EXPECT_EQ(got, q == alpha ? beta : out_t{}) << i;
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << i;
|
||||
}
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
|
||||
k0.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
int rejected = 0;
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
const in_t q = static_cast<in_t>(i);
|
||||
dpf::proof_token a{}, b{};
|
||||
(void)*dpf::eval_point(k0, q, dpf::prove(a));
|
||||
(void)*dpf::eval_point(k1, q, dpf::prove(b));
|
||||
if (!dpf::verify(a, b))
|
||||
++rejected;
|
||||
}
|
||||
EXPECT_GT(rejected, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@ static std::tuple
|
|||
param_type<uint16_t, dpf::bitstring<150>>,
|
||||
param_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
param_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
param_type<uint16_t, dpf::gf2>,
|
||||
param_type<uint16_t, dpf::gf22>,
|
||||
param_type<uint16_t, dpf::gf24>,
|
||||
param_type<uint16_t, dpf::gf28>,
|
||||
param_type<uint16_t, dpf::gf216>,
|
||||
param_type<uint16_t, dpf::gf232>,
|
||||
param_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
param_type<custom_input_type, uint64_t>,
|
||||
|
|
@ -555,6 +562,62 @@ static std::tuple
|
|||
std::make_tuple(uint16_t(0xFFFF), dpf::xor_wrapper<uint64_t>(uint64_t(0xAAAAAAAAAAAAAAAA))),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::xor_wrapper<uint64_t>(uint64_t(0xFFFFFFFFFFFFFFFF)))
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf2{1})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf22{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf22{2}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf22{3}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf22{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf22{2}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf22{3})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf24{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf24{0x6}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf24{0xa}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf24{0xf}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf24{0x2}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf24{0xd})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf28{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf28{0x1b}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf28{0x80}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf28{0xff}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf28{0x02}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf28{0x5a})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf216{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf216{0x2d}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf216{0x8000}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf216{0xffff}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf216{0x0002}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf216{0x1234})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf232{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf232{0x90200001u}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf232{0x80000000u}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf232{0xffffffffu}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf232{0x00000002u}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf232{0x13579bdfu})
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf264{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf264{0x11}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf264{0x8000000000000000ull}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf264{~std::uint64_t{0}}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf264{0x2}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf264{0x123456789abcdefull})
|
||||
},
|
||||
{
|
||||
std::make_tuple(custom_input_type(0x0000), uint64_t(0x0000000000000001)),
|
||||
std::make_tuple(custom_input_type(0x0000), uint64_t(0x5555555555555555)),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@ static std::tuple
|
|||
param_type<uint16_t, dpf::bitstring<150>>,
|
||||
param_type<uint16_t, dpf::xor_wrapper<int64_t>>,
|
||||
param_type<uint16_t, dpf::xor_wrapper<uint64_t>>,
|
||||
param_type<uint16_t, dpf::gf2>,
|
||||
param_type<uint16_t, dpf::gf22>,
|
||||
param_type<uint16_t, dpf::gf24>,
|
||||
param_type<uint16_t, dpf::gf28>,
|
||||
param_type<uint16_t, dpf::gf216>,
|
||||
param_type<uint16_t, dpf::gf232>,
|
||||
param_type<uint16_t, dpf::gf264>,
|
||||
|
||||
// custom types
|
||||
param_type<custom_input_type, uint64_t>,
|
||||
|
|
@ -591,6 +598,188 @@ static std::tuple
|
|||
dpf::xor_wrapper<uint64_t>(uint64_t(0xAAAAAAAAAAAAAAAA)),
|
||||
dpf::xor_wrapper<uint64_t>(uint64_t(0xFFFFFFFFFFFFFFFF)))
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1},
|
||||
dpf::gf2{1}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf22{1},
|
||||
dpf::gf22{2},
|
||||
dpf::gf22{3},
|
||||
dpf::gf22{1}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf22{2},
|
||||
dpf::gf22{3},
|
||||
dpf::gf22{1},
|
||||
dpf::gf22{2}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf22{3},
|
||||
dpf::gf22{1},
|
||||
dpf::gf22{2},
|
||||
dpf::gf22{3}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf22{1},
|
||||
dpf::gf22{2},
|
||||
dpf::gf22{3},
|
||||
dpf::gf22{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf22{2},
|
||||
dpf::gf22{3},
|
||||
dpf::gf22{1},
|
||||
dpf::gf22{2}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf22{3},
|
||||
dpf::gf22{1},
|
||||
dpf::gf22{2},
|
||||
dpf::gf22{3}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf24{1},
|
||||
dpf::gf24{0x6},
|
||||
dpf::gf24{0xa},
|
||||
dpf::gf24{0xf}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf24{0x6},
|
||||
dpf::gf24{0xa},
|
||||
dpf::gf24{0xf},
|
||||
dpf::gf24{0x2}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf24{0xa},
|
||||
dpf::gf24{0xf},
|
||||
dpf::gf24{0x2},
|
||||
dpf::gf24{0xd}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf24{0xf},
|
||||
dpf::gf24{0x2},
|
||||
dpf::gf24{0xd},
|
||||
dpf::gf24{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf24{0x2},
|
||||
dpf::gf24{0xd},
|
||||
dpf::gf24{1},
|
||||
dpf::gf24{0x6}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf24{0xd},
|
||||
dpf::gf24{1},
|
||||
dpf::gf24{0x6},
|
||||
dpf::gf24{0xa}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf28{1},
|
||||
dpf::gf28{0x1b},
|
||||
dpf::gf28{0x80},
|
||||
dpf::gf28{0xff}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf28{0x1b},
|
||||
dpf::gf28{0x80},
|
||||
dpf::gf28{0xff},
|
||||
dpf::gf28{0x02}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf28{0x80},
|
||||
dpf::gf28{0xff},
|
||||
dpf::gf28{0x02},
|
||||
dpf::gf28{0x5a}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf28{0xff},
|
||||
dpf::gf28{0x02},
|
||||
dpf::gf28{0x5a},
|
||||
dpf::gf28{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf28{0x02},
|
||||
dpf::gf28{0x5a},
|
||||
dpf::gf28{1},
|
||||
dpf::gf28{0x1b}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf28{0x5a},
|
||||
dpf::gf28{1},
|
||||
dpf::gf28{0x1b},
|
||||
dpf::gf28{0x80}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf216{1},
|
||||
dpf::gf216{0x2d},
|
||||
dpf::gf216{0x8000},
|
||||
dpf::gf216{0xffff}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf216{0x2d},
|
||||
dpf::gf216{0x8000},
|
||||
dpf::gf216{0xffff},
|
||||
dpf::gf216{0x0002}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf216{0x8000},
|
||||
dpf::gf216{0xffff},
|
||||
dpf::gf216{0x0002},
|
||||
dpf::gf216{0x1234}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf216{0xffff},
|
||||
dpf::gf216{0x0002},
|
||||
dpf::gf216{0x1234},
|
||||
dpf::gf216{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf216{0x0002},
|
||||
dpf::gf216{0x1234},
|
||||
dpf::gf216{1},
|
||||
dpf::gf216{0x2d}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf216{0x1234},
|
||||
dpf::gf216{1},
|
||||
dpf::gf216{0x2d},
|
||||
dpf::gf216{0x8000}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf232{1},
|
||||
dpf::gf232{0x90200001u},
|
||||
dpf::gf232{0x80000000u},
|
||||
dpf::gf232{0xffffffffu}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf232{0x90200001u},
|
||||
dpf::gf232{0x80000000u},
|
||||
dpf::gf232{0xffffffffu},
|
||||
dpf::gf232{0x00000002u}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf232{0x80000000u},
|
||||
dpf::gf232{0xffffffffu},
|
||||
dpf::gf232{0x00000002u},
|
||||
dpf::gf232{0x13579bdfu}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf232{0xffffffffu},
|
||||
dpf::gf232{0x00000002u},
|
||||
dpf::gf232{0x13579bdfu},
|
||||
dpf::gf232{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf232{0x00000002u},
|
||||
dpf::gf232{0x13579bdfu},
|
||||
dpf::gf232{1},
|
||||
dpf::gf232{0x90200001u}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf232{0x13579bdfu},
|
||||
dpf::gf232{1},
|
||||
dpf::gf232{0x90200001u},
|
||||
dpf::gf232{0x80000000u}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(uint16_t(0x0000), dpf::gf264{1},
|
||||
dpf::gf264{0x11},
|
||||
dpf::gf264{0x8000000000000000ull},
|
||||
dpf::gf264{~std::uint64_t{0}}),
|
||||
std::make_tuple(uint16_t(0x5555), dpf::gf264{0x11},
|
||||
dpf::gf264{0x8000000000000000ull},
|
||||
dpf::gf264{~std::uint64_t{0}},
|
||||
dpf::gf264{0x2}),
|
||||
std::make_tuple(uint16_t(0x7FFF), dpf::gf264{0x8000000000000000ull},
|
||||
dpf::gf264{~std::uint64_t{0}},
|
||||
dpf::gf264{0x2},
|
||||
dpf::gf264{0x123456789abcdefull}),
|
||||
std::make_tuple(uint16_t(0x8000), dpf::gf264{~std::uint64_t{0}},
|
||||
dpf::gf264{0x2},
|
||||
dpf::gf264{0x123456789abcdefull},
|
||||
dpf::gf264{1}),
|
||||
std::make_tuple(uint16_t(0xAAAA), dpf::gf264{0x2},
|
||||
dpf::gf264{0x123456789abcdefull},
|
||||
dpf::gf264{1},
|
||||
dpf::gf264{0x11}),
|
||||
std::make_tuple(uint16_t(0xFFFF), dpf::gf264{0x123456789abcdefull},
|
||||
dpf::gf264{1},
|
||||
dpf::gf264{0x11},
|
||||
dpf::gf264{0x8000000000000000ull}),
|
||||
},
|
||||
{
|
||||
std::make_tuple(custom_input_type(0x0000), uint64_t(0x0000000000000001),
|
||||
uint64_t(0x5555555555555555),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
|
|
@ -113,8 +114,8 @@ TEST(Ic, MemoizerAgrees)
|
|||
{
|
||||
const uint8_t r = 40, p = 7, q = 90;
|
||||
auto keys = dpf::make_dpf(r, dpf::ic(p, q, uint32_t{11}, uint32_t{2}));
|
||||
dpf::basic_path_memoizer<decltype(keys.first.key)> memo0;
|
||||
dpf::basic_path_memoizer<decltype(keys.second.key)> memo1;
|
||||
dpf::basic_path_memoizer<decltype(keys.first.dpf_key)> memo0;
|
||||
dpf::basic_path_memoizer<decltype(keys.second.dpf_key)> memo1;
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto a = dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(x), memo0);
|
||||
|
|
@ -131,7 +132,7 @@ TEST(Ic, IntervalAndSequenceBuffers)
|
|||
auto keys = dpf::make_dpf(r, dpf::ic(p, q, uint16_t{9}));
|
||||
auto buf0 = dpf::make_output_buffer(dpf::ic, keys.first, uint8_t{3}, uint8_t{18});
|
||||
auto buf1 = dpf::make_output_buffer(dpf::ic, keys.second, uint8_t{3}, uint8_t{18});
|
||||
dpf::basic_path_memoizer<decltype(keys.first.key)> memo;
|
||||
dpf::basic_path_memoizer<decltype(keys.first.dpf_key)> memo;
|
||||
dpf::eval_interval(dpf::ic, keys.first, uint8_t{3}, uint8_t{18}, buf0, memo);
|
||||
dpf::eval_interval(dpf::ic, keys.second, uint8_t{3}, uint8_t{18}, buf1);
|
||||
for (std::size_t i = 0; i < buf0.size(); ++i)
|
||||
|
|
@ -226,6 +227,7 @@ TEST(Ic, Geneval)
|
|||
rngs, dpf::ic(p, q, beta));
|
||||
ASSERT_EQ(opened.party0.size(), 7u);
|
||||
ASSERT_EQ(opened.live_levels, 8u);
|
||||
EXPECT_TRUE(dpf::verify(opened.proof0, opened.proof1));
|
||||
const uint8_t r = static_cast<uint8_t>(r0 ^ r1);
|
||||
for (std::size_t i = 0; i < 7; ++i)
|
||||
{
|
||||
|
|
@ -239,8 +241,7 @@ TEST(Ic, Geneval)
|
|||
|
||||
TEST(Ic, RejectsWrappedBounds)
|
||||
{
|
||||
EXPECT_THROW(dpf::make_dpf(uint8_t{1}, dpf::ic(uint8_t{9}, uint8_t{2}, uint32_t{1})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_dpf(uint8_t{1}, dpf::ic(uint8_t{9}, uint8_t{2}, uint32_t{1})), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Ic, BitPayload)
|
||||
|
|
@ -334,14 +335,14 @@ TEST(Ic, AdditiveGeneval)
|
|||
std::begin(none), std::begin(none), ic_rng(), dpf::ic(p, q, beta));
|
||||
EXPECT_TRUE(empty.party0.empty());
|
||||
EXPECT_EQ(empty.live_levels, 0u);
|
||||
// Empty-range tokens stay zero and must not verify as a matching pair.
|
||||
EXPECT_FALSE(dpf::verify(empty.proof0, empty.proof1));
|
||||
}
|
||||
|
||||
TEST(Ic, IntervalRejectsDescendingEndpoints)
|
||||
{
|
||||
auto keys = dpf::make_dpf(uint8_t{4}, dpf::ic(uint8_t{1}, uint8_t{6}, uint32_t{1}));
|
||||
auto buf = dpf::make_output_buffer(dpf::ic, keys.first, 1);
|
||||
EXPECT_THROW(dpf::eval_interval(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}, buf),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_output_buffer(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::eval_interval(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}, buf), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::make_output_buffer(dpf::ic, keys.first, uint8_t{9}, uint8_t{2}), std::invalid_argument);
|
||||
}
|
||||
|
|
|
|||
90
test/tests/idpf_agg_test.cpp
Normal file
90
test/tests/idpf_agg_test.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using key0_t = decltype(dpf::make_dpf(std::uint16_t{0},
|
||||
dpf::idpf_ones<16>()).first);
|
||||
using key1_t = decltype(dpf::make_dpf(std::uint16_t{0},
|
||||
dpf::idpf_ones<16>()).second);
|
||||
|
||||
void split_keys(const std::vector<std::uint16_t> & values,
|
||||
std::vector<key0_t> & k0, std::vector<key1_t> & k1)
|
||||
{
|
||||
k0.clear();
|
||||
k1.clear();
|
||||
for (auto v : values)
|
||||
{
|
||||
auto [a, b] = dpf::make_dpf(v, dpf::idpf_ones<16>());
|
||||
k0.push_back(std::move(a));
|
||||
k1.push_back(std::move(b));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(IdpfAgg, MaxAndKthAgainstPlaintext)
|
||||
{
|
||||
const std::vector<std::uint16_t> values{3, 100, 7, 100, 42};
|
||||
std::vector<key0_t> k0;
|
||||
std::vector<key1_t> k1;
|
||||
split_keys(values, k0, k1);
|
||||
|
||||
EXPECT_EQ(dpf::idpf_agg_max(k0, k1), 100u);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 1), 100u);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, values.size()), 3u);
|
||||
|
||||
auto sorted = values;
|
||||
std::sort(sorted.begin(), sorted.end(), std::greater<>{});
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 3), sorted[2]);
|
||||
}
|
||||
|
||||
TEST(IdpfAgg, AllEqual)
|
||||
{
|
||||
const std::vector<std::uint16_t> values{0x00AA, 0x00AA, 0x00AA};
|
||||
std::vector<key0_t> k0;
|
||||
std::vector<key1_t> k1;
|
||||
split_keys(values, k0, k1);
|
||||
EXPECT_EQ(dpf::idpf_agg_max(k0, k1), 0x00AAu);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 1), 0x00AAu);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 3), 0x00AAu);
|
||||
}
|
||||
|
||||
TEST(IdpfAgg, KEqualsOneAndN)
|
||||
{
|
||||
const std::vector<std::uint16_t> values{1, 2, 3, 4};
|
||||
std::vector<key0_t> k0;
|
||||
std::vector<key1_t> k1;
|
||||
split_keys(values, k0, k1);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 1), 4u);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 4), 1u);
|
||||
}
|
||||
|
||||
TEST(IdpfAgg, Duplicates)
|
||||
{
|
||||
const std::vector<std::uint16_t> values{9, 9, 1, 9, 5};
|
||||
std::vector<key0_t> k0;
|
||||
std::vector<key1_t> k1;
|
||||
split_keys(values, k0, k1);
|
||||
EXPECT_EQ(dpf::idpf_agg_max(k0, k1), 9u);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 2), 9u);
|
||||
EXPECT_EQ(dpf::idpf_agg_kth(k0, k1, 4), 5u);
|
||||
}
|
||||
|
||||
// Seam: max equals kth(k=1); both agree with a plaintext sort.
|
||||
TEST(IdpfAgg, MaxEqualsKthOne)
|
||||
{
|
||||
const std::vector<std::uint16_t> values{4, 90, 4, 15};
|
||||
std::vector<key0_t> k0;
|
||||
std::vector<key1_t> k1;
|
||||
split_keys(values, k0, k1);
|
||||
EXPECT_EQ(dpf::idpf_agg_max(k0, k1), dpf::idpf_agg_kth(k0, k1, 1));
|
||||
}
|
||||
497
test/tests/iknp_party_test.cpp
Normal file
497
test/tests/iknp_party_test.cpp
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/iknp.hpp"
|
||||
#include "dpf/prg_aes_ccr.hpp"
|
||||
#include "flow_util.hpp"
|
||||
#include "iknp_deal.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::net::role;
|
||||
using dpf::net::trio;
|
||||
|
||||
template <typename Fn0, typename Fn1>
|
||||
void run_pair(Fn0 && fn0, Fn1 && fn1)
|
||||
{
|
||||
const auto dir = std::filesystem::temp_directory_path()
|
||||
/ ("libdpf_iknp_" + std::to_string(::getpid()));
|
||||
std::filesystem::create_directories(dir);
|
||||
std::exception_ptr ep0;
|
||||
std::exception_ptr ep1;
|
||||
std::thread t0([&] {
|
||||
try
|
||||
{
|
||||
auto net = trio::connect_pair(role::p0, dir.string());
|
||||
fn0(net);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ep0 = std::current_exception();
|
||||
}
|
||||
});
|
||||
std::thread t1([&] {
|
||||
try
|
||||
{
|
||||
auto net = trio::connect_pair(role::p1, dir.string());
|
||||
fn1(net);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ep1 = std::current_exception();
|
||||
}
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
std::filesystem::remove_all(dir);
|
||||
if (ep0)
|
||||
std::rethrow_exception(ep0);
|
||||
if (ep1)
|
||||
std::rethrow_exception(ep1);
|
||||
}
|
||||
|
||||
simde__m128i xor_block(simde__m128i a, simde__m128i b)
|
||||
{
|
||||
return simde_mm_xor_si128(a, b);
|
||||
}
|
||||
|
||||
TEST(iknp, pads_match_dealer_relations)
|
||||
{
|
||||
constexpr std::size_t nblock = 4;
|
||||
constexpr std::size_t nbit = 5;
|
||||
constexpr std::size_t nb2a = 3;
|
||||
constexpr std::size_t ncw = 2;
|
||||
dpf::iknp::material m0, m1;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
m0 = dpf::iknp::sample(net.to(role::p1), 0, nblock, nbit, nb2a, ncw);
|
||||
},
|
||||
[&](trio & net) {
|
||||
m1 = dpf::iknp::sample(net.to(role::p0), 1, nblock, nbit, nb2a, ncw);
|
||||
});
|
||||
ASSERT_EQ(m0.blocks.size(), nblock);
|
||||
ASSERT_EQ(m1.blocks.size(), nblock);
|
||||
for (std::size_t i = 0; i < nblock; ++i)
|
||||
{
|
||||
const auto a = static_cast<std::uint8_t>(m0.blocks[i].a ^ m1.blocks[i].a);
|
||||
const auto b = xor_block(m0.blocks[i].b, m1.blocks[i].b);
|
||||
const auto c = xor_block(m0.blocks[i].c, m1.blocks[i].c);
|
||||
const auto expect = (a & 1u) ? b : simde_mm_setzero_si128();
|
||||
EXPECT_EQ(std::memcmp(&c, &expect, sizeof(c)), 0);
|
||||
}
|
||||
for (std::size_t i = 0; i < nbit; ++i)
|
||||
{
|
||||
const auto a = static_cast<std::uint8_t>(m0.bits[i].a ^ m1.bits[i].a);
|
||||
const auto b = static_cast<std::uint8_t>(m0.bits[i].b ^ m1.bits[i].b);
|
||||
const auto c = static_cast<std::uint8_t>(m0.bits[i].c ^ m1.bits[i].c);
|
||||
EXPECT_EQ(c, static_cast<std::uint8_t>((a & b) & 1u));
|
||||
}
|
||||
for (std::size_t i = 0; i < nb2a; ++i)
|
||||
{
|
||||
const auto bit = static_cast<std::uint64_t>(m0.b2a[i].r ^ m1.b2a[i].r);
|
||||
EXPECT_EQ(m0.b2a[i].add + m1.b2a[i].add, bit);
|
||||
}
|
||||
// gamma0 ⊕ gamma1 = (bit1·rand0) ⊕ (bit0·rand1) (XOR shares; neither
|
||||
// party learns the peer pad bit).
|
||||
for (std::size_t i = 0; i < ncw; ++i)
|
||||
{
|
||||
const auto prod = xor_block(
|
||||
(m1.cws[i].bit & 1u) ? m0.cws[i].rand : simde_mm_setzero_si128(),
|
||||
(m0.cws[i].bit & 1u) ? m1.cws[i].rand : simde_mm_setzero_si128());
|
||||
const auto got = xor_block(m0.cws[i].gamma, m1.cws[i].gamma);
|
||||
EXPECT_EQ(std::memcmp(&got, &prod, sizeof(prod)), 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Point>
|
||||
void expect_point(Point alpha, Point x0, Point x1, std::uint64_t beta, bool additive)
|
||||
{
|
||||
using namespace dpf::party;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p0, x0, x1, beta,
|
||||
[&](const auto & key) {
|
||||
const auto y = util::share_bits(*dpf::eval_point(key, alpha));
|
||||
const auto open = util::open_subtractive(net, role::p0, y);
|
||||
EXPECT_EQ(open, beta);
|
||||
const Point other = static_cast<Point>(
|
||||
static_cast<unsigned>(alpha) ^ 1u);
|
||||
const auto z = util::share_bits(*dpf::eval_point(key, other));
|
||||
const auto oz = util::open_subtractive(net, role::p0, z);
|
||||
EXPECT_EQ(oz, 0u);
|
||||
},
|
||||
[](const auto &) {}, false, additive);
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p1, x0, x1, beta,
|
||||
[](const auto &) {},
|
||||
[&](const auto & key) {
|
||||
const auto y = util::share_bits(*dpf::eval_point(key, alpha));
|
||||
(void)util::open_subtractive(net, role::p1, y);
|
||||
const Point other = static_cast<Point>(
|
||||
static_cast<unsigned>(alpha) ^ 1u);
|
||||
const auto z = util::share_bits(*dpf::eval_point(key, other));
|
||||
(void)util::open_subtractive(net, role::p1, z);
|
||||
}, false, additive);
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(iknp, point_reveal)
|
||||
{
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
expect_point<std::uint8_t>(alpha, 0x10, static_cast<std::uint8_t>(alpha ^ 0x10),
|
||||
7, false);
|
||||
}
|
||||
|
||||
TEST(iknp, point_additive)
|
||||
{
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
const std::uint8_t x0 = 0x10;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha - x0);
|
||||
expect_point<std::uint8_t>(alpha, x0, x1, 11, true);
|
||||
}
|
||||
|
||||
TEST(iknp, half_tree_reveal)
|
||||
{
|
||||
using namespace dpf::party;
|
||||
using Ht = dpf::prg::aes128_ccr;
|
||||
const std::uint8_t alpha = 7;
|
||||
const std::uint8_t x0 = 0x25;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
|
||||
const std::uint64_t beta = 9;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp<Ht, Ht, true>(net, role::p0, x0, x1, beta,
|
||||
[&](const auto & key) {
|
||||
const auto open = util::open_subtractive(net, role::p0,
|
||||
util::share_bits(*dpf::eval_point(key, alpha)));
|
||||
EXPECT_EQ(open, beta);
|
||||
},
|
||||
[](const auto &) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp<Ht, Ht, true>(net, role::p1, x0, x1, beta,
|
||||
[](const auto &) {},
|
||||
[&](const auto & key) {
|
||||
(void)util::open_subtractive(net, role::p1,
|
||||
util::share_bits(*dpf::eval_point(key, alpha)));
|
||||
});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(iknp, comparison_reveal_and_oblivious)
|
||||
{
|
||||
using namespace dpf::party;
|
||||
const std::uint8_t alpha = 0x40;
|
||||
const std::uint8_t x0 = 0x14;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
|
||||
const std::uint64_t beta = 9;
|
||||
auto check = [&](bool reveal) {
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
auto on = [&](const auto & key) {
|
||||
const auto mask = key.cmp().mask;
|
||||
const auto below = util::open_additive(net, role::p0,
|
||||
util::share_bits(dpf::eval_point(dpf::cmp, key,
|
||||
static_cast<std::uint8_t>(alpha - 1)))) & mask;
|
||||
const auto at = util::open_additive(net, role::p0,
|
||||
util::share_bits(dpf::eval_point(dpf::cmp, key, alpha)))
|
||||
& mask;
|
||||
EXPECT_EQ(below, beta);
|
||||
EXPECT_EQ(at, 0u);
|
||||
};
|
||||
if (reveal)
|
||||
dist_with_cmp_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p0, x0, x1, dpf::lt(beta), on, [](const auto &) {});
|
||||
else
|
||||
dist_with_cmp_key_iknp(net, role::p0, x0, x1, dpf::lt(beta),
|
||||
on, [](const auto &) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
auto on = [&](const auto & key) {
|
||||
const auto mask = key.cmp().mask;
|
||||
(void)(util::open_additive(net, role::p1,
|
||||
util::share_bits(dpf::eval_point(dpf::cmp, key,
|
||||
static_cast<std::uint8_t>(alpha - 1)))) & mask);
|
||||
(void)(util::open_additive(net, role::p1,
|
||||
util::share_bits(dpf::eval_point(dpf::cmp, key, alpha)))
|
||||
& mask);
|
||||
};
|
||||
if (reveal)
|
||||
dist_with_cmp_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p1, x0, x1, dpf::lt(beta), [](const auto &) {}, on);
|
||||
else
|
||||
dist_with_cmp_key_iknp(net, role::p1, x0, x1, dpf::lt(beta),
|
||||
[](const auto &) {}, on);
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
};
|
||||
check(true);
|
||||
check(false);
|
||||
}
|
||||
|
||||
TEST(iknp, interval_shared_and_reveal)
|
||||
{
|
||||
using namespace dpf::party;
|
||||
const std::uint8_t r = 40, p = 7, q = 90;
|
||||
const std::uint8_t r0 = 0x13;
|
||||
const std::uint8_t r1 = static_cast<std::uint8_t>(r ^ r0);
|
||||
const std::uint32_t if_true = 11, if_false = 2;
|
||||
auto check = [&](bool reveal) {
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
auto on = [&](const auto & key) {
|
||||
const auto gmask = key.group_mask;
|
||||
const auto nmask = key.input_mask;
|
||||
for (unsigned x : {0u, 7u, 40u, 90u, 200u})
|
||||
{
|
||||
const auto mine = util::share_bits(
|
||||
dpf::eval_point(dpf::ic, key, static_cast<std::uint8_t>(x)));
|
||||
const auto peer = net.exchange_with(role::p1, mine);
|
||||
const auto w = (x - static_cast<unsigned>(r)) & nmask;
|
||||
const bool inside = w >= p && w <= q;
|
||||
const auto want = (inside ? if_true : if_false) & gmask;
|
||||
EXPECT_EQ((mine + peer) & gmask, want);
|
||||
}
|
||||
};
|
||||
if (reveal)
|
||||
dist_with_ic_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p0, r0, r1, dpf::ic(p, q, if_true, if_false),
|
||||
on, [](const auto &) {});
|
||||
else
|
||||
dist_with_ic_key_iknp(net, role::p0, r0, r1,
|
||||
dpf::ic(p, q, if_true, if_false), on, [](const auto &) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
auto on = [&](const auto & key) {
|
||||
for (unsigned x : {0u, 7u, 40u, 90u, 200u})
|
||||
{
|
||||
const auto mine = util::share_bits(
|
||||
dpf::eval_point(dpf::ic, key, static_cast<std::uint8_t>(x)));
|
||||
(void)net.exchange_with(role::p0, mine);
|
||||
}
|
||||
};
|
||||
if (reveal)
|
||||
dist_with_ic_key_iknp<dpf::prg::aes128, dpf::prg::aes128, true>(
|
||||
net, role::p1, r0, r1, dpf::ic(p, q, if_true, if_false),
|
||||
[](const auto &) {}, on);
|
||||
else
|
||||
dist_with_ic_key_iknp(net, role::p1, r0, r1,
|
||||
dpf::ic(p, q, if_true, if_false), [](const auto &) {}, on);
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
};
|
||||
check(false);
|
||||
check(true);
|
||||
}
|
||||
|
||||
template <typename Key, typename Share>
|
||||
void assign_wildcard(Key & key, Share my_share, trio & net, role self)
|
||||
{
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto & wrap = std::get<0>(key.leaf_nodes);
|
||||
if (wrap.is_ready())
|
||||
wrap.begin_update();
|
||||
auto blinded = wrap.compute_and_get_blinded_output_share(my_share);
|
||||
auto peer_blinded = net.exchange_with(peer, blinded);
|
||||
auto leaf = wrap.compute_and_get_leaf_share(peer_blinded);
|
||||
auto peer_leaf = net.exchange_with(peer, leaf);
|
||||
wrap.reconstruct_correction_word(peer_leaf);
|
||||
}
|
||||
|
||||
TEST(iknp, wildcard_assign)
|
||||
{
|
||||
using namespace dpf::party::util;
|
||||
const std::uint8_t alpha = 0xAA;
|
||||
const std::uint8_t x0 = 0x31;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
|
||||
const std::uint32_t y = 0xAAAAAAAAu;
|
||||
const std::uint32_t y0 = 0x12345678u;
|
||||
const std::uint32_t y1 = y - y0;
|
||||
using out_t = dpf::wildcard_value<std::uint32_t>;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dpf::party::dist_with_point_key_iknp(net, role::p0, x0, x1, out_t{},
|
||||
[&](auto key) {
|
||||
assign_wildcard(key, y0, net, role::p0);
|
||||
const auto on = open_subtractive(net, role::p0,
|
||||
share_bits(*dpf::eval_point(key, alpha)));
|
||||
const auto off = open_subtractive(net, role::p0,
|
||||
share_bits(*dpf::eval_point(key,
|
||||
static_cast<std::uint8_t>(alpha ^ 1u))));
|
||||
EXPECT_EQ(on, y);
|
||||
EXPECT_EQ(off, 0u);
|
||||
},
|
||||
[](auto) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
dpf::party::dist_with_point_key_iknp(net, role::p1, x0, x1, out_t{},
|
||||
[](auto) {},
|
||||
[&](auto key) {
|
||||
assign_wildcard(key, y1, net, role::p1);
|
||||
(void)open_subtractive(net, role::p1,
|
||||
share_bits(*dpf::eval_point(key, alpha)));
|
||||
(void)open_subtractive(net, role::p1,
|
||||
share_bits(*dpf::eval_point(key,
|
||||
static_cast<std::uint8_t>(alpha ^ 1u))));
|
||||
});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(iknp, extractable_sketch)
|
||||
{
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
const std::uint8_t x0 = 0x10;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
|
||||
const dpf::fp61 beta{7};
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dpf::party::dist_with_extractable_point_key_iknp(
|
||||
net, role::p0, x0, x1, beta,
|
||||
[&](const auto & key) {
|
||||
const std::array<dpf::fp61, 1> rs{dpf::fp61{3}};
|
||||
dpf::sketch_share local{};
|
||||
auto sk = dpf::sketch(local, rs);
|
||||
(void)*dpf::eval_point(key, alpha, sk);
|
||||
auto theirs = net.exchange_with(role::p1, local,
|
||||
dpf::net::msg::sketch_share);
|
||||
EXPECT_TRUE(dpf::sketch_verify(local, theirs));
|
||||
},
|
||||
[](const auto &) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
dpf::party::dist_with_extractable_point_key_iknp(
|
||||
net, role::p1, x0, x1, beta,
|
||||
[](const auto &) {},
|
||||
[&](const auto & key) {
|
||||
const std::array<dpf::fp61, 1> rs{dpf::fp61{3}};
|
||||
dpf::sketch_share local{};
|
||||
auto sk = dpf::sketch(local, rs);
|
||||
(void)*dpf::eval_point(key, alpha, sk);
|
||||
auto theirs = net.exchange_with(role::p0, local,
|
||||
dpf::net::msg::sketch_share);
|
||||
EXPECT_TRUE(dpf::sketch_verify(theirs, local));
|
||||
});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(iknp, oblivious_point_hash)
|
||||
{
|
||||
using namespace dpf::party;
|
||||
// Default (non-reveal) point keygen hashes the shared prefix, so this
|
||||
// exercises the per-level AND tape as well as the correction words.
|
||||
const std::uint8_t alpha = 0x2a;
|
||||
const std::uint8_t x0 = 0x10;
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
|
||||
const std::uint64_t beta = 4;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp(net, role::p0, x0, x1, beta,
|
||||
[&](const auto & key) {
|
||||
const auto on = util::open_subtractive(net, role::p0,
|
||||
util::share_bits(*dpf::eval_point(key, alpha)));
|
||||
const auto off = util::open_subtractive(net, role::p0,
|
||||
util::share_bits(*dpf::eval_point(key,
|
||||
static_cast<std::uint8_t>(alpha ^ 1u))));
|
||||
EXPECT_EQ(on, beta);
|
||||
EXPECT_EQ(off, 0u);
|
||||
},
|
||||
[](const auto &) {});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
},
|
||||
[&](trio & net) {
|
||||
dist_with_point_key_iknp(net, role::p1, x0, x1, beta,
|
||||
[](const auto &) {},
|
||||
[&](const auto & key) {
|
||||
(void)util::open_subtractive(net, role::p1,
|
||||
util::share_bits(*dpf::eval_point(key, alpha)));
|
||||
(void)util::open_subtractive(net, role::p1,
|
||||
util::share_bits(*dpf::eval_point(key,
|
||||
static_cast<std::uint8_t>(alpha ^ 1u))));
|
||||
});
|
||||
EXPECT_TRUE(net.dealer_inbox_done());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(iknp, transfer_labels_follows_choice_and_reuses_base)
|
||||
{
|
||||
constexpr std::size_t n = 7;
|
||||
std::vector<simde__m128i> m0(n), m1(n);
|
||||
std::vector<std::uint8_t> choices(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
m0[i] = dpf::uniform_sample<simde__m128i>();
|
||||
m1[i] = dpf::uniform_sample<simde__m128i>();
|
||||
choices[i] = static_cast<std::uint8_t>(i & 1u);
|
||||
}
|
||||
std::vector<simde__m128i> again0(2), again1(2);
|
||||
again0[0] = dpf::uniform_sample<simde__m128i>();
|
||||
again0[1] = dpf::uniform_sample<simde__m128i>();
|
||||
again1[0] = dpf::uniform_sample<simde__m128i>();
|
||||
again1[1] = dpf::uniform_sample<simde__m128i>();
|
||||
const std::uint8_t again_choice[2] = {1, 0};
|
||||
|
||||
std::vector<simde__m128i> got, got2;
|
||||
run_pair(
|
||||
[&](trio & net) {
|
||||
dpf::iknp::detail::role_state st;
|
||||
std::vector<simde__m128i> unused;
|
||||
dpf::iknp::transfer_labels(net.to(role::p1), 0, true, st, m0, m1,
|
||||
{}, unused);
|
||||
EXPECT_TRUE(unused.empty());
|
||||
std::vector<simde__m128i> empty_out;
|
||||
dpf::iknp::transfer_labels(net.to(role::p1), 0, true, st, {}, {},
|
||||
{}, empty_out);
|
||||
dpf::iknp::transfer_labels(net.to(role::p1), 0, true, st, again0,
|
||||
again1, {}, unused);
|
||||
},
|
||||
[&](trio & net) {
|
||||
dpf::iknp::detail::role_state st;
|
||||
dpf::iknp::transfer_labels(net.to(role::p0), 1, false, st, {}, {},
|
||||
choices, got);
|
||||
std::vector<std::uint8_t> none;
|
||||
std::vector<simde__m128i> empty_out;
|
||||
dpf::iknp::transfer_labels(net.to(role::p0), 1, false, st, {}, {},
|
||||
none, empty_out);
|
||||
EXPECT_TRUE(empty_out.empty());
|
||||
dpf::iknp::transfer_labels(net.to(role::p0), 1, false, st, {}, {},
|
||||
std::vector<std::uint8_t>(again_choice, again_choice + 2), got2);
|
||||
});
|
||||
|
||||
ASSERT_EQ(got.size(), n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const auto & expect = choices[i] ? m1[i] : m0[i];
|
||||
EXPECT_EQ(std::memcmp(&got[i], &expect, sizeof(expect)), 0) << i;
|
||||
}
|
||||
ASSERT_EQ(got2.size(), 2u);
|
||||
EXPECT_EQ(std::memcmp(&got2[0], &again1[0], sizeof(again1[0])), 0);
|
||||
EXPECT_EQ(std::memcmp(&got2[1], &again0[1], sizeof(again0[1])), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/fixedpoint.hpp"
|
||||
|
|
@ -1895,3 +1896,315 @@ TEST_F(IncrementalDpfTest, SequenceRecipeAtPrefixSlot)
|
|||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Regressions for bugs found under ASan/UBSan:
|
||||
// * eq(..., if_false) must absorb on every eval surface, not only point
|
||||
// * path memoizer resume past a full-width cmp depth must not shift by nbits
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_F(IncrementalDpfTest, EqIfFalseAbsorbsOnEveryEvalSurface)
|
||||
{
|
||||
const uint8_t alpha = 5;
|
||||
const uint8_t yt = 7;
|
||||
const uint8_t yf = 3;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(yt, yf));
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
EXPECT_TRUE(KT::is_multilevel);
|
||||
EXPECT_EQ(std::get<0>(k0.public_addends), yf);
|
||||
constexpr auto opl = KT::template outputs_per_leaf_of<0>;
|
||||
|
||||
auto expect_at = [&](uint8_t q) {
|
||||
return (q == alpha) ? yt : yf;
|
||||
};
|
||||
|
||||
for (unsigned q = 0; q < 8u; ++q)
|
||||
{
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, uint8_t(q)),
|
||||
*dpf::eval_point(k1, uint8_t(q))),
|
||||
expect_at(uint8_t(q)))
|
||||
<< "point q=" << q;
|
||||
}
|
||||
|
||||
auto [f0, itf0] = dpf::eval_full(k0);
|
||||
auto [f1, itf1] = dpf::eval_full(k1);
|
||||
(void)itf0;
|
||||
(void)itf1;
|
||||
for (unsigned q = 0; q < 8u; ++q)
|
||||
{
|
||||
EXPECT_EQ(recon(f0[q], f1[q]), expect_at(uint8_t(q)))
|
||||
<< "full q=" << q;
|
||||
}
|
||||
|
||||
// Leaf-aligned interval so buffer index == lane.
|
||||
constexpr uint8_t from = 0;
|
||||
constexpr uint8_t to = 15; // one full packing leaf when opl==16
|
||||
auto [iv0, ii0] = dpf::eval_interval(dpf::out<0>, k0, from, to);
|
||||
auto [iv1, ii1] = dpf::eval_interval(dpf::out<0>, k1, from, to);
|
||||
(void)ii0;
|
||||
(void)ii1;
|
||||
ASSERT_EQ(iv0.size(), static_cast<std::size_t>(to - from + 1));
|
||||
for (unsigned q = from; q <= to; ++q)
|
||||
{
|
||||
const uint8_t want = (q < 8u) ? expect_at(uint8_t(q)) : yf;
|
||||
EXPECT_EQ(recon(iv0[q - from], iv1[q - from]), want)
|
||||
<< "interval q=" << q;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> pts{{7, 0, 5, 2, 1, 6, 3, 4}};
|
||||
auto s0 = dpf::make_output_buffer_for(k0, pts.size());
|
||||
auto s1 = dpf::make_output_buffer_for(k1, pts.size());
|
||||
dpf::eval_sequence(dpf::out<0>, k0, pts.begin(), pts.end(), s0);
|
||||
dpf::eval_sequence(dpf::out<0>, k1, pts.begin(), pts.end(), s1);
|
||||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||||
{
|
||||
auto e0 = dpf::eval_point(dpf::out<0>, k0, pts[i]);
|
||||
auto e1 = dpf::eval_point(dpf::out<0>, k1, pts[i]);
|
||||
EXPECT_EQ(recon(s0[i * opl + e0.offset], s1[i * opl + e1.offset]),
|
||||
expect_at(pts[i]))
|
||||
<< "sequence i=" << i;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> sorted{{0, 1, 2, 3, 4, 5, 6, 7}};
|
||||
auto b0 = dpf::eval_sequence_breadth_first(dpf::out<0>, k0, sorted.begin(),
|
||||
sorted.end());
|
||||
auto b1 = dpf::eval_sequence_breadth_first(dpf::out<0>, k1, sorted.begin(),
|
||||
sorted.end());
|
||||
for (std::size_t i = 0; i < sorted.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(recon(b0[i], b1[i]), expect_at(sorted[i]))
|
||||
<< "breadth i=" << i;
|
||||
}
|
||||
|
||||
// Weights must cover every packing lane the interval exterior materialises.
|
||||
std::vector<uint64_t> w(iv0.size(), 1);
|
||||
uint64_t expect_ip = 0;
|
||||
for (std::size_t i = 0; i < iv0.size(); ++i)
|
||||
expect_ip += static_cast<uint64_t>(recon(iv0[i], iv1[i])) * w[i];
|
||||
const auto ip0 = dpf::eval_inner_product(dpf::out<0>, k0, from, to, w);
|
||||
const auto ip1 = dpf::eval_inner_product(dpf::out<0>, k1, from, to, w);
|
||||
EXPECT_EQ(static_cast<uint8_t>(recon(ip0, ip1)),
|
||||
static_cast<uint8_t>(expect_ip));
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, EqAtIfFalseWithPackedIntervalAndXor)
|
||||
{
|
||||
const uint32_t alpha = 0x00c0ffeeu;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::eq_at<16>(uint16_t{99}, uint16_t{7}),
|
||||
dpf::eq(dpf::xor_wrapper<uint32_t>{0x00ff00ffu},
|
||||
dpf::xor_wrapper<uint32_t>{0x00001111u}));
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
constexpr auto opl = KT::template outputs_per_leaf_of<0>;
|
||||
|
||||
const uint16_t lane = static_cast<uint16_t>(alpha >> 16);
|
||||
// Align to a packing leaf so buffer index math is exact.
|
||||
const uint16_t from = static_cast<uint16_t>(lane & ~(opl - 1u));
|
||||
const uint16_t to = static_cast<uint16_t>(from + opl - 1u);
|
||||
auto [buf0, iit0] = dpf::eval_interval(dpf::out<0, 16>, k0, from, to);
|
||||
auto [buf1, iit1] = dpf::eval_interval(dpf::out<0, 16>, k1, from, to);
|
||||
(void)iit0;
|
||||
(void)iit1;
|
||||
for (uint16_t q = from; q <= to; ++q)
|
||||
{
|
||||
const std::size_t idx = static_cast<std::size_t>(q - from);
|
||||
const uint16_t want = (q == lane) ? uint16_t{99} : uint16_t{7};
|
||||
EXPECT_EQ(recon(buf0[idx], buf1[idx]), want) << "eq_at lane=" << q;
|
||||
const uint32_t full = static_cast<uint32_t>(q) << 16;
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, full),
|
||||
*dpf::eval_point(dpf::out<0, 16>, k1, full)),
|
||||
want);
|
||||
}
|
||||
|
||||
// Spot-check the full-width xor_wrapper eq (avoid 2^32 full-domain walk).
|
||||
const auto on = dpf::xor_wrapper<uint32_t>{0x00ff00ffu};
|
||||
const auto off = dpf::xor_wrapper<uint32_t>{0x00001111u};
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha),
|
||||
*dpf::eval_point(dpf::out<1>, k1, alpha)),
|
||||
on);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha ^ 1u),
|
||||
*dpf::eval_point(dpf::out<1>, k1, alpha ^ 1u)),
|
||||
off);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, 0u),
|
||||
*dpf::eval_point(dpf::out<1>, k1, 0u)),
|
||||
off);
|
||||
// Leaf-aligned interval around α for the xor slot.
|
||||
using KT1 = std::decay_t<decltype(k0)>;
|
||||
constexpr auto opl1 = KT1::template outputs_per_leaf_of<1>;
|
||||
const uint32_t xfrom = alpha & ~(opl1 - 1u);
|
||||
const uint32_t xto = xfrom + static_cast<uint32_t>(opl1 - 1u);
|
||||
auto [xf0, xi0] = dpf::eval_interval(dpf::out<1>, k0, xfrom, xto);
|
||||
auto [xf1, xi1] = dpf::eval_interval(dpf::out<1>, k1, xfrom, xto);
|
||||
(void)xi0;
|
||||
(void)xi1;
|
||||
for (uint32_t q = xfrom; q <= xto; ++q)
|
||||
{
|
||||
const auto want = (q == alpha) ? on : off;
|
||||
EXPECT_EQ(recon(xf0[q - xfrom], xf1[q - xfrom]), want) << "xor q=" << q;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, EqIfFalseDealerMatchesDoernerShelatSurfaces)
|
||||
{
|
||||
const uint8_t alpha = 0x2au;
|
||||
const uint8_t x0s = 0x11u;
|
||||
const uint8_t x1s = static_cast<uint8_t>(alpha ^ x0s);
|
||||
auto dealer = dpf::make_dpf(alpha, dpf::eq(uint8_t{9}, uint8_t{4}));
|
||||
auto ds = dpf::make_dpf_doerner_shelat(x0s, x1s,
|
||||
dpf::eq(uint8_t{9}, uint8_t{4}));
|
||||
|
||||
for (unsigned q = 0; q < 256u; q += 17u)
|
||||
{
|
||||
const uint8_t qq = static_cast<uint8_t>(q);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dealer.first, qq),
|
||||
*dpf::eval_point(dealer.second, qq)),
|
||||
recon(*dpf::eval_point(ds.first, qq),
|
||||
*dpf::eval_point(ds.second, qq)));
|
||||
}
|
||||
auto [fa0, ia0] = dpf::eval_full(dealer.first);
|
||||
auto [fa1, ia1] = dpf::eval_full(dealer.second);
|
||||
auto [fb0, ib0] = dpf::eval_full(ds.first);
|
||||
auto [fb1, ib1] = dpf::eval_full(ds.second);
|
||||
(void)ia0;
|
||||
(void)ia1;
|
||||
(void)ib0;
|
||||
(void)ib1;
|
||||
for (unsigned q = 0; q < 256u; q += 17u)
|
||||
EXPECT_EQ(recon(fa0[q], fa1[q]), recon(fb0[q], fb1[q]));
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, PathMemoizerResumePastFullWidthCmpDepth)
|
||||
{
|
||||
const uint32_t alpha = 0x00abcdefu;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(uint64_t{3}));
|
||||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||||
|
||||
const uint64_t mask = k0.cmp().mask;
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0),
|
||||
dpf::eval_point(dpf::cmp, k1, alpha, path1))
|
||||
& mask,
|
||||
0u);
|
||||
EXPECT_EQ(dpf::reconstruct(
|
||||
dpf::eval_point(dpf::cmp, k0, alpha - 1u, path0),
|
||||
dpf::eval_point(dpf::cmp, k1, alpha - 1u, path1))
|
||||
& mask,
|
||||
3u);
|
||||
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||||
*dpf::eval_point(k1, alpha, path1)),
|
||||
7u);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u, path0),
|
||||
*dpf::eval_point(k1, alpha ^ 1u, path1)),
|
||||
0u);
|
||||
|
||||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, p0),
|
||||
*dpf::eval_point(k1, alpha, p1)),
|
||||
7u);
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, p0),
|
||||
dpf::eval_point(dpf::cmp, k1, alpha, p1))
|
||||
& mask,
|
||||
0u);
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 2u, p0),
|
||||
*dpf::eval_point(k1, alpha ^ 2u, p1)),
|
||||
0u);
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, PathMemoizerResumeCmpOnlyFullWidth)
|
||||
{
|
||||
const uint32_t alpha = 0x80000001u;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::geq(uint64_t{5}, uint64_t{1}));
|
||||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||||
const uint64_t mask = k0.cmp().mask;
|
||||
auto rq = [&](uint32_t q) {
|
||||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q, path0),
|
||||
dpf::eval_point(dpf::cmp, k1, q, path1))
|
||||
& mask;
|
||||
};
|
||||
EXPECT_EQ(rq(alpha), 5u);
|
||||
EXPECT_EQ(rq(alpha), 5u);
|
||||
EXPECT_EQ(rq(alpha - 1u), 1u);
|
||||
EXPECT_EQ(rq(alpha + 1u), 5u);
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, EqMixedWithBlockedCmpSharesMemoizer)
|
||||
{
|
||||
const uint8_t alpha = 0x5au;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint8_t{11}, uint8_t{2}),
|
||||
dpf::block_width<3>(dpf::lt(uint64_t{9}, uint64_t{1})));
|
||||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||||
|
||||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||||
*dpf::eval_point(k1, alpha, path1)),
|
||||
11u);
|
||||
const uint64_t mask = k0.cmp().mask;
|
||||
EXPECT_EQ(dpf::reconstruct(
|
||||
dpf::eval_point(dpf::cmp, k0, uint8_t(alpha - 1), path0),
|
||||
dpf::eval_point(dpf::cmp, k1, uint8_t(alpha - 1), path1))
|
||||
& mask,
|
||||
9u);
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0),
|
||||
dpf::eval_point(dpf::cmp, k1, alpha, path1))
|
||||
& mask,
|
||||
1u);
|
||||
|
||||
auto [f0, i0] = dpf::eval_full(k0);
|
||||
auto [f1, i1] = dpf::eval_full(k1);
|
||||
(void)i0;
|
||||
(void)i1;
|
||||
EXPECT_EQ(recon(f0[alpha], f1[alpha]), 11u);
|
||||
EXPECT_EQ(recon(f0[uint8_t(alpha ^ 1)], f1[uint8_t(alpha ^ 1)]), 2u);
|
||||
}
|
||||
|
||||
TEST_F(IncrementalDpfTest, IntervalMemoizerReuseAcrossEqAndShallowAt)
|
||||
{
|
||||
const uint32_t alpha = 0x00a1b2c3u;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{8}, uint32_t{1}),
|
||||
dpf::at<8>(uint8_t{42}));
|
||||
using KT = std::decay_t<decltype(k0)>;
|
||||
constexpr auto opl0 = KT::template outputs_per_leaf_of<0>;
|
||||
|
||||
const uint32_t efrom = alpha & ~(opl0 - 1u);
|
||||
const uint32_t eto = efrom + static_cast<uint32_t>(opl0 - 1u);
|
||||
auto [f0, fi0] = dpf::eval_interval(dpf::out<0>, k0, efrom, eto);
|
||||
auto [f1, fi1] = dpf::eval_interval(dpf::out<0>, k1, efrom, eto);
|
||||
(void)fi0;
|
||||
(void)fi1;
|
||||
for (uint32_t q = efrom; q <= eto; ++q)
|
||||
{
|
||||
const uint32_t want = (q == alpha) ? 8u : 1u;
|
||||
EXPECT_EQ(recon(f0[q - efrom], f1[q - efrom]), want) << "eq q=" << q;
|
||||
}
|
||||
|
||||
const uint8_t top = static_cast<uint8_t>(alpha >> 24);
|
||||
auto [ait0, ai0] = dpf::eval_interval(dpf::out<1, 8>, k0, uint8_t{0},
|
||||
uint8_t{255});
|
||||
auto [ait1, ai1] = dpf::eval_interval(dpf::out<1, 8>, k1, uint8_t{0},
|
||||
uint8_t{255});
|
||||
(void)ai0;
|
||||
(void)ai1;
|
||||
EXPECT_EQ(recon(ait0[top], ait1[top]), 42u);
|
||||
EXPECT_EQ(recon(ait0[uint8_t(top ^ 1)], ait1[uint8_t(top ^ 1)]), 0u);
|
||||
|
||||
// Re-walk a different eq window after the shallow interval.
|
||||
const uint32_t from = alpha - 3u;
|
||||
const uint32_t to = alpha + 3u;
|
||||
const uint32_t afrom = from & ~(opl0 - 1u);
|
||||
const uint32_t ato = (to + opl0 - 1u) & ~(opl0 - 1u);
|
||||
const uint32_t ato_inclusive = ato + static_cast<uint32_t>(opl0 - 1u);
|
||||
auto [eit0, ee0] = dpf::eval_interval(dpf::out<0>, k0, afrom,
|
||||
ato_inclusive);
|
||||
auto [eit1, ee1] = dpf::eval_interval(dpf::out<0>, k1, afrom,
|
||||
ato_inclusive);
|
||||
(void)ee0;
|
||||
(void)ee1;
|
||||
for (uint32_t q = from; q <= to; ++q)
|
||||
{
|
||||
const uint32_t want = (q == alpha) ? 8u : 1u;
|
||||
EXPECT_EQ(recon(eit0[q - afrom], eit1[q - afrom]), want) << "q=" << q;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
311
test/tests/interleave_leaves_test.cpp
Normal file
311
test/tests/interleave_leaves_test.cpp
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename LaneT>
|
||||
using storage = dpf::leaf_storage_t<LaneT>;
|
||||
|
||||
template <typename LaneT>
|
||||
constexpr unsigned lane_bits()
|
||||
{
|
||||
if constexpr (dpf::utils::is_packed_subbyte_v<LaneT>)
|
||||
return static_cast<unsigned>(dpf::utils::packed_lane_bits_v<LaneT>);
|
||||
else
|
||||
return static_cast<unsigned>(sizeof(LaneT) * 8u);
|
||||
}
|
||||
|
||||
template <typename LaneT>
|
||||
constexpr std::uint64_t lane_mask()
|
||||
{
|
||||
if constexpr (dpf::utils::is_packed_subbyte_v<LaneT>)
|
||||
return (std::uint64_t{1} << dpf::utils::packed_lane_bits_v<LaneT>) - 1u;
|
||||
else if constexpr (sizeof(LaneT) >= 8)
|
||||
return ~std::uint64_t{0};
|
||||
else
|
||||
return (std::uint64_t{1} << (sizeof(LaneT) * 8u)) - 1u;
|
||||
}
|
||||
|
||||
template <typename LaneT>
|
||||
std::uint64_t get_lane(const storage<LaneT> * buf, std::size_t i)
|
||||
{
|
||||
if constexpr (dpf::utils::is_packed_subbyte_v<LaneT>)
|
||||
{
|
||||
constexpr unsigned w = dpf::utils::packed_lane_bits_v<LaneT>;
|
||||
const std::size_t bit = i * w;
|
||||
const std::size_t word = bit / 64u;
|
||||
const unsigned shift = static_cast<unsigned>(bit % 64u);
|
||||
std::uint64_t v = buf[word] >> shift;
|
||||
if (shift + w > 64u)
|
||||
v |= buf[word + 1u] << (64u - shift);
|
||||
return v & lane_mask<LaneT>();
|
||||
}
|
||||
else if constexpr (std::is_class_v<LaneT>)
|
||||
{
|
||||
return static_cast<std::uint64_t>(
|
||||
static_cast<typename LaneT::integral_type>(buf[i])) & lane_mask<LaneT>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<std::uint64_t>(buf[i]) & lane_mask<LaneT>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LaneT>
|
||||
void set_lane(storage<LaneT> * buf, std::size_t i, std::uint64_t val)
|
||||
{
|
||||
val &= lane_mask<LaneT>();
|
||||
if constexpr (dpf::utils::is_packed_subbyte_v<LaneT>)
|
||||
{
|
||||
constexpr unsigned w = dpf::utils::packed_lane_bits_v<LaneT>;
|
||||
const std::size_t bit = i * w;
|
||||
const std::size_t word = bit / 64u;
|
||||
const unsigned shift = static_cast<unsigned>(bit % 64u);
|
||||
const std::uint64_t mask = lane_mask<LaneT>();
|
||||
buf[word] = (buf[word] & ~(mask << shift)) | (val << shift);
|
||||
if (shift + w > 64u)
|
||||
{
|
||||
const unsigned lo = 64u - shift;
|
||||
buf[word + 1u] = (buf[word + 1u] & ~(mask >> lo)) | (val >> lo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[i] = static_cast<storage<LaneT>>(val);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LaneT>
|
||||
std::vector<storage<LaneT>> make_key(std::size_t nleaves, std::size_t key_id)
|
||||
{
|
||||
std::vector<storage<LaneT>> v(dpf::leaf_storage_words<LaneT>(nleaves),
|
||||
storage<LaneT>{0});
|
||||
for (std::size_t i = 0; i < nleaves; ++i)
|
||||
{
|
||||
// Distinct pattern per (key, leaf); fits every width's mask.
|
||||
const std::uint64_t val
|
||||
= (key_id * 131u + i * 17u + 3u) & lane_mask<LaneT>();
|
||||
set_lane<LaneT>(v.data(), i, val);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename LaneT>
|
||||
void check_roundtrip(std::size_t nkeys, std::size_t nleaves)
|
||||
{
|
||||
std::vector<std::vector<storage<LaneT>>> owned;
|
||||
owned.reserve(nkeys);
|
||||
std::vector<const storage<LaneT> *> in_ptrs;
|
||||
std::vector<storage<LaneT> *> out_ptrs;
|
||||
in_ptrs.reserve(nkeys);
|
||||
out_ptrs.reserve(nkeys);
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
owned.push_back(make_key<LaneT>(nleaves, k));
|
||||
in_ptrs.push_back(owned.back().data());
|
||||
}
|
||||
|
||||
std::vector<storage<LaneT>> interleaved(
|
||||
dpf::leaf_storage_words<LaneT>(nkeys * nleaves), storage<LaneT>{0});
|
||||
dpf::interleave_leaves<LaneT>(interleaved.data(), in_ptrs.data(), nkeys,
|
||||
nleaves);
|
||||
|
||||
for (std::size_t i = 0; i < nleaves; ++i)
|
||||
{
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
const std::size_t g = dpf::cohort_index(i, k, nkeys);
|
||||
EXPECT_EQ(get_lane<LaneT>(interleaved.data(), g),
|
||||
get_lane<LaneT>(owned[k].data(), i))
|
||||
<< "LaneT bits=" << lane_bits<LaneT>() << " nkeys=" << nkeys
|
||||
<< " nleaves=" << nleaves << " i=" << i << " k=" << k;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<storage<LaneT>>> round;
|
||||
round.reserve(nkeys);
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
round.emplace_back(dpf::leaf_storage_words<LaneT>(nleaves),
|
||||
storage<LaneT>{0});
|
||||
out_ptrs.push_back(round.back().data());
|
||||
}
|
||||
dpf::deinterleave_leaves<LaneT>(out_ptrs.data(), interleaved.data(), nkeys,
|
||||
nleaves);
|
||||
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
for (std::size_t i = 0; i < nleaves; ++i)
|
||||
{
|
||||
EXPECT_EQ(get_lane<LaneT>(round[k].data(), i),
|
||||
get_lane<LaneT>(owned[k].data(), i))
|
||||
<< "deinterleave bits=" << lane_bits<LaneT>()
|
||||
<< " nkeys=" << nkeys << " i=" << i << " k=" << k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename LaneT>
|
||||
class InterleaveLeavesTest : public ::testing::Test
|
||||
{
|
||||
};
|
||||
|
||||
using InterleaveTypes = ::testing::Types<
|
||||
std::uint64_t,
|
||||
std::uint32_t,
|
||||
std::uint16_t,
|
||||
std::uint8_t,
|
||||
dpf::nyble,
|
||||
dpf::twobit,
|
||||
dpf::bit,
|
||||
dpf::gf2,
|
||||
dpf::gf22,
|
||||
dpf::gf24,
|
||||
dpf::gf28,
|
||||
dpf::gf216,
|
||||
dpf::gf232,
|
||||
dpf::gf264>;
|
||||
|
||||
TYPED_TEST_SUITE(InterleaveLeavesTest, InterleaveTypes);
|
||||
|
||||
TYPED_TEST(InterleaveLeavesTest, KeyCountsAndOddLength)
|
||||
{
|
||||
// Length not a multiple of pack width (8 for bits-in-byte, 32 for
|
||||
// 2-bit-in-u64, 16 for 4-bit-in-u64, etc.): 13 is odd for all of those.
|
||||
constexpr std::size_t nleaves = 13;
|
||||
for (std::size_t nkeys : {std::size_t{1}, std::size_t{3}, std::size_t{5},
|
||||
std::size_t{16}})
|
||||
check_roundtrip<TypeParam>(nkeys, nleaves);
|
||||
}
|
||||
|
||||
TYPED_TEST(InterleaveLeavesTest, LongerNotMultipleOfWord)
|
||||
{
|
||||
// 70 leaves: not a multiple of 64 (bit word) or 32 (twobit word).
|
||||
check_roundtrip<TypeParam>(5, 70);
|
||||
check_roundtrip<TypeParam>(8, 70);
|
||||
}
|
||||
|
||||
TEST(InterleaveLeaves, BitPackingIsKeyMajorWithinLeaf)
|
||||
{
|
||||
// Three keys, five 1-bit leaves. Logical order of bits in the output
|
||||
// stream is (i0,k0),(i0,k1),(i0,k2),(i1,k0),... — not a byte index of
|
||||
// i*m+k into a uint8_t array.
|
||||
constexpr std::size_t nkeys = 3;
|
||||
constexpr std::size_t nleaves = 5;
|
||||
std::array<std::uint64_t, 1> k0{0}, k1{0}, k2{0};
|
||||
// key0: 1 0 1 1 0
|
||||
// key1: 0 1 1 0 1
|
||||
// key2: 1 1 0 0 1
|
||||
k0[0] = 0b01101ull;
|
||||
k1[0] = 0b10110ull;
|
||||
k2[0] = 0b10011ull;
|
||||
const std::uint64_t * keys[3] = {k0.data(), k1.data(), k2.data()};
|
||||
std::uint64_t out = 0;
|
||||
dpf::interleave_leaves<dpf::bit>(&out, keys, nkeys, nleaves);
|
||||
|
||||
// Expected 15 bits, low-first: for i=0..4, bits of keys 0,1,2
|
||||
// i0: 1,0,1 | i1: 0,1,1 | i2: 1,1,0 | i3: 1,0,0 | i4: 0,1,1
|
||||
// bit0..14 = 1,0,1,0,1,1,1,1,0,1,0,0,0,1,1
|
||||
const std::uint64_t expect = 0b110001011110101ull;
|
||||
EXPECT_EQ(out & ((1ull << 15) - 1ull), expect);
|
||||
}
|
||||
|
||||
TEST(InterleaveLeaves, InnerProductModintFromBits)
|
||||
{
|
||||
// Same three keys as above. Leaf integers (key 0 in the low bit):
|
||||
// i0 = 0b101 = 5, i1 = 0b110 = 6, i2 = 0b011 = 3, i3 = 0b001 = 1, i4 = 0b110 = 6.
|
||||
constexpr std::size_t nkeys = 3;
|
||||
constexpr std::size_t nleaves = 5;
|
||||
std::array<std::uint64_t, 1> k0{0b01101ull}, k1{0b10110ull}, k2{0b10011ull};
|
||||
const std::uint64_t * keys[3] = {k0.data(), k1.data(), k2.data()};
|
||||
std::uint64_t bits = 0;
|
||||
dpf::interleave_leaves<dpf::bit>(&bits, keys, nkeys, nleaves);
|
||||
|
||||
const std::uint64_t w[5] = {1, 2, 3, 4, 5};
|
||||
const auto got = dpf::interleaved_bits_inner_product<3>(&bits, nleaves, nkeys, w);
|
||||
// 5*1 + 6*2 + 3*3 + 1*4 + 6*5 = 5+12+9+4+30 = 60 ≡ 60-7*8 = 4 (mod 8)
|
||||
EXPECT_EQ(static_cast<unsigned>(static_cast<dpf::modint<3>::integral_type>(got)), 4u);
|
||||
|
||||
std::array<dpf::modint<3>, 5> mw{1, 2, 3, 4, 5};
|
||||
const auto got_m = dpf::interleaved_bits_inner_product<3>(&bits, nleaves, nkeys, mw);
|
||||
EXPECT_EQ(static_cast<unsigned>(static_cast<dpf::modint<3>::integral_type>(got_m)), 4u);
|
||||
}
|
||||
|
||||
TEST(InterleaveLeaves, InnerProductModint128)
|
||||
{
|
||||
constexpr std::size_t nkeys = 128;
|
||||
constexpr std::size_t nleaves = 3;
|
||||
std::vector<std::uint64_t> key_bits(nkeys * 2, 0);
|
||||
std::vector<const std::uint64_t *> ptrs(nkeys);
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
// Leaf 0: bit k set. Leaf 1: only key 0. Leaf 2: zero.
|
||||
if (k == 0)
|
||||
key_bits[k * 2] = 0b011ull;
|
||||
else
|
||||
key_bits[k * 2] = 0b001ull;
|
||||
ptrs[k] = key_bits.data() + k * 2;
|
||||
}
|
||||
std::vector<std::uint64_t> bits(dpf::leaf_storage_words<dpf::bit>(nkeys * nleaves));
|
||||
dpf::interleave_leaves<dpf::bit>(bits.data(), ptrs.data(), nkeys, nleaves);
|
||||
|
||||
const std::uint64_t w[3] = {1, 3, 9};
|
||||
const auto got = dpf::interleaved_bits_inner_product<128>(
|
||||
bits.data(), nleaves, nkeys, w);
|
||||
using limb = dpf::modint<128>::integral_type;
|
||||
const limb all = ~limb{0};
|
||||
const limb expect = all * limb{1} + limb{1} * limb{3};
|
||||
EXPECT_EQ(static_cast<limb>(got), expect);
|
||||
}
|
||||
|
||||
TEST(InterleaveLeaves, InnerProductLowBitsOfWideGroup)
|
||||
{
|
||||
// Eight 1-bit keys, read as modint<4>: only keys 0..3.
|
||||
constexpr std::size_t nkeys = 8;
|
||||
constexpr std::size_t nleaves = 2;
|
||||
std::array<std::uint64_t, 8> raw{};
|
||||
const std::uint64_t * ptrs[8];
|
||||
for (std::size_t k = 0; k < nkeys; ++k)
|
||||
{
|
||||
raw[k] = (k < 4) ? 0b11ull : 0b01ull; // both leaves set for low keys
|
||||
ptrs[k] = &raw[k];
|
||||
}
|
||||
std::uint64_t bits[4]{};
|
||||
dpf::interleave_leaves<dpf::bit>(bits, ptrs, nkeys, nleaves);
|
||||
const std::uint64_t w[2] = {1, 1};
|
||||
const auto got = dpf::interleaved_bits_inner_product<4>(bits, nleaves, nkeys, w);
|
||||
// Each leaf's low 4 bits are 0b1111 = 15. 15+15 = 30 ≡ 14 (mod 16).
|
||||
EXPECT_EQ(static_cast<unsigned>(static_cast<dpf::modint<4>::integral_type>(got)), 14u);
|
||||
}
|
||||
|
||||
TYPED_TEST(InterleaveLeavesTest, EmptyAndSingleLeaf)
|
||||
{
|
||||
check_roundtrip<TypeParam>(0, 0);
|
||||
check_roundtrip<TypeParam>(3, 0);
|
||||
check_roundtrip<TypeParam>(0, 5);
|
||||
check_roundtrip<TypeParam>(1, 1);
|
||||
check_roundtrip<TypeParam>(4, 1);
|
||||
}
|
||||
|
||||
TEST(InterleaveLeaves, InnerProductEmptyIsZero)
|
||||
{
|
||||
const std::uint64_t w[1] = {9};
|
||||
EXPECT_EQ(static_cast<unsigned>(static_cast<dpf::modint<3>::integral_type>(
|
||||
dpf::interleaved_bits_inner_product<3>(
|
||||
static_cast<const std::uint64_t *>(nullptr), 0, 3, w))),
|
||||
0u);
|
||||
std::uint64_t bits = 0;
|
||||
EXPECT_EQ(static_cast<unsigned>(static_cast<dpf::modint<3>::integral_type>(
|
||||
dpf::interleaved_bits_inner_product<3>(&bits, 5, 0, w))),
|
||||
0u);
|
||||
}
|
||||
68
test/tests/it_dpf3_test.cpp
Normal file
68
test/tests/it_dpf3_test.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
TEST(ItDpf3, OnPointAndOffPoint)
|
||||
{
|
||||
constexpr std::uint8_t alpha = 42;
|
||||
constexpr std::uint64_t beta = 7;
|
||||
auto [k0, k1, k2] = dpf::make_it_dpf3(alpha, beta);
|
||||
|
||||
EXPECT_FALSE((std::is_same_v<decltype(k0),
|
||||
decltype(std::get<0>(dpf::make_dpf3(alpha, dpf::fp61{1})))>));
|
||||
|
||||
const auto on = dpf::eval_it_dpf3(k0, alpha) + dpf::eval_it_dpf3(k1, alpha)
|
||||
+ dpf::eval_it_dpf3(k2, alpha);
|
||||
EXPECT_EQ(on, beta);
|
||||
|
||||
const auto off = dpf::eval_it_dpf3(k0, std::uint8_t{41})
|
||||
+ dpf::eval_it_dpf3(k1, std::uint8_t{41})
|
||||
+ dpf::eval_it_dpf3(k2, std::uint8_t{41});
|
||||
EXPECT_EQ(off, 0u);
|
||||
}
|
||||
|
||||
TEST(ItDpf3, InnerProductIsTableEntry)
|
||||
{
|
||||
constexpr std::uint8_t index = 17;
|
||||
std::vector<std::uint64_t> table(256);
|
||||
for (std::size_t i = 0; i < table.size(); ++i)
|
||||
table[i] = i * i + 3;
|
||||
|
||||
auto [k0, k1, k2] = dpf::make_it_dpf3(index, 1);
|
||||
const auto s0 = dpf::eval_it_dpf3_inner_product(k0, table);
|
||||
const auto s1 = dpf::eval_it_dpf3_inner_product(k1, table);
|
||||
const auto s2 = dpf::eval_it_dpf3_inner_product(k2, table);
|
||||
EXPECT_EQ(s0 + s1 + s2, table[index]);
|
||||
}
|
||||
|
||||
// Seam: same PIR shape as make_dpf3 + shamir reconstruct, different key type
|
||||
// and three-way sum instead of any-two Lagrange.
|
||||
TEST(ItDpf3, PirDotDistinctFromShamirDpf3)
|
||||
{
|
||||
constexpr std::uint8_t index = 42;
|
||||
std::vector<std::uint64_t> table(256);
|
||||
for (std::size_t i = 0; i < table.size(); ++i)
|
||||
table[i] = i + 1;
|
||||
|
||||
auto [i0, i1, i2] = dpf::make_it_dpf3(index, 1);
|
||||
const auto it_opened =
|
||||
dpf::eval_it_dpf3_inner_product(i0, table)
|
||||
+ dpf::eval_it_dpf3_inner_product(i1, table)
|
||||
+ dpf::eval_it_dpf3_inner_product(i2, table);
|
||||
EXPECT_EQ(it_opened, table[index]);
|
||||
|
||||
std::vector<dpf::fp61> ftable(256);
|
||||
for (std::size_t i = 0; i < ftable.size(); ++i)
|
||||
ftable[i] = dpf::fp61{static_cast<std::uint64_t>(i + 1)};
|
||||
auto [d1, d2, d3] = dpf::make_dpf3(index, dpf::fp61{1});
|
||||
const auto s1 = dpf::eval_full_inner_product(d1, ftable);
|
||||
const auto s2 = dpf::eval_full_inner_product(d2, ftable);
|
||||
const auto opened = dpf::shamir3::reconstruct(
|
||||
dpf::as_share(d1, s1), dpf::as_share(d2, s2));
|
||||
EXPECT_EQ(opened.raw(), table[index]);
|
||||
EXPECT_FALSE((std::is_same_v<decltype(i0), decltype(d1)>));
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
|
|
@ -164,8 +165,8 @@ void expect_interval(In alpha, Lane y, In from, In to)
|
|||
EXPECT_EQ(p1, std::end(b.second));
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
void expect_live_words(const Key & key, const auto & g)
|
||||
template <typename Key, typename GenevalResult>
|
||||
void expect_live_words(const Key & key, const GenevalResult & g)
|
||||
{
|
||||
ASSERT_LE(g.live_levels, g.correction_words.size());
|
||||
for (std::size_t i = 0; i < g.live_levels; ++i)
|
||||
|
|
@ -326,8 +327,7 @@ TEST(LaneBlast, Uint8TwobitEveryInputAndEveryShape)
|
|||
EXPECT_EQ(f0, std::end(fi0));
|
||||
|
||||
const in_t unsorted[] = {255, 40, 0};
|
||||
EXPECT_THROW(dpf::make_sequence_recipe(k0, std::begin(unsorted), std::end(unsorted)),
|
||||
std::runtime_error);
|
||||
EXPECT_THROW(dpf::make_sequence_recipe(k0, std::begin(unsorted), std::end(unsorted)), std::runtime_error);
|
||||
const in_t seq[] = {0, 40, 40, 41, 104, 255};
|
||||
auto recipe = dpf::make_sequence_recipe(k0, std::begin(seq), std::end(seq));
|
||||
auto s0 = dpf::eval_sequence(k0, recipe);
|
||||
|
|
|
|||
149
test/tests/leaf_later_test.cpp
Normal file
149
test/tests/leaf_later_test.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Full-block payload ⇒ outputs_per_leaf = 1, so a single public F matches the
|
||||
// leaf correction word share-for-share.
|
||||
using input_t = std::uint8_t;
|
||||
using output_t = simde_uint128;
|
||||
constexpr std::size_t n = 256;
|
||||
|
||||
output_t leaf_cw_as_output(const dpf::utils::dpf_type_t<dpf::prg::aes128,
|
||||
dpf::prg::aes128, input_t, output_t> & key)
|
||||
{
|
||||
using exterior = typename std::decay_t<decltype(key)>::exterior_node;
|
||||
return dpf::extract_leaf<exterior, output_t>(key.template leaf<0>(), 0);
|
||||
}
|
||||
|
||||
// party_key wrappers from make_dpf
|
||||
template <typename Key>
|
||||
output_t leaf_cw_as_output(const Key & key)
|
||||
{
|
||||
using exterior = typename Key::exterior_node;
|
||||
return dpf::extract_leaf<exterior, output_t>(key.template leaf<0>(), 0);
|
||||
}
|
||||
|
||||
output_t make_payload(std::uint64_t lo)
|
||||
{
|
||||
return output_t{lo};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(LeafLater, MatchesCorrectedImmediately)
|
||||
{
|
||||
const input_t alpha = 42;
|
||||
const output_t beta = make_payload(7);
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
// Public leaf CW (same on both keys); applying it recovers the immediate path.
|
||||
const output_t F = leaf_cw_as_output(k0);
|
||||
EXPECT_EQ(F, leaf_cw_as_output(k1));
|
||||
|
||||
auto full0 = dpf::eval_full(k0);
|
||||
auto full1 = dpf::eval_full(k1);
|
||||
|
||||
std::vector<output_t> buf0(n), buf1(n);
|
||||
std::vector<std::uint8_t> c0(n), c1(n);
|
||||
dpf::eval_full(buf0, c0, k0, dpf::leaf_later{});
|
||||
dpf::eval_full(buf1, c1, k1, dpf::leaf_later{});
|
||||
dpf::apply_leaf_correction(buf0, c0, F);
|
||||
dpf::apply_leaf_correction(buf1, c1, F);
|
||||
|
||||
auto it0 = std::begin(full0.second);
|
||||
auto it1 = std::begin(full1.second);
|
||||
for (std::size_t i = 0; i < n; ++i, ++it0, ++it1)
|
||||
{
|
||||
const auto imm0 = dpf::detail_walk::group_value(*it0);
|
||||
const auto imm1 = dpf::detail_walk::group_value(*it1);
|
||||
EXPECT_EQ(buf0[i], imm0) << "party0 @" << i;
|
||||
EXPECT_EQ(buf1[i], imm1) << "party1 @" << i;
|
||||
EXPECT_EQ(buf0[i] - buf1[i], imm0 - imm1) << i;
|
||||
const auto got = buf0[i] - buf1[i];
|
||||
if (i == alpha)
|
||||
EXPECT_EQ(got, beta);
|
||||
else
|
||||
EXPECT_EQ(got, output_t{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LeafLater, ApplyAfterRotateMatchesCorrectThenRotate)
|
||||
{
|
||||
const input_t alpha = 10;
|
||||
const output_t beta = make_payload(99);
|
||||
const std::size_t s = 32;
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
const output_t F = leaf_cw_as_output(k0);
|
||||
|
||||
auto full0 = dpf::eval_full(k0);
|
||||
auto full1 = dpf::eval_full(k1);
|
||||
std::vector<output_t> early0(n), early1(n);
|
||||
{
|
||||
auto a = std::begin(full0.second);
|
||||
auto b = std::begin(full1.second);
|
||||
for (std::size_t i = 0; i < n; ++i, ++a, ++b)
|
||||
{
|
||||
early0[i] = dpf::detail_walk::group_value(*a);
|
||||
early1[i] = dpf::detail_walk::group_value(*b);
|
||||
}
|
||||
}
|
||||
early0 = dpf::cyclic_shift(early0, s);
|
||||
early1 = dpf::cyclic_shift(early1, s);
|
||||
|
||||
std::vector<output_t> late0(n), late1(n);
|
||||
std::vector<std::uint8_t> c0(n), c1(n);
|
||||
dpf::eval_full(late0, c0, k0, dpf::leaf_later{});
|
||||
dpf::eval_full(late1, c1, k1, dpf::leaf_later{});
|
||||
dpf::cyclic_shift_pair(late0, c0, s);
|
||||
dpf::cyclic_shift_pair(late1, c1, s);
|
||||
dpf::apply_leaf_correction(late0, c0, F);
|
||||
dpf::apply_leaf_correction(late1, c1, F);
|
||||
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
EXPECT_EQ(late0[i], early0[i]) << i;
|
||||
EXPECT_EQ(late1[i], early1[i]) << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LeafLater, DuoramShapedUpdate)
|
||||
{
|
||||
// Unit at r, rotate by i*−r, then apply a public F built from the leaf CW
|
||||
// of a key keyed at the online message (same as correcting immediately for
|
||||
// that message). Neighbours stay unchanged.
|
||||
const input_t r = 10;
|
||||
const input_t i_star = 42;
|
||||
const std::size_t shift = static_cast<std::size_t>(i_star - r);
|
||||
const output_t message = make_payload(7);
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(r, message);
|
||||
const output_t F = leaf_cw_as_output(k0);
|
||||
|
||||
std::vector<output_t> mem0(n), mem1(n);
|
||||
mem0[i_star] = make_payload(100);
|
||||
|
||||
std::vector<output_t> v0(n), v1(n);
|
||||
std::vector<std::uint8_t> t0(n), t1(n);
|
||||
dpf::eval_full_add_into(v0, t0, k0, dpf::leaf_later{}, dpf::rotate{shift});
|
||||
dpf::eval_full_add_into(v1, t1, k1, dpf::leaf_later{}, dpf::rotate{shift});
|
||||
dpf::apply_leaf_correction(v0, t0, F);
|
||||
dpf::apply_leaf_correction(v1, t1, F);
|
||||
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
mem0[i] = mem0[i] + v0[i];
|
||||
mem1[i] = mem1[i] + v1[i];
|
||||
}
|
||||
|
||||
EXPECT_EQ(mem0[i_star] - mem1[i_star], make_payload(100) + message);
|
||||
EXPECT_EQ(mem0[r] - mem1[r], output_t{0});
|
||||
EXPECT_EQ(mem0[0] - mem1[0], output_t{0});
|
||||
}
|
||||
744
test/tests/log_test.cpp
Normal file
744
test/tests/log_test.cpp
Normal file
|
|
@ -0,0 +1,744 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dpf/app_flow.hpp"
|
||||
#include "dpf/experiment.hpp"
|
||||
#include "dpf/launch.hpp"
|
||||
#include "dpf/log.hpp"
|
||||
#include "dpf/prg_aes.hpp"
|
||||
#include "dpf/prg_chacha.hpp"
|
||||
#include "dpf/prg_count.hpp"
|
||||
#include "dpf/run_config.hpp"
|
||||
#include "dpf/run_log.hpp"
|
||||
#include "dpf/yao.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string temp_path(const char * tag)
|
||||
{
|
||||
return "/tmp/libdpf_log_test_" + std::to_string(::getpid()) + "_" + tag + ".log";
|
||||
}
|
||||
|
||||
std::vector<std::string> read_lines(const std::string & path)
|
||||
{
|
||||
std::ifstream in(path);
|
||||
std::vector<std::string> out;
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
out.push_back(line);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool has(const std::string & line, const std::string & needle)
|
||||
{
|
||||
return line.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
std::vector<std::string> with_event(const std::vector<std::string> & lines,
|
||||
const std::string & ev)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
for (const auto & l : lines)
|
||||
if (has(l, " ev=" + ev + " ") || (l.size() >= ev.size() + 4
|
||||
&& l.compare(l.size() - ev.size() - 4, std::string::npos, " ev=" + ev) == 0))
|
||||
out.push_back(l);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Route the log to a fresh file for one test; silence it afterwards.
|
||||
struct file_log
|
||||
{
|
||||
std::string path;
|
||||
explicit file_log(const char * tag, dpf::log::level l = dpf::log::level::debug,
|
||||
dpf::log::seed_policy seeds = dpf::log::seed_policy::full)
|
||||
: path(temp_path(tag))
|
||||
{
|
||||
std::remove(path.c_str());
|
||||
dpf::log::settings s;
|
||||
s.threshold = l;
|
||||
s.sinks = "file:" + path;
|
||||
s.seeds = seeds;
|
||||
dpf::log::configure(s);
|
||||
}
|
||||
~file_log()
|
||||
{
|
||||
dpf::log::settings s;
|
||||
s.sinks = "none";
|
||||
dpf::log::configure(s);
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
std::vector<std::string> lines() const { return read_lines(path); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Runs first: nothing has configured the log yet in this process.
|
||||
TEST(Log, SilentUntilConfigured)
|
||||
{
|
||||
EXPECT_FALSE(dpf::log::enabled(dpf::log::level::error));
|
||||
EXPECT_FALSE(dpf::log::enabled(dpf::log::level::info));
|
||||
int evaluated = 0;
|
||||
DPF_LOG(error, "never").kv("x", ++evaluated);
|
||||
EXPECT_EQ(evaluated, 0);
|
||||
}
|
||||
|
||||
TEST(Log, LevelAndSeedPolicyNames)
|
||||
{
|
||||
using dpf::log::level;
|
||||
EXPECT_EQ(dpf::log::parse_level("silent"), level::silent);
|
||||
EXPECT_EQ(dpf::log::parse_level("warn"), level::warning);
|
||||
EXPECT_EQ(dpf::log::parse_level("3"), level::info);
|
||||
EXPECT_EQ(dpf::log::parse_level("absurd"), level::trace);
|
||||
EXPECT_THROW(dpf::log::parse_level("loud"), std::invalid_argument);
|
||||
EXPECT_EQ(dpf::log::parse_seed_policy("hash"), dpf::log::seed_policy::hash);
|
||||
EXPECT_EQ(dpf::log::parse_seed_policy("off"), dpf::log::seed_policy::off);
|
||||
EXPECT_THROW(dpf::log::parse_seed_policy("maybe"), std::invalid_argument);
|
||||
dpf::log::settings bad;
|
||||
bad.sinks = "stderr,carrier-pigeon";
|
||||
EXPECT_THROW(dpf::log::configure(bad), std::invalid_argument);
|
||||
EXPECT_FALSE(dpf::log::enabled(dpf::log::level::error));
|
||||
}
|
||||
|
||||
TEST(Log, RecordFormatAndThreshold)
|
||||
{
|
||||
std::string path;
|
||||
{
|
||||
file_log log("format", dpf::log::level::info);
|
||||
path = log.path;
|
||||
DPF_LOG(info, "demo").kv("plain", "abc").kv("spaced", "a b")
|
||||
.kv("quote", "say \"hi\"").kv("eq", "k=v").kv("n", std::size_t{42})
|
||||
.kv("neg", -3).kv("flag", true).kv("empty", "").hex("h", "\x01\xff", 2);
|
||||
DPF_LOG(debug, "hidden").kv("x", 1);
|
||||
DPF_LOG(warning, "shown").kv("x", 2);
|
||||
const auto lines = log.lines();
|
||||
ASSERT_EQ(lines.size(), 2u);
|
||||
const auto & l = lines[0];
|
||||
EXPECT_EQ(l.rfind("ts=", 0), 0u);
|
||||
EXPECT_TRUE(has(l, "Z lvl=info inv=" + dpf::log::invocation_id() + " pid="));
|
||||
EXPECT_EQ(dpf::log::invocation_id().size(), 16u);
|
||||
EXPECT_TRUE(has(l, " ev=demo plain=abc spaced=\"a b\" quote=\"say \\\"hi\\\"\" "
|
||||
"eq=\"k=v\" n=42 neg=-3 flag=1 empty=\"\" h=01ff"));
|
||||
EXPECT_TRUE(has(lines[1], "lvl=warning"));
|
||||
EXPECT_TRUE(has(lines[1], "ev=shown x=2"));
|
||||
}
|
||||
EXPECT_FALSE(dpf::log::enabled(dpf::log::level::error));
|
||||
}
|
||||
|
||||
TEST(Log, RoleScopeTagsAndRestores)
|
||||
{
|
||||
file_log log("role");
|
||||
DPF_LOG(info, "outside");
|
||||
{
|
||||
const dpf::log::role_scope a("p1");
|
||||
DPF_LOG(info, "inside");
|
||||
{
|
||||
const dpf::log::role_scope b("dealer");
|
||||
DPF_LOG(info, "nested");
|
||||
}
|
||||
DPF_LOG(info, "back");
|
||||
}
|
||||
const auto lines = log.lines();
|
||||
ASSERT_EQ(lines.size(), 4u);
|
||||
EXPECT_FALSE(has(lines[0], " role="));
|
||||
EXPECT_TRUE(has(lines[1], " role=p1 ev=inside"));
|
||||
EXPECT_TRUE(has(lines[2], " role=dealer ev=nested"));
|
||||
EXPECT_TRUE(has(lines[3], " role=p1 ev=back"));
|
||||
}
|
||||
|
||||
TEST(Log, SeedPolicies)
|
||||
{
|
||||
const std::uint8_t seed[4] = {0xde, 0xad, 0xbe, 0xef};
|
||||
{
|
||||
file_log log("seed_full", dpf::log::level::info, dpf::log::seed_policy::full);
|
||||
DPF_LOG(info, "s").seed("value", seed, sizeof(seed));
|
||||
EXPECT_TRUE(has(log.lines().at(0), "value=deadbeef"));
|
||||
}
|
||||
{
|
||||
file_log log("seed_hash", dpf::log::level::info, dpf::log::seed_policy::hash);
|
||||
DPF_LOG(info, "s").seed("value", seed, sizeof(seed));
|
||||
const auto l = log.lines().at(0);
|
||||
EXPECT_TRUE(has(l, "value=sha256:"));
|
||||
EXPECT_FALSE(has(l, "deadbeef"));
|
||||
const auto pos = l.find("sha256:") + 7;
|
||||
EXPECT_EQ(l.substr(pos).size(), 16u);
|
||||
}
|
||||
{
|
||||
file_log log("seed_off", dpf::log::level::info, dpf::log::seed_policy::off);
|
||||
DPF_LOG(info, "s").seed("value", seed, sizeof(seed));
|
||||
EXPECT_TRUE(has(log.lines().at(0), "value=withheld"));
|
||||
}
|
||||
const auto abc = dpf::log::detail::sha256("",
|
||||
reinterpret_cast<const std::uint8_t *>("abc"), 3);
|
||||
std::string hex;
|
||||
dpf::log::detail::append_hex(hex, abc.data(), abc.size());
|
||||
EXPECT_EQ(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
||||
}
|
||||
|
||||
TEST(Log, ConcurrentRecordsStayWholeLines)
|
||||
{
|
||||
file_log log("threads", dpf::log::level::info);
|
||||
constexpr int threads = 8;
|
||||
constexpr int each = 400;
|
||||
std::vector<std::thread> ts;
|
||||
for (int t = 0; t < threads; ++t)
|
||||
ts.emplace_back([t] {
|
||||
const dpf::log::role_scope role("p" + std::to_string(t));
|
||||
for (int i = 0; i < each; ++i)
|
||||
DPF_LOG(info, "tick").kv("thread", t).kv("i", i)
|
||||
.kv("pad", std::string(64, static_cast<char>('a' + t)));
|
||||
});
|
||||
for (auto & t : ts)
|
||||
t.join();
|
||||
const auto lines = log.lines();
|
||||
ASSERT_EQ(lines.size(), static_cast<std::size_t>(threads * each));
|
||||
std::map<int, int> per;
|
||||
for (const auto & l : lines)
|
||||
{
|
||||
ASSERT_EQ(l.rfind("ts=", 0), 0u) << l;
|
||||
const auto p = l.find(" thread=");
|
||||
ASSERT_NE(p, std::string::npos) << l;
|
||||
const int t = std::stoi(l.substr(p + 8));
|
||||
EXPECT_TRUE(has(l, " role=p" + std::to_string(t) + " ")) << l;
|
||||
EXPECT_TRUE(has(l, "pad=" + std::string(64, static_cast<char>('a' + t)))) << l;
|
||||
++per[t];
|
||||
}
|
||||
for (int t = 0; t < threads; ++t)
|
||||
EXPECT_EQ(per[t], each);
|
||||
}
|
||||
|
||||
TEST(Log, RunConfigKeysAndDescribe)
|
||||
{
|
||||
dpf::app::run_config cfg;
|
||||
cfg.set("log_level", "debug");
|
||||
cfg.set("log", "file:/tmp/x.log,stderr");
|
||||
cfg.set("log_seeds", "hash");
|
||||
cfg.set("cpu2", "3");
|
||||
EXPECT_EQ(cfg.log_level, dpf::log::level::debug);
|
||||
EXPECT_EQ(cfg.log_sinks, "file:/tmp/x.log,stderr");
|
||||
EXPECT_EQ(cfg.log_seeds, dpf::log::seed_policy::hash);
|
||||
EXPECT_EQ(cfg.cpu[2], 3);
|
||||
EXPECT_THROW(cfg.set("log_level", "chatty"), std::invalid_argument);
|
||||
EXPECT_THROW(cfg.set("cpu0", "3abc"), std::invalid_argument);
|
||||
std::map<std::string, std::string> kv;
|
||||
for (const auto & p : cfg.describe())
|
||||
kv[p.first] = p.second;
|
||||
for (const char * k : {"log_level", "log", "log_seeds", "cpu2", "compact",
|
||||
"keepalive_idle", "connect_ms", "accept_ms", "join_ms", "handshake_ms"})
|
||||
EXPECT_EQ(kv.count(k), 1u) << k;
|
||||
EXPECT_EQ(kv["log_level"], "debug");
|
||||
EXPECT_EQ(kv["cpu2"], "3");
|
||||
|
||||
::setenv("DPF_LOG_LEVEL", "warning", 1);
|
||||
::setenv("DPF_LOG_SEEDS", "off", 1);
|
||||
const auto env = dpf::app::run_config::from_env();
|
||||
::unsetenv("DPF_LOG_LEVEL");
|
||||
::unsetenv("DPF_LOG_SEEDS");
|
||||
EXPECT_EQ(env.log_level, dpf::log::level::warning);
|
||||
EXPECT_EQ(env.log_seeds, dpf::log::seed_policy::off);
|
||||
}
|
||||
|
||||
TEST(Log, BannerRecordsProvenance)
|
||||
{
|
||||
const std::string path = temp_path("banner");
|
||||
std::remove(path.c_str());
|
||||
::setenv("DPF_TEST_MARKER", "banner-check", 1);
|
||||
::setenv("DPF_TEST_SEED", "0123456789abcdef", 1);
|
||||
dpf::app::run_config cfg;
|
||||
cfg.log_sinks = "file:" + path;
|
||||
cfg.log_seeds = dpf::log::seed_policy::hash;
|
||||
dpf::app::start_logging(cfg);
|
||||
const auto lines = read_lines(path);
|
||||
::unsetenv("DPF_TEST_MARKER");
|
||||
::unsetenv("DPF_TEST_SEED");
|
||||
for (const char * ev : {"start", "build", "host", "argv", "env", "log", "config"})
|
||||
EXPECT_FALSE(with_event(lines, ev).empty()) << ev;
|
||||
const auto build = with_event(lines, "build").at(0);
|
||||
EXPECT_TRUE(has(build, " rev=")) << build;
|
||||
EXPECT_TRUE(has(build, " entropy=")) << build;
|
||||
EXPECT_TRUE(has(with_event(lines, "start").at(0), " utc=")) << lines[0];
|
||||
bool marker = false;
|
||||
bool seed_hidden = false;
|
||||
for (const auto & l : with_event(lines, "env"))
|
||||
{
|
||||
marker = marker || has(l, "name=DPF_TEST_MARKER value=banner-check");
|
||||
seed_hidden = seed_hidden
|
||||
|| (has(l, "name=DPF_TEST_SEED value=sha256:") && !has(l, "0123456789abcdef"));
|
||||
}
|
||||
EXPECT_TRUE(marker);
|
||||
EXPECT_TRUE(seed_hidden);
|
||||
const auto config = with_event(lines, "config").at(0);
|
||||
EXPECT_TRUE(has(config, " transport=async")) << config;
|
||||
EXPECT_TRUE(has(config, " log_seeds=hash")) << config;
|
||||
dpf::log::settings off;
|
||||
off.sinks = "none";
|
||||
dpf::log::configure(off);
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
|
||||
TEST(Log, LinkUpNamesEndpointsAndAuthentication)
|
||||
{
|
||||
for (const char * encryption : {"off", "on"})
|
||||
{
|
||||
file_log log("links", dpf::log::level::info);
|
||||
dpf::protocol::composer c0(0);
|
||||
dpf::protocol::composer c1(1);
|
||||
auto x0 = c0.input(dpf::protocol::domain::a, 8);
|
||||
auto x1 = c1.input(dpf::protocol::domain::a, 8);
|
||||
auto o0 = c0.exchange(x0);
|
||||
(void)c1.exchange(x1);
|
||||
auto p0 = c0.schedule();
|
||||
auto p1 = c1.schedule();
|
||||
dpf::app::party_values v0(p0.nodes().size()), v1(p1.nodes().size());
|
||||
const std::uint64_t a = 11, b = 31;
|
||||
v0[x0.id].assign(8, 0);
|
||||
v1[x1.id].assign(8, 0);
|
||||
std::memcpy(v0[x0.id].data(), &a, 8);
|
||||
std::memcpy(v1[x1.id].data(), &b, 8);
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = dpf::net::transport::mux;
|
||||
cfg.set("encryption", encryption);
|
||||
const bool tls = std::strcmp(encryption, "on") == 0;
|
||||
const auto r = dpf::run_two_party(p0, p1, v0, v1, {}, cfg);
|
||||
std::uint64_t open = 0;
|
||||
std::memcpy(&open, v0[o0.id].data(), 8);
|
||||
EXPECT_EQ(open, 42u) << encryption;
|
||||
ASSERT_EQ(r.party_wall_ns.size(), 2u);
|
||||
const auto lines = log.lines();
|
||||
const auto ups = with_event(lines, "link.up");
|
||||
ASSERT_EQ(ups.size(), 2u) << encryption;
|
||||
bool saw_accept = false, saw_connect = false;
|
||||
for (const auto & l : ups)
|
||||
{
|
||||
EXPECT_TRUE(has(l, " transport=mux")) << l;
|
||||
EXPECT_TRUE(has(l, " local=127.0.0.1:")) << l;
|
||||
EXPECT_TRUE(has(l, " remote=127.0.0.1:")) << l;
|
||||
if (tls)
|
||||
{
|
||||
EXPECT_TRUE(has(l, " encryption=TLSv1.3/")) << l;
|
||||
EXPECT_TRUE(has(l, " peer_key=")) << l;
|
||||
EXPECT_TRUE(has(l, " auth=")) << l;
|
||||
}
|
||||
else
|
||||
EXPECT_TRUE(has(l, " auth=none encryption=none")) << l;
|
||||
EXPECT_TRUE(has(l, " sndbuf=")) << l;
|
||||
EXPECT_TRUE(has(l, " rtt_us=")) << l;
|
||||
saw_accept = saw_accept || (has(l, " role=p0 ") && has(l, " how=accept peer=p1"));
|
||||
saw_connect = saw_connect || (has(l, " role=p1 ") && has(l, " how=connect peer=p0"));
|
||||
}
|
||||
EXPECT_TRUE(saw_accept) << encryption;
|
||||
EXPECT_TRUE(saw_connect) << encryption;
|
||||
EXPECT_FALSE(with_event(lines, "listen").empty());
|
||||
EXPECT_EQ(with_event(lines, "plan").size(), 2u);
|
||||
const auto done = with_event(lines, "party.done");
|
||||
ASSERT_EQ(done.size(), 2u);
|
||||
for (const auto & l : done)
|
||||
{
|
||||
EXPECT_TRUE(has(l, " wall_ns=")) << l;
|
||||
EXPECT_TRUE(has(l, " wire_out=")) << l;
|
||||
}
|
||||
EXPECT_EQ(with_event(lines, "parties.done").size(), 1u);
|
||||
EXPECT_TRUE(with_event(lines, "link.plaintext").empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Log, NodeArgumentsNameTheBadField)
|
||||
{
|
||||
const char * bad_party[] = {"node", "--party=zero", "--peers=a:1,b:2"};
|
||||
try
|
||||
{
|
||||
(void)dpf::app::parse_node_args(3, const_cast<char **>(bad_party),
|
||||
dpf::app::run_config{});
|
||||
FAIL() << "expected a parse error";
|
||||
}
|
||||
catch (const std::invalid_argument & e)
|
||||
{
|
||||
EXPECT_TRUE(has(e.what(), "--party needs a number")) << e.what();
|
||||
}
|
||||
const char * bad_port[] = {"node", "--party=0", "--peers=a:1,b:70000"};
|
||||
EXPECT_THROW((void)dpf::app::parse_node_args(3, const_cast<char **>(bad_port),
|
||||
dpf::app::run_config{}),
|
||||
std::invalid_argument);
|
||||
const char * memory[] = {"node", "--party=1", "--peers=a:1,b:2", "--transport=async"};
|
||||
const auto a = dpf::app::parse_node_args(4, const_cast<char **>(memory),
|
||||
dpf::app::run_config{});
|
||||
EXPECT_EQ(a.cfg.kind, dpf::net::transport::mux);
|
||||
EXPECT_EQ(a.replaced_transport, "async");
|
||||
}
|
||||
|
||||
TEST(SymCount, PurposeAndPrimitiveCells)
|
||||
{
|
||||
using dpf::prg::primitive;
|
||||
using dpf::prg::purpose;
|
||||
dpf::prg::reset_eval_count();
|
||||
auto seed = simde_mm_set_epi64x(3, 4);
|
||||
auto blk = dpf::prg::aes128::eval(seed, 0);
|
||||
blk = dpf::prg::aes128::eval(blk, 1);
|
||||
{
|
||||
const dpf::prg::purpose_scope h(purpose::hash);
|
||||
(void)dpf::prg::aes128::eval01(blk);
|
||||
{
|
||||
const dpf::prg::purpose_scope inner(purpose::harness);
|
||||
(void)dpf::prg::aes128::eval(blk, 7);
|
||||
}
|
||||
(void)dpf::prg::aes128::eval(blk, 8);
|
||||
}
|
||||
(void)dpf::prg::chacha<20>::eval(seed, 0);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::expand, primitive::aes128), 2u);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::hash, primitive::aes128), 3u);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::harness, primitive::aes128), 1u);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::expand, primitive::chacha), 1u);
|
||||
EXPECT_EQ(dpf::prg::eval_count(), 6u);
|
||||
const auto snap = dpf::prg::snapshot();
|
||||
std::uint64_t all = 0;
|
||||
for (auto n : snap)
|
||||
all += n;
|
||||
EXPECT_EQ(all, 7u);
|
||||
dpf::prg::reset_eval_count();
|
||||
EXPECT_EQ(dpf::prg::eval_count(), 0u);
|
||||
}
|
||||
|
||||
TEST(SymCount, GarblingHashesCountAsHash)
|
||||
{
|
||||
using dpf::prg::primitive;
|
||||
using dpf::prg::purpose;
|
||||
dpf::prg::reset_eval_count();
|
||||
const auto x = simde_mm_set_epi64x(9, 10);
|
||||
(void)dpf::yao::detail::cr_hash(x, 5);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::hash, primitive::aes128), 1u);
|
||||
EXPECT_EQ(dpf::prg::count(purpose::expand, primitive::aes128), 0u);
|
||||
dpf::prg::reset_eval_count();
|
||||
}
|
||||
|
||||
TEST(SymCount, ExperimentSeedStreamIsNotProtocolWork)
|
||||
{
|
||||
using dpf::prg::primitive;
|
||||
using dpf::prg::purpose;
|
||||
dpf::experiment ex("stream");
|
||||
ex.begin_timing();
|
||||
std::array<std::uint8_t, 1600> buf{};
|
||||
dpf::uniform_fill(buf);
|
||||
ex.end_timing();
|
||||
EXPECT_EQ(ex.random_bytes(), 1600u);
|
||||
EXPECT_EQ(ex.prg_evals(), 0u);
|
||||
const auto & sym = ex.sym_counts();
|
||||
EXPECT_EQ(sym[static_cast<std::size_t>(purpose::harness) * dpf::prg::primitive_count
|
||||
+ static_cast<std::size_t>(primitive::aes128)],
|
||||
100u);
|
||||
}
|
||||
|
||||
TEST(Experiment, SeedSourceIsLoggedAndWritten)
|
||||
{
|
||||
const std::string dir = "/tmp/libdpf_log_test_csv_" + std::to_string(::getpid());
|
||||
(void)::system(("rm -rf '" + dir + "'").c_str());
|
||||
file_log log("seed_source", dpf::log::level::info);
|
||||
dpf::experiment::master_seed master{};
|
||||
{
|
||||
dpf::experiment ex("fresh_one");
|
||||
master = ex.seed();
|
||||
EXPECT_FALSE(ex.seed_provided());
|
||||
ex.begin_timing();
|
||||
(void)dpf::prg::aes128::eval(simde_mm_set_epi64x(1, 2), 0);
|
||||
ex.end_timing();
|
||||
ex.write_csv(dir);
|
||||
}
|
||||
{
|
||||
auto again = dpf::experiment::replay("fresh_one", master);
|
||||
EXPECT_TRUE(again.seed_provided());
|
||||
again.set_run_id(1);
|
||||
again.write_csv(dir);
|
||||
}
|
||||
const auto seeds = with_event(log.lines(), "seed");
|
||||
ASSERT_EQ(seeds.size(), 2u);
|
||||
std::string hex;
|
||||
dpf::log::detail::append_hex(hex, master.data(), master.size());
|
||||
EXPECT_TRUE(has(seeds[0], " source=fresh")) << seeds[0];
|
||||
EXPECT_TRUE(has(seeds[0], " value=" + hex)) << seeds[0];
|
||||
EXPECT_TRUE(has(seeds[1], " source=provided")) << seeds[1];
|
||||
EXPECT_TRUE(has(seeds[1], " value=" + hex)) << seeds[1];
|
||||
const auto runs = read_lines(dir + "/runs.csv");
|
||||
ASSERT_EQ(runs.size(), 3u);
|
||||
EXPECT_EQ(runs[0], "name,party,run_id,invocation,seed_source,written_utc");
|
||||
EXPECT_TRUE(has(runs[1], "fresh_one,p0,0," + dpf::log::invocation_id() + ",fresh,"));
|
||||
EXPECT_TRUE(has(runs[2], "fresh_one,p0,1," + dpf::log::invocation_id() + ",provided,"));
|
||||
const auto sym = read_lines(dir + "/sym.csv");
|
||||
ASSERT_GE(sym.size(), 2u);
|
||||
EXPECT_EQ(sym[0], "name,party,run_id,purpose,primitive,blocks,invocation");
|
||||
EXPECT_EQ(sym[1], "fresh_one,p0,0,expand,aes128,1," + dpf::log::invocation_id());
|
||||
(void)::system(("rm -rf '" + dir + "'").c_str());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr std::uint32_t k_draw = 94201;
|
||||
|
||||
struct chain
|
||||
{
|
||||
dpf::protocol::plan plan;
|
||||
dpf::protocol::node x;
|
||||
dpf::protocol::node out;
|
||||
};
|
||||
|
||||
chain make_chain(std::size_t party, int steps)
|
||||
{
|
||||
dpf::protocol::composer c(party);
|
||||
chain ch;
|
||||
ch.x = c.input(dpf::protocol::domain::a, 8);
|
||||
auto cur = ch.x;
|
||||
for (int i = 0; i < steps; ++i)
|
||||
cur = c.compute(k_draw, {c.exchange(cur)}, dpf::protocol::domain::a, 8);
|
||||
ch.out = cur;
|
||||
ch.plan = c.schedule();
|
||||
return ch;
|
||||
}
|
||||
|
||||
struct draw_log
|
||||
{
|
||||
std::mutex mu;
|
||||
std::vector<std::uint64_t> values;
|
||||
std::size_t hooked = 0;
|
||||
};
|
||||
|
||||
/// Adds 1 to its input, draws one word, and runs `aes` AES blocks.
|
||||
dpf::protocol::kernel_fn draw_kernel(draw_log & log, int aes)
|
||||
{
|
||||
return [&log, aes](std::uint32_t, const std::vector<dpf::protocol::node> &,
|
||||
const std::vector<dpf::protocol::block_span> & inputs,
|
||||
dpf::protocol::block_span output, std::size_t lanes) {
|
||||
const bool hooked = dpf::detail::uniform_bytes_hook != nullptr;
|
||||
const auto r = dpf::uniform_sample<std::uint64_t>();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(log.mu);
|
||||
log.values.push_back(r);
|
||||
log.hooked += hooked ? 1 : 0;
|
||||
}
|
||||
auto blk = simde_mm_set_epi64x(1, 2);
|
||||
for (int i = 0; i < aes; ++i)
|
||||
blk = dpf::prg::aes128::eval(blk, static_cast<psnip_uint32_t>(i));
|
||||
asm volatile("" : "+x"(blk));
|
||||
for (std::size_t l = 0; l < lanes; ++l)
|
||||
{
|
||||
std::uint64_t v = 0;
|
||||
std::memcpy(&v, inputs[0].at(l), 8);
|
||||
++v;
|
||||
std::memcpy(output.at(l), &v, 8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct chain_run
|
||||
{
|
||||
std::vector<dpf::protocol::plan> plans;
|
||||
std::vector<dpf::app::party_values> inputs;
|
||||
};
|
||||
|
||||
chain_run two_party_chain(int steps)
|
||||
{
|
||||
chain_run r;
|
||||
for (std::size_t p = 0; p < 2; ++p)
|
||||
{
|
||||
auto ch = make_chain(p, steps);
|
||||
dpf::app::party_values v(ch.plan.nodes().size());
|
||||
v[ch.x.id].assign(8, 0);
|
||||
const std::uint64_t in = p == 0 ? 5 : 6;
|
||||
std::memcpy(v[ch.x.id].data(), &in, 8);
|
||||
r.plans.push_back(ch.plan);
|
||||
r.inputs.push_back(v);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> sorted_draws(draw_log & log)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(log.mu);
|
||||
auto v = log.values;
|
||||
log.values.clear();
|
||||
log.hooked = 0;
|
||||
std::sort(v.begin(), v.end());
|
||||
return v;
|
||||
}
|
||||
|
||||
void expect_replay(dpf::net::transport kind, std::size_t pool, std::size_t trials)
|
||||
{
|
||||
const auto run = two_party_chain(3);
|
||||
draw_log log;
|
||||
std::map<std::uint32_t, dpf::protocol::kernel_fn> k{{k_draw, draw_kernel(log, 0)}};
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = kind;
|
||||
cfg.compute_threads = pool;
|
||||
cfg.trials = trials;
|
||||
dpf::experiment first("replay");
|
||||
(void)dpf::app::exercise_parties(run.plans, run.inputs, k, &first, cfg);
|
||||
const std::size_t hooked = log.hooked;
|
||||
const auto a = sorted_draws(log);
|
||||
auto again = dpf::experiment::replay("replay", first.seed());
|
||||
(void)dpf::app::exercise_parties(run.plans, run.inputs, k, &again, cfg);
|
||||
const auto b = sorted_draws(log);
|
||||
ASSERT_EQ(a.size(), 6u * trials) << dpf::net::transport_name(kind) << " pool " << pool;
|
||||
EXPECT_EQ(hooked, a.size()) << dpf::net::transport_name(kind) << " pool " << pool;
|
||||
EXPECT_EQ(a, b) << dpf::net::transport_name(kind) << " pool " << pool;
|
||||
EXPECT_TRUE(first.has_seed_named("p0/master"));
|
||||
EXPECT_TRUE(first.has_seed_named("p1/master"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Replay, RecordedMasterReplaysEveryParty)
|
||||
{
|
||||
expect_replay(dpf::net::transport::async_memory, 0, 1);
|
||||
expect_replay(dpf::net::transport::memory_sink, 0, 1);
|
||||
expect_replay(dpf::net::transport::mux, 0, 3);
|
||||
}
|
||||
|
||||
TEST(Replay, ComputePoolKernelsDrawFromTheirPartysStream)
|
||||
{
|
||||
expect_replay(dpf::net::transport::async_memory, 2, 1);
|
||||
}
|
||||
|
||||
TEST(Replay, PartyStreamsDifferAndDependOnlyOnTheMaster)
|
||||
{
|
||||
const dpf::experiment::master_seed m{};
|
||||
auto a = dpf::experiment::replay("x", m);
|
||||
const auto p0 = a.derive_party(0).seed();
|
||||
const auto p1 = a.derive_party(1).seed();
|
||||
auto b = dpf::experiment::replay("other name", m);
|
||||
EXPECT_NE(p0, p1);
|
||||
EXPECT_EQ(b.derive_party(1).seed(), p1);
|
||||
EXPECT_EQ(b.derive_party(1).seed_origin(), dpf::experiment::origin::derived);
|
||||
}
|
||||
|
||||
TEST(Counting, ComputePoolWorkIsChargedToItsParty)
|
||||
{
|
||||
const auto run = two_party_chain(3);
|
||||
draw_log log;
|
||||
std::map<std::uint32_t, dpf::protocol::kernel_fn> k{{k_draw, draw_kernel(log, 20000)}};
|
||||
for (std::size_t pool : {std::size_t{0}, std::size_t{2}})
|
||||
{
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = dpf::net::transport::async_memory;
|
||||
cfg.compute_threads = pool;
|
||||
dpf::experiment ex("pool");
|
||||
(void)dpf::app::exercise_parties(run.plans, run.inputs, k, &ex, cfg);
|
||||
EXPECT_EQ(ex.prg_evals(), 3u * 20000u) << "pool " << pool;
|
||||
EXPECT_EQ(ex.random_bytes(), 3u * 8u) << "pool " << pool;
|
||||
EXPECT_GT(ex.cpu_ns(), ex.wall_ns() / 4) << "pool " << pool;
|
||||
(void)sorted_draws(log);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Bytes, ReceivedBytesMatchSentOnASymmetricChain)
|
||||
{
|
||||
const auto run = two_party_chain(3);
|
||||
draw_log log;
|
||||
std::map<std::uint32_t, dpf::protocol::kernel_fn> k{{k_draw, draw_kernel(log, 0)}};
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = dpf::net::transport::mux;
|
||||
dpf::experiment ex("bytes");
|
||||
(void)dpf::app::exercise_parties(run.plans, run.inputs, k, &ex, cfg);
|
||||
EXPECT_EQ(ex.bytes_out(), ex.plan_bytes_out());
|
||||
EXPECT_EQ(ex.bytes_in(), ex.bytes_out());
|
||||
EXPECT_EQ(ex.edge_bytes_in(dpf::protocol::edge_channel::peer),
|
||||
ex.edge_bytes_out(dpf::protocol::edge_channel::peer));
|
||||
ASSERT_EQ(ex.rounds().size(), 3u);
|
||||
for (const auto & r : ex.rounds())
|
||||
EXPECT_EQ(r.bytes_in, 8u) << "round " << r.round;
|
||||
}
|
||||
|
||||
TEST(Trials, EveryPartyIsTimedAndMediansReachSummary)
|
||||
{
|
||||
const auto run = two_party_chain(2);
|
||||
draw_log log;
|
||||
std::map<std::uint32_t, dpf::protocol::kernel_fn> k{{k_draw, draw_kernel(log, 0)}};
|
||||
dpf::app::run_config cfg;
|
||||
cfg.kind = dpf::net::transport::async_memory;
|
||||
cfg.warmup = 1;
|
||||
cfg.trials = 3;
|
||||
dpf::experiment ex("trials");
|
||||
(void)dpf::app::exercise_parties(run.plans, run.inputs, k, &ex, cfg);
|
||||
EXPECT_EQ(ex.trials().size(), 3u);
|
||||
EXPECT_GE(ex.slowest_median_ns(), ex.median_trial_ns());
|
||||
const std::string dir = "/tmp/libdpf_log_test_trials_" + std::to_string(::getpid());
|
||||
(void)::system(("rm -rf '" + dir + "'").c_str());
|
||||
ex.write_csv(dir);
|
||||
const auto trials = read_lines(dir + "/trials.csv");
|
||||
ASSERT_EQ(trials.size(), 1u + 3u * 2u);
|
||||
EXPECT_EQ(trials[0], "name,party,run_id,trial,wall_ns,invocation");
|
||||
EXPECT_EQ(trials[1].rfind("trials,p0,0,0,", 0), 0u) << trials[1];
|
||||
EXPECT_EQ(trials[2].rfind("trials,p1,0,0,", 0), 0u) << trials[2];
|
||||
const auto summary = read_lines(dir + "/summary.csv");
|
||||
ASSERT_EQ(summary.size(), 2u);
|
||||
EXPECT_TRUE(has(summary[0], ",median_ns,slowest_median_ns,trials,invocation"));
|
||||
EXPECT_TRUE(has(summary[1], "," + std::to_string(ex.median_trial_ns()) + ","
|
||||
+ std::to_string(ex.slowest_median_ns()) + ",3," + dpf::log::invocation_id()));
|
||||
(void)::system(("rm -rf '" + dir + "'").c_str());
|
||||
}
|
||||
|
||||
TEST(Csv, ChangedHeaderMovesTheOldFileAside)
|
||||
{
|
||||
const std::string dir = "/tmp/libdpf_log_test_rotate_" + std::to_string(::getpid());
|
||||
(void)::system(("rm -rf '" + dir + "' && mkdir -p '" + dir + "'").c_str());
|
||||
{
|
||||
std::ofstream old(dir + "/summary.csv");
|
||||
old << "name,party,run_id,wall_ns\nold,p0,0,1\n";
|
||||
}
|
||||
file_log log("rotate", dpf::log::level::warning);
|
||||
dpf::experiment ex("rotate");
|
||||
ex.write_csv(dir);
|
||||
const auto now = read_lines(dir + "/summary.csv");
|
||||
ASSERT_EQ(now.size(), 2u);
|
||||
EXPECT_TRUE(has(now[0], ",invocation"));
|
||||
std::size_t moved = 0;
|
||||
for (const auto & e : std::filesystem::directory_iterator(dir))
|
||||
if (e.path().filename().string().rfind("summary.before-", 0) == 0)
|
||||
{
|
||||
++moved;
|
||||
EXPECT_EQ(read_lines(e.path().string()).at(1), "old,p0,0,1");
|
||||
}
|
||||
EXPECT_EQ(moved, 1u);
|
||||
EXPECT_EQ(with_event(log.lines(), "csv.moved").size(), 1u);
|
||||
(void)::system(("rm -rf '" + dir + "'").c_str());
|
||||
}
|
||||
|
||||
TEST(Gate, AllPartiesStartTogetherAndAFailureReleasesTheRest)
|
||||
{
|
||||
{
|
||||
dpf::app::detail::start_gate gate(3);
|
||||
std::atomic<int> through{0};
|
||||
std::vector<std::thread> ts;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
ts.emplace_back([&] { through += gate.arrive_and_wait() ? 1 : 0; });
|
||||
for (auto & t : ts)
|
||||
t.join();
|
||||
EXPECT_EQ(through.load(), 3);
|
||||
}
|
||||
{
|
||||
dpf::app::detail::start_gate gate(3);
|
||||
std::atomic<int> through{0};
|
||||
std::vector<std::thread> ts;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
ts.emplace_back([&] { through += gate.arrive_and_wait() ? 1 : 0; });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
gate.fail();
|
||||
for (auto & t : ts)
|
||||
t.join();
|
||||
EXPECT_EQ(through.load(), 0);
|
||||
}
|
||||
}
|
||||
472
test/tests/lut_union_test.cpp
Normal file
472
test/tests/lut_union_test.cpp
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dpf/compose.hpp"
|
||||
#include "dpf/verifiable.hpp"
|
||||
#include "grotto/lut_union.hpp"
|
||||
|
||||
#include "simde/simde/x86/sse2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using grotto::offset_horner_group_add;
|
||||
using grotto::offset_horner_group_sub;
|
||||
|
||||
grotto::piecewise_lut<uint8_t> lut_a()
|
||||
{
|
||||
grotto::piecewise_lut<uint8_t> lut;
|
||||
lut.knots = {0, 10, 50};
|
||||
lut.coeff = {{1, 0}, {0, 2}, {7, 1}};
|
||||
return lut;
|
||||
}
|
||||
|
||||
grotto::piecewise_lut<uint8_t> lut_b()
|
||||
{
|
||||
grotto::piecewise_lut<uint8_t> lut;
|
||||
lut.knots = {0, 4, 12, 80};
|
||||
lut.coeff = {{3, 0, 0}, {1, 0, 1}, {9, 2, 0}, {4, 1, 0}};
|
||||
return lut;
|
||||
}
|
||||
|
||||
grotto::piecewise_lut<uint8_t> lut_c()
|
||||
{
|
||||
grotto::piecewise_lut<uint8_t> lut;
|
||||
lut.knots = {0, 7, 90};
|
||||
lut.coeff = {{8, 1, 0}, {2, 0, 3}, {1, 1, 1}};
|
||||
return lut;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> open_union(const grotto::offset_poly_keys<uint8_t> & mat,
|
||||
const grotto::lut_union_plan<uint8_t> & plan)
|
||||
{
|
||||
const auto s0 = grotto::lut_union_eval<0>(mat, plan);
|
||||
const auto s1 = grotto::lut_union_eval<1>(mat, plan);
|
||||
EXPECT_EQ(s0.size(), s1.size());
|
||||
std::vector<uint64_t> out(s0.size());
|
||||
for (std::size_t i = 0; i < s0.size(); ++i)
|
||||
out[i] = s0[i] + s1[i];
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename InputT>
|
||||
void expect_partition(const grotto::lut_union_plan<InputT> & plan)
|
||||
{
|
||||
if (plan.knots.empty())
|
||||
{
|
||||
ADD_FAILURE() << "empty union";
|
||||
return;
|
||||
}
|
||||
for (const auto & func : plan.funcs)
|
||||
{
|
||||
std::vector<int> seen(plan.knots.size(), 0);
|
||||
for (const auto & span : func)
|
||||
{
|
||||
auto mark = [&](std::size_t j) {
|
||||
if (j >= seen.size())
|
||||
{
|
||||
ADD_FAILURE() << "span index " << j;
|
||||
return;
|
||||
}
|
||||
seen[j] += 1;
|
||||
};
|
||||
if (span.begin <= span.end)
|
||||
{
|
||||
for (std::size_t j = span.begin; j < span.end; ++j)
|
||||
mark(j);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t j = span.begin; j < plan.knots.size(); ++j)
|
||||
mark(j);
|
||||
for (std::size_t j = 0; j < span.end; ++j)
|
||||
mark(j);
|
||||
}
|
||||
}
|
||||
for (int bit : seen)
|
||||
EXPECT_EQ(bit, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(LutUnion, WideDomainMapsTheWrapAcrossTheFront)
|
||||
{
|
||||
grotto::piecewise_lut<uint64_t> a;
|
||||
a.knots = {10, 30};
|
||||
a.coeff = {{1}, {2}};
|
||||
grotto::piecewise_lut<uint64_t> b;
|
||||
b.knots = {0, 20};
|
||||
b.coeff = {{3}, {4}};
|
||||
const auto plan = grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<uint64_t>>{a, b}, uint64_t{0});
|
||||
|
||||
ASSERT_EQ(plan.knots, (std::vector<uint64_t>{0, 10, 20, 30}));
|
||||
EXPECT_EQ(plan.comparisons, 1u);
|
||||
EXPECT_EQ(plan.prefix_walks, 1u);
|
||||
EXPECT_EQ(plan.depth, 64u);
|
||||
EXPECT_EQ(plan.geneval_rounds(), plan.depth);
|
||||
EXPECT_EQ(plan.degree, 0u);
|
||||
|
||||
ASSERT_EQ(plan.funcs[0].size(), 2u);
|
||||
EXPECT_EQ(plan.funcs[0][0].begin, 1u);
|
||||
EXPECT_EQ(plan.funcs[0][0].end, 3u);
|
||||
EXPECT_EQ(plan.funcs[0][1].begin, 3u);
|
||||
EXPECT_EQ(plan.funcs[0][1].end, 1u);
|
||||
|
||||
ASSERT_EQ(plan.funcs[1].size(), 2u);
|
||||
EXPECT_EQ(plan.funcs[1][0].begin, 0u);
|
||||
EXPECT_EQ(plan.funcs[1][0].end, 2u);
|
||||
EXPECT_EQ(plan.funcs[1][1].begin, 2u);
|
||||
EXPECT_EQ(plan.funcs[1][1].end, 0u);
|
||||
expect_partition(plan);
|
||||
}
|
||||
|
||||
TEST(LutUnion, OneWalkMatchesEachLutOnItsOwnKnots)
|
||||
{
|
||||
const auto a = lut_a();
|
||||
const auto b = lut_b();
|
||||
const uint8_t center = 12;
|
||||
const auto plan = grotto::make_lut_union_plan({a, b}, uint8_t{3});
|
||||
EXPECT_GT(plan.endpoints(), a.knots.size());
|
||||
EXPECT_EQ(plan.degree, 2u);
|
||||
EXPECT_EQ(plan.lanes, 3u);
|
||||
EXPECT_EQ(plan.depth, 8u);
|
||||
EXPECT_EQ(plan.comparisons, 1u);
|
||||
EXPECT_EQ(plan.prefix_walks, 1u);
|
||||
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, plan.degree);
|
||||
for (int eta = 0; eta < 256; eta += 17)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const auto here = grotto::make_lut_union_plan({a, b}, e);
|
||||
const auto got = open_union(mat, here);
|
||||
ASSERT_EQ(got.size(), 2u);
|
||||
EXPECT_EQ(got[0], grotto::offset_poly_clear<uint8_t>(center, a.knots, a.coeff, e))
|
||||
<< eta;
|
||||
EXPECT_EQ(got[1], grotto::offset_poly_clear<uint8_t>(center, b.knots, b.coeff, e))
|
||||
<< eta;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LutUnion, GenevalPlanIsOneComparisonHoweverManyLuts)
|
||||
{
|
||||
const uint8_t eta = 3;
|
||||
const auto few = grotto::make_lut_union_plan({lut_a(), lut_b()}, eta);
|
||||
const auto many = grotto::make_lut_union_plan({lut_a(), lut_b(), lut_c()}, eta);
|
||||
EXPECT_GT(many.endpoints(), few.endpoints());
|
||||
EXPECT_EQ(few.geneval_rounds(), many.geneval_rounds());
|
||||
EXPECT_EQ(few.funcs.size(), 2u);
|
||||
EXPECT_EQ(many.funcs.size(), 3u);
|
||||
|
||||
dpf::protocol::composer c0(0);
|
||||
grotto::schedule_lut_union(c0, few);
|
||||
dpf::protocol::composer c1(0);
|
||||
grotto::schedule_lut_union(c1, many);
|
||||
const auto p0 = c0.default_plan();
|
||||
const auto p1 = c1.default_plan();
|
||||
EXPECT_EQ(p0.rounds(), few.depth);
|
||||
EXPECT_EQ(p1.rounds(), many.depth);
|
||||
EXPECT_EQ(p0.rounds(), p1.rounds());
|
||||
EXPECT_EQ(p0.slot_bytes(0), grotto::lut_union_slot_bytes(few.lanes));
|
||||
EXPECT_EQ(p1.slot_bytes(0), grotto::lut_union_slot_bytes(many.lanes));
|
||||
}
|
||||
|
||||
TEST(LutUnion, GenevalXorAndAdditiveSharesMatchTheClearLuts)
|
||||
{
|
||||
const uint8_t center = 12;
|
||||
const uint8_t eta = 3;
|
||||
const auto a = lut_a();
|
||||
const auto b = lut_b();
|
||||
const auto plan = grotto::make_lut_union_plan({a, b}, eta);
|
||||
const uint8_t share = 0x3c;
|
||||
const uint8_t other = static_cast<uint8_t>(center ^ share);
|
||||
|
||||
const auto xor_got = grotto::geneval_lut_union(share, other, plan);
|
||||
EXPECT_EQ(xor_got.eta, eta);
|
||||
ASSERT_EQ(xor_got.value0.size(), 2u);
|
||||
EXPECT_EQ(xor_got.value0[0] + xor_got.value1[0],
|
||||
grotto::offset_poly_clear<uint8_t>(center, a.knots, a.coeff, eta));
|
||||
EXPECT_EQ(xor_got.value0[1] + xor_got.value1[1],
|
||||
grotto::offset_poly_clear<uint8_t>(center, b.knots, b.coeff, eta));
|
||||
|
||||
const uint8_t c0 = 5;
|
||||
const uint8_t c1 = offset_horner_group_sub(center, c0);
|
||||
const auto add_got = grotto::geneval_lut_union(dpf::arith_input, c0, c1, plan);
|
||||
EXPECT_EQ(add_got.value0[0] + add_got.value1[0], xor_got.value0[0] + xor_got.value1[0]);
|
||||
EXPECT_EQ(add_got.value0[1] + add_got.value1[1], xor_got.value0[1] + xor_got.value1[1]);
|
||||
}
|
||||
|
||||
TEST(LutUnion, EasyAndConstantLutsShareTheUnion)
|
||||
{
|
||||
const auto relu = grotto::piecewise_from_easy(grotto::make_relu_lut<int8_t>());
|
||||
const auto clip = grotto::piecewise_from_easy(
|
||||
grotto::make_clip_lut<int8_t>(0, -2, 3));
|
||||
grotto::constant_lut<int8_t> sign;
|
||||
sign.bounds = {std::numeric_limits<int8_t>::min(), 0};
|
||||
sign.values = {-1, 1};
|
||||
const auto step = grotto::piecewise_from_constant(sign);
|
||||
|
||||
const int8_t center = -20;
|
||||
const int8_t eta = 15;
|
||||
const auto plan = grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<int8_t>>{relu, clip, step}, eta);
|
||||
EXPECT_EQ(plan.comparisons, 1u);
|
||||
EXPECT_EQ(plan.prefix_walks, 1u);
|
||||
EXPECT_GT(plan.endpoints(), relu.knots.size());
|
||||
|
||||
const auto mat = grotto::make_offset_poly_keys<int8_t>(center, plan.degree);
|
||||
const auto s0 = grotto::lut_union_eval<0>(mat, plan);
|
||||
const auto s1 = grotto::lut_union_eval<1>(mat, plan);
|
||||
const int8_t wrapped = offset_horner_group_add(center, eta);
|
||||
const uint64_t opened[3] = {s0[0] + s1[0], s0[1] + s1[1], s0[2] + s1[2]};
|
||||
EXPECT_EQ(opened[0], static_cast<uint64_t>(grotto::make_relu_lut<int8_t>()(wrapped)));
|
||||
EXPECT_EQ(opened[1], static_cast<uint64_t>(grotto::make_clip_lut<int8_t>(0, -2, 3)(wrapped)));
|
||||
EXPECT_EQ(opened[2], static_cast<uint64_t>(sign(wrapped)));
|
||||
EXPECT_EQ(opened[0], grotto::offset_poly_clear<int8_t>(center, relu.knots, relu.coeff, eta));
|
||||
}
|
||||
|
||||
TEST(LutUnion, RejectsARoundingDenominatorAndANarrowKey)
|
||||
{
|
||||
EXPECT_THROW(grotto::piecewise_from_easy(grotto::make_leaky_relu_lut<int8_t>(1)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<uint8_t>>{}, uint8_t{0}),
|
||||
std::invalid_argument);
|
||||
|
||||
const auto plan = grotto::make_lut_union_plan({lut_b()}, uint8_t{1});
|
||||
const auto narrow = grotto::make_offset_poly_keys<uint8_t>(4, 0);
|
||||
EXPECT_THROW(grotto::lut_union_eval<0>(narrow, plan), std::invalid_argument);
|
||||
|
||||
grotto::piecewise_lut<uint8_t> unsorted;
|
||||
unsorted.knots = {0, 5, 3};
|
||||
unsorted.coeff = {{1}, {1}, {1}};
|
||||
EXPECT_THROW(grotto::make_lut_union_plan({unsorted}, uint8_t{0}), std::invalid_argument);
|
||||
|
||||
grotto::piecewise_lut<uint8_t> ragged;
|
||||
ragged.knots = {0, 1};
|
||||
ragged.coeff = {{1}, {1, 2}};
|
||||
EXPECT_THROW(grotto::make_lut_union_plan({ragged}, uint8_t{0}), std::invalid_argument);
|
||||
|
||||
grotto::piecewise_lut<uint8_t> empty_row;
|
||||
empty_row.knots = {0};
|
||||
empty_row.coeff = {{}};
|
||||
EXPECT_THROW(grotto::make_lut_union_plan({empty_row}, uint8_t{0}), std::invalid_argument);
|
||||
|
||||
grotto::piecewise_lut<uint8_t> too_wide;
|
||||
too_wide.knots = {0};
|
||||
too_wide.coeff = {std::vector<uint64_t>(grotto::offset_poly_max_degree + 2, 1)};
|
||||
EXPECT_THROW(grotto::make_lut_union_plan({too_wide}, uint8_t{0}), std::invalid_argument);
|
||||
|
||||
grotto::easy_lut<int8_t> broken;
|
||||
broken.bounds = {0};
|
||||
broken.c0 = {0};
|
||||
EXPECT_THROW(grotto::piecewise_from_easy(broken), std::invalid_argument);
|
||||
|
||||
grotto::constant_lut<int8_t> bare;
|
||||
bare.bounds = {std::numeric_limits<int8_t>::min()};
|
||||
EXPECT_THROW(grotto::piecewise_from_constant(bare), std::invalid_argument);
|
||||
|
||||
dpf::protocol::composer composer(0);
|
||||
EXPECT_THROW(grotto::schedule_lut_union(composer, grotto::lut_union_plan<uint8_t>{}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(LutUnion, CoarsePieceCoversSeveralUnionKnots)
|
||||
{
|
||||
const auto plan = grotto::make_lut_union_plan({lut_a(), lut_b()}, uint8_t{0});
|
||||
expect_partition(plan);
|
||||
ASSERT_EQ(plan.knots, (std::vector<uint8_t>{0, 4, 10, 12, 50, 80}));
|
||||
const auto & first = plan.funcs[0][0];
|
||||
EXPECT_EQ(first.end - first.begin, 2u);
|
||||
EXPECT_EQ(first.kappa, 0);
|
||||
EXPECT_EQ(first.coeff, (std::vector<uint64_t>{1, 0}));
|
||||
|
||||
const uint8_t center = 200;
|
||||
const uint8_t eta = 100;
|
||||
const auto carried = grotto::make_lut_union_plan({lut_a(), lut_b()}, eta);
|
||||
expect_partition(carried);
|
||||
EXPECT_NE(std::find(carried.knots.begin(), carried.knots.end(), uint8_t{156}),
|
||||
carried.knots.end());
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, carried.degree);
|
||||
const auto got = open_union(mat, carried);
|
||||
EXPECT_EQ(got[0], grotto::offset_poly_clear<uint8_t>(center, lut_a().knots, lut_a().coeff, eta));
|
||||
EXPECT_EQ(got[1], grotto::offset_poly_clear<uint8_t>(center, lut_b().knots, lut_b().coeff, eta));
|
||||
}
|
||||
|
||||
TEST(LutUnion, EveryEtaMatchesASeparatePolynomialEval)
|
||||
{
|
||||
const auto a = lut_a();
|
||||
const auto b = lut_b();
|
||||
const uint8_t center = 12;
|
||||
const auto probe = grotto::make_lut_union_plan({a, b}, uint8_t{0});
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, probe.degree);
|
||||
const auto wide = grotto::make_offset_poly_keys<uint8_t>(center, probe.degree + 2);
|
||||
const auto alone_a = grotto::make_offset_poly_keys<uint8_t>(center, 1);
|
||||
const auto alone_b = grotto::make_offset_poly_keys<uint8_t>(center, 2);
|
||||
for (int eta = 0; eta < 256; ++eta)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const auto plan = grotto::make_lut_union_plan({a, b}, e);
|
||||
expect_partition(plan);
|
||||
const auto got = open_union(mat, plan);
|
||||
const auto again = open_union(mat, plan);
|
||||
EXPECT_EQ(got, again);
|
||||
EXPECT_EQ(got, open_union(wide, plan));
|
||||
const uint64_t a_alone = grotto::offset_poly_eval<0>(alone_a, a.knots, a.coeff, e)
|
||||
+ grotto::offset_poly_eval<1>(alone_a, a.knots, a.coeff, e);
|
||||
const uint64_t b_alone = grotto::offset_poly_eval<0>(alone_b, b.knots, b.coeff, e)
|
||||
+ grotto::offset_poly_eval<1>(alone_b, b.knots, b.coeff, e);
|
||||
EXPECT_EQ(got[0], a_alone) << eta;
|
||||
EXPECT_EQ(got[1], b_alone) << eta;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LutUnion, OnePieceLutCoversTheWholeUnion)
|
||||
{
|
||||
grotto::piecewise_lut<uint8_t> whole;
|
||||
whole.knots = {0};
|
||||
whole.coeff = {{5, 1}};
|
||||
const auto plan = grotto::make_lut_union_plan({whole, lut_a()}, uint8_t{0});
|
||||
expect_partition(plan);
|
||||
ASSERT_EQ(plan.funcs[0].size(), 1u);
|
||||
EXPECT_EQ(plan.funcs[0][0].begin, 0u);
|
||||
EXPECT_EQ(plan.funcs[0][0].end, plan.knots.size());
|
||||
|
||||
// A nonzero eta inserts the carry cut, so the one public knot becomes two
|
||||
// refined pieces. They still partition the union.
|
||||
const uint8_t eta = 9;
|
||||
const auto split = grotto::make_lut_union_plan({whole, lut_a()}, eta);
|
||||
expect_partition(split);
|
||||
EXPECT_GT(split.funcs[0].size(), 1u);
|
||||
const uint8_t center = 40;
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, split.degree);
|
||||
const auto got = open_union(mat, split);
|
||||
EXPECT_EQ(got[0], grotto::offset_poly_clear<uint8_t>(center, whole.knots, whole.coeff, eta));
|
||||
EXPECT_EQ(got[1], grotto::offset_poly_clear<uint8_t>(center, lut_a().knots, lut_a().coeff, eta));
|
||||
}
|
||||
|
||||
TEST(LutUnion, IdenticalKnotsStayASingleCopy)
|
||||
{
|
||||
const auto a = lut_a();
|
||||
const auto plan = grotto::make_lut_union_plan({a, a}, uint8_t{0});
|
||||
EXPECT_EQ(plan.knots, a.knots);
|
||||
EXPECT_EQ(plan.funcs.size(), 2u);
|
||||
EXPECT_EQ(plan.prefix_walks, 1u);
|
||||
expect_partition(plan);
|
||||
}
|
||||
|
||||
TEST(LutUnion, VerifiableTokensCoverEveryLane)
|
||||
{
|
||||
const auto a = lut_a();
|
||||
const auto b = lut_b();
|
||||
const uint8_t center = 12;
|
||||
const uint8_t eta = 5;
|
||||
const auto plan = grotto::make_lut_union_plan({a, b}, eta);
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, plan.degree, dpf::verifiable{});
|
||||
std::vector<dpf::proof_token> tok0(plan.lanes), tok1(plan.lanes);
|
||||
const auto s0 = grotto::lut_union_eval<0>(mat, plan, tok0.data());
|
||||
const auto s1 = grotto::lut_union_eval<1>(mat, plan, tok1.data());
|
||||
EXPECT_EQ(s0[0] + s1[0], grotto::offset_poly_clear<uint8_t>(center, a.knots, a.coeff, eta));
|
||||
EXPECT_EQ(s0[1] + s1[1], grotto::offset_poly_clear<uint8_t>(center, b.knots, b.coeff, eta));
|
||||
for (std::size_t m = 0; m < plan.lanes; ++m)
|
||||
EXPECT_TRUE(dpf::verify(tok0[m], tok1[m])) << m;
|
||||
tok0[0][0] = simde_mm_xor_si128(tok0[0][0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(tok0[0], tok1[0]));
|
||||
EXPECT_TRUE(dpf::verify(tok0[1], tok1[1]));
|
||||
}
|
||||
|
||||
TEST(LutUnion, ScheduleOnAnExistingSeedStaysOneWalk)
|
||||
{
|
||||
const auto few = grotto::make_lut_union_plan({lut_a()}, uint8_t{1});
|
||||
const auto many = grotto::make_lut_union_plan({lut_a(), lut_b(), lut_c()}, uint8_t{1});
|
||||
EXPECT_EQ(few.lanes, 2u);
|
||||
EXPECT_EQ(grotto::lut_union_slot_bytes(1), 16u);
|
||||
EXPECT_EQ(grotto::lut_union_slot_bytes(few.lanes), 16u);
|
||||
EXPECT_EQ(grotto::lut_union_slot_bytes(many.lanes), 24u);
|
||||
|
||||
dpf::protocol::composer composer(0);
|
||||
auto seed = composer.input(dpf::protocol::domain::fss, 16);
|
||||
grotto::schedule_lut_union(composer, seed, many);
|
||||
const auto scheduled = composer.default_plan();
|
||||
ASSERT_EQ(scheduled.rounds(), many.depth);
|
||||
for (std::uint16_t round = 0; round < scheduled.rounds(); ++round)
|
||||
EXPECT_EQ(scheduled.slot_bytes(round), grotto::lut_union_slot_bytes(many.lanes));
|
||||
}
|
||||
|
||||
TEST(LutUnion, SignedDomainAndRealTables)
|
||||
{
|
||||
const auto relu = grotto::piecewise_from_easy(grotto::make_relu_lut<int8_t>());
|
||||
const auto abs_lut = grotto::piecewise_from_easy(grotto::make_abs_lut<int8_t>());
|
||||
const auto square = grotto::piecewise_from_easy(grotto::make_squared_relu_lut<int8_t>(0));
|
||||
for (const auto & row : relu.coeff)
|
||||
EXPECT_EQ(row.size(), 2u);
|
||||
for (const auto & row : abs_lut.coeff)
|
||||
EXPECT_EQ(row.size(), 2u);
|
||||
for (const auto & row : square.coeff)
|
||||
EXPECT_EQ(row.size(), 3u);
|
||||
|
||||
const int8_t center = -20;
|
||||
const auto probe = grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<int8_t>>{relu, abs_lut, square}, int8_t{0});
|
||||
EXPECT_EQ(probe.degree, 2u);
|
||||
EXPECT_EQ(probe.depth, 8u);
|
||||
const auto mat = grotto::make_offset_poly_keys<int8_t>(center, probe.degree);
|
||||
const auto relu_f = grotto::make_relu_lut<int8_t>();
|
||||
const auto abs_f = grotto::make_abs_lut<int8_t>();
|
||||
const auto sq_f = grotto::make_squared_relu_lut<int8_t>(0);
|
||||
for (int eta = -128; eta < 128; eta += 7)
|
||||
{
|
||||
const auto e = static_cast<int8_t>(eta);
|
||||
const auto plan = grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<int8_t>>{relu, abs_lut, square}, e);
|
||||
expect_partition(plan);
|
||||
const auto s0 = grotto::lut_union_eval<0>(mat, plan);
|
||||
const auto s1 = grotto::lut_union_eval<1>(mat, plan);
|
||||
const int8_t wrapped = offset_horner_group_add(center, e);
|
||||
EXPECT_EQ(s0[0] + s1[0], static_cast<uint64_t>(relu_f(wrapped))) << eta;
|
||||
EXPECT_EQ(s0[1] + s1[1], static_cast<uint64_t>(abs_f(wrapped))) << eta;
|
||||
EXPECT_EQ(s0[2] + s1[2], static_cast<uint64_t>(sq_f(wrapped))) << eta;
|
||||
}
|
||||
|
||||
const int8_t share = 3;
|
||||
const int8_t other = static_cast<int8_t>(center ^ share);
|
||||
const auto plan = grotto::make_lut_union_plan(
|
||||
std::vector<grotto::piecewise_lut<int8_t>>{relu, abs_lut, square}, int8_t{-3});
|
||||
const auto got = grotto::geneval_lut_union(share, other, plan);
|
||||
const int8_t wrapped = offset_horner_group_add(center, int8_t{-3});
|
||||
EXPECT_EQ(got.value0[0] + got.value1[0], static_cast<uint64_t>(relu_f(wrapped)));
|
||||
EXPECT_EQ(got.value0[1] + got.value1[1], static_cast<uint64_t>(abs_f(wrapped)));
|
||||
EXPECT_EQ(got.value0[2] + got.value1[2], static_cast<uint64_t>(sq_f(wrapped)));
|
||||
}
|
||||
|
||||
TEST(LutUnion, SixteenBitDomainKeepsOneComparison)
|
||||
{
|
||||
grotto::piecewise_lut<uint16_t> left;
|
||||
left.knots = {0, 1000};
|
||||
left.coeff = {{3, 1}, {8, 0}};
|
||||
grotto::piecewise_lut<uint16_t> right;
|
||||
right.knots = {0, 400, 2000};
|
||||
right.coeff = {{1, 0, 2}, {9, 1, 0}, {4, 0, 1}};
|
||||
const uint16_t center = 50;
|
||||
const uint16_t eta = 40000;
|
||||
const auto plan = grotto::make_lut_union_plan({left, right}, eta);
|
||||
EXPECT_EQ(plan.depth, 16u);
|
||||
EXPECT_EQ(plan.geneval_rounds(), 16u);
|
||||
EXPECT_EQ(plan.comparisons, 1u);
|
||||
expect_partition(plan);
|
||||
const auto mat = grotto::make_offset_poly_keys<uint16_t>(center, plan.degree);
|
||||
const auto s0 = grotto::lut_union_eval<0>(mat, plan);
|
||||
const auto s1 = grotto::lut_union_eval<1>(mat, plan);
|
||||
EXPECT_EQ(s0[0] + s1[0], grotto::offset_poly_clear<uint16_t>(center, left.knots, left.coeff, eta));
|
||||
EXPECT_EQ(s0[1] + s1[1], grotto::offset_poly_clear<uint16_t>(center, right.knots, right.coeff, eta));
|
||||
|
||||
dpf::protocol::composer composer(0);
|
||||
grotto::schedule_lut_union(composer, plan);
|
||||
EXPECT_EQ(composer.default_plan().rounds(), 16u);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
|
@ -36,7 +37,7 @@ TEST(Multipoint, PlainPointsAndZeros)
|
|||
EXPECT_FALSE(decltype(k0)::is_verifiable);
|
||||
EXPECT_TRUE(decltype(k0)::is_multipoint);
|
||||
EXPECT_EQ(k0.bucket_count, k1.bucket_count);
|
||||
EXPECT_EQ(k0.bucket_domain, k1.bucket_domain);
|
||||
EXPECT_TRUE(k0.bucket_domain == k1.bucket_domain);
|
||||
EXPECT_GT(k0.bucket_count, alphas.size());
|
||||
|
||||
expect_points(k0, k1, alphas, betas);
|
||||
|
|
@ -93,8 +94,14 @@ TEST(Multipoint, TamperedBucketRejects)
|
|||
const std::vector<std::uint64_t> betas{1, 1, 1, 1};
|
||||
auto [k0, k1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{});
|
||||
|
||||
auto & cs = const_cast<dpf::cs_block &>(k0.buckets[0].correction_seeds()[0]);
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
// A single level-0 seed is invisible when that party's root control bit is 0.
|
||||
ASSERT_FALSE(k0.buckets.empty());
|
||||
for (auto & bucket : k0.buckets)
|
||||
{
|
||||
using arr = typename std::decay_t<decltype(bucket)>::correction_seeds_array;
|
||||
for (auto & cs : const_cast<arr &>(bucket.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
}
|
||||
|
||||
dpf::proof_token a0{}, a1{};
|
||||
dpf::audit_multipoint(k0, dpf::prove(a0));
|
||||
|
|
@ -152,29 +159,92 @@ TEST(Multipoint, RejectsAnEmptyListAndALengthMismatch)
|
|||
{
|
||||
const std::vector<std::uint8_t> alphas{1, 2};
|
||||
const std::vector<std::uint64_t> betas{1};
|
||||
EXPECT_THROW(
|
||||
{
|
||||
EXPECT_THROW({
|
||||
auto keys = dpf::make_multipoint(alphas, betas);
|
||||
(void)keys;
|
||||
},
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(
|
||||
{
|
||||
}, std::invalid_argument);
|
||||
EXPECT_THROW({
|
||||
auto keys = dpf::make_multipoint(
|
||||
std::vector<std::uint8_t>{}, std::vector<std::uint64_t>{});
|
||||
(void)keys;
|
||||
},
|
||||
std::invalid_argument);
|
||||
}, std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Multipoint, FullDomainProofsRejectABucketWordFlip)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const std::vector<Input> alphas{1, 9, 40};
|
||||
const std::vector<std::uint64_t> betas{7, 11, 3};
|
||||
auto [k0, k1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{});
|
||||
int mismatches = 0;
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto y = dpf::reconstruct(
|
||||
dpf::eval_multipoint(k0, q, dpf::prove(a)),
|
||||
dpf::eval_multipoint(k1, q, dpf::prove(b)));
|
||||
std::uint64_t want = 0;
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
if (alphas[i] == q)
|
||||
want = betas[i];
|
||||
EXPECT_EQ(y, want) << int(q);
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << int(q);
|
||||
}
|
||||
for (auto & word : const_cast<typename std::decay_t<decltype(k0.buckets[0])>::correction_words_array &>(
|
||||
k0.buckets[0].correction_words()))
|
||||
word = simde_mm_xor_si128(word, simde_mm_set1_epi8(0x3c));
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0.buckets[0])>::correction_seeds_array &>(
|
||||
k0.buckets[0].correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
const auto y = dpf::reconstruct(dpf::eval_multipoint(k0, q),
|
||||
dpf::eval_multipoint(k1, q));
|
||||
std::uint64_t want = 0;
|
||||
for (std::size_t i = 0; i < alphas.size(); ++i)
|
||||
if (alphas[i] == q)
|
||||
want = betas[i];
|
||||
if (y != want)
|
||||
++mismatches;
|
||||
}
|
||||
EXPECT_GT(mismatches, 0);
|
||||
dpf::proof_token a0{}, a1{};
|
||||
dpf::audit_multipoint(k0, dpf::prove(a0));
|
||||
dpf::audit_multipoint(k1, dpf::prove(a1));
|
||||
EXPECT_FALSE(dpf::verify(a0, a1));
|
||||
}
|
||||
|
||||
TEST(Multipoint, WideDomain)
|
||||
{
|
||||
using Input = std::uint64_t;
|
||||
const std::vector<Input> alphas{
|
||||
0x100000001ull,
|
||||
0x8000000000000001ull,
|
||||
42ull,
|
||||
0xfffffffffffffffeull};
|
||||
const std::vector<std::uint64_t> betas{9, 8, 7, 6};
|
||||
auto [k0, k1] = dpf::make_multipoint(alphas, betas);
|
||||
expect_points(k0, k1, alphas, betas);
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::eval_multipoint(k0, Input{1}),
|
||||
dpf::eval_multipoint(k1, Input{1})),
|
||||
0u);
|
||||
|
||||
auto [v0, v1] = dpf::make_multipoint(alphas, betas, dpf::verifiable{});
|
||||
dpf::proof_token p0{}, p1{};
|
||||
const auto y0 = dpf::eval_multipoint(v0, alphas[1], dpf::prove(p0));
|
||||
const auto y1 = dpf::eval_multipoint(v1, alphas[1], dpf::prove(p1));
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), betas[1]);
|
||||
EXPECT_TRUE(dpf::verify(p0, p1));
|
||||
}
|
||||
|
||||
TEST(Multipoint, RejectsDuplicates)
|
||||
{
|
||||
const std::vector<std::uint8_t> alphas{1, 2, 1, 4};
|
||||
const std::vector<std::uint64_t> betas{1, 2, 3, 4};
|
||||
EXPECT_THROW(
|
||||
{
|
||||
EXPECT_THROW({
|
||||
auto keys = dpf::make_multipoint(alphas, betas);
|
||||
(void)keys;
|
||||
},
|
||||
std::invalid_argument);
|
||||
}, std::invalid_argument);
|
||||
}
|
||||
|
|
|
|||
1092
test/tests/net_control_test.cpp
Normal file
1092
test/tests/net_control_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/nmod.hpp"
|
||||
|
||||
|
|
@ -164,8 +165,7 @@ TEST(Nmod, RejectsAZeroReciprocalAndAHugeQuotient)
|
|||
EXPECT_THROW(grotto::nmod(1, 0, 0, 0, 4), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::nmod(1, 0, 1, 0, 64), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::nmod_pow2(1, 0, 128, 4), std::overflow_error);
|
||||
EXPECT_THROW(grotto::nmod(INT64_MAX, 0, u128{1} << 80, 0, 4),
|
||||
std::overflow_error);
|
||||
EXPECT_THROW(grotto::nmod(INT64_MAX, 0, u128{1} << 80, 0, 4), std::overflow_error);
|
||||
}
|
||||
|
||||
TEST(Nmod, ScalePastTheProductWindow)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/offset_horner.hpp"
|
||||
|
|
@ -445,23 +446,17 @@ TEST(OffsetHorner, WrapSharesHideThePayload)
|
|||
}
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, PowerKeysAreIndependentComparisons)
|
||||
TEST(OffsetHorner, PowersShareOneSeedSpine)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const uint16_t center = 1000;
|
||||
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
||||
const auto & k0 = std::get<0>(mat.keys[0]);
|
||||
const auto & k1 = std::get<0>(mat.keys[1]);
|
||||
const auto & k0 = std::get<0>(mat.keys);
|
||||
const auto & k1 = std::get<1>(mat.keys);
|
||||
EXPECT_NE(std::memcmp(&k0.root(), &k1.root(), sizeof(k0.root())), 0);
|
||||
bool cw_differs = false;
|
||||
const std::size_t depth = std::remove_reference_t<decltype(k0)>::depth;
|
||||
for (std::size_t level = 0; level < depth; ++level)
|
||||
{
|
||||
if (k0.value_cw(level) != k1.value_cw(level))
|
||||
cw_differs = true;
|
||||
}
|
||||
EXPECT_TRUE(cw_differs);
|
||||
EXPECT_EQ(mat.keys.size(), D + 1);
|
||||
EXPECT_GT(depth, 0u);
|
||||
EXPECT_EQ(k0.value_cw(0), k1.value_cw(0));
|
||||
}
|
||||
|
||||
TEST(OffsetHorner, DegreeZeroMatchesSignRespectingDot)
|
||||
|
|
@ -744,7 +739,6 @@ TEST(OffsetHorner, GenevalXorSharesMatchTheDealerPoint)
|
|||
EXPECT_EQ(grotto::geneval_offset_horner_center(center, uint8_t{0}), center);
|
||||
|
||||
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.eta, eta);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got.value0 + got.value1, 30u);
|
||||
|
|
@ -769,7 +763,6 @@ TEST(OffsetHorner, GenevalFromAdditiveSharesOfXAndR)
|
|||
const uint8_t eta = offset_horner_group_sub(x, r);
|
||||
const uint8_t center = offset_horner_group_add(r, r);
|
||||
EXPECT_EQ(got.eta, eta);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
EXPECT_EQ(got.value0 + got.value1, 88u);
|
||||
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
||||
|
|
@ -796,7 +789,6 @@ TEST(OffsetHorner, GenevalSignedSharesUseGenevalConvention)
|
|||
const int8_t eta = -3;
|
||||
EXPECT_EQ(grotto::geneval_offset_horner_center(share, other), center);
|
||||
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||
EXPECT_EQ(got.center, center);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||
|
||||
const int8_t x = 40;
|
||||
|
|
@ -809,7 +801,6 @@ TEST(OffsetHorner, GenevalSignedSharesUseGenevalConvention)
|
|||
knots, coeff);
|
||||
const int8_t expect_center = offset_horner_group_add(r, r);
|
||||
const int8_t expect_eta = offset_horner_group_sub(x, r);
|
||||
EXPECT_EQ(from_mask.center, expect_center);
|
||||
EXPECT_EQ(from_mask.eta, expect_eta);
|
||||
EXPECT_EQ(from_mask.value0 + from_mask.value1,
|
||||
gold<D>(expect_center, expect_eta, knots, coeff));
|
||||
|
|
@ -891,7 +882,6 @@ void expect_geneval(T center, T eta, const std::vector<T> & knots,
|
|||
const T other = static_cast<T>(center ^ share);
|
||||
const auto g = grotto::geneval_offset_horner<Degree>(share, other, eta, knots, coeff);
|
||||
const uint64_t want = gold<Degree>(center, eta, knots, coeff);
|
||||
EXPECT_EQ(g.center, center) << where;
|
||||
EXPECT_EQ(g.value0 + g.value1, want) << where;
|
||||
uint64_t summed = 0;
|
||||
for (std::size_t k = 0; k <= Degree; ++k)
|
||||
|
|
@ -1244,14 +1234,17 @@ template <typename T>
|
|||
bool addition_leaves_domain(T center, T eta)
|
||||
{
|
||||
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
|
||||
if (bits > 62)
|
||||
if constexpr (bits > 62)
|
||||
return false;
|
||||
const int64_t sum = math_of(center) + math_of(eta);
|
||||
const int64_t mod = int64_t{1} << bits;
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
return sum >= (mod >> 1) || sum < -(mod >> 1);
|
||||
else
|
||||
return sum >= mod;
|
||||
{
|
||||
const int64_t sum = math_of(center) + math_of(eta);
|
||||
const int64_t mod = int64_t{1} << bits;
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
return sum >= (mod >> 1) || sum < -(mod >> 1);
|
||||
else
|
||||
return sum >= mod;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
|
@ -1399,10 +1392,10 @@ void exercise_big_domain()
|
|||
const T r1 = offset_horner_group_sub(r, r0);
|
||||
const auto got = grotto::geneval_offset_horner<D>(x0, x1, r0, r1, knots, coeff);
|
||||
const T sum = offset_horner_group_add(x, r);
|
||||
EXPECT_EQ(got.center, offset_horner_group_add(r, r));
|
||||
const T expect_center = offset_horner_group_add(r, r);
|
||||
EXPECT_EQ(got.eta, offset_horner_group_sub(x, r));
|
||||
EXPECT_EQ(offset_horner_group_add(got.center, got.eta), sum);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(got.center, got.eta, knots, coeff));
|
||||
EXPECT_EQ(offset_horner_group_add(expect_center, got.eta), sum);
|
||||
EXPECT_EQ(got.value0 + got.value1, gold<D>(expect_center, got.eta, knots, coeff));
|
||||
if (::testing::Test::HasFailure())
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
192
test/tests/offset_jet_test.cpp
Normal file
192
test/tests/offset_jet_test.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/offset_jet.hpp"
|
||||
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
uint64_t binom_ref(std::int64_t n, unsigned k)
|
||||
{
|
||||
if (k == 0)
|
||||
return 1;
|
||||
if (n >= 0)
|
||||
{
|
||||
unsigned __int128 acc = 1;
|
||||
for (unsigned i = 1; i <= k; ++i)
|
||||
acc = acc * static_cast<unsigned __int128>(n - static_cast<std::int64_t>(k - i)) / i;
|
||||
return static_cast<uint64_t>(acc);
|
||||
}
|
||||
const uint64_t mag = binom_ref(-n + static_cast<std::int64_t>(k) - 1, k);
|
||||
return (k & 1u) ? static_cast<uint64_t>(0) - mag : mag;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OffsetJet, BinomMatchesReference)
|
||||
{
|
||||
for (uint64_t n = 0; n < 40; ++n)
|
||||
for (unsigned k = 0; k <= 16; ++k)
|
||||
EXPECT_EQ(grotto::offset_jet_binom(n, k), binom_ref(static_cast<std::int64_t>(n), k))
|
||||
<< "n=" << n << " k=" << k;
|
||||
|
||||
for (std::int64_t n = -20; n < 0; ++n)
|
||||
for (unsigned k = 0; k <= 10; ++k)
|
||||
EXPECT_EQ(grotto::offset_jet_binom(n, k), binom_ref(n, k))
|
||||
<< "n=" << n << " k=" << k;
|
||||
|
||||
// Chu–Vandermonde sanity for a large center and a public kappa.
|
||||
const uint64_t c = (uint64_t{1} << 40) + 17;
|
||||
const std::int64_t kappa = -3;
|
||||
for (unsigned k = 0; k <= 8; ++k)
|
||||
{
|
||||
uint64_t lhs = grotto::offset_jet_binom(
|
||||
static_cast<std::int64_t>(c) + kappa, k);
|
||||
uint64_t rhs = 0;
|
||||
for (unsigned j = 0; j <= k; ++j)
|
||||
rhs += grotto::offset_jet_binom(c, j)
|
||||
* grotto::offset_jet_binom(kappa, k - j);
|
||||
EXPECT_EQ(lhs, rhs) << "k=" << k;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetJet, PublicCoefficientsMatchTheWrappedPoint)
|
||||
{
|
||||
const std::size_t degree = 3;
|
||||
const uint8_t center = 9;
|
||||
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, degree);
|
||||
// f(x) = 2 + 3 C(x,1) + C(x,3)
|
||||
const std::vector<uint64_t> coeff{2, 3, 0, 1};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
for (int eta = 0; eta < 256; eta += 19)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
|
||||
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
|
||||
const uint64_t clear = grotto::offset_jet_clear<uint8_t>(center, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear);
|
||||
const uint8_t wrapped = static_cast<uint8_t>(center + e);
|
||||
uint64_t expect = 0;
|
||||
for (std::size_t k = 0; k <= degree; ++k)
|
||||
expect += coeff[k] * grotto::offset_jet_binom(static_cast<uint64_t>(wrapped),
|
||||
static_cast<unsigned>(k));
|
||||
EXPECT_EQ(clear, expect);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetJet, CarrySplitStillEvaluates)
|
||||
{
|
||||
const uint8_t center = 200;
|
||||
const uint8_t eta = 100;
|
||||
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, 1);
|
||||
const std::vector<uint64_t> coeff{5, 1};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint64_t got = grotto::offset_jet_eval<0>(mat, knots, coeff, eta)
|
||||
+ grotto::offset_jet_eval<1>(mat, knots, coeff, eta);
|
||||
// wrapped = 200+100 = 44; f = 5 + C(44,1) = 5+44
|
||||
EXPECT_EQ(got, uint64_t{5} + 44);
|
||||
}
|
||||
|
||||
TEST(OffsetJet, DifferenceAndPrefixFromOneJet)
|
||||
{
|
||||
const std::size_t degree = 3;
|
||||
const uint8_t center = 12;
|
||||
const uint8_t eta = 3;
|
||||
// Need degree+1 for prefix of a degree-2 polynomial; use degree 3 key.
|
||||
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, degree);
|
||||
const std::vector<uint64_t> poly{4, 2, 1}; // 4 + 2 C(x,1) + C(x,2)
|
||||
std::vector<uint64_t> coeff = poly;
|
||||
coeff.push_back(0); // pad to degree 3
|
||||
const std::vector<uint8_t> knots{0};
|
||||
|
||||
const auto j0 = grotto::offset_jet_shares<0>(mat, knots, eta);
|
||||
const auto j1 = grotto::offset_jet_shares<1>(mat, knots, eta);
|
||||
std::vector<uint64_t> jet(degree + 1);
|
||||
for (std::size_t k = 0; k <= degree; ++k)
|
||||
jet[k] = j0[k] + j1[k];
|
||||
|
||||
const uint8_t x = static_cast<uint8_t>(center + eta);
|
||||
const uint64_t value = grotto::offset_jet_dot(coeff, jet);
|
||||
uint64_t expect_v = 0;
|
||||
for (std::size_t k = 0; k < poly.size(); ++k)
|
||||
expect_v += poly[k] * grotto::offset_jet_binom(static_cast<uint64_t>(x),
|
||||
static_cast<unsigned>(k));
|
||||
EXPECT_EQ(value, expect_v);
|
||||
|
||||
const auto dcoeff = grotto::offset_jet_difference_coeff(coeff);
|
||||
const uint64_t diff = grotto::offset_jet_dot(dcoeff, jet);
|
||||
const uint64_t fx1 = expect_v
|
||||
- poly[0] * 0 // recompute f(x+1) - f(x)
|
||||
+ 0;
|
||||
uint64_t expect_fx1 = 0;
|
||||
for (std::size_t k = 0; k < poly.size(); ++k)
|
||||
expect_fx1 += poly[k] * grotto::offset_jet_binom(
|
||||
static_cast<uint64_t>(static_cast<uint8_t>(x + 1)),
|
||||
static_cast<unsigned>(k));
|
||||
EXPECT_EQ(diff, static_cast<uint64_t>(expect_fx1 - expect_v));
|
||||
(void)fx1;
|
||||
|
||||
// Prefix needs degree+1: key degree 3, poly degree <= 2.
|
||||
const auto pcoeff = grotto::offset_jet_prefix_coeff(poly);
|
||||
ASSERT_EQ(pcoeff.size(), degree + 1u);
|
||||
const uint64_t prefix = grotto::offset_jet_dot(pcoeff, jet);
|
||||
uint64_t expect_p = 0;
|
||||
for (uint8_t i = 0; i < x; ++i)
|
||||
{
|
||||
for (std::size_t k = 0; k < poly.size(); ++k)
|
||||
expect_p += poly[k] * grotto::offset_jet_binom(static_cast<uint64_t>(i),
|
||||
static_cast<unsigned>(k));
|
||||
}
|
||||
EXPECT_EQ(prefix, expect_p);
|
||||
}
|
||||
|
||||
TEST(OffsetJet, VerifiableProofs)
|
||||
{
|
||||
const std::size_t degree = 2;
|
||||
const auto mat = grotto::make_offset_jet_keys<uint8_t>(2, degree, dpf::verifiable{});
|
||||
const std::vector<uint64_t> coeff{1, 0, 3};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint8_t eta = 5;
|
||||
std::vector<dpf::proof_token> a(degree + 1), b(degree + 1);
|
||||
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, eta, a.data());
|
||||
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, eta, b.data());
|
||||
EXPECT_EQ(s0 + s1, grotto::offset_jet_clear<uint8_t>(2, knots, coeff, eta));
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
EXPECT_TRUE(dpf::verify(a[m], b[m]));
|
||||
}
|
||||
|
||||
TEST(OffsetJet, RejectsOversizeDegree)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_offset_jet_keys<uint8_t>(1, grotto::offset_jet_max_degree + 1), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(OffsetJet, NegativeCenterDegreeTwoPlus)
|
||||
{
|
||||
const std::size_t degree = 3;
|
||||
const int8_t center = -7;
|
||||
const auto mat = grotto::make_offset_jet_keys<int8_t>(center, degree);
|
||||
// f(x) = 1 + C(x,1) + 3 C(x,2) + C(x,3)
|
||||
const std::vector<uint64_t> coeff{1, 1, 3, 1};
|
||||
const std::vector<int8_t> knots{static_cast<int8_t>(-128)};
|
||||
for (int eta = -20; eta <= 20; eta += 5)
|
||||
{
|
||||
const auto e = static_cast<int8_t>(eta);
|
||||
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
|
||||
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
|
||||
const uint64_t clear = grotto::offset_jet_clear<int8_t>(center, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear) << "eta=" << eta;
|
||||
|
||||
const int8_t wrapped = grotto::offset_horner_group_add(center, e);
|
||||
uint64_t expect = 0;
|
||||
for (std::size_t k = 0; k <= degree; ++k)
|
||||
expect += coeff[k] * grotto::offset_jet_binom(
|
||||
static_cast<std::int64_t>(wrapped), static_cast<unsigned>(k));
|
||||
EXPECT_EQ(clear, expect) << "eta=" << eta;
|
||||
}
|
||||
}
|
||||
158
test/tests/offset_poly_test.cpp
Normal file
158
test/tests/offset_poly_test.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/offset_poly.hpp"
|
||||
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
uint64_t wrapped_pow(uint8_t center, uint8_t eta, std::size_t degree)
|
||||
{
|
||||
const uint64_t point = static_cast<uint64_t>(static_cast<uint8_t>(center + eta));
|
||||
uint64_t acc = 0;
|
||||
uint64_t pow = 1;
|
||||
std::vector<uint64_t> coeff(degree + 1, 0);
|
||||
coeff[0] = 3;
|
||||
coeff[degree] = 1;
|
||||
for (uint64_t c : coeff)
|
||||
{
|
||||
acc += c * pow;
|
||||
pow *= point;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OffsetPoly, PublicCoefficientsMatchTheWrappedPolynomial)
|
||||
{
|
||||
const std::size_t degree = 4;
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(2, degree);
|
||||
std::vector<uint64_t> coeff(degree + 1, 0);
|
||||
coeff[0] = 3;
|
||||
coeff[degree] = 1;
|
||||
const std::vector<uint8_t> knots{0};
|
||||
for (int eta = 0; eta < 256; eta += 17)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const uint64_t s0 = grotto::offset_poly_eval<0>(mat, knots, {coeff}, e);
|
||||
const uint64_t s1 = grotto::offset_poly_eval<1>(mat, knots, {coeff}, e);
|
||||
const uint64_t clear = grotto::offset_poly_clear<uint8_t>(2, knots, {coeff}, e);
|
||||
EXPECT_EQ(s0 + s1, clear);
|
||||
EXPECT_EQ(clear, wrapped_pow(2, e, degree));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetPoly, CarrySplitStillEvaluatesTheWrappedPoint)
|
||||
{
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(200, 1);
|
||||
const std::vector<uint64_t> coeff{5, 1};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint8_t eta = 100;
|
||||
const uint64_t got = grotto::offset_poly_eval<0>(mat, knots, {coeff}, eta)
|
||||
+ grotto::offset_poly_eval<1>(mat, knots, {coeff}, eta);
|
||||
EXPECT_EQ(got, uint64_t{5} + 44);
|
||||
}
|
||||
|
||||
TEST(OffsetPoly, SharedCoefficientsUseOneBeaverDot)
|
||||
{
|
||||
const std::size_t degree = 2;
|
||||
const uint8_t center = 9;
|
||||
const uint8_t eta = 4;
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(center, degree);
|
||||
const std::vector<uint64_t> coeff{4, 0, 2};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const std::vector<uint64_t> share0{1, 7, 9};
|
||||
std::vector<uint64_t> share1(degree + 1);
|
||||
for (std::size_t i = 0; i < coeff.size(); ++i)
|
||||
share1[i] = coeff[i] - share0[i];
|
||||
|
||||
const auto kappas = grotto::offset_poly_kappas<uint8_t>(knots, degree, eta);
|
||||
const auto p0 = grotto::offset_poly_power_shares<0>(mat, knots, eta);
|
||||
const auto p1 = grotto::offset_poly_power_shares<1>(mat, knots, eta);
|
||||
ASSERT_EQ(p0.size(), kappas.size());
|
||||
|
||||
std::vector<uint64_t> q0, q1, y0, y1;
|
||||
for (std::size_t piece = 0; piece < kappas.size(); ++piece)
|
||||
{
|
||||
const auto a0 = grotto::offset_poly_shift_share(share0, kappas[piece]);
|
||||
const auto a1 = grotto::offset_poly_shift_share(share1, kappas[piece]);
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
{
|
||||
q0.push_back(a0[m]);
|
||||
q1.push_back(a1[m]);
|
||||
y0.push_back(p0[piece][m]);
|
||||
y1.push_back(p1[piece][m]);
|
||||
}
|
||||
}
|
||||
auto triple = dpf::beavers::sample_dot<uint64_t>(q0.size());
|
||||
std::vector<uint64_t> opened_d(q0.size()), opened_e(y0.size());
|
||||
for (std::size_t i = 0; i < q0.size(); ++i)
|
||||
{
|
||||
// Open only masked differences q-a and power-b (F_Poly).
|
||||
opened_d[i] = (q0[i] - triple.x[i].p0) + (q1[i] - triple.x[i].p1);
|
||||
opened_e[i] = (y0[i] - triple.y[i].p0) + (y1[i] - triple.y[i].p1);
|
||||
}
|
||||
const uint64_t v0 = grotto::offset_poly_beaver_share(0, q0, y0, opened_d, opened_e, triple);
|
||||
const uint64_t v1 = grotto::offset_poly_beaver_share(1, q1, y1, opened_d, opened_e, triple);
|
||||
const uint64_t clear = grotto::offset_poly_clear<uint8_t>(center, knots, {coeff}, eta);
|
||||
EXPECT_EQ(v0 + v1, clear);
|
||||
const uint64_t point = static_cast<uint8_t>(center + eta);
|
||||
EXPECT_EQ(clear, 4 + 2 * point * point);
|
||||
}
|
||||
|
||||
TEST(OffsetPoly, VerifiableTokensRejectATamperedProof)
|
||||
{
|
||||
const std::size_t degree = 2;
|
||||
const auto mat = grotto::make_offset_poly_keys<uint8_t>(2, degree, dpf::verifiable{});
|
||||
const std::vector<uint64_t> coeff{1, 0, 3};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint8_t eta = 5;
|
||||
std::vector<dpf::proof_token> a(degree + 1), b(degree + 1);
|
||||
const uint64_t s0 = grotto::offset_poly_eval<0>(mat, knots, {coeff}, eta, a.data());
|
||||
const uint64_t s1 = grotto::offset_poly_eval<1>(mat, knots, {coeff}, eta, b.data());
|
||||
EXPECT_EQ(s0 + s1, grotto::offset_poly_clear<uint8_t>(2, knots, {coeff}, eta));
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
EXPECT_TRUE(dpf::verify(a[m], b[m])) << m;
|
||||
a[0][0] = simde_mm_xor_si128(a[0][0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(a[0], b[0]));
|
||||
EXPECT_TRUE(dpf::verify(a[1], b[1]));
|
||||
}
|
||||
|
||||
TEST(OffsetPoly, HornerAndPolyAgreeOnTheSameKnots)
|
||||
{
|
||||
constexpr std::size_t D = 3;
|
||||
const uint8_t center = 17;
|
||||
const uint8_t eta = 9;
|
||||
const std::vector<uint8_t> knots{0, 40, 100};
|
||||
std::vector<std::array<uint64_t, D + 1>> horner_coeff{
|
||||
{2, 0, 1, 0},
|
||||
{0, 3, 0, 1},
|
||||
{5, 1, 0, 0},
|
||||
};
|
||||
std::vector<std::vector<uint64_t>> poly_coeff(horner_coeff.size());
|
||||
for (std::size_t i = 0; i < horner_coeff.size(); ++i)
|
||||
poly_coeff[i].assign(horner_coeff[i].begin(), horner_coeff[i].end());
|
||||
|
||||
auto hmat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||
auto pmat = grotto::make_offset_poly_keys<uint8_t>(center, D);
|
||||
const uint64_t h = grotto::offset_horner_eval<0, D>(hmat, knots, horner_coeff, eta)
|
||||
+ grotto::offset_horner_eval<1, D>(hmat, knots, horner_coeff, eta);
|
||||
const uint64_t p = grotto::offset_poly_eval<0>(pmat, knots, poly_coeff, eta)
|
||||
+ grotto::offset_poly_eval<1>(pmat, knots, poly_coeff, eta);
|
||||
EXPECT_EQ(h, p);
|
||||
EXPECT_EQ(h, grotto::offset_horner_clear<D>(center, knots, horner_coeff, eta));
|
||||
EXPECT_EQ(p, grotto::offset_poly_clear(center, knots, poly_coeff, eta));
|
||||
}
|
||||
|
||||
TEST(OffsetPoly, RejectsADegreePastTheCap)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_offset_poly_keys<uint8_t>(1, grotto::offset_poly_max_degree + 1), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_offset_poly_keys<uint8_t>(1, grotto::offset_poly_max_degree + 1, dpf::verifiable{}), std::invalid_argument);
|
||||
}
|
||||
172
test/tests/offset_repr_test.cpp
Normal file
172
test/tests/offset_repr_test.cpp
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/offset_repr.hpp"
|
||||
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
uint64_t fib_ref(uint64_t n)
|
||||
{
|
||||
if (n == 0)
|
||||
return 0;
|
||||
uint64_t a = 0, b = 1;
|
||||
for (uint64_t i = 1; i < n; ++i)
|
||||
{
|
||||
const uint64_t c = a + b;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> matvec2(
|
||||
const std::vector<std::vector<uint64_t>> & M,
|
||||
const std::vector<uint64_t> & v)
|
||||
{
|
||||
return {M[0][0] * v[0] + M[0][1] * v[1], M[1][0] * v[0] + M[1][1] * v[1]};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OffsetRepr, FibonacciStateMatchesReference)
|
||||
{
|
||||
for (uint64_t n = 0; n < 90; ++n)
|
||||
{
|
||||
const auto S = grotto::offset_repr_fibonacci_state(n);
|
||||
ASSERT_EQ(S.size(), 2u);
|
||||
EXPECT_EQ(S[1], fib_ref(n)) << "F_" << n;
|
||||
EXPECT_EQ(S[0], fib_ref(n + 1)) << "F_" << (n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, MatrixPowAdvancesFibonacci)
|
||||
{
|
||||
const auto M = grotto::offset_repr_fibonacci_matrix();
|
||||
const auto S0 = grotto::offset_repr_fibonacci_state(0);
|
||||
for (std::int64_t k = 0; k < 40; ++k)
|
||||
{
|
||||
const auto Mk = grotto::offset_repr_matrix_pow(M, k);
|
||||
const auto advanced = matvec2(Mk, S0);
|
||||
const auto expect = grotto::offset_repr_fibonacci_state(static_cast<uint64_t>(k));
|
||||
EXPECT_EQ(advanced, expect) << "k=" << k;
|
||||
}
|
||||
// Negative: M^{-k} S_k = S_0.
|
||||
for (std::int64_t k = 1; k < 20; ++k)
|
||||
{
|
||||
const auto Sk = grotto::offset_repr_fibonacci_state(static_cast<uint64_t>(k));
|
||||
const auto Minv = grotto::offset_repr_matrix_pow(M, -k);
|
||||
const auto back = matvec2(Minv, Sk);
|
||||
EXPECT_EQ(back, S0) << "back from k=" << k;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, GeometricIsScalarPow)
|
||||
{
|
||||
const uint64_t lambda = 3;
|
||||
const auto M = grotto::offset_repr_geometric_matrix(lambda);
|
||||
uint64_t expect = 1;
|
||||
for (std::int64_t e = 0; e < 20; ++e)
|
||||
{
|
||||
const auto P = grotto::offset_repr_matrix_pow(M, e);
|
||||
EXPECT_EQ(P[0][0], expect);
|
||||
expect *= lambda;
|
||||
}
|
||||
const auto Minv = grotto::offset_repr_matrix_pow(M, -1);
|
||||
EXPECT_EQ(Minv[0][0] * lambda, uint64_t{1});
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, Crc32JumpMatchesNaive)
|
||||
{
|
||||
constexpr std::uint32_t poly = 0xEDB88320u;
|
||||
auto step1 = [](std::uint32_t s) {
|
||||
return (s >> 1) ^ (poly & (0u - (s & 1u)));
|
||||
};
|
||||
for (std::uint32_t seed : {0u, 1u, 0xFFFFFFFFu, 0x12345678u})
|
||||
{
|
||||
for (unsigned steps = 0; steps < 200; ++steps)
|
||||
{
|
||||
std::uint32_t naive = seed;
|
||||
for (unsigned i = 0; i < steps; ++i)
|
||||
naive = step1(naive);
|
||||
EXPECT_EQ(grotto::offset_repr_crc32_jump(seed, steps), naive)
|
||||
<< "seed=" << seed << " steps=" << steps;
|
||||
}
|
||||
}
|
||||
// Doubling path for large jumps.
|
||||
const std::uint32_t seed = 0xA5A5A5A5u;
|
||||
std::uint32_t naive = seed;
|
||||
for (unsigned i = 0; i < 1000; ++i)
|
||||
naive = step1(naive);
|
||||
EXPECT_EQ(grotto::offset_repr_crc32_jump(seed, 1000), naive);
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, KeyedFibonacciMatchesClear)
|
||||
{
|
||||
const uint8_t center = 10;
|
||||
const auto state = grotto::offset_repr_fibonacci_state(center);
|
||||
const auto M = grotto::offset_repr_fibonacci_matrix();
|
||||
const auto mat = grotto::make_offset_repr_keys<uint8_t>(center, state);
|
||||
const std::vector<uint8_t> knots{0};
|
||||
for (int eta = 0; eta < 256; eta += 17)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const auto s0 = grotto::offset_repr_eval<0>(mat, M, knots, e);
|
||||
const auto s1 = grotto::offset_repr_eval<1>(mat, M, knots, e);
|
||||
const auto clear = grotto::offset_repr_clear(center, state, M, knots, e);
|
||||
ASSERT_EQ(s0.size(), 2u);
|
||||
ASSERT_EQ(s1.size(), 2u);
|
||||
ASSERT_EQ(clear.size(), 2u);
|
||||
EXPECT_EQ(s0[0] + s1[0], clear[0]);
|
||||
EXPECT_EQ(s0[1] + s1[1], clear[1]);
|
||||
const uint8_t wrapped = static_cast<uint8_t>(center + e);
|
||||
const auto expect = grotto::offset_repr_fibonacci_state(wrapped);
|
||||
EXPECT_EQ(clear, expect) << "eta=" << eta;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, CarrySplitStillAdvances)
|
||||
{
|
||||
const uint8_t center = 200;
|
||||
const uint8_t eta = 100; // wraps: 44
|
||||
const auto state = grotto::offset_repr_fibonacci_state(center);
|
||||
const auto M = grotto::offset_repr_fibonacci_matrix();
|
||||
const auto mat = grotto::make_offset_repr_keys<uint8_t>(center, state);
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const auto got0 = grotto::offset_repr_eval<0>(mat, M, knots, eta);
|
||||
const auto got1 = grotto::offset_repr_eval<1>(mat, M, knots, eta);
|
||||
const auto expect = grotto::offset_repr_fibonacci_state(44);
|
||||
EXPECT_EQ(got0[0] + got1[0], expect[0]);
|
||||
EXPECT_EQ(got0[1] + got1[1], expect[1]);
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, VerifiableProofs)
|
||||
{
|
||||
const uint8_t center = 7;
|
||||
const auto state = grotto::offset_repr_fibonacci_state(center);
|
||||
const auto M = grotto::offset_repr_fibonacci_matrix();
|
||||
const auto mat = grotto::make_offset_repr_keys<uint8_t>(center, state, dpf::verifiable{});
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint8_t eta = 5;
|
||||
std::vector<dpf::proof_token> a(2), b(2);
|
||||
const auto s0 = grotto::offset_repr_eval<0>(mat, M, knots, eta, a.data());
|
||||
const auto s1 = grotto::offset_repr_eval<1>(mat, M, knots, eta, b.data());
|
||||
const auto clear = grotto::offset_repr_clear(center, state, M, knots, eta);
|
||||
EXPECT_EQ(s0[0] + s1[0], clear[0]);
|
||||
EXPECT_EQ(s0[1] + s1[1], clear[1]);
|
||||
EXPECT_TRUE(dpf::verify(a[0], b[0]));
|
||||
EXPECT_TRUE(dpf::verify(a[1], b[1]));
|
||||
}
|
||||
|
||||
TEST(OffsetRepr, RejectsBadDim)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_offset_repr_keys<uint8_t>(1, {}), std::invalid_argument);
|
||||
EXPECT_THROW(grotto::make_offset_repr_keys<uint8_t>(1,
|
||||
std::vector<uint64_t>(grotto::offset_repr_max_dim + 1, 0)), std::invalid_argument);
|
||||
}
|
||||
153
test/tests/offset_twist_test.cpp
Normal file
153
test/tests/offset_twist_test.cpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/offset_twist.hpp"
|
||||
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
uint64_t pow_u64(uint64_t base, uint64_t exp)
|
||||
{
|
||||
uint64_t acc = 1;
|
||||
while (exp != 0)
|
||||
{
|
||||
if (exp & 1u)
|
||||
acc *= base;
|
||||
base *= base;
|
||||
exp >>= 1;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
uint64_t twisted_poly(uint64_t x, uint64_t lambda, const std::vector<uint64_t> & coeff)
|
||||
{
|
||||
uint64_t acc = 0;
|
||||
uint64_t pow = 1;
|
||||
for (uint64_t c : coeff)
|
||||
{
|
||||
acc += c * pow;
|
||||
pow *= x;
|
||||
}
|
||||
return acc * pow_u64(lambda, x);
|
||||
}
|
||||
|
||||
uint64_t twisted_half(uint64_t x, const std::vector<uint64_t> & coeff)
|
||||
{
|
||||
uint64_t acc = 0;
|
||||
uint64_t pow = 1;
|
||||
for (uint64_t c : coeff)
|
||||
{
|
||||
acc += c * pow;
|
||||
pow *= x;
|
||||
}
|
||||
if (x >= 64)
|
||||
return 0;
|
||||
return acc >> static_cast<unsigned>(x);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OffsetTwist, OddLambdaMatchesClear)
|
||||
{
|
||||
const uint8_t center = 9;
|
||||
const uint64_t lambda = 3;
|
||||
const std::size_t degree = 2;
|
||||
const auto mat = grotto::make_offset_twist_keys<uint8_t>(center, degree, lambda);
|
||||
// f(x) = (2 + 5x + x^2) * 3^x
|
||||
const std::vector<uint64_t> coeff{2, 5, 1};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
for (int eta = 0; eta < 256; eta += 19)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const uint64_t s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, e);
|
||||
const uint64_t s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, e);
|
||||
const uint64_t clear = grotto::offset_twist_clear(
|
||||
center, lambda, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear) << "eta=" << eta;
|
||||
const uint8_t wrapped = static_cast<uint8_t>(center + e);
|
||||
EXPECT_EQ(clear, twisted_poly(wrapped, lambda, coeff)) << "eta=" << eta;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, CarrySplitStillTwists)
|
||||
{
|
||||
const uint8_t center = 200;
|
||||
const uint8_t eta = 100; // wraps to 44
|
||||
const uint64_t lambda = 5;
|
||||
const auto mat = grotto::make_offset_twist_keys<uint8_t>(center, 1, lambda);
|
||||
const std::vector<uint64_t> coeff{1, 2}; // (1 + 2x) 5^x
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint64_t got = grotto::offset_twist_eval<0>(mat, knots, coeff, eta)
|
||||
+ grotto::offset_twist_eval<1>(mat, knots, coeff, eta);
|
||||
EXPECT_EQ(got, twisted_poly(44, lambda, coeff));
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, HalfShiftsOnShares)
|
||||
{
|
||||
const uint8_t center = 4;
|
||||
const std::size_t degree = 2;
|
||||
const auto mat = grotto::make_offset_twist_keys<uint8_t>(center, degree, grotto::twist_half);
|
||||
// Dyadic path returns shares of the shifted value; untwisted sum stays shared.
|
||||
const std::vector<uint64_t> coeff{7, 3, 1};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
for (int eta = 0; eta < 40; eta += 3)
|
||||
{
|
||||
const auto e = static_cast<uint8_t>(eta);
|
||||
const uint64_t s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, e);
|
||||
const uint64_t s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, e);
|
||||
const uint8_t wrapped = static_cast<uint8_t>(center + e);
|
||||
const uint64_t clear = grotto::offset_twist_clear(
|
||||
center, grotto::twist_half, knots, coeff, e);
|
||||
EXPECT_EQ(s0 + s1, clear) << "eta=" << eta;
|
||||
EXPECT_EQ(clear, twisted_half(wrapped, coeff)) << "eta=" << eta;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, ArithmeticoGeometricClosedForm)
|
||||
{
|
||||
const uint64_t lambda = 3;
|
||||
for (uint64_t n = 1; n < 30; ++n)
|
||||
{
|
||||
uint64_t naive = 0;
|
||||
for (uint64_t k = 1; k <= n; ++k)
|
||||
naive += k * pow_u64(lambda, k);
|
||||
EXPECT_EQ(grotto::offset_twist_arithmetico_geometric(n, lambda), naive)
|
||||
<< "n=" << n;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, VerifiableProofs)
|
||||
{
|
||||
const uint8_t center = 6;
|
||||
const uint64_t lambda = 7;
|
||||
const std::size_t degree = 2;
|
||||
const auto mat = grotto::make_offset_twist_keys<uint8_t>(
|
||||
center, degree, lambda, dpf::verifiable{});
|
||||
const std::vector<uint64_t> coeff{1, 0, 4};
|
||||
const std::vector<uint8_t> knots{0};
|
||||
const uint8_t eta = 3;
|
||||
std::vector<dpf::proof_token> a(degree + 1), b(degree + 1);
|
||||
const uint64_t s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, eta, a.data());
|
||||
const uint64_t s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, eta, b.data());
|
||||
EXPECT_EQ(s0 + s1,
|
||||
grotto::offset_twist_clear(center, lambda, knots, coeff, eta));
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
EXPECT_TRUE(dpf::verify(a[m], b[m]));
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, RejectsEvenLambda)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_offset_twist_keys<uint8_t>(1, 1, uint64_t{2}), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(OffsetTwist, RejectsOversizeDegree)
|
||||
{
|
||||
EXPECT_THROW(grotto::make_offset_twist_keys<uint8_t>(
|
||||
1, grotto::offset_twist_max_degree + 1, uint64_t{3}), std::invalid_argument);
|
||||
}
|
||||
316
test/tests/opt_in_malicious_test.cpp
Normal file
316
test/tests/opt_in_malicious_test.cpp
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
/// @file opt_in_malicious_test.cpp
|
||||
/// @brief Opt-in verifiable / extractable / MAC checks stay off by default.
|
||||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/beaver.hpp"
|
||||
#include "grotto/carry.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Input = std::uint8_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
struct Counter
|
||||
{
|
||||
int draws = 0;
|
||||
u64 operator()()
|
||||
{
|
||||
++draws;
|
||||
return 0x9e3779b97f4a7c15ull * static_cast<u64>(draws);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OptInProfile, SemiHonestKeyHasNoProofOrSketch)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf_profile<dpf::semi_honest>(Input{0x2a}, u64{7});
|
||||
EXPECT_FALSE(decltype(k0)::is_verifiable);
|
||||
EXPECT_FALSE(decltype(k0)::is_extractable);
|
||||
EXPECT_FALSE(decltype(k1)::is_verifiable);
|
||||
EXPECT_FALSE(decltype(k1)::is_extractable);
|
||||
|
||||
auto plain = dpf::make_dpf(Input{0x2a}, u64{7});
|
||||
EXPECT_FALSE(decltype(plain.first)::is_verifiable);
|
||||
EXPECT_FALSE(decltype(plain.first)::is_extractable);
|
||||
}
|
||||
|
||||
TEST(OptInProfile, CheckedKeyOptsIn)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf_profile<dpf::checked>(Input{0x2a}, dpf::fp61{7});
|
||||
EXPECT_TRUE(decltype(k0)::is_verifiable);
|
||||
EXPECT_TRUE(decltype(k0)::is_extractable);
|
||||
}
|
||||
|
||||
TEST(OptInProfile, FlagsAreIndependentOfTheOutputMacBit)
|
||||
{
|
||||
using ver_only = dpf::auth_profile<true, false, false>;
|
||||
using ext_only = dpf::auth_profile<false, true, false>;
|
||||
using mac_only = dpf::auth_profile<false, false, true>;
|
||||
static_assert(dpf::checked::output_mac);
|
||||
static_assert(!dpf::semi_honest::output_mac);
|
||||
static_assert(std::tuple_size_v<decltype(dpf::key_tags_tuple<dpf::checked>())> == 2);
|
||||
static_assert(std::tuple_size_v<decltype(dpf::key_tags_tuple<mac_only>())> == 0);
|
||||
static_assert(std::tuple_size_v<decltype(dpf::key_tags<ver_only>())> == 1);
|
||||
|
||||
auto ver = dpf::make_dpf_profile<ver_only>(Input{1}, u64{4});
|
||||
EXPECT_TRUE(decltype(ver.first)::is_verifiable);
|
||||
EXPECT_FALSE(decltype(ver.first)::is_extractable);
|
||||
auto ext = dpf::make_dpf_profile<ext_only>(Input{1}, dpf::fp61{4});
|
||||
EXPECT_FALSE(decltype(ext.first)::is_verifiable);
|
||||
EXPECT_TRUE(decltype(ext.first)::is_extractable);
|
||||
auto mac = dpf::make_dpf_profile<mac_only>(Input{1}, u64{4});
|
||||
EXPECT_FALSE(decltype(mac.first)::is_verifiable);
|
||||
EXPECT_FALSE(decltype(mac.first)::is_extractable);
|
||||
}
|
||||
|
||||
TEST(OptInProfile, CheckedUint8DomainProofSketchAndSeedFlip)
|
||||
{
|
||||
const Input alpha = 0x2a;
|
||||
const dpf::fp61 beta{9};
|
||||
auto [k0, k1] = dpf::make_dpf_profile<dpf::checked>(alpha, beta);
|
||||
std::array<dpf::fp61, 256> s0{}, s1{}, r{};
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto y0 = *dpf::eval_point(k0, q, dpf::prove(a));
|
||||
const auto y1 = *dpf::eval_point(k1, q, dpf::prove(b));
|
||||
s0[static_cast<std::size_t>(x)] = y0.raw();
|
||||
s1[static_cast<std::size_t>(x)] = y1.raw();
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << x;
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), x == alpha ? beta : dpf::fp61{0});
|
||||
r[static_cast<std::size_t>(x)] = dpf::fp61{static_cast<std::uint64_t>(3 + x)};
|
||||
}
|
||||
EXPECT_TRUE(dpf::sketch_verify(dpf::sketch_fold(s0, r), dpf::sketch_fold(s1, r)));
|
||||
auto forged = s0;
|
||||
forged[0] = forged[0] + beta;
|
||||
EXPECT_FALSE(dpf::sketch_verify(dpf::sketch_fold(forged, r), dpf::sketch_fold(s1, r)));
|
||||
|
||||
auto bad = k0;
|
||||
using arr = typename decltype(k0)::correction_seeds_array;
|
||||
for (auto & cs : const_cast<arr &>(bad.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
int failed = 0;
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
dpf::proof_token a{}, b{};
|
||||
(void)*dpf::eval_point(bad, static_cast<Input>(x), dpf::prove(a));
|
||||
(void)*dpf::eval_point(k1, static_cast<Input>(x), dpf::prove(b));
|
||||
if (!dpf::verify(a, b))
|
||||
++failed;
|
||||
}
|
||||
EXPECT_GT(failed, 0);
|
||||
}
|
||||
|
||||
TEST(OptInMac, DefaultSessionHasNoTagVectors)
|
||||
{
|
||||
dpf::beavers::session<u64> s;
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s(x * y);
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
auto tape = s.export_party(0);
|
||||
EXPECT_FALSE(tape.has_mac);
|
||||
EXPECT_TRUE(tape.lambda_tag.empty());
|
||||
EXPECT_TRUE(tape.dot_cross_tag.empty());
|
||||
(void)z;
|
||||
}
|
||||
|
||||
TEST(OptInMac, HonestHornerOpeningsVerify)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
dpf::beavers::session<u64> s;
|
||||
s.set_mac_key(key);
|
||||
auto x = s.input();
|
||||
auto z = s.horner(x, {u64{1}, u64{2}, u64{3}});
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
s.bind(x, u64{5}, rng);
|
||||
s.evaluate();
|
||||
EXPECT_EQ(s.open(z), 1u + 2u * 5u + 3u * 25u);
|
||||
EXPECT_TRUE(s.verify_delta(x));
|
||||
EXPECT_TRUE(s.verify_all());
|
||||
}
|
||||
|
||||
TEST(OptInMac, FlippedTagRejects)
|
||||
{
|
||||
auto key = dpf::sample_mac_key<u64>();
|
||||
dpf::beavers::session<u64> s;
|
||||
s.set_mac_key(key);
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s(x * y);
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
s.bind(x, u64{3}, rng);
|
||||
s.bind(y, u64{5}, rng);
|
||||
s.evaluate();
|
||||
EXPECT_EQ(s.open(z), 15u);
|
||||
auto a = s.delta_auth(x);
|
||||
dpf::beavers::auth_opening<u64> o0{a.value.p0, a.tag.p0};
|
||||
dpf::beavers::auth_opening<u64> o1{a.value.p1, a.tag.p1};
|
||||
EXPECT_TRUE(dpf::beavers::verify_auth_opening(o0, o1, key));
|
||||
o0.tag ^= 1ull;
|
||||
EXPECT_FALSE(dpf::beavers::verify_auth_opening(o0, o1, key));
|
||||
}
|
||||
|
||||
TEST(OptInExtractable, NoteSketchNoOpOnPlainKey)
|
||||
{
|
||||
using key_t = decltype(dpf::make_dpf(Input{1}, u64{1}).first);
|
||||
dpf::sketch_share sk{};
|
||||
const std::array<u64, 1> ys{9};
|
||||
const std::array<dpf::fp61, 1> rs{dpf::fp61{2}};
|
||||
dpf::note_sketch<key_t>(sk, ys, rs);
|
||||
EXPECT_EQ(sk.z1, dpf::fp61{0});
|
||||
EXPECT_EQ(sk.z2, dpf::fp61{0});
|
||||
EXPECT_EQ(sk.z3, dpf::fp61{0});
|
||||
}
|
||||
|
||||
TEST(OptInExtractable, HonestSketchAcceptsTamperRejects)
|
||||
{
|
||||
const Input alpha = 0x11;
|
||||
const dpf::fp61 beta{42};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::extractable{});
|
||||
EXPECT_TRUE(decltype(k0)::is_extractable);
|
||||
EXPECT_FALSE(decltype(k0)::is_verifiable);
|
||||
|
||||
std::array<Input, 4> pts{0x10, 0x11, 0x12, 0x13};
|
||||
std::array<dpf::fp61, 4> r{
|
||||
dpf::fp61{3}, dpf::fp61{5}, dpf::fp61{7}, dpf::fp61{11}};
|
||||
std::array<dpf::fp61, 4> s0{}, s1{};
|
||||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||||
{
|
||||
s0[i] = (*dpf::eval_point(k0, pts[i])).raw();
|
||||
s1[i] = (*dpf::eval_point(k1, pts[i])).raw();
|
||||
}
|
||||
dpf::sketch_share sk0{}, sk1{};
|
||||
dpf::note_sketch<decltype(k0)>(sk0, s0, r);
|
||||
dpf::note_sketch<decltype(k1)>(sk1, s1, r);
|
||||
EXPECT_TRUE(dpf::sketch_verify(sk0, sk1));
|
||||
s0[0] = s0[0] + beta;
|
||||
dpf::note_sketch<decltype(k0)>(sk0, s0, r);
|
||||
EXPECT_FALSE(dpf::sketch_verify(sk0, sk1));
|
||||
}
|
||||
|
||||
TEST(OptInMemoProve, HonestIdempotentAndTamper)
|
||||
{
|
||||
const Input alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::lt(u64{1})), dpf::verifiable{});
|
||||
using KT0 = decltype(k0);
|
||||
using KT1 = decltype(k1);
|
||||
const Input from = 0x20;
|
||||
const Input to = 0x2f;
|
||||
const auto nbits = static_cast<std::size_t>(k0.cmp().nbits);
|
||||
using integral = typename KT0::integral_type;
|
||||
const auto a = static_cast<integral>(from);
|
||||
const auto b = static_cast<integral>(to);
|
||||
const auto excl = dpf::detail::incr::cmp_exclusive_end(b);
|
||||
const auto count = dpf::detail::incr::cmp_inclusive_count(a, b);
|
||||
constexpr std::size_t stop =
|
||||
KT0::cmp_depth == 0 ? KT0::depth : KT0::cmp_depth;
|
||||
const std::size_t levels = KT0::cmp_h;
|
||||
|
||||
dpf::detail::incr::cmp_full_interval_memo<KT0, stop> memo0{count};
|
||||
dpf::detail::incr::cmp_full_interval_memo<KT1, stop> memo1{count};
|
||||
dpf::detail::incr::eval_cmp_interval_impl_interior(k0, a, excl, nbits, memo0,
|
||||
levels, nullptr);
|
||||
dpf::detail::incr::eval_cmp_interval_impl_interior(k1, a, excl, nbits, memo1,
|
||||
levels, nullptr);
|
||||
|
||||
const integral lane = static_cast<integral>(alpha);
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
dpf::detail::vdpf::init_proof(pi0, k0);
|
||||
dpf::detail::vdpf::init_proof(pi1, k1);
|
||||
dpf::basic_path_memoizer<KT0> path0{};
|
||||
dpf::basic_path_memoizer<KT1> path1{};
|
||||
dpf::detail::blocked::eval_share_memo(k0, lane, a, excl, memo0, &pi0,
|
||||
&path0);
|
||||
dpf::detail::blocked::eval_share_memo(k1, lane, a, excl, memo1, &pi1,
|
||||
&path1);
|
||||
EXPECT_TRUE(dpf::verify(pi0, pi1));
|
||||
|
||||
const auto snap0 = pi0;
|
||||
dpf::detail::blocked::eval_share_memo(k0, lane, a, excl, memo0, &pi0,
|
||||
&path0);
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(pi0, snap0));
|
||||
|
||||
// CW tamper on party 0.
|
||||
auto k0_bad = k0;
|
||||
for (auto & cs : const_cast<typename KT0::correction_seeds_array &>(
|
||||
k0_bad.correction_seeds()))
|
||||
{
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(0x5a));
|
||||
}
|
||||
dpf::detail::incr::cmp_full_interval_memo<KT0, stop> memo_bad{count};
|
||||
dpf::detail::incr::eval_cmp_interval_impl_interior(k0_bad, a, excl, nbits,
|
||||
memo_bad, levels, nullptr);
|
||||
dpf::proof_token q0{}, q1{};
|
||||
dpf::detail::vdpf::init_proof(q0, k0_bad);
|
||||
dpf::detail::vdpf::init_proof(q1, k1);
|
||||
dpf::basic_path_memoizer<KT0> pb0{};
|
||||
dpf::basic_path_memoizer<KT1> pb1{};
|
||||
dpf::detail::blocked::eval_share_memo(k0_bad, lane, a, excl, memo_bad,
|
||||
&q0, &pb0);
|
||||
dpf::detail::blocked::eval_share_memo(k1, lane, a, excl, memo1, &q1,
|
||||
&pb1);
|
||||
EXPECT_FALSE(dpf::verify(q0, q1));
|
||||
}
|
||||
|
||||
TEST(OptInMemoProve, BfsIntervalProveUnchanged)
|
||||
{
|
||||
const Input alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::lt(u64{1})), dpf::verifiable{});
|
||||
const Input from = 0x00;
|
||||
const Input to = 0x3f;
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_interval(k0, from, to, dpf::prove(a));
|
||||
dpf::prove_cmp_interval(k1, from, to, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
|
||||
// Re-proving the same interval yields the same BFS transcript.
|
||||
dpf::proof_token a2{}, b2{};
|
||||
dpf::prove_cmp_interval(k0, from, to, dpf::prove(a2));
|
||||
dpf::prove_cmp_interval(k1, from, to, dpf::prove(b2));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(a, a2));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(b, b2));
|
||||
|
||||
// Interval eval with prove still verifies (memo walk does not fold again).
|
||||
auto buf0 = dpf::eval_interval(dpf::cmp, k0, from, to, dpf::prove(a2));
|
||||
auto buf1 = dpf::eval_interval(dpf::cmp, k1, from, to, dpf::prove(b2));
|
||||
EXPECT_TRUE(dpf::verify(a2, b2));
|
||||
EXPECT_EQ(buf0.size(), buf1.size());
|
||||
}
|
||||
|
||||
TEST(OptInCarry, VerifiableAndMacRoundTrip)
|
||||
{
|
||||
grotto::carry_auth auth{};
|
||||
auth.verifiable = true;
|
||||
auth.output_mac = true;
|
||||
auto keys = grotto::make_carry_in_keys(8, 3, auth);
|
||||
ASSERT_TRUE(keys.has_mac);
|
||||
ASSERT_TRUE(keys.low_lt_v.has_value());
|
||||
const std::uint64_t opened = 0x3cu;
|
||||
dpf::proof_token t0[4]{}, t1[4]{};
|
||||
const auto n0 = grotto::prove_carry_keys(keys, 0, opened, t0, 4);
|
||||
const auto n1 = grotto::prove_carry_keys(keys, 1, opened, t1, 4);
|
||||
EXPECT_EQ(n0, n1);
|
||||
EXPECT_TRUE(dpf::verify_batch(
|
||||
std::vector<dpf::proof_token>(t0, t0 + n0),
|
||||
std::vector<dpf::proof_token>(t1, t1 + n1)));
|
||||
const auto y0 = grotto::eval_carry_in(keys, 0, opened);
|
||||
const auto y1 = grotto::eval_carry_in(keys, 1, opened);
|
||||
auto [m0, m1] = grotto::mac_carry_result(keys, y0.value, y1.value);
|
||||
EXPECT_TRUE(dpf::mac_verify(m0, m1, keys.mac, t0[0], t1[0]));
|
||||
}
|
||||
94
test/tests/path_sketch_test.cpp
Normal file
94
test/tests/path_sketch_test.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::vector<dpf::fp61> random_challenges(std::size_t n)
|
||||
{
|
||||
std::vector<dpf::fp61> rs(n);
|
||||
for (auto & r : rs)
|
||||
r = dpf::uniform_sample<dpf::fp61>();
|
||||
return rs;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(PathSketch, HonestIdpfAccepts)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0xA0; // prefix 1010_0000 → length-3 prefix 101
|
||||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||||
dpf::idpf(std::uint64_t{5}, std::uint64_t{5}, std::uint64_t{5}));
|
||||
|
||||
const auto rs = random_challenges(dpf::path_sketch_challenge_count(3));
|
||||
EXPECT_TRUE((dpf::verify_idpf_path<3>(k0, k1, rs)));
|
||||
}
|
||||
|
||||
TEST(PathSketch, HonestUnitWeightAccepts)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0b10100101},
|
||||
dpf::idpf(std::uint64_t{1}, std::uint64_t{1}, std::uint64_t{1},
|
||||
std::uint64_t{1}));
|
||||
const auto rs = random_challenges(dpf::path_sketch_challenge_count(4));
|
||||
EXPECT_TRUE((dpf::verify_idpf_path<4>(k0, k1, rs)));
|
||||
}
|
||||
|
||||
TEST(PathSketch, LevelSketchRejectsTwoHot)
|
||||
{
|
||||
// Manufacture a two-hot vector and feed sketch_fold directly.
|
||||
std::vector<dpf::fp61> y0 = {dpf::fp61{3}, dpf::fp61{0}, dpf::fp61{0},
|
||||
dpf::fp61{0}};
|
||||
std::vector<dpf::fp61> y1 = {dpf::fp61{0}, dpf::fp61{0}, dpf::fp61{2},
|
||||
dpf::fp61{0}};
|
||||
// Opened values: 3, 0, -2, 0 — two nonzeros.
|
||||
std::vector<dpf::fp61> r = {dpf::fp61{9}, dpf::fp61{4}, dpf::fp61{7},
|
||||
dpf::fp61{2}};
|
||||
const auto s0 = dpf::sketch_fold(y0, r);
|
||||
const auto s1 = dpf::sketch_fold(y1, r);
|
||||
EXPECT_FALSE(dpf::sketch_verify(s0, s1));
|
||||
}
|
||||
|
||||
TEST(PathSketch, ParentGapRejectsBrokenRelation)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0xA0},
|
||||
dpf::idpf(std::uint64_t{5}, std::uint64_t{5}, std::uint64_t{5}));
|
||||
|
||||
auto gamma = random_challenges(2);
|
||||
// Honest parent gap opens to 0.
|
||||
const auto g0 = dpf::sketch_path_parent(dpf::out<0, 1>, dpf::out<1, 2>,
|
||||
k0, gamma.data(), gamma.size());
|
||||
const auto g1 = dpf::sketch_path_parent(dpf::out<0, 1>, dpf::out<1, 2>,
|
||||
k1, gamma.data(), gamma.size());
|
||||
EXPECT_TRUE(dpf::sketch_path_verify_parent(g0, g1));
|
||||
|
||||
// Bias party 0's gap — must reject.
|
||||
EXPECT_FALSE(dpf::sketch_path_verify_parent(g0 + dpf::fp61{1}, g1));
|
||||
}
|
||||
|
||||
TEST(PathSketch, ChallengeCountFormula)
|
||||
{
|
||||
// Depth 1: only level sketch at N=1 → 2 challenges.
|
||||
EXPECT_EQ(dpf::path_sketch_challenge_count(1), 2u);
|
||||
// Depth 2: level1 (2) + parent(2) + level2 (4) = 8.
|
||||
EXPECT_EQ(dpf::path_sketch_challenge_count(2), 8u);
|
||||
// Depth 3: 8 + parent@2 (4) + level3 (8) = 20.
|
||||
EXPECT_EQ(dpf::path_sketch_challenge_count(3), 20u);
|
||||
}
|
||||
|
||||
TEST(PathSketch, MasticClientsVerify)
|
||||
{
|
||||
auto [a0, a1] = dpf::make_dpf(std::uint8_t{0xA0},
|
||||
dpf::idpf(std::uint64_t{5}, std::uint64_t{5}, std::uint64_t{5}));
|
||||
auto [b0, b1] = dpf::make_dpf(std::uint8_t{0xB0},
|
||||
dpf::idpf(std::uint64_t{3}, std::uint64_t{3}, std::uint64_t{3}));
|
||||
const auto rs = random_challenges(dpf::path_sketch_challenge_count(3));
|
||||
EXPECT_TRUE((dpf::verify_idpf_path<3>(a0, a1, rs)));
|
||||
EXPECT_TRUE((dpf::verify_idpf_path<3>(b0, b1, rs)));
|
||||
}
|
||||
331
test/tests/pprf_test.cpp
Normal file
331
test/tests/pprf_test.cpp
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename Block>
|
||||
bool eq_block(const Block & a, const Block & b)
|
||||
{
|
||||
return std::memcmp(&a, &b, sizeof(Block)) == 0;
|
||||
}
|
||||
|
||||
template <typename InputT>
|
||||
void check_pprf_spread(InputT alpha)
|
||||
{
|
||||
auto master = dpf::make_pprf_master<InputT>();
|
||||
auto punctured = dpf::puncture(master, alpha, /*program_alpha=*/true);
|
||||
|
||||
const auto master_at_alpha = dpf::pprf_eval(master, alpha);
|
||||
EXPECT_TRUE(eq_block(master_at_alpha, *punctured.programmed));
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(punctured, alpha), master_at_alpha));
|
||||
|
||||
std::vector<InputT> points = {
|
||||
InputT{0},
|
||||
InputT{1},
|
||||
static_cast<InputT>(alpha + 1),
|
||||
static_cast<InputT>(alpha ^ InputT{1}),
|
||||
};
|
||||
if constexpr (dpf::utils::bitlength_of_v<InputT> > 8)
|
||||
{
|
||||
points.push_back(static_cast<InputT>(
|
||||
InputT{1} << (dpf::utils::bitlength_of_v<InputT> / 2)));
|
||||
points.push_back(static_cast<InputT>(alpha ^ (InputT{1} << 7)));
|
||||
}
|
||||
|
||||
for (InputT x : points)
|
||||
{
|
||||
if (x == alpha)
|
||||
continue;
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(master, x),
|
||||
dpf::pprf_eval(punctured, x)));
|
||||
}
|
||||
|
||||
auto bare = dpf::puncture(master, alpha, /*program_alpha=*/false);
|
||||
EXPECT_FALSE(bare.programmed.has_value());
|
||||
EXPECT_THROW((void)dpf::pprf_eval(bare, alpha), std::invalid_argument);
|
||||
for (InputT x : points)
|
||||
{
|
||||
if (x == alpha)
|
||||
continue;
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(master, x),
|
||||
dpf::pprf_eval(bare, x)));
|
||||
}
|
||||
|
||||
EXPECT_EQ(punctured.siblings.size(), dpf::utils::bitlength_of_v<InputT>);
|
||||
}
|
||||
|
||||
bool node_has_seed(const dpf::pprf_copath<std::uint8_t> & copath,
|
||||
const dpf::prg::aes128::block_type & seed)
|
||||
{
|
||||
for (const auto & node : copath.nodes)
|
||||
{
|
||||
if (eq_block(node.seed, seed))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Pprf, PathBitHelpersAreConstexpr)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
static_assert(dpf::detail::pprf_impl::path_bit<input_t>(0b1010'0000u, 0));
|
||||
static_assert(!dpf::detail::pprf_impl::path_bit<input_t>(0b0010'0000u, 0));
|
||||
static_assert(dpf::detail::pprf_impl::path_prefix<input_t>(0b1010'1100u, 4)
|
||||
== input_t{0b1010u});
|
||||
static_assert(dpf::detail::pprf_impl::prefix_matches<input_t>(
|
||||
0b1010'1100u, 4, input_t{0b1010u}));
|
||||
static_assert(!dpf::detail::pprf_impl::prefix_matches<input_t>(
|
||||
0b1010'1100u, 4, input_t{0b1011u}));
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(Pprf, Uint32)
|
||||
{
|
||||
check_pprf_spread<std::uint32_t>(0x00c0ffeeu);
|
||||
}
|
||||
|
||||
TEST(Pprf, Uint128)
|
||||
{
|
||||
using input_t = simde_uint128;
|
||||
const input_t alpha = (input_t{1} << 120) | input_t{0xdeadbeefull};
|
||||
check_pprf_spread<input_t>(alpha);
|
||||
}
|
||||
|
||||
TEST(PprfCopath, OneHiddenAgreesWithOnePoint)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
constexpr std::size_t n = dpf::utils::bitlength_of_v<input_t>;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
const input_t alpha = 0x2au;
|
||||
|
||||
auto one = dpf::puncture(master, alpha, /*program_alpha=*/true);
|
||||
const input_t hidden[] = {alpha};
|
||||
auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden),
|
||||
/*program_hidden=*/true);
|
||||
|
||||
ASSERT_EQ(copath.nodes.size(), n);
|
||||
ASSERT_EQ(copath.programmed.size(), 1u);
|
||||
EXPECT_TRUE(eq_block(copath.programmed[0], *one.programmed));
|
||||
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
EXPECT_EQ(copath.nodes[i].level, i + 1);
|
||||
EXPECT_TRUE(eq_block(copath.nodes[i].seed, one.siblings[i]));
|
||||
EXPECT_EQ(copath.nodes[i].prefix,
|
||||
dpf::detail::pprf_impl::path_prefix(
|
||||
static_cast<input_t>(
|
||||
alpha ^ static_cast<input_t>(input_t{1} << (n - 1 - i))),
|
||||
i + 1));
|
||||
}
|
||||
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(one, xi)));
|
||||
if (xi != alpha)
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
|
||||
auto bare = dpf::puncture(master, std::begin(hidden), std::end(hidden),
|
||||
/*program_hidden=*/false);
|
||||
EXPECT_TRUE(bare.programmed.empty());
|
||||
EXPECT_THROW((void)dpf::pprf_eval(bare, alpha), std::invalid_argument);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
if (xi == alpha)
|
||||
continue;
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(bare, xi), dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PprfCopath, AdjacentAndSeparatedSets)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
constexpr std::size_t n = dpf::utils::bitlength_of_v<input_t>;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
|
||||
const input_t adjacent[] = {2, 3};
|
||||
auto adj = dpf::puncture(master, std::begin(adjacent), std::end(adjacent));
|
||||
// Share a 7-bit prefix: one sibling per shared level, none at the leaves.
|
||||
EXPECT_EQ(adj.nodes.size(), n - 1);
|
||||
EXPECT_LE(adj.nodes.size(), n * adj.hidden.size());
|
||||
EXPECT_LT(adj.nodes.size(), 2 * n);
|
||||
|
||||
const input_t separated[] = {2, 5};
|
||||
auto sep = dpf::puncture(master, std::begin(separated), std::end(separated));
|
||||
EXPECT_LE(sep.nodes.size(), n * sep.hidden.size());
|
||||
EXPECT_GT(sep.nodes.size(), adj.nodes.size());
|
||||
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
const bool in_adj = (xi == 2 || xi == 3);
|
||||
const bool in_sep = (xi == 2 || xi == 5);
|
||||
if (in_adj)
|
||||
EXPECT_THROW((void)dpf::pprf_eval(adj, xi), std::invalid_argument);
|
||||
else
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(adj, xi), dpf::pprf_eval(master, xi)));
|
||||
if (in_sep)
|
||||
EXPECT_THROW((void)dpf::pprf_eval(sep, xi), std::invalid_argument);
|
||||
else
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(sep, xi), dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PprfCopath, HiddenLeafNotPublishedWhenSiblingHidden)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
const input_t leaf = 2;
|
||||
const auto leaf_seed = dpf::pprf_eval(master, leaf);
|
||||
|
||||
const input_t alone[] = {leaf};
|
||||
auto solo = dpf::puncture(master, std::begin(alone), std::end(alone),
|
||||
/*program_hidden=*/true);
|
||||
EXPECT_TRUE(node_has_seed(solo, dpf::pprf_eval(master, static_cast<input_t>(3))));
|
||||
|
||||
const input_t both[] = {2, 3};
|
||||
auto pair = dpf::puncture(master, std::begin(both), std::end(both),
|
||||
/*program_hidden=*/true);
|
||||
EXPECT_FALSE(node_has_seed(pair, leaf_seed));
|
||||
EXPECT_FALSE(node_has_seed(pair, dpf::pprf_eval(master, static_cast<input_t>(3))));
|
||||
ASSERT_EQ(pair.programmed.size(), 2u);
|
||||
EXPECT_TRUE(eq_block(pair.programmed[0], leaf_seed));
|
||||
EXPECT_TRUE(eq_block(pair.programmed[1],
|
||||
dpf::pprf_eval(master, static_cast<input_t>(3))));
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(pair, leaf), leaf_seed));
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(pair, static_cast<input_t>(3)),
|
||||
dpf::pprf_eval(master, static_cast<input_t>(3))));
|
||||
}
|
||||
|
||||
TEST(PprfCopath, EmptyAndDuplicates)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
|
||||
const std::vector<input_t> none{};
|
||||
auto empty = dpf::puncture(master, none.begin(), none.end());
|
||||
ASSERT_EQ(empty.nodes.size(), 1u);
|
||||
EXPECT_EQ(empty.nodes[0].level, 0u);
|
||||
EXPECT_EQ(empty.nodes[0].prefix, input_t{});
|
||||
EXPECT_TRUE(eq_block(empty.nodes[0].seed, master.root));
|
||||
EXPECT_TRUE(empty.hidden.empty());
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(empty, xi), dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
|
||||
const input_t dups[] = {5, 2, 5, 2, 5};
|
||||
auto once = dpf::puncture(master, std::begin(dups), std::end(dups));
|
||||
const input_t unique[] = {2, 5};
|
||||
auto clean = dpf::puncture(master, std::begin(unique), std::end(unique));
|
||||
ASSERT_EQ(once.hidden.size(), 2u);
|
||||
EXPECT_EQ(once.hidden, clean.hidden);
|
||||
ASSERT_EQ(once.nodes.size(), clean.nodes.size());
|
||||
for (std::size_t i = 0; i < once.nodes.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(once.nodes[i].level, clean.nodes[i].level);
|
||||
EXPECT_EQ(once.nodes[i].prefix, clean.nodes[i].prefix);
|
||||
EXPECT_TRUE(eq_block(once.nodes[i].seed, clean.nodes[i].seed));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PprfCopath, FullDomainPublishesNothing)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
std::vector<input_t> all(256);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
all[x] = static_cast<input_t>(x);
|
||||
|
||||
auto bare = dpf::puncture(master, all.begin(), all.end(),
|
||||
/*program_hidden=*/false);
|
||||
EXPECT_TRUE(bare.nodes.empty());
|
||||
ASSERT_EQ(bare.hidden.size(), 256u);
|
||||
EXPECT_TRUE(bare.programmed.empty());
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
EXPECT_THROW((void)dpf::pprf_eval(bare, static_cast<input_t>(x)),
|
||||
std::invalid_argument);
|
||||
|
||||
auto programmed = dpf::puncture(master, all.begin(), all.end(),
|
||||
/*program_hidden=*/true);
|
||||
EXPECT_TRUE(programmed.nodes.empty());
|
||||
ASSERT_EQ(programmed.programmed.size(), 256u);
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(programmed, xi),
|
||||
dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PprfCopath, NodePrefixesAreConsistent)
|
||||
{
|
||||
using input_t = std::uint8_t;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
const input_t hidden[] = {2, 5, 200};
|
||||
auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden));
|
||||
|
||||
EXPECT_LE(copath.nodes.size(),
|
||||
dpf::utils::bitlength_of_v<input_t> * copath.hidden.size());
|
||||
|
||||
for (const auto & node : copath.nodes)
|
||||
{
|
||||
ASSERT_GE(node.level, 1u);
|
||||
ASSERT_LE(node.level, dpf::utils::bitlength_of_v<input_t>);
|
||||
// Right-aligned prefix fits in `level` bits.
|
||||
if (node.level < dpf::utils::bitlength_of_v<input_t>)
|
||||
{
|
||||
EXPECT_LT(static_cast<unsigned>(node.prefix),
|
||||
1u << node.level);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto xi = static_cast<input_t>(x);
|
||||
if (std::binary_search(std::begin(hidden), std::end(hidden), xi))
|
||||
continue;
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(master, xi)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PprfCopath, Uint32SetMatchesMaster)
|
||||
{
|
||||
using input_t = std::uint32_t;
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
const input_t hidden[] = {0u, 0x00c0ffeeu, 0xffffffffu};
|
||||
auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden),
|
||||
/*program_hidden=*/true);
|
||||
|
||||
ASSERT_EQ(copath.programmed.size(), 3u);
|
||||
for (input_t h : hidden)
|
||||
EXPECT_TRUE(eq_block(dpf::pprf_eval(copath, h), dpf::pprf_eval(master, h)));
|
||||
|
||||
const input_t samples[] = {1u, 2u, 0x00c0ffefu, 0x80000000u, 0xfffffffeu};
|
||||
for (input_t x : samples)
|
||||
{
|
||||
if (std::find(std::begin(hidden), std::end(hidden), x) != std::end(hidden))
|
||||
continue;
|
||||
EXPECT_TRUE(
|
||||
eq_block(dpf::pprf_eval(copath, x), dpf::pprf_eval(master, x)));
|
||||
}
|
||||
}
|
||||
|
|
@ -439,3 +439,41 @@ TEST(KPpvc, RejectsASharedHiddenIndex)
|
|||
EXPECT_EQ(st.copies[0].i, st.copies[1].i);
|
||||
EXPECT_FALSE(scheme::check_well_formed(pp, com, st));
|
||||
}
|
||||
|
||||
TEST(Ppvc, AuditedReplicaReexpandsFromCopath)
|
||||
{
|
||||
using scheme = dpf::ppvc<std::uint8_t, 2, 8>;
|
||||
using input_t = std::uint8_t;
|
||||
constexpr input_t live = 1;
|
||||
constexpr input_t audited = 3;
|
||||
constexpr input_t pool[] = {0, 1, 2, 3};
|
||||
|
||||
const auto pp = scheme::setup_from_seed(seed_block(90, 91));
|
||||
auto master = dpf::make_pprf_master<input_t>();
|
||||
|
||||
std::array<scheme::commitment, 4> commitments{};
|
||||
std::array<scheme::state, 4> states{};
|
||||
std::array<block, 4> seeds{};
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
seeds[i] = dpf::pprf_eval(master, pool[i]);
|
||||
auto made = scheme::commit_from_seed(pp, seeds[i]);
|
||||
commitments[i] = std::move(made.first);
|
||||
states[i] = std::move(made.second);
|
||||
ASSERT_TRUE(scheme::check_well_formed(pp, commitments[i], states[i]));
|
||||
}
|
||||
|
||||
const input_t hidden[] = {live};
|
||||
auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden),
|
||||
/*program_hidden=*/false);
|
||||
|
||||
EXPECT_THROW((void)dpf::pprf_eval(copath, live), std::invalid_argument);
|
||||
|
||||
const auto opened = dpf::pprf_eval(copath, audited);
|
||||
EXPECT_TRUE(same_root(opened, seeds[3]));
|
||||
|
||||
auto [com_again, st_again] = scheme::commit_from_seed(pp, opened);
|
||||
EXPECT_TRUE(scheme::check_well_formed(pp, com_again, st_again));
|
||||
EXPECT_TRUE(scheme::audit(pp, commitments[3], opened));
|
||||
EXPECT_EQ(st_again.i, states[3].i);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/principal_lut.hpp"
|
||||
|
||||
|
|
|
|||
1047
test/tests/protocol_batch_test.cpp
Normal file
1047
test/tests/protocol_batch_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
#include "dpf/random.hpp"
|
||||
#include <tuple>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
|
@ -311,7 +312,7 @@ TEST_F(RandomTest, ForkedProcessesDoNotRepeatEntropy)
|
|||
{
|
||||
// A buffered stdio read of the device copies the unread buffer into the
|
||||
// child. Prime the generator, then compare the next draw on each side.
|
||||
(void)dpf::uniform_sample<std::uint64_t>();
|
||||
dpf::uniform_sample<std::uint64_t>();
|
||||
|
||||
int fds[2];
|
||||
ASSERT_EQ(::pipe(fds), 0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/range_lut.hpp"
|
||||
|
||||
|
|
@ -103,16 +104,26 @@ bool near_odd_multiple_of_half_pi(long double x)
|
|||
|
||||
TEST(RangeLut, LogarithmsTrackLibmOnEveryPrecision)
|
||||
{
|
||||
const grotto::reduced maps[] = {
|
||||
grotto::reduced::ln, grotto::reduced::lg, grotto::reduced::log10,
|
||||
};
|
||||
const long double samples[] = {
|
||||
0.125L, 0.3L, 0.5L, 0.75L, 1.0L, 1.5L, 2.0L, 3.0L, 7.5L, 16.0L, 24.0L, 100.0L,
|
||||
};
|
||||
for (auto which : maps)
|
||||
for (unsigned k : grotto::principal_precisions)
|
||||
for (long double x : samples)
|
||||
expect_ulps(which, k, x, 6.0L);
|
||||
for (unsigned k : grotto::principal_precisions)
|
||||
{
|
||||
for (long double x : samples)
|
||||
{
|
||||
expect_ulps(grotto::reduced::ln, k, x, 1.0L);
|
||||
expect_ulps(grotto::reduced::log10, k, x, 1.0L);
|
||||
expect_ulps(grotto::reduced::lg, k, x, 2.0L);
|
||||
}
|
||||
for (int exp : {-20, 20})
|
||||
{
|
||||
const long double x = std::ldexp(1.0L, exp);
|
||||
if (raw_of(x, k) == 0)
|
||||
continue;
|
||||
expect_ulps(grotto::reduced::ln, k, x, 1.0L);
|
||||
expect_ulps(grotto::reduced::log10, k, x, 1.0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeLut, ExponentialsTrackLibmOnEveryPrecision)
|
||||
|
|
@ -120,10 +131,12 @@ TEST(RangeLut, ExponentialsTrackLibmOnEveryPrecision)
|
|||
const long double samples[] = {
|
||||
-2.0L, -1.5L, -0.5L, -0.1L, 0.0L, 0.1L, 0.5L, 1.0L, 1.5L, 2.0L,
|
||||
};
|
||||
for (auto which : {grotto::reduced::exp, grotto::reduced::exp2})
|
||||
for (unsigned k : grotto::principal_precisions)
|
||||
for (long double x : samples)
|
||||
expect_ulps(which, k, x, 16.0L);
|
||||
for (unsigned k : grotto::principal_precisions)
|
||||
for (long double x : samples)
|
||||
{
|
||||
expect_ulps(grotto::reduced::exp, k, x, 1.0L);
|
||||
expect_ulps(grotto::reduced::exp2, k, x, 2.0L);
|
||||
}
|
||||
for (unsigned k : grotto::principal_precisions)
|
||||
{
|
||||
for (long double x : {-0.9L, -0.25L, 0.0L, 0.25L, 0.9L})
|
||||
|
|
@ -259,7 +272,7 @@ TEST(RangeLut, TanAndSecPolesThrow)
|
|||
{
|
||||
try
|
||||
{
|
||||
(void)grotto::eval_reduced(which, k, raw);
|
||||
grotto::eval_reduced(which, k, raw);
|
||||
}
|
||||
catch (const std::domain_error &)
|
||||
{
|
||||
|
|
@ -290,7 +303,7 @@ TEST(RangeLut, Exp10RejectsAnIntegerPowerPast18)
|
|||
{
|
||||
try
|
||||
{
|
||||
(void)grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(static_cast<long double>(n), k));
|
||||
grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(static_cast<long double>(n), k));
|
||||
}
|
||||
catch (const std::overflow_error &)
|
||||
{
|
||||
|
|
@ -302,10 +315,8 @@ TEST(RangeLut, Exp10RejectsAnIntegerPowerPast18)
|
|||
ASSERT_LE(first_overflow, 19);
|
||||
EXPECT_NO_THROW(grotto::eval_reduced(grotto::reduced::exp10, k,
|
||||
raw_of(static_cast<long double>(first_overflow - 1), k)));
|
||||
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(19.0L, k)),
|
||||
std::overflow_error);
|
||||
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(-19.0L, k)),
|
||||
std::overflow_error);
|
||||
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(19.0L, k)), std::overflow_error);
|
||||
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(-19.0L, k)), std::overflow_error);
|
||||
}
|
||||
|
||||
TEST(RangeLut, Expm1AndLog1pTrackLibm)
|
||||
|
|
@ -325,9 +336,28 @@ TEST(RangeLut, Expm1AndLog1pTrackLibm)
|
|||
expect_ulps(grotto::reduced::log1p, k, std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
||||
expect_ulps(grotto::reduced::log1p, k, -std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
||||
for (long double x : expm1_samples)
|
||||
expect_ulps(grotto::reduced::expm1, k, x, 20.0L);
|
||||
expect_ulps(grotto::reduced::expm1, k, x, 1.0L);
|
||||
for (long double x : log1p_samples)
|
||||
expect_ulps(grotto::reduced::log1p, k, x, 8.0L);
|
||||
expect_ulps(grotto::reduced::log1p, k, x, 1.0L);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeLut, ReciprocalFamilyStaysInsideOneUlpAfterTheLift)
|
||||
{
|
||||
const long double samples[] = {
|
||||
std::ldexp(1.0L, -12), 0.015625L, 0.125L, 0.5L, 0.75L, 1.0L, 1.5L, 3.0L, 8.0L, 100.0L,
|
||||
};
|
||||
for (auto which : {grotto::reduced::inv, grotto::reduced::rsqrt, grotto::reduced::invsq})
|
||||
{
|
||||
for (unsigned k : {16u, 32u})
|
||||
{
|
||||
for (long double x : samples)
|
||||
{
|
||||
if (k == 16 && x < std::ldexp(1.0L, -10))
|
||||
continue;
|
||||
expect_ulps(which, k, x, 1.0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
128
test/tests/ring_switch_test.cpp
Normal file
128
test/tests/ring_switch_test.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "grotto/ring_switch.hpp"
|
||||
|
||||
#include "dpf/field128.hpp"
|
||||
#include "dpf/p256_scalar.hpp"
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
TEST(Residue, Zn64Arithmetic)
|
||||
{
|
||||
using Z = grotto::zn64<7>;
|
||||
EXPECT_EQ((Z{3} + Z{5}).raw(), 1u);
|
||||
EXPECT_EQ((-Z{3}).raw(), 4u);
|
||||
EXPECT_EQ((Z{3} - Z{5}).raw(), 5u);
|
||||
}
|
||||
|
||||
TEST(Residue, Zn128Arithmetic)
|
||||
{
|
||||
using Z = grotto::zn128<1009, 0>;
|
||||
EXPECT_EQ((Z{1000} + Z{20}).lo(), 11u);
|
||||
EXPECT_EQ((-Z{1}).lo(), 1008u);
|
||||
}
|
||||
|
||||
TEST(Residue, CrtFactor)
|
||||
{
|
||||
using Z = grotto::zn64<15>;
|
||||
const Z share{11};
|
||||
EXPECT_EQ(grotto::ring_switch_factor<3>(share).raw(), 2u);
|
||||
EXPECT_EQ(grotto::ring_switch_factor<5>(share).raw(), 1u);
|
||||
}
|
||||
|
||||
TEST(RingSwitch, Zn64WrapAndNoWrap)
|
||||
{
|
||||
using Z = grotto::zn64<1009>;
|
||||
const std::uint8_t r = 200;
|
||||
const std::uint8_t eta_nw = 10; // 200+10 < 256
|
||||
const std::uint8_t eta_w = 100; // 200+100 >= 256
|
||||
const std::uint8_t x_nw = static_cast<std::uint8_t>(r + eta_nw);
|
||||
const std::uint8_t x_w = static_cast<std::uint8_t>(r + eta_w);
|
||||
|
||||
auto mat = grotto::make_ring_switch_keys<Z>(r);
|
||||
const Z s0 = grotto::ring_switch_eval<0>(mat, eta_nw);
|
||||
const Z s1 = grotto::ring_switch_eval<1>(mat, eta_nw);
|
||||
EXPECT_EQ(s0 + s1, grotto::ring_switch_clear<Z>(x_nw, r, eta_nw));
|
||||
EXPECT_EQ((s0 + s1).raw(), static_cast<std::uint64_t>(x_nw) % 1009);
|
||||
|
||||
const Z t0 = grotto::ring_switch_eval<0>(mat, eta_w);
|
||||
const Z t1 = grotto::ring_switch_eval<1>(mat, eta_w);
|
||||
EXPECT_EQ(t0 + t1, grotto::ring_switch_clear<Z>(x_w, r, eta_w));
|
||||
EXPECT_EQ((t0 + t1).raw(), static_cast<std::uint64_t>(x_w) % 1009);
|
||||
}
|
||||
|
||||
TEST(RingSwitch, Zn64EtaZero)
|
||||
{
|
||||
using Z = grotto::zn64<97>;
|
||||
const std::uint8_t r = 55;
|
||||
const std::uint8_t eta = 0;
|
||||
auto mat = grotto::make_ring_switch_keys<Z>(r);
|
||||
const Z got = grotto::ring_switch_eval<0>(mat, eta)
|
||||
+ grotto::ring_switch_eval<1>(mat, eta);
|
||||
EXPECT_EQ(got.raw(), 55u);
|
||||
}
|
||||
|
||||
TEST(RingSwitch, FullWidthLimb)
|
||||
{
|
||||
using Z = grotto::zn64<10007>;
|
||||
const std::uint64_t r = 0xffff'ffff'ffff'ff00ull;
|
||||
const std::uint64_t eta = 0x200ull; // wraps
|
||||
const std::uint64_t x = r + eta; // wraps in uint64
|
||||
auto mat = grotto::make_ring_switch_keys<Z>(r);
|
||||
const Z got = grotto::ring_switch_eval<0>(mat, eta)
|
||||
+ grotto::ring_switch_eval<1>(mat, eta);
|
||||
EXPECT_EQ(got, grotto::ring_switch_clear<Z>(x, r, eta));
|
||||
EXPECT_EQ(got.raw(), x % 10007);
|
||||
}
|
||||
|
||||
TEST(RingSwitch, Field128)
|
||||
{
|
||||
const std::uint16_t r = 40000;
|
||||
const std::uint16_t eta = 30000; // wraps
|
||||
const std::uint16_t x = static_cast<std::uint16_t>(r + eta);
|
||||
auto mat = grotto::make_ring_switch_keys<dpf::field128>(r);
|
||||
const auto got = grotto::ring_switch_eval<0>(mat, eta)
|
||||
+ grotto::ring_switch_eval<1>(mat, eta);
|
||||
EXPECT_EQ(got, grotto::ring_switch_clear<dpf::field128>(x, r, eta));
|
||||
EXPECT_EQ(got, dpf::field128{x});
|
||||
}
|
||||
|
||||
TEST(RingSwitch, P256Scalar)
|
||||
{
|
||||
const std::uint8_t r = 200;
|
||||
const std::uint8_t eta = 100;
|
||||
const std::uint8_t x = static_cast<std::uint8_t>(r + eta);
|
||||
auto mat = grotto::make_ring_switch_keys<dpf::p256_scalar>(r);
|
||||
const auto got = grotto::ring_switch_eval<0>(mat, eta)
|
||||
+ grotto::ring_switch_eval<1>(mat, eta);
|
||||
EXPECT_EQ(got, grotto::ring_switch_clear<dpf::p256_scalar>(x, r, eta));
|
||||
EXPECT_EQ(got, dpf::p256_scalar{x});
|
||||
}
|
||||
|
||||
TEST(RingSwitch, Zn128)
|
||||
{
|
||||
using Z = grotto::zn128<0x9a57'0000'0000'0001ull, 0>;
|
||||
const std::uint8_t r = 10;
|
||||
const std::uint8_t eta = 20;
|
||||
const std::uint8_t x = 30;
|
||||
auto mat = grotto::make_ring_switch_keys<Z>(r);
|
||||
const Z got = grotto::ring_switch_eval<0>(mat, eta)
|
||||
+ grotto::ring_switch_eval<1>(mat, eta);
|
||||
EXPECT_EQ(got, grotto::ring_switch_clear<Z>(x, r, eta));
|
||||
EXPECT_EQ(got.lo(), 30u);
|
||||
}
|
||||
|
||||
TEST(RingSwitch, Verifiable)
|
||||
{
|
||||
using Z = grotto::zn64<1009>;
|
||||
const std::uint8_t r = 17;
|
||||
const std::uint8_t eta = 200;
|
||||
const std::uint8_t x = static_cast<std::uint8_t>(r + eta);
|
||||
auto mat = grotto::make_ring_switch_keys<Z>(r, dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
const Z s0 = grotto::ring_switch_eval<0>(mat, eta, &a);
|
||||
const Z s1 = grotto::ring_switch_eval<1>(mat, eta, &b);
|
||||
EXPECT_EQ(s0 + s1, grotto::ring_switch_clear<Z>(x, r, eta));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -10,6 +13,14 @@
|
|||
namespace
|
||||
{
|
||||
|
||||
struct dpf3_party_tag
|
||||
{
|
||||
static constexpr bool is_dpf3 = true;
|
||||
static constexpr int party = 3;
|
||||
};
|
||||
|
||||
|
||||
|
||||
TEST(SecretShare, LayoutMatchesValueType)
|
||||
{
|
||||
using T = std::uint64_t;
|
||||
|
|
@ -140,4 +151,365 @@ TEST(SecretShare, PrgExpandSubtractive)
|
|||
EXPECT_EQ(dpf::reconstruct(t0, t1), 0u);
|
||||
}
|
||||
|
||||
TEST(SecretShare, Additive3LayoutAndPlaintextSplit)
|
||||
{
|
||||
using T = std::uint64_t;
|
||||
static_assert(dpf::sharing_parties_v<dpf::sharing::additive3> == 3);
|
||||
static_assert(dpf::sharing_threshold_v<dpf::sharing::additive3> == 3);
|
||||
static_assert(dpf::is_secret_share_v<dpf::additive3_share<T, 2>>);
|
||||
static_assert(dpf::share_party_v<dpf::additive3_share<T, 2>> == 2u);
|
||||
static_assert(dpf::share_scheme_v<dpf::additive3_share<T, 1>>
|
||||
== dpf::sharing::additive3);
|
||||
EXPECT_EQ(sizeof(dpf::additive3_share<T, 2>), sizeof(T));
|
||||
EXPECT_TRUE((std::is_trivially_copyable_v<dpf::additive3_share<T, 2>>));
|
||||
EXPECT_TRUE((std::is_standard_layout_v<dpf::additive3_share<T, 0>>));
|
||||
|
||||
constexpr auto split = dpf::make_additive3_shares(std::uint32_t{9});
|
||||
static_assert(dpf::reconstruct(
|
||||
std::get<0>(split), std::get<1>(split), std::get<2>(split)) == 9u);
|
||||
|
||||
const std::uint32_t secret = 0xdeadbeefu;
|
||||
auto [a0, a1, a2] = dpf::make_additive3_shares(secret);
|
||||
EXPECT_EQ(a1.raw(), 0u);
|
||||
EXPECT_EQ(a2.raw(), 0u);
|
||||
EXPECT_EQ(dpf::reconstruct(a2, a0, a1), secret);
|
||||
}
|
||||
|
||||
TEST(SecretShare, Additive3LinearComboAndAbsorb)
|
||||
{
|
||||
auto [a0, a1, a2] = dpf::make_additive3_shares(std::uint32_t{10});
|
||||
auto [b0, b1, b2] = dpf::make_additive3_shares(std::uint32_t{3});
|
||||
auto c0 = a0 * 2 + b0;
|
||||
auto c1 = a1 * 2 + b1;
|
||||
auto c2 = a2 * 2 + b2;
|
||||
EXPECT_EQ(dpf::reconstruct(c0, c1, c2), 23u);
|
||||
|
||||
a0 += std::uint32_t{10};
|
||||
a1 += std::uint32_t{10};
|
||||
a2 += std::uint32_t{10};
|
||||
EXPECT_EQ(dpf::reconstruct(a0, a1, a2), 20u);
|
||||
|
||||
const auto raw = std::numeric_limits<std::int32_t>::min();
|
||||
auto s0 = dpf::additive3_share<std::int32_t, 0>::from_raw(raw);
|
||||
auto s1 = dpf::additive3_share<std::int32_t, 1>::from_raw(-1);
|
||||
auto s2 = dpf::additive3_share<std::int32_t, 2>::from_raw(1);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1, s2), raw);
|
||||
}
|
||||
|
||||
TEST(SecretShare, Additive3RandomAndXor)
|
||||
{
|
||||
using X = dpf::xor_wrapper<std::uint32_t>;
|
||||
const X secret{0xA5A5A5A5u};
|
||||
auto [x0, x1, x2] = dpf::additively_share3(secret);
|
||||
EXPECT_EQ(dpf::reconstruct(x0, x1, x2), secret);
|
||||
|
||||
const std::int32_t n = -42;
|
||||
auto [a, b, c] = dpf::additively_share3(n);
|
||||
EXPECT_EQ(dpf::reconstruct(c, b, a), n);
|
||||
|
||||
const float f = 1.5f;
|
||||
auto [f0, f1, f2] = dpf::additively_share3(f);
|
||||
EXPECT_EQ(dpf::reconstruct(f0, f1, f2), f);
|
||||
}
|
||||
|
||||
TEST(SecretShare, ReplicatedLayoutPlaintextAndPairs)
|
||||
{
|
||||
using T = std::uint32_t;
|
||||
static_assert(dpf::sharing_parties_v<dpf::sharing::replicated> == 3);
|
||||
static_assert(dpf::sharing_threshold_v<dpf::sharing::replicated> == 2);
|
||||
static_assert(dpf::share_scheme_v<dpf::replicated_share<T, 2>>
|
||||
== dpf::sharing::replicated);
|
||||
EXPECT_EQ(sizeof(dpf::replicated_share<T, 0>), 2 * sizeof(T));
|
||||
EXPECT_TRUE((std::is_trivially_copyable_v<dpf::replicated_share<T, 1>>));
|
||||
EXPECT_TRUE((std::is_standard_layout_v<dpf::replicated_share<T, 2>>));
|
||||
|
||||
constexpr auto split = dpf::make_replicated_shares(T{11});
|
||||
static_assert(dpf::reconstruct(std::get<0>(split), std::get<1>(split)) == 11u);
|
||||
static_assert(dpf::reconstruct(std::get<1>(split), std::get<2>(split)) == 11u);
|
||||
static_assert(dpf::reconstruct(std::get<2>(split), std::get<0>(split)) == 11u);
|
||||
|
||||
auto [r0, r1, r2] = dpf::make_replicated_shares(T{11});
|
||||
EXPECT_EQ(r0.own, 11u);
|
||||
EXPECT_EQ(r0.next, 0u);
|
||||
EXPECT_EQ(r1.own, 0u);
|
||||
EXPECT_EQ(r1.next, 0u);
|
||||
EXPECT_EQ(r2.own, 0u);
|
||||
EXPECT_EQ(r2.next, 11u);
|
||||
EXPECT_EQ(dpf::reconstruct(r0, r1, r2), 11u);
|
||||
EXPECT_EQ(r0.as_additive3().raw(), 11u);
|
||||
EXPECT_EQ(r0.next_additive3().raw(), r1.as_additive3().raw());
|
||||
}
|
||||
|
||||
TEST(SecretShare, ReplicatedLinearComboAbsorbAndAdditive3)
|
||||
{
|
||||
auto [a0, a1, a2] = dpf::make_replicated_shares(std::uint32_t{10});
|
||||
auto [b0, b1, b2] = dpf::make_replicated_shares(std::uint32_t{3});
|
||||
auto c0 = a0 * 2 - b0;
|
||||
auto c1 = a1 * 2 - b1;
|
||||
auto c2 = a2 * 2 - b2;
|
||||
EXPECT_EQ(dpf::reconstruct(c0, c1), 17u);
|
||||
EXPECT_EQ(dpf::reconstruct(c1, c2), 17u);
|
||||
EXPECT_EQ(dpf::reconstruct(c0, c2, c1), 17u);
|
||||
|
||||
c0 += std::uint32_t{4};
|
||||
c1 += std::uint32_t{4};
|
||||
c2 += std::uint32_t{4};
|
||||
EXPECT_EQ(c1.own, (a1 * 2 - b1).own);
|
||||
EXPECT_EQ(c1.next, (a1 * 2 - b1).next);
|
||||
EXPECT_EQ(dpf::reconstruct(c2, c0), 21u);
|
||||
|
||||
auto [d0, d1, d2] = dpf::make_additive3_shares(std::uint32_t{1});
|
||||
d1 = dpf::additive3_share<std::uint32_t, 1>::from_raw(2u);
|
||||
d2 = dpf::additive3_share<std::uint32_t, 2>::from_raw(4u);
|
||||
auto [e0, e1, e2] = dpf::add_replicated(c0, c1, c2, d0, d1, d2);
|
||||
EXPECT_EQ(e0.next, e1.own);
|
||||
EXPECT_EQ(e1.next, e2.own);
|
||||
EXPECT_EQ(e2.next, e0.own);
|
||||
EXPECT_EQ(dpf::reconstruct(e0, e1), 28u);
|
||||
EXPECT_EQ(dpf::reconstruct(e1, e2, e0), dpf::reconstruct(e0, e2));
|
||||
}
|
||||
|
||||
TEST(SecretShare, ReplicatedFromComponentsAndRandom)
|
||||
{
|
||||
using X = dpf::xor_wrapper<std::uint16_t>;
|
||||
const X secret{0xBEEFu};
|
||||
auto [a0, a1, a2] = dpf::additively_share3(secret);
|
||||
auto [r0, r1, r2] = dpf::make_replicated_shares(a0, a1, a2);
|
||||
EXPECT_EQ(r0.own, a0.raw());
|
||||
EXPECT_EQ(r0.next, a1.raw());
|
||||
EXPECT_EQ(r2.next, a0.raw());
|
||||
EXPECT_EQ(dpf::reconstruct(r2, r1), secret);
|
||||
EXPECT_EQ(dpf::reconstruct(r0.as_additive3(), r1.as_additive3(),
|
||||
r2.as_additive3()), secret);
|
||||
|
||||
auto built = dpf::replicated_share<X, 0>::from_additive3(
|
||||
a0, a1);
|
||||
EXPECT_EQ(built, r0);
|
||||
|
||||
const std::int64_t n = std::numeric_limits<std::int64_t>::min() + 7;
|
||||
auto [s0, s1, s2] = dpf::share_replicated(n);
|
||||
EXPECT_EQ(s0.next, s1.own);
|
||||
EXPECT_EQ(s1.next, s2.own);
|
||||
EXPECT_EQ(s2.next, s0.own);
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), n);
|
||||
EXPECT_EQ(dpf::reconstruct(s1, s2), n);
|
||||
EXPECT_EQ(dpf::reconstruct(s2, s0), n);
|
||||
EXPECT_EQ(dpf::reconstruct(s2, s1, s0), n);
|
||||
|
||||
auto [p0, p1, p2] = dpf::make_replicated_shares(std::uint32_t{8});
|
||||
dpf::replicated_share<std::uint32_t, 0> slot{};
|
||||
dpf::assign_share_slot(slot, p0);
|
||||
EXPECT_EQ(slot, p0);
|
||||
std::uint32_t word = 0;
|
||||
dpf::assign_share_slot(word, p0.as_additive3());
|
||||
EXPECT_EQ(word, 8u);
|
||||
(void)p1;
|
||||
(void)p2;
|
||||
}
|
||||
|
||||
TEST(SecretShare, TwoPartyConversionsPreserveSecret)
|
||||
{
|
||||
auto a0 = dpf::additive_share<std::int32_t, 0>::from_raw(10);
|
||||
auto a1 = dpf::additive_share<std::int32_t, 1>::from_raw(5);
|
||||
auto b0 = dpf::a2b(a0);
|
||||
auto b1 = dpf::a2b(a1);
|
||||
EXPECT_EQ(b0.raw(), 10);
|
||||
EXPECT_EQ(b1.raw(), -5);
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), 15);
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::b2a(b0), dpf::b2a(b1)), 15);
|
||||
|
||||
auto f0 = dpf::a2fss(a0);
|
||||
auto f1 = dpf::a2fss(a1);
|
||||
EXPECT_EQ(f1.raw(), -5);
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::fss2a(f0), dpf::fss2a(f1)), 15);
|
||||
EXPECT_EQ(dpf::fss2b(f1).raw(), f1.raw());
|
||||
EXPECT_EQ(dpf::b2fss(b1).raw(), b1.raw());
|
||||
|
||||
// Subtractive and FSS share a sign, so party 1 adds the raw words.
|
||||
auto mixed = b1 + f1;
|
||||
EXPECT_EQ(mixed.raw(), -10);
|
||||
// Additive and FSS disagree on party 1, so the FSS word is flipped.
|
||||
auto flipped = a1 + dpf::fss_share<std::int32_t, 1>::from_raw(4);
|
||||
EXPECT_EQ(flipped.raw(), 1);
|
||||
}
|
||||
|
||||
TEST(SecretShare, ShamirRoundTripAndReplicatedProduct)
|
||||
{
|
||||
const dpf::fp61 secret{20};
|
||||
const dpf::fp61 slope{3};
|
||||
auto [s0, s1, s2] = dpf::make_shamir_shares(secret, slope);
|
||||
EXPECT_EQ((dpf::shamir_share<dpf::fp61, 0>::point), 1u);
|
||||
EXPECT_EQ(s0.raw(), secret + slope * dpf::fp61{1});
|
||||
EXPECT_EQ(s1.raw(), secret + slope * dpf::fp61{2});
|
||||
EXPECT_EQ(s2.raw(), secret + slope * dpf::fp61{3});
|
||||
EXPECT_EQ(dpf::reconstruct(s0, s1), secret);
|
||||
EXPECT_EQ(dpf::reconstruct(s1, s2), secret);
|
||||
EXPECT_EQ(dpf::reconstruct(s2, s0, s1), secret);
|
||||
|
||||
s0 += dpf::fp61{4};
|
||||
s1 += dpf::fp61{4};
|
||||
s2 += dpf::fp61{4};
|
||||
EXPECT_EQ(dpf::reconstruct(s2, s0), secret + dpf::fp61{4});
|
||||
|
||||
auto [y0, y1, y2] = dpf::s2y(s0, s1);
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1, y2), secret + dpf::fp61{4});
|
||||
auto back = dpf::y2s(y0, y1, y2, slope);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<2>(back)),
|
||||
secret + dpf::fp61{4});
|
||||
|
||||
auto [r0, r1, r2] = dpf::s2rss(s0, s2);
|
||||
EXPECT_EQ(dpf::reconstruct(r0, r1), secret + dpf::fp61{4});
|
||||
auto shamir_again = dpf::rss2s(r1, r2, slope);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<1>(shamir_again), std::get<2>(shamir_again)),
|
||||
secret + dpf::fp61{4});
|
||||
|
||||
auto tagged = dpf::as_shamir_share(dpf3_party_tag{}, dpf::fp61{7});
|
||||
EXPECT_EQ(decltype(tagged)::party, 2u);
|
||||
EXPECT_EQ(tagged.raw(), dpf::fp61{7});
|
||||
|
||||
auto runtime = dpf::shamir3::runtime_share(s0);
|
||||
EXPECT_EQ(runtime.party, 1);
|
||||
EXPECT_EQ(dpf::shamir3::typed_share<0>(runtime), s0);
|
||||
EXPECT_EQ(dpf::shamir3::reconstruct(runtime,
|
||||
dpf::shamir3::runtime_share(s1)), secret + dpf::fp61{4});
|
||||
|
||||
auto [x0, x1, x2] = dpf::make_replicated_shares(
|
||||
std::uint32_t{3}, std::uint32_t{5}, std::uint32_t{1});
|
||||
auto [z0, z1, z2] = dpf::make_replicated_shares(
|
||||
std::uint32_t{4}, std::uint32_t{2}, std::uint32_t{6});
|
||||
EXPECT_EQ(dpf::reconstruct(dpf::rss_mul(x0, z0), dpf::rss_mul(x1, z1),
|
||||
dpf::rss_mul(x2, z2)), 108u);
|
||||
auto [p0, p1, p2] = dpf::rss_mul(x0, x1, x2, z0, z1, z2);
|
||||
EXPECT_EQ(p0.next, p1.own);
|
||||
EXPECT_EQ(dpf::reconstruct(p0, p2), 108u);
|
||||
|
||||
auto m0 = dpf::additive3_share<std::uint32_t, 0>::from_raw(9u);
|
||||
auto m1 = dpf::additive3_share<std::uint32_t, 1>::from_raw(1u);
|
||||
auto m2 = dpf::additive3_share<std::uint32_t, 2>::from_raw(
|
||||
static_cast<std::uint32_t>(0u - 9u - 1u));
|
||||
auto [q0, q1, q2] = dpf::rss_mul(x0, x1, x2, z0, z1, z2, m0, m1, m2);
|
||||
EXPECT_EQ(dpf::reconstruct(q1, q0), 108u);
|
||||
EXPECT_NE(q0.own, p0.own);
|
||||
(void)q2;
|
||||
|
||||
auto [h0, h1, h2] = dpf::y2rss(dpf::rss2y(p0), dpf::rss2y(p1), dpf::rss2y(p2));
|
||||
EXPECT_EQ(h0, p0);
|
||||
EXPECT_EQ(h1, p1);
|
||||
EXPECT_EQ(h2, p2);
|
||||
}
|
||||
|
||||
TEST(SecretShare, ShamirKnSpecializesTwoOfThree)
|
||||
{
|
||||
using F = dpf::fp61;
|
||||
static_assert(std::is_same_v<
|
||||
dpf::shamir::share<F, 0, 2, 3>, dpf::shamir_share<F, 0>>);
|
||||
static_assert(std::is_same_v<
|
||||
dpf::shamir::share<F, 2, 2, 3>, dpf::shamir_share<F, 2>>);
|
||||
static_assert(dpf::shamir::two_of_three::threshold == 2);
|
||||
static_assert(dpf::shamir::two_of_three::parties == 3);
|
||||
static_assert(dpf::shamir::two_of_three::degree == 1);
|
||||
static_assert(dpf::shamir_share<F, 1>::threshold == 2);
|
||||
static_assert(dpf::shamir_share<F, 1>::parties == 3);
|
||||
static_assert(!dpf::is_secret_share_v<dpf::shamir::share<F, 0, 3, 5>>);
|
||||
static_assert(dpf::shamir::is_share_v<dpf::shamir::share<F, 0, 3, 5>>);
|
||||
static_assert(dpf::shamir::is_share_v<dpf::shamir_share<F, 0>>);
|
||||
EXPECT_EQ(sizeof(dpf::shamir::share<F, 4, 3, 5>), sizeof(F));
|
||||
EXPECT_TRUE((std::is_trivially_copyable_v<dpf::shamir::share<F, 4, 3, 5>>));
|
||||
EXPECT_TRUE((std::is_standard_layout_v<dpf::shamir_share<F, 0>>));
|
||||
|
||||
// p(x) = 10 + 2x + 3x^2. Points 1..5 are 15, 26, 43, 66, 95.
|
||||
const F secret{10};
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const F expect[5] = {F{15}, F{26}, F{43}, F{66}, F{95}};
|
||||
auto dealt = dpf::make_shamir_shares<3, 5>(secret, coeff);
|
||||
const std::array<F, 5> raw{{
|
||||
std::get<0>(dealt).raw(), std::get<1>(dealt).raw(),
|
||||
std::get<2>(dealt).raw(), std::get<3>(dealt).raw(),
|
||||
std::get<4>(dealt).raw()}};
|
||||
for (int i = 0; i < 5; ++i)
|
||||
EXPECT_EQ(raw[static_cast<std::size_t>(i)], expect[i]);
|
||||
EXPECT_EQ(std::get<4>(dealt).point, 5u);
|
||||
EXPECT_EQ(std::get<4>(dealt).party, 4u);
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
for (int j = i + 1; j < 5; ++j)
|
||||
for (int k = j + 1; k < 5; ++k)
|
||||
{
|
||||
const std::array<dpf::shamir::point_share<F>, 3> subset{{
|
||||
{i + 1, raw[static_cast<std::size_t>(i)]},
|
||||
{j + 1, raw[static_cast<std::size_t>(j)]},
|
||||
{k + 1, raw[static_cast<std::size_t>(k)]}}};
|
||||
const F opened = dpf::shamir::reconstruct<F, 3, 5>(subset);
|
||||
EXPECT_EQ(opened, secret);
|
||||
}
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(
|
||||
std::get<0>(dealt), std::get<2>(dealt), std::get<4>(dealt)), secret);
|
||||
|
||||
auto scaled0 = std::get<0>(dealt) * F{3};
|
||||
auto scaled1 = std::get<1>(dealt) * F{3};
|
||||
auto scaled2 = std::get<2>(dealt) * F{3};
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(scaled0, scaled1, scaled2), secret * F{3});
|
||||
|
||||
auto sum0 = std::get<0>(dealt) + std::get<0>(dealt);
|
||||
auto sum1 = std::get<1>(dealt) + std::get<1>(dealt);
|
||||
auto sum2 = std::get<2>(dealt) + std::get<2>(dealt);
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(sum0, sum1, sum2), secret + secret);
|
||||
|
||||
std::get<0>(dealt) += F{4};
|
||||
std::get<1>(dealt) += F{4};
|
||||
std::get<2>(dealt) += F{4};
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(
|
||||
std::get<0>(dealt), std::get<1>(dealt), std::get<2>(dealt)),
|
||||
secret + F{4});
|
||||
|
||||
std::array<dpf::shamir::point_share<F>, 4> extra{{
|
||||
{1, raw[0]}, {2, raw[1]}, {3, raw[2]}, {4, raw[3]}}};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(extra)), secret);
|
||||
extra[3].value = extra[3].value + F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(extra)), std::runtime_error);
|
||||
|
||||
const std::array<dpf::shamir::point_share<F>, 2> too_few{{
|
||||
{1, raw[0]}, {2, raw[1]}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(too_few)),
|
||||
std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 2> dup{{
|
||||
{1, raw[0]}, {1, raw[0]}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 2, 5>(dup)), std::invalid_argument);
|
||||
|
||||
// Threshold 2 is not tied to three shareholders. p(x) = 8 + 5x.
|
||||
const auto line = dpf::shamir::deal<F, 2, 4>(F{8}, std::array<F, 1>{{F{5}}});
|
||||
EXPECT_EQ(std::get<0>(line).raw(), F{13});
|
||||
EXPECT_EQ(std::get<3>(line).raw(), F{28});
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(std::get<1>(line), std::get<3>(line)), F{8});
|
||||
|
||||
// Threshold 1 copies the secret. Threshold N needs every share.
|
||||
const auto copied = dpf::shamir::deal<F, 1, 3>(F{9}, std::array<F, 0>{});
|
||||
EXPECT_EQ(std::get<0>(copied).raw(), F{9});
|
||||
EXPECT_EQ(std::get<2>(copied).raw(), F{9});
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(std::get<1>(copied)), F{9});
|
||||
const auto plain = dpf::shamir::share_secret<F, 1, 3>(F{6});
|
||||
EXPECT_EQ(std::get<0>(plain).raw(), F{6});
|
||||
EXPECT_EQ(std::get<2>(plain).raw(), F{6});
|
||||
|
||||
const std::array<F, 3> dense{{F{1}, F{0}, F{4}}};
|
||||
const auto all = dpf::shamir::deal<F, 4, 4>(F{7}, dense);
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(std::get<0>(all), std::get<1>(all),
|
||||
std::get<2>(all), std::get<3>(all)), F{7});
|
||||
const std::array<dpf::shamir::point_share<F>, 3> missing{{
|
||||
{1, std::get<0>(all).raw()},
|
||||
{2, std::get<1>(all).raw()},
|
||||
{3, std::get<2>(all).raw()}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 4, 4>(missing)),
|
||||
std::invalid_argument);
|
||||
|
||||
const auto rnd = dpf::shamir::share_secret<F, 2, 3>(F{11});
|
||||
static_assert(std::is_same_v<std::decay_t<decltype(std::get<0>(rnd))>,
|
||||
dpf::shamir_share<F, 0>>);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<0>(rnd), std::get<2>(rnd)), F{11});
|
||||
|
||||
const auto wider = dpf::shamir::share_secret<F, 3, 5>(F{12});
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(
|
||||
std::get<0>(wider), std::get<2>(wider), std::get<4>(wider)), F{12});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
|||
1142
test/tests/security_test.cpp
Normal file
1142
test/tests/security_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
111
test/tests/sfss_test.cpp
Normal file
111
test/tests/sfss_test.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename K0, typename K1, typename InputT>
|
||||
dpf::fp61 open_sdpf(const K0 & k0, const K1 & k1, InputT x,
|
||||
const dpf::sfss_ct & ct)
|
||||
{
|
||||
return dpf::reconstruct(
|
||||
dpf::subtractive_share<dpf::fp61, 0>::from_raw(
|
||||
dpf::eval_sdpf(k0, x, ct)),
|
||||
dpf::subtractive_share<dpf::fp61, 1>::from_raw(
|
||||
dpf::eval_sdpf(k1, x, ct)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Sfss, PointBetaOneOnAndOff)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x2a;
|
||||
auto [k0, k1, ke, st] = dpf::make_sdpf(alpha);
|
||||
EXPECT_EQ(ke.beta.raw(), 1u);
|
||||
|
||||
const dpf::fp61 m{42};
|
||||
const auto ct = dpf::sfss_enc(st, ke, m);
|
||||
EXPECT_EQ(ct.j, 1u);
|
||||
EXPECT_EQ(st.ctr, 2u);
|
||||
|
||||
EXPECT_EQ(open_sdpf(k0, k1, alpha, ct), m);
|
||||
EXPECT_EQ(open_sdpf(k0, k1, Input{0}, ct), dpf::fp61{0});
|
||||
EXPECT_EQ(open_sdpf(k0, k1, static_cast<Input>(alpha ^ 1), ct), dpf::fp61{0});
|
||||
}
|
||||
|
||||
TEST(Sfss, WeightedBeta)
|
||||
{
|
||||
using Input = std::uint16_t;
|
||||
const Input alpha = 1000;
|
||||
const dpf::fp61 beta{7};
|
||||
auto [k0, k1, ke, st] = dpf::make_sdpf(alpha, beta);
|
||||
EXPECT_EQ(ke.beta, beta);
|
||||
|
||||
const dpf::fp61 m{11};
|
||||
const auto ct = dpf::sfss_enc(st, ke, m);
|
||||
EXPECT_EQ(open_sdpf(k0, k1, alpha, ct), beta * m);
|
||||
EXPECT_EQ(open_sdpf(k0, k1, Input{0}, ct), dpf::fp61{0});
|
||||
}
|
||||
|
||||
TEST(Sfss, ManyStreamMessages)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 7;
|
||||
auto [k0, k1, ke, st] = dpf::make_sdpf(alpha, dpf::fp61{3});
|
||||
|
||||
for (std::uint64_t i = 0; i < 20; ++i)
|
||||
{
|
||||
const dpf::fp61 m{i + 1};
|
||||
const auto ct = dpf::sfss_enc(st, ke, m);
|
||||
EXPECT_EQ(ct.j, i + 1);
|
||||
EXPECT_EQ(open_sdpf(k0, k1, alpha, ct), ke.beta * m) << i;
|
||||
EXPECT_EQ(open_sdpf(k0, k1, Input{8}, ct), dpf::fp61{0}) << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Sfss, WindowTelescopes)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 3;
|
||||
auto [k0, k1, ke, st] = dpf::make_sdpf(alpha, dpf::fp61{5});
|
||||
|
||||
const std::uint64_t j_lo = st.ctr;
|
||||
dpf::fp61 sum_m{};
|
||||
dpf::fp61 c_agg{};
|
||||
constexpr std::size_t window = 8;
|
||||
for (std::size_t i = 0; i < window; ++i)
|
||||
{
|
||||
const dpf::fp61 m{static_cast<std::uint64_t>(i + 2)};
|
||||
sum_m = sum_m + m;
|
||||
const auto ct = dpf::sfss_enc_window(st, ke, m);
|
||||
c_agg = c_agg + ct.c;
|
||||
}
|
||||
const std::uint64_t j_hi = j_lo + window - 1;
|
||||
|
||||
const auto s0 = dpf::eval_sdpf_window(k0, alpha, c_agg, j_lo, j_hi);
|
||||
const auto s1 = dpf::eval_sdpf_window(k1, alpha, c_agg, j_lo, j_hi);
|
||||
EXPECT_EQ(dpf::reconstruct(
|
||||
dpf::subtractive_share<dpf::fp61, 0>::from_raw(s0),
|
||||
dpf::subtractive_share<dpf::fp61, 1>::from_raw(s1)),
|
||||
ke.beta * sum_m);
|
||||
|
||||
const auto z0 = dpf::eval_sdpf_window(k0, Input{4}, c_agg, j_lo, j_hi);
|
||||
const auto z1 = dpf::eval_sdpf_window(k1, Input{4}, c_agg, j_lo, j_hi);
|
||||
EXPECT_EQ(dpf::reconstruct(
|
||||
dpf::subtractive_share<dpf::fp61, 0>::from_raw(z0),
|
||||
dpf::subtractive_share<dpf::fp61, 1>::from_raw(z1)),
|
||||
dpf::fp61{0});
|
||||
}
|
||||
|
||||
TEST(Sfss, RejectZeroBeta)
|
||||
{
|
||||
EXPECT_THROW((void)dpf::make_sdpf(std::uint8_t{1}, dpf::fp61{0}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
347
test/tests/shamir_adversarial_test.cpp
Normal file
347
test/tests/shamir_adversarial_test.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
/// @file shamir_adversarial_test.cpp
|
||||
/// @brief Corruption and misuse checks for (K,N) Shamir sharing.
|
||||
/// @details Exactly K shares accept any values, including a mislabeled point.
|
||||
/// Each further share is checked against the polynomial of the first
|
||||
/// K, wherever the bad share sits in that list. A full set of shares
|
||||
/// of a different secret is consistent and opens that secret. A zero
|
||||
/// leading coefficient drops the real threshold. (2,3) shamir3
|
||||
/// rejects a party outside 1..3 and an inconsistent third share.
|
||||
/// Threshold 1 hides nothing: every share is the secret. When K = N
|
||||
/// there is no extra share, so a tampered full set is not detected.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using F = dpf::fp61;
|
||||
|
||||
template <typename Tuple, std::size_t... I>
|
||||
std::array<F, sizeof...(I)> raws(const Tuple & shares, std::index_sequence<I...>)
|
||||
{
|
||||
return {{std::get<I>(shares).raw()...}};
|
||||
}
|
||||
|
||||
template <std::size_t N, typename Tuple>
|
||||
std::array<F, N> raws(const Tuple & shares)
|
||||
{
|
||||
return raws(shares, std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
std::array<dpf::shamir::point_share<F>, N> points(const std::array<F, N> & value)
|
||||
{
|
||||
std::array<dpf::shamir::point_share<F>, N> out{};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
out[i] = {static_cast<int>(i + 1), value[i]};
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ShamirAdversarial, ExactlyKAcceptsALie)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
auto bad = std::get<0>(dealt);
|
||||
bad += F{1};
|
||||
const F opened = dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(opened, F{10});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ExtraShareCatchesALieInAnySlot)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
const auto honest = points<5>(raws<5>(dealt));
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(honest)), F{10});
|
||||
|
||||
for (std::size_t slot = 0; slot < 5; ++slot)
|
||||
{
|
||||
auto lied = honest;
|
||||
lied[slot].value = lied[slot].value + F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(lied)), std::runtime_error)
|
||||
<< "slot " << slot;
|
||||
|
||||
// The same lie, moved to the front of the argument list.
|
||||
std::array<dpf::shamir::point_share<F>, 5> front{};
|
||||
front[0] = lied[slot];
|
||||
std::size_t n = 1;
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
if (i != slot)
|
||||
front[n++] = honest[i];
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(front)), std::runtime_error)
|
||||
<< "front slot " << slot;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ConsistentForgeryOpensTheForgedSecret)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{1}, F{4}}};
|
||||
const auto forged = dpf::shamir::deal<F, 3, 5>(F{99}, coeff);
|
||||
const auto held = points<5>(raws<5>(forged));
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(held)), F{99});
|
||||
|
||||
const auto real = dpf::shamir::deal<F, 3, 5>(F{10}, std::array<F, 2>{{F{2}, F{3}}});
|
||||
auto mixed = points<5>(raws<5>(real));
|
||||
mixed[4] = held[4];
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(mixed)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, OrderOfAConsistentSetDoesNotMatter)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
const F values[5] = {
|
||||
std::get<0>(dealt).raw(), std::get<1>(dealt).raw(),
|
||||
std::get<2>(dealt).raw(), std::get<3>(dealt).raw(),
|
||||
std::get<4>(dealt).raw()};
|
||||
int idx[3] = {0, 2, 4};
|
||||
do
|
||||
{
|
||||
const std::array<dpf::shamir::point_share<F>, 3> subset{{
|
||||
{idx[0] + 1, values[idx[0]]},
|
||||
{idx[1] + 1, values[idx[1]]},
|
||||
{idx[2] + 1, values[idx[2]]}}};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(subset)), F{10});
|
||||
}
|
||||
while (std::next_permutation(idx, idx + 3));
|
||||
|
||||
const auto forward = points<5>(raws<5>(dealt));
|
||||
std::array<dpf::shamir::point_share<F>, 5> backward{};
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
backward[i] = forward[4 - i];
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(backward)), F{10});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, WrongThresholdAndMislabeledPoint)
|
||||
{
|
||||
// p(x) = 8 + 5x + x^2. The line through p(1)=14 and p(2)=22 opens 6, not 8.
|
||||
const auto steep = dpf::shamir::deal<F, 3, 5>(F{8}, std::array<F, 2>{{F{5}, F{1}}});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> line{{
|
||||
{1, std::get<0>(steep).raw()}, {2, std::get<1>(steep).raw()}}};
|
||||
EXPECT_EQ(std::get<0>(steep).raw(), F{14});
|
||||
EXPECT_EQ(std::get<1>(steep).raw(), F{22});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 2, 5>(line)), F{6});
|
||||
EXPECT_NE((dpf::shamir::reconstruct<F, 2, 5>(line)), F{8});
|
||||
|
||||
// A zero leading coefficient is only a (2,5) sharing.
|
||||
const auto flat = dpf::shamir::deal<F, 3, 5>(F{8}, std::array<F, 2>{{F{5}, F{0}}});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> still_line{{
|
||||
{1, std::get<0>(flat).raw()}, {4, std::get<3>(flat).raw()}}};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 2, 5>(still_line)), F{8});
|
||||
|
||||
auto [s0, s1, s2] = dpf::make_shamir_shares(F{20}, F{3});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> swapped{{
|
||||
{1, s1.raw()}, {2, s0.raw()}}};
|
||||
EXPECT_NE((dpf::shamir::reconstruct<F, 2, 3>(swapped)), F{20});
|
||||
const std::array<dpf::shamir::point_share<F>, 3> swapped3{{
|
||||
{1, s1.raw()}, {2, s0.raw()}, {3, s2.raw()}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 2, 3>(swapped3)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, OneShareDoesNotFixTheSecret)
|
||||
{
|
||||
// p(1) = 13 for both (secret, slope) = (10, 3) and (11, 2).
|
||||
auto [a0, a1, a2] = dpf::make_shamir_shares(F{10}, F{3});
|
||||
auto [b0, b1, b2] = dpf::make_shamir_shares(F{11}, F{2});
|
||||
EXPECT_EQ(a0.raw(), b0.raw());
|
||||
EXPECT_EQ(a0.raw(), F{13});
|
||||
EXPECT_EQ((dpf::reconstruct(a0, a1)), F{10});
|
||||
EXPECT_EQ((dpf::reconstruct(b0, b1)), F{11});
|
||||
EXPECT_NE((dpf::reconstruct(a0, b1)), F{10});
|
||||
(void)a2;
|
||||
(void)b2;
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, RejectsCountPointsAndOneSidedEdit)
|
||||
{
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, std::array<F, 2>{{F{2}, F{3}}});
|
||||
const auto honest = points<5>(raws<5>(dealt));
|
||||
const std::array<dpf::shamir::point_share<F>, 2> too_few{{honest[0], honest[1]}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(too_few)), std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(honest.data(), 0)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(honest.data(), 6)),
|
||||
std::invalid_argument);
|
||||
|
||||
const std::array<dpf::shamir::point_share<F>, 3> dup{{
|
||||
{1, honest[0].value}, {1, honest[0].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(dup)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> zero_pt{{
|
||||
{0, honest[0].value}, {2, honest[1].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(zero_pt)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> negative{{
|
||||
{-1, honest[0].value}, {2, honest[1].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(negative)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> past{{
|
||||
{1, honest[0].value}, {2, honest[1].value}, {6, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(past)), std::invalid_argument);
|
||||
|
||||
auto one = std::get<0>(dealt);
|
||||
one += F{4};
|
||||
const F sided = dpf::shamir::reconstruct(one, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(sided, F{10});
|
||||
auto with_extra = honest;
|
||||
with_extra[0].value = one.raw();
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(with_extra)), std::runtime_error);
|
||||
|
||||
auto all = dealt;
|
||||
std::get<0>(all) += F{4};
|
||||
std::get<1>(all) += F{4};
|
||||
std::get<2>(all) += F{4};
|
||||
std::get<3>(all) += F{4};
|
||||
std::get<4>(all) += F{4};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(points<5>(raws<5>(all)))), F{14});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ThresholdOneHidesNothingAndFullThresholdNeedsEveryShare)
|
||||
{
|
||||
const auto copied = dpf::shamir::deal<F, 1, 4>(F{4}, std::array<F, 0>{});
|
||||
EXPECT_EQ(std::get<0>(copied).raw(), F{4});
|
||||
EXPECT_EQ(std::get<3>(copied).raw(), F{4});
|
||||
auto lie = std::get<0>(copied);
|
||||
lie += F{1};
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(lie), F{5});
|
||||
auto all = points<4>(raws<4>(copied));
|
||||
all[2].value = all[2].value + F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 1, 4>(all)), std::runtime_error);
|
||||
|
||||
const auto quad = dpf::shamir::deal<F, 4, 4>(
|
||||
F{9}, std::array<F, 3>{{F{1}, F{2}, F{3}}});
|
||||
const F opened4 = dpf::shamir::reconstruct(std::get<0>(quad), std::get<1>(quad),
|
||||
std::get<2>(quad), std::get<3>(quad));
|
||||
EXPECT_EQ(opened4, F{9});
|
||||
const std::array<dpf::shamir::point_share<F>, 3> missing{{
|
||||
{1, std::get<0>(quad).raw()},
|
||||
{2, std::get<1>(quad).raw()},
|
||||
{3, std::get<2>(quad).raw()}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 4, 4>(missing)), std::invalid_argument);
|
||||
// K = N, so the full set is exactly K shares and a lie is not detected.
|
||||
auto bad = std::get<3>(quad);
|
||||
bad += F{1};
|
||||
const F lied = dpf::shamir::reconstruct(std::get<0>(quad), std::get<1>(quad),
|
||||
std::get<2>(quad), bad);
|
||||
EXPECT_NE(lied, F{9});
|
||||
|
||||
const auto room = dpf::shamir::deal<F, 3, 4>(F{9}, std::array<F, 2>{{F{1}, F{2}}});
|
||||
auto bad_extra = std::get<3>(room);
|
||||
bad_extra += F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(room), std::get<1>(room),
|
||||
std::get<2>(room), bad_extra)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, Shamir3RejectsBadPartiesAndMatchesTheGeneralOpen)
|
||||
{
|
||||
const auto s = dpf::shamir3::share_secret(F{42});
|
||||
const F from_general = dpf::shamir::reconstruct<F, 2, 3>(
|
||||
std::array<dpf::shamir::point_share<F>, 2>{{
|
||||
{s[0].party, s[0].value}, {s[2].party, s[2].value}}});
|
||||
const F from_shamir3 = dpf::shamir3::reconstruct(s[0], s[2]);
|
||||
EXPECT_EQ(from_shamir3, from_general);
|
||||
const F from_three = dpf::shamir3::reconstruct(s[0], s[1], s[2]);
|
||||
EXPECT_EQ(from_three, F{42});
|
||||
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{0, F{1}}, dpf::shamir3::share{1, F{1}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{1, F{1}}, dpf::shamir3::share{4, F{1}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{2, F{1}}, dpf::shamir3::share{2, F{2}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(s[0], s[1], dpf::shamir3::share{4, s[2].value})),
|
||||
std::invalid_argument);
|
||||
|
||||
auto bad = s[2];
|
||||
bad.value = bad.value + F{1};
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(s[0], s[1], bad)), std::runtime_error);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 2, 3>(
|
||||
std::array<dpf::shamir::point_share<F>, 3>{{
|
||||
{s[0].party, s[0].value},
|
||||
{s[1].party, s[1].value},
|
||||
{bad.party, bad.value}}})),
|
||||
std::runtime_error);
|
||||
|
||||
EXPECT_THROW((dpf::shamir3::add(s[0], s[1])), std::invalid_argument);
|
||||
const auto doubled = dpf::shamir3::add(s[0], s[0]);
|
||||
EXPECT_EQ(doubled.value, s[0].value + s[0].value);
|
||||
EXPECT_THROW((dpf::shamir3::typed_share<0>(s[1])), std::invalid_argument);
|
||||
const auto typed = dpf::shamir3::typed_share<0>(s[0]);
|
||||
EXPECT_EQ(typed.raw(), s[0].value);
|
||||
|
||||
auto [t0, t1, t2] = dpf::make_shamir_shares(F{42}, F{0});
|
||||
t2 += F{1};
|
||||
EXPECT_THROW((dpf::reconstruct(t0, t1, t2)), std::runtime_error);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(t2, t0, t1)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ZeroAndFieldEdge)
|
||||
{
|
||||
const auto zeros = dpf::shamir::deal<F, 3, 4>(F{0}, std::array<F, 2>{{F{0}, F{0}}});
|
||||
EXPECT_EQ(std::get<0>(zeros).raw(), F{0});
|
||||
EXPECT_EQ(std::get<3>(zeros).raw(), F{0});
|
||||
const F opened0 = dpf::shamir::reconstruct(
|
||||
std::get<1>(zeros), std::get<2>(zeros), std::get<3>(zeros));
|
||||
EXPECT_EQ(opened0, F{0});
|
||||
|
||||
const F edge{dpf::fp61_mod - 1};
|
||||
const auto high = dpf::shamir::deal<F, 2, 3>(edge, std::array<F, 1>{{edge}});
|
||||
const F opened_edge = dpf::reconstruct(std::get<0>(high), std::get<2>(high));
|
||||
EXPECT_EQ(opened_edge, edge);
|
||||
static_assert(std::is_same_v<dpf::shamir::share<F, 0, 2, 3>, dpf::shamir_share<F, 0>>);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, Gf2PointsMustFitAndLiesAreXor)
|
||||
{
|
||||
// GF(2^8) has room for points 1..5. A one-bit lie is invisible at K=3
|
||||
// and caught when an honest extra share is present.
|
||||
using G = dpf::gf28;
|
||||
const auto dealt = dpf::shamir::deal<G, 3, 5>(
|
||||
G{0x1b}, std::array<G, 2>{{G{2}, G{9}}});
|
||||
auto bad = std::get<0>(dealt);
|
||||
bad += G{1};
|
||||
const G sided = dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(sided, G{0x1b});
|
||||
auto extra = std::get<3>(dealt);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt), extra)),
|
||||
std::runtime_error);
|
||||
|
||||
// Adding a share to itself is XOR, so the opened secret is 0, not twice.
|
||||
const G doubled = dpf::shamir::reconstruct(
|
||||
std::get<0>(dealt) + std::get<0>(dealt),
|
||||
std::get<1>(dealt) + std::get<1>(dealt),
|
||||
std::get<2>(dealt) + std::get<2>(dealt));
|
||||
EXPECT_EQ(doubled, G{0});
|
||||
|
||||
// GF(16) holds points 1..15. Point 16 is 0. Point 17 aliases point 1.
|
||||
using H = dpf::gf24;
|
||||
const auto fit = dpf::shamir::deal<H, 2, 15>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<0>(fit), std::get<14>(fit))), H{0xa});
|
||||
const auto zero_pt = dpf::shamir::deal<H, 2, 16>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
// Point 16 is 0 in GF(16), so that share is p(0), the secret.
|
||||
EXPECT_EQ(std::get<15>(zero_pt).raw(), H{0xa}.raw());
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(zero_pt), std::get<15>(zero_pt))),
|
||||
std::invalid_argument);
|
||||
const auto alias = dpf::shamir::deal<H, 2, 17>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
EXPECT_EQ(std::get<0>(alias).raw(), std::get<16>(alias).raw());
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(alias), std::get<16>(alias))),
|
||||
std::invalid_argument);
|
||||
|
||||
// GF(4) holds a (2,3) sharing. Point 4 is 0.
|
||||
const auto small = dpf::shamir::deal<dpf::gf22, 2, 4>(
|
||||
dpf::gf22{1}, std::array<dpf::gf22, 1>{{dpf::gf22{2}}});
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(small), std::get<3>(small))),
|
||||
std::invalid_argument);
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<0>(small), std::get<2>(small))), dpf::gf22{1});
|
||||
}
|
||||
2643
test/tests/share_runtime_test.cpp
Normal file
2643
test/tests/share_runtime_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
93
test/tests/shared_and_test.cpp
Normal file
93
test/tests/shared_and_test.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "aes_mmo_ref.hpp"
|
||||
#include "aes_sbox_bp.hpp"
|
||||
#include "dpf/doerner_shelat.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::uint8_t sbox_circuit(std::uint8_t x)
|
||||
{
|
||||
std::uint8_t w[aes_bp::wire_count]{};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
w[i] = static_cast<std::uint8_t>((x >> (7 - i)) & 1u);
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
{
|
||||
const auto kind = aes_bp::ops[oi][0];
|
||||
const auto dst = aes_bp::ops[oi][1];
|
||||
const auto a = aes_bp::ops[oi][2];
|
||||
const auto b = aes_bp::ops[oi][3];
|
||||
if (kind == 0)
|
||||
w[dst] = static_cast<std::uint8_t>(w[a] ^ w[b]);
|
||||
else if (kind == 1)
|
||||
w[dst] = static_cast<std::uint8_t>(w[a] & w[b]);
|
||||
else
|
||||
w[dst] = static_cast<std::uint8_t>(w[a] ^ w[b] ^ 1u);
|
||||
}
|
||||
std::uint8_t out = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
out = static_cast<std::uint8_t>(
|
||||
out | (w[aes_bp::out_wire[static_cast<std::size_t>(i)]] << (7 - i)));
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(SharedAnd, BoyarPeraltaMatchesAesSbox)
|
||||
{
|
||||
int ands = 0;
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
if (aes_bp::ops[oi][0] == 1)
|
||||
++ands;
|
||||
EXPECT_EQ(ands, static_cast<int>(aes_bp::and_count));
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto byte = static_cast<std::uint8_t>(x);
|
||||
EXPECT_EQ(sbox_circuit(byte), dpf::party::aes_ref::sbox_at(byte))
|
||||
<< "byte " << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SharedAnd, PublicProductTermIsAddedOnce)
|
||||
{
|
||||
// Every XOR-share of a bit AND. The opened masks are `d = x XOR a` and
|
||||
// `e = y XOR b`. Party 0 alone adds the public `d AND e`. Adding it on
|
||||
// both parties cancels that term and the product is wrong whenever it
|
||||
// is 1.
|
||||
int cancelled = 0;
|
||||
for (unsigned bits = 0; bits < 256; ++bits)
|
||||
{
|
||||
const std::uint8_t a = static_cast<std::uint8_t>(bits & 1u);
|
||||
const std::uint8_t b = static_cast<std::uint8_t>((bits >> 1) & 1u);
|
||||
const std::uint8_t x = static_cast<std::uint8_t>((bits >> 2) & 1u);
|
||||
const std::uint8_t y = static_cast<std::uint8_t>((bits >> 3) & 1u);
|
||||
const std::uint8_t a0 = static_cast<std::uint8_t>((bits >> 4) & 1u);
|
||||
const std::uint8_t b0 = static_cast<std::uint8_t>((bits >> 5) & 1u);
|
||||
const std::uint8_t x0 = static_cast<std::uint8_t>((bits >> 6) & 1u);
|
||||
const std::uint8_t y0 = static_cast<std::uint8_t>((bits >> 7) & 1u);
|
||||
const std::uint8_t c = static_cast<std::uint8_t>(a & b);
|
||||
const std::uint8_t c0 = static_cast<std::uint8_t>((a0 & b0) ^ ((bits * 3u) & 1u));
|
||||
const std::uint8_t a1 = static_cast<std::uint8_t>(a0 ^ a);
|
||||
const std::uint8_t b1 = static_cast<std::uint8_t>(b0 ^ b);
|
||||
const std::uint8_t c1 = static_cast<std::uint8_t>(c0 ^ c);
|
||||
const std::uint8_t x1 = static_cast<std::uint8_t>(x0 ^ x);
|
||||
const std::uint8_t y1 = static_cast<std::uint8_t>(y0 ^ y);
|
||||
const std::uint8_t d = static_cast<std::uint8_t>((x0 ^ a0) ^ (x1 ^ a1));
|
||||
const std::uint8_t e = static_cast<std::uint8_t>((y0 ^ b0) ^ (y1 ^ b1));
|
||||
const std::uint8_t z0 = dpf::detail::ds_bit_and_party(d, e, a0, b0, c0, true);
|
||||
const std::uint8_t z1 = dpf::detail::ds_bit_and_party(d, e, a1, b1, c1, false);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(z0 ^ z1), static_cast<std::uint8_t>(x & y));
|
||||
const std::uint8_t both = static_cast<std::uint8_t>(
|
||||
dpf::detail::ds_bit_and_party(d, e, a0, b0, c0, true)
|
||||
^ dpf::detail::ds_bit_and_party(d, e, a1, b1, c1, true));
|
||||
if ((d & e) != 0)
|
||||
{
|
||||
EXPECT_NE(both, static_cast<std::uint8_t>(x & y));
|
||||
++cancelled;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(cancelled, 0);
|
||||
}
|
||||
346
test/tests/shuffle_hidden_test.cpp
Normal file
346
test/tests/shuffle_hidden_test.cpp
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/compose.hpp"
|
||||
#include "dpf/shuffle.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> replay(const std::vector<T> & v, const dpf::rss::seed_bundle & bundle,
|
||||
std::uint64_t index, unsigned passes)
|
||||
{
|
||||
const dpf::rss::seed_block seeds[3] = {bundle.k01, bundle.k12, bundle.k20};
|
||||
auto out = v;
|
||||
for (unsigned p = 0; p < passes; ++p)
|
||||
{
|
||||
out = dpf::shuffle::permute(out,
|
||||
dpf::shuffle::permutation_from_seed(seeds[p], v.size(), index));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void deal(const std::vector<T> & clear, dpf::shuffle::shuffle_party_view<T> held[3])
|
||||
{
|
||||
const std::size_t n = clear.size();
|
||||
for (unsigned p = 0; p < 3; ++p)
|
||||
{
|
||||
held[p].own.assign(n, T{});
|
||||
held[p].next.assign(n, T{});
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const T a = dpf::uniform_sample<T>();
|
||||
const T b = dpf::uniform_sample<T>();
|
||||
const T c = static_cast<T>(clear[i] - a - b);
|
||||
held[0].own[i] = a;
|
||||
held[0].next[i] = b;
|
||||
held[1].own[i] = b;
|
||||
held[1].next[i] = c;
|
||||
held[2].own[i] = c;
|
||||
held[2].next[i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> open_owns(const dpf::shuffle::shuffle_party_view<T> held[3])
|
||||
{
|
||||
std::vector<T> out(held[0].own.size());
|
||||
for (std::size_t i = 0; i < out.size(); ++i)
|
||||
out[i] = static_cast<T>(held[0].own[i] + held[1].own[i] + held[2].own[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void run_passes(dpf::shuffle::shuffle_party_view<T> held[3],
|
||||
const dpf::rss::seed_bundle & bundle, std::uint64_t index, unsigned passes)
|
||||
{
|
||||
const unsigned order[3] = {2u, 0u, 1u};
|
||||
for (unsigned step = 0; step < passes; ++step)
|
||||
{
|
||||
const unsigned left = order[step];
|
||||
const unsigned u = dpf::shuffle::hidden_u_party(left);
|
||||
const unsigned side = dpf::shuffle::hidden_side_party(left);
|
||||
auto u_step = dpf::shuffle::shuffle_hidden_pass<T>(
|
||||
u, dpf::rss::party_seeds::from_bundle(bundle, u), held[u], index, left,
|
||||
nullptr);
|
||||
auto s_step = dpf::shuffle::shuffle_hidden_pass<T>(
|
||||
side, dpf::rss::party_seeds::from_bundle(bundle, side), held[side],
|
||||
index, left, &u_step.out.data);
|
||||
auto l_step = dpf::shuffle::shuffle_hidden_pass<T>(
|
||||
left, dpf::rss::party_seeds::from_bundle(bundle, left), held[left],
|
||||
index, left, &s_step.out.data);
|
||||
EXPECT_EQ(u_step.out.data.size(), held[u].own.size());
|
||||
EXPECT_EQ(s_step.out.to, left);
|
||||
EXPECT_EQ(held[u].own.size(), l_step.view.own.size());
|
||||
held[u] = std::move(u_step.view);
|
||||
held[side] = std::move(s_step.view);
|
||||
held[left] = std::move(l_step.view);
|
||||
ASSERT_EQ(held[0].next, held[1].own);
|
||||
ASSERT_EQ(held[1].next, held[2].own);
|
||||
ASSERT_EQ(held[2].next, held[0].own);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ShuffleHidden, ThreePassesMatchTheSeedPermutations)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
std::vector<std::uint64_t> v(8);
|
||||
std::iota(v.begin(), v.end(), 0);
|
||||
const std::uint64_t index = 4;
|
||||
auto opened = dpf::shuffle::shuffle_hidden_triple(v, bundle, index);
|
||||
auto expect = dpf::shuffle::permute(v,
|
||||
dpf::shuffle::permutation_from_seed(bundle.k01, v.size(), index));
|
||||
expect = dpf::shuffle::permute(expect,
|
||||
dpf::shuffle::permutation_from_seed(bundle.k12, v.size(), index));
|
||||
expect = dpf::shuffle::permute(expect,
|
||||
dpf::shuffle::permutation_from_seed(bundle.k20, v.size(), index));
|
||||
EXPECT_EQ(opened, expect);
|
||||
EXPECT_TRUE(dpf::shuffle::is_permutation_of(v, opened));
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, LeftOutPartyUsesOnlyItsTwoSeeds)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
const std::size_t n = 4;
|
||||
dpf::shuffle::shuffle_party_view<std::uint64_t> in{
|
||||
std::vector<std::uint64_t>(n, 1),
|
||||
std::vector<std::uint64_t>(n, 2),
|
||||
};
|
||||
auto seeds = dpf::rss::party_seeds::from_bundle(bundle, 2);
|
||||
std::vector<std::uint64_t> inbound(n, 9);
|
||||
auto step = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
2, seeds, in, 0, 2, &inbound);
|
||||
EXPECT_FALSE(step.out.sends);
|
||||
EXPECT_EQ(step.view.own, inbound);
|
||||
EXPECT_EQ(step.view.next.size(), n);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, ReplicationHoldsAfterEachPass)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
const std::size_t n = 5;
|
||||
dpf::shuffle::shuffle_party_view<std::uint64_t> held[3];
|
||||
for (unsigned p = 0; p < 3; ++p)
|
||||
{
|
||||
held[p].own.assign(n, 0);
|
||||
held[p].next.assign(n, 0);
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const auto a = dpf::uniform_sample<std::uint64_t>();
|
||||
const auto b = dpf::uniform_sample<std::uint64_t>();
|
||||
held[0].own[i] = a;
|
||||
held[0].next[i] = b;
|
||||
held[1].own[i] = b;
|
||||
held[1].next[i] = static_cast<std::uint64_t>(i - a - b);
|
||||
held[2].own[i] = held[1].next[i];
|
||||
held[2].next[i] = a;
|
||||
}
|
||||
for (unsigned left : {2u, 0u, 1u})
|
||||
{
|
||||
const unsigned u = dpf::shuffle::hidden_u_party(left);
|
||||
const unsigned side = dpf::shuffle::hidden_side_party(left);
|
||||
auto u_step = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
u, dpf::rss::party_seeds::from_bundle(bundle, u), held[u], 1, left,
|
||||
nullptr);
|
||||
auto s_step = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
side, dpf::rss::party_seeds::from_bundle(bundle, side), held[side],
|
||||
1, left, &u_step.out.data);
|
||||
auto l_step = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
left, dpf::rss::party_seeds::from_bundle(bundle, left), held[left],
|
||||
1, left, &s_step.out.data);
|
||||
EXPECT_TRUE(u_step.out.sends);
|
||||
EXPECT_EQ(u_step.out.to, side);
|
||||
EXPECT_TRUE(s_step.out.sends);
|
||||
EXPECT_EQ(s_step.out.to, left);
|
||||
EXPECT_FALSE(l_step.out.sends);
|
||||
held[u] = std::move(u_step.view);
|
||||
held[side] = std::move(s_step.view);
|
||||
held[left] = std::move(l_step.view);
|
||||
EXPECT_EQ(held[0].next, held[1].own);
|
||||
EXPECT_EQ(held[1].next, held[2].own);
|
||||
EXPECT_EQ(held[2].next, held[0].own);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, ComposerRecordsThreeSends)
|
||||
{
|
||||
dpf::protocol::composer c;
|
||||
auto out = c.shuffle_hidden(8, sizeof(std::uint64_t));
|
||||
auto plan = c.schedule();
|
||||
EXPECT_EQ(plan.rounds(), 3u);
|
||||
std::vector<std::uint32_t> left_out;
|
||||
for (std::size_t w = 0; w < plan.waves(); ++w)
|
||||
{
|
||||
for (auto ex : plan.wave(w).exchanges)
|
||||
{
|
||||
EXPECT_EQ(plan.opcode_of(ex.id), dpf::protocol::opcodes::shuffle_send);
|
||||
left_out.push_back(plan.aux_of(ex.id));
|
||||
EXPECT_EQ(dpf::protocol::detail::exchange_channel(plan, ex.id),
|
||||
dpf::protocol::edge_channel::rss_next);
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(left_out, (std::vector<std::uint32_t>{2u, 0u, 1u}));
|
||||
EXPECT_EQ(plan.value_bytes_of(out.id), 8u * sizeof(std::uint64_t));
|
||||
std::size_t prev = 0;
|
||||
bool seen = false;
|
||||
for (std::size_t w = 0; w < plan.waves(); ++w)
|
||||
{
|
||||
if (plan.wave(w).exchanges.empty())
|
||||
continue;
|
||||
if (seen)
|
||||
EXPECT_GT(w, prev);
|
||||
prev = w;
|
||||
seen = true;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, EachPassMatchesOneSeedPermutation)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
std::vector<std::uint64_t> v(9);
|
||||
for (std::size_t i = 0; i < v.size(); ++i)
|
||||
v[i] = 1000u + static_cast<std::uint64_t>(i * 17u);
|
||||
dpf::shuffle::shuffle_party_view<std::uint64_t> held[3];
|
||||
deal(v, held);
|
||||
for (unsigned passes = 1; passes <= 3; ++passes)
|
||||
{
|
||||
deal(v, held);
|
||||
run_passes(held, bundle, 9, passes);
|
||||
EXPECT_EQ(open_owns(held), replay(v, bundle, 9, passes));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, LengthsRingsAndHighBits)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
for (std::size_t n : {0u, 1u, 2u, 3u, 7u, 16u, 64u})
|
||||
{
|
||||
std::vector<std::uint64_t> v(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
v[i] = (i * 0x9E3779B97F4A7C15ull) ^ 0x8000000000000000ull;
|
||||
auto opened = dpf::shuffle::shuffle_hidden_triple(v, bundle, 3);
|
||||
EXPECT_EQ(opened, replay(v, bundle, 3, 3));
|
||||
if (n == 1)
|
||||
EXPECT_EQ(opened, v);
|
||||
}
|
||||
std::vector<std::uint8_t> bytes{0, 255, 1, 128, 7, 7, 255};
|
||||
EXPECT_EQ(dpf::shuffle::shuffle_hidden_triple(bytes, bundle, 2),
|
||||
replay(bytes, bundle, 2, 3));
|
||||
std::vector<std::uint32_t> words{0u, 0xffffffffu, 1u, 0x80000000u};
|
||||
EXPECT_EQ(dpf::shuffle::shuffle_hidden_triple(words, bundle, 5),
|
||||
replay(words, bundle, 5, 3));
|
||||
std::vector<std::uint64_t> same(12, 42);
|
||||
EXPECT_EQ(dpf::shuffle::shuffle_hidden_triple(same, bundle, 1), same);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, ReplayIsStableAndIndexChangesTheOrder)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
std::vector<std::uint64_t> v(16);
|
||||
std::iota(v.begin(), v.end(), 0);
|
||||
auto a = dpf::shuffle::shuffle_hidden_triple(v, bundle, 0);
|
||||
auto again = dpf::shuffle::shuffle_hidden_triple(v, bundle, 0);
|
||||
auto other = dpf::shuffle::shuffle_hidden_triple(v, bundle, 99);
|
||||
EXPECT_EQ(a, again);
|
||||
EXPECT_EQ(a, replay(v, bundle, 0, 3));
|
||||
EXPECT_NE(a, other);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, UPartyIgnoresInboundAndABadMessageBreaksTheOpen)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
std::vector<std::uint64_t> v(8);
|
||||
std::iota(v.begin(), v.end(), 3);
|
||||
dpf::shuffle::shuffle_party_view<std::uint64_t> held[3];
|
||||
deal(v, held);
|
||||
const unsigned left = 2;
|
||||
const unsigned u = dpf::shuffle::hidden_u_party(left);
|
||||
const unsigned side = dpf::shuffle::hidden_side_party(left);
|
||||
std::vector<std::uint64_t> junk(v.size(), 123);
|
||||
auto ignored = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
u, dpf::rss::party_seeds::from_bundle(bundle, u), held[u], 0, left, &junk);
|
||||
auto clean = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
u, dpf::rss::party_seeds::from_bundle(bundle, u), held[u], 0, left, nullptr);
|
||||
EXPECT_EQ(ignored.view.own, clean.view.own);
|
||||
EXPECT_EQ(ignored.view.next, clean.view.next);
|
||||
junk[0] ^= 1u;
|
||||
auto bad = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
side, dpf::rss::party_seeds::from_bundle(bundle, side), held[side], 0,
|
||||
left, &junk);
|
||||
auto good = dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
side, dpf::rss::party_seeds::from_bundle(bundle, side), held[side], 0,
|
||||
left, &clean.out.data);
|
||||
EXPECT_NE(bad.view.own, good.view.own);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, LeftOutMaskMatchesAndMissesThePermutationSeed)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
auto party2 = dpf::rss::party_seeds::from_bundle(bundle, 2);
|
||||
EXPECT_EQ(std::memcmp(&party2.with_prev, &bundle.k12, sizeof(bundle.k12)), 0);
|
||||
EXPECT_EQ(std::memcmp(&party2.with_next, &bundle.k20, sizeof(bundle.k20)), 0);
|
||||
EXPECT_NE(std::memcmp(&party2.with_prev, &bundle.k01, sizeof(bundle.k01)), 0);
|
||||
EXPECT_NE(std::memcmp(&party2.with_next, &bundle.k01, sizeof(bundle.k01)), 0);
|
||||
const auto pi_known = dpf::shuffle::permutation_from_seed(bundle.k01, 8, 0);
|
||||
const auto pi_prev = dpf::shuffle::permutation_from_seed(party2.with_prev, 8, 0);
|
||||
const auto pi_next = dpf::shuffle::permutation_from_seed(party2.with_next, 8, 0);
|
||||
EXPECT_NE(pi_known, pi_prev);
|
||||
EXPECT_NE(pi_known, pi_next);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, RejectsBadPartiesAndShortMessages)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
dpf::shuffle::shuffle_party_view<std::uint64_t> in{
|
||||
{1, 2},
|
||||
{3, 4},
|
||||
};
|
||||
auto seeds = dpf::rss::party_seeds::from_bundle(bundle, 0);
|
||||
EXPECT_THROW(
|
||||
dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(1, seeds, in, 0, 2, nullptr),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(
|
||||
dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(0, seeds, in, 0, 3, nullptr),
|
||||
std::invalid_argument);
|
||||
in.next.pop_back();
|
||||
EXPECT_THROW(
|
||||
dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(0, seeds, in, 0, 2, nullptr),
|
||||
std::invalid_argument);
|
||||
in.next.push_back(4);
|
||||
auto side = dpf::rss::party_seeds::from_bundle(bundle, 1);
|
||||
EXPECT_THROW(
|
||||
dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(1, side, in, 0, 2, nullptr),
|
||||
std::invalid_argument);
|
||||
std::vector<std::uint64_t> short_msg{1};
|
||||
EXPECT_THROW(dpf::shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
1, side, in, 0, 2, &short_msg),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::shuffle::permute(std::vector<std::uint64_t>{1},
|
||||
std::vector<std::size_t>{}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, PublicShuffleStillFollowsK01)
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
std::vector<std::uint64_t> v{4, 9, 1, 7, 3};
|
||||
EXPECT_EQ(dpf::shuffle::shuffle_party_triple(v, bundle, 6),
|
||||
dpf::shuffle::shuffle_clear(v, bundle, 6));
|
||||
}
|
||||
|
||||
TEST(ShuffleHidden, ComposerRejectsAnEmptyColumn)
|
||||
{
|
||||
dpf::protocol::composer c;
|
||||
EXPECT_THROW(c.shuffle_hidden(0, 8), std::invalid_argument);
|
||||
EXPECT_THROW(c.shuffle_hidden(4, 0), std::invalid_argument);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/constant_lut.hpp"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// incremental_test.cpp.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/fixedpoint.hpp"
|
||||
|
|
@ -242,6 +243,10 @@ void assign_wildcard_leaf_local(Key0 & k0, Key1 & k1, const ShareT & shr0,
|
|||
{
|
||||
auto & w0 = std::get<I>(k0.leaf_nodes);
|
||||
auto & w1 = std::get<I>(k1.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);
|
||||
|
|
@ -552,7 +557,7 @@ TEST_F(StressScenariosTest, MlWildcardEvalPointThrowsBeforeAssign)
|
|||
auto [k0, k1] = dpf::make_dpf(x,
|
||||
dpf::at<8>(uint8_t{7}), dpf::at<12>(wc), uint16_t{1});
|
||||
// Slot 1 is an unassigned wildcard: evaluating it must throw.
|
||||
EXPECT_ANY_THROW((void)dpf::eval_point(dpf::out<1, 12>, k0, x));
|
||||
EXPECT_ANY_THROW(dpf::eval_point(dpf::out<1, 12>, k0, x));
|
||||
(void)k1;
|
||||
}
|
||||
|
||||
|
|
@ -2038,7 +2043,7 @@ TEST_F(StressScenariosTest, MlPackedSmallWildcardAtNonTerminalAssignAll)
|
|||
EXPECT_NE(KT::meta[0].group_id, KT::meta[8].group_id);
|
||||
|
||||
// Unassigned packed wildcards throw; deepest concrete still works.
|
||||
EXPECT_ANY_THROW((void)dpf::eval_point(dpf::out<0, 12>, k0, x));
|
||||
EXPECT_ANY_THROW(dpf::eval_point(dpf::out<0, 12>, k0, x));
|
||||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8>, k0, x),
|
||||
*dpf::eval_point(dpf::out<8>, k1, x)),
|
||||
uint32_t{999});
|
||||
|
|
@ -2248,8 +2253,8 @@ TEST_F(StressScenariosTest, PrgCounterWrapperAesClassicEvalIncrements)
|
|||
const auto after_gen = prg::count();
|
||||
EXPECT_GT(after_gen, before);
|
||||
|
||||
(void)dpf::eval_point(k0, x);
|
||||
(void)dpf::eval_full(k0);
|
||||
dpf::eval_point(k0, x);
|
||||
dpf::eval_full(k0);
|
||||
const auto after_eval = prg::count();
|
||||
EXPECT_GT(after_eval, after_gen);
|
||||
|
||||
|
|
@ -2271,8 +2276,8 @@ TEST_F(StressScenariosTest, PrgCounterWrapperLowmcMultilevelEvalIncrements)
|
|||
|
||||
const auto bi2 = interior::count();
|
||||
const auto be2 = exterior::count();
|
||||
(void)dpf::eval_point(dpf::out<0, 8>, k0, x);
|
||||
(void)dpf::eval_full(dpf::out<1, 16>, k0);
|
||||
dpf::eval_point(dpf::out<0, 8>, k0, x);
|
||||
dpf::eval_full(dpf::out<1, 16>, k0);
|
||||
EXPECT_GT(interior::count(), bi2);
|
||||
EXPECT_GT(exterior::count(), be2);
|
||||
|
||||
|
|
@ -2302,3 +2307,80 @@ TEST_F(StressScenariosTest, PrgLowmcBothSidesPackedInterval)
|
|||
*dpf::eval_point(dpf::out<4>, k1, x)), uint32_t{55});
|
||||
}
|
||||
|
||||
TEST_F(StressScenariosTest, PairedInnerProductLeafAndAncestor)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(In{9}, std::uint32_t{7}, std::uint32_t{11});
|
||||
std::vector<std::array<std::uint32_t, 2>> rows;
|
||||
std::uint64_t expect = 0;
|
||||
for (In x = 4;; ++x)
|
||||
{
|
||||
const auto y0 = recon(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x));
|
||||
const auto y1 = recon(*dpf::eval_point<1>(k0, x), *dpf::eval_point<1>(k1, x));
|
||||
const std::uint32_t w0 = x * 3u + 1u;
|
||||
const std::uint32_t w1 = x * 5u + 2u;
|
||||
rows.push_back({w0, w1});
|
||||
expect += static_cast<std::uint64_t>(y0) * w0
|
||||
+ static_cast<std::uint64_t>(y1) * w1;
|
||||
if (x == 12)
|
||||
break;
|
||||
}
|
||||
const auto a = dpf::eval_inner_product<0, 1>(dpf::paired, k0, In{4}, In{12}, rows);
|
||||
const auto b = dpf::eval_inner_product<0, 1>(dpf::paired, k1, In{4}, In{12}, rows);
|
||||
EXPECT_EQ(recon(a, b), expect);
|
||||
|
||||
const std::vector<In> pts{2, 9, 40};
|
||||
const std::vector<std::array<std::uint64_t, 2>> seq_rows{{1, 2}, {3, 4}, {5, 6}};
|
||||
std::uint64_t seq_expect = 0;
|
||||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||||
{
|
||||
const auto y0 = recon(*dpf::eval_point<0>(k0, pts[i]),
|
||||
*dpf::eval_point<0>(k1, pts[i]));
|
||||
const auto y1 = recon(*dpf::eval_point<1>(k0, pts[i]),
|
||||
*dpf::eval_point<1>(k1, pts[i]));
|
||||
seq_expect += static_cast<std::uint64_t>(y0) * seq_rows[i][0]
|
||||
+ static_cast<std::uint64_t>(y1) * seq_rows[i][1];
|
||||
}
|
||||
EXPECT_EQ(recon(
|
||||
dpf::eval_sequence_inner_product<0, 1>(k0, pts.begin(), pts.end(), seq_rows),
|
||||
dpf::eval_sequence_inner_product<0, 1>(k1, pts.begin(), pts.end(), seq_rows)),
|
||||
seq_expect);
|
||||
const auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
|
||||
EXPECT_EQ(recon(
|
||||
dpf::eval_sequence_inner_product<0, 1>(k0, recipe, pts.begin(), pts.end(), seq_rows),
|
||||
dpf::eval_sequence_inner_product<0, 1>(k1, recipe, pts.begin(), pts.end(), seq_rows)),
|
||||
seq_expect);
|
||||
|
||||
auto [a0, a1] = dpf::make_dpf(In{0x2a}, dpf::at<4>(std::uint8_t{5}), std::uint8_t{9});
|
||||
const std::vector<In> ap{0x20, 0x2a, 0x2b, 0x30};
|
||||
const std::vector<std::array<std::uint32_t, 2>> ar{{1, 1}, {2, 3}, {4, 5}, {6, 7}};
|
||||
std::uint64_t anc = 0;
|
||||
for (std::size_t i = 0; i < ap.size(); ++i)
|
||||
{
|
||||
const auto y0 = recon(*dpf::eval_point(dpf::out<0>, a0, ap[i]),
|
||||
*dpf::eval_point(dpf::out<0>, a1, ap[i]));
|
||||
const auto y1 = recon(*dpf::eval_point(dpf::out<1>, a0, ap[i]),
|
||||
*dpf::eval_point(dpf::out<1>, a1, ap[i]));
|
||||
anc += static_cast<std::uint64_t>(y0) * ar[i][0]
|
||||
+ static_cast<std::uint64_t>(y1) * ar[i][1];
|
||||
}
|
||||
EXPECT_EQ(recon(
|
||||
dpf::eval_sequence_inner_product<0, 1>(a0, ap.begin(), ap.end(), ar),
|
||||
dpf::eval_sequence_inner_product<0, 1>(a1, ap.begin(), ap.end(), ar)),
|
||||
anc);
|
||||
|
||||
std::vector<std::uint64_t> full(256);
|
||||
std::uint64_t full_expect = 0;
|
||||
for (unsigned x = 0; x < 256; ++x)
|
||||
{
|
||||
full[x] = (x * 3u + 1u) & 0x1fu;
|
||||
const auto y = recon(*dpf::eval_point(k0, static_cast<In>(x)),
|
||||
*dpf::eval_point(k1, static_cast<In>(x)));
|
||||
full_expect += static_cast<std::uint64_t>(y) * full[x];
|
||||
}
|
||||
EXPECT_EQ(recon(
|
||||
dpf::eval_full_inner_product<0>(dpf::paired, k0, full),
|
||||
dpf::eval_full_inner_product<0>(dpf::paired, k1, full)),
|
||||
full_expect);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
|
@ -248,7 +249,7 @@ TEST(TypeTraits, IntegralSelectionAndBuiltinWidths)
|
|||
EXPECT_TRUE(dpf::utils::has_operators_plus_minus_v<uint32_t>);
|
||||
EXPECT_TRUE(dpf::utils::has_operators_plus_minus_v<float>);
|
||||
EXPECT_FALSE(dpf::utils::has_characteristic_two_v<uint32_t>);
|
||||
EXPECT_FALSE(dpf::utils::has_characteristic_two_v<dpf::bit>);
|
||||
EXPECT_TRUE(dpf::utils::has_characteristic_two_v<dpf::bit>);
|
||||
EXPECT_FALSE(dpf::utils::is_xor_wrapper_v<uint32_t>);
|
||||
EXPECT_FALSE(dpf::is_wildcard_v<uint32_t>);
|
||||
}
|
||||
|
|
@ -293,6 +294,27 @@ TEST(TypeTraits, BoundaryWidthsAndOutputPacking)
|
|||
EXPECT_EQ((dpf::outputs_per_leaf_v<uint8_t, simde__m128i>), 16u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<uint32_t, simde__m128i>), 4u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::bit, simde__m128i>), 128u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf2, simde__m128i>), 1u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf22, simde__m128i>), 2u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf24, simde__m128i>), 4u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf28, simde__m128i>), 8u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf216, simde__m128i>), 16u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf232, simde__m128i>), 32u);
|
||||
EXPECT_EQ((dpf::utils::bitlength_of_output_v<dpf::gf264, simde__m128i>), 64u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf2, simde__m128i>), 128u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf22, simde__m128i>), 64u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf24, simde__m128i>), 32u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf28, simde__m128i>), 16u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf216, simde__m128i>), 8u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf232, simde__m128i>), 4u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf264, simde__m128i>), 2u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf2, simde__m256i>), 256u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::gf264, simde__m256i>), 4u);
|
||||
EXPECT_TRUE(dpf::utils::is_packed_subbyte_v<dpf::gf2>);
|
||||
EXPECT_TRUE(dpf::utils::is_packed_subbyte_v<dpf::gf24>);
|
||||
EXPECT_FALSE(dpf::utils::is_packed_subbyte_v<dpf::gf28>);
|
||||
EXPECT_TRUE(dpf::utils::has_characteristic_two_v<dpf::gf264>);
|
||||
EXPECT_EQ((dpf::block_length_of_leaf_v<dpf::gf264, simde__m128i>), 1u);
|
||||
EXPECT_EQ((dpf::outputs_per_leaf_v<dpf::modint<10>, simde__m128i>), 8u);
|
||||
EXPECT_EQ((dpf::block_length_of_leaf_v<uint32_t, simde__m128i>), 1u);
|
||||
EXPECT_EQ((dpf::block_length_of_leaf_v<uint256_t, simde__m128i>), 2u);
|
||||
|
|
|
|||
248
test/tests/vdpf_adversarial_test.cpp
Normal file
248
test/tests/vdpf_adversarial_test.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/// @file vdpf_adversarial_test.cpp
|
||||
/// @brief Brute-force and corruption checks for verifiable evaluation.
|
||||
/// @details Covers bug classes seen while bringing the proofs up: subtractive
|
||||
/// reconstruction is not commutative, a single untouched control bit
|
||||
/// can hide a seed flip, and a proof convention must still reject a
|
||||
/// flipped token. Small domains are checked at every point.
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Input = std::uint8_t;
|
||||
|
||||
struct Pad
|
||||
{
|
||||
std::uint64_t n = 1;
|
||||
simde__m128i block()
|
||||
{
|
||||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||||
static_cast<long long>(n * 9 + 3));
|
||||
n += 2;
|
||||
return v;
|
||||
}
|
||||
void fill(void * p, std::size_t nbytes)
|
||||
{
|
||||
auto * b = static_cast<unsigned char *>(p);
|
||||
for (std::size_t i = 0; i < nbytes; ++i)
|
||||
b[i] = static_cast<unsigned char>(n + i * 17);
|
||||
n += nbytes;
|
||||
}
|
||||
std::uint8_t bit() { return static_cast<std::uint8_t>(n++ & 1u); }
|
||||
};
|
||||
|
||||
dpf::ds_randomness<simde__m128i (*)(), Pad> tape()
|
||||
{
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
return {dpf::uniform_sample<simde__m128i>, Pad{}};
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
}
|
||||
|
||||
template <typename Y0, typename Y1, typename Want>
|
||||
void expect_ordered_reconstruct(const Y0 & y0, const Y1 & y1, const Want & want)
|
||||
{
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), want);
|
||||
// The typed overload accepts either party order. A raw subtraction does not.
|
||||
EXPECT_EQ(dpf::reconstruct(y1, y0), want);
|
||||
const auto swapped = static_cast<Want>(y1.raw() - y0.raw());
|
||||
if (y0.raw() != y1.raw())
|
||||
EXPECT_NE(swapped, want);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(VdpfAdversarial, PointFullDomainValuesAndProofs)
|
||||
{
|
||||
const Input alpha = 0;
|
||||
const std::uint64_t beta = 9;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::verifiable{});
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto y0 = *dpf::eval_point(k0, q, dpf::prove(a));
|
||||
const auto y1 = *dpf::eval_point(k1, q, dpf::prove(b));
|
||||
const std::uint64_t want = q == alpha ? beta : 0;
|
||||
expect_ordered_reconstruct(y0, y1, want);
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << int(q);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, HalfTreeFullDomainValuesAndProofs)
|
||||
{
|
||||
using Ht = dpf::prg::aes128_ccr;
|
||||
const Input alpha = 255;
|
||||
const std::uint64_t beta = 0x1001;
|
||||
auto [k0, k1] = dpf::make_dpf<Ht, Ht>(alpha, beta, dpf::verifiable{});
|
||||
EXPECT_TRUE(decltype(k0)::tree::is_half_tree);
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto y0 = *dpf::eval_point(k0, q, dpf::prove(a));
|
||||
const auto y1 = *dpf::eval_point(k1, q, dpf::prove(b));
|
||||
expect_ordered_reconstruct(y0, y1, q == alpha ? beta : 0ull);
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << int(q);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, ComparisonAndBlockedFullDomain)
|
||||
{
|
||||
const Input alpha = 0x40;
|
||||
auto native = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}), dpf::verifiable{});
|
||||
auto blocked = dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token n0{}, n1{}, b0{}, b1{};
|
||||
const auto ny0 = dpf::eval_point(dpf::cmp, native.first, q, dpf::prove(n0));
|
||||
const auto ny1 = dpf::eval_point(dpf::cmp, native.second, q, dpf::prove(n1));
|
||||
const auto by0 = dpf::eval_point(dpf::cmp, blocked.first, q, dpf::prove(b0));
|
||||
const auto by1 = dpf::eval_point(dpf::cmp, blocked.second, q, dpf::prove(b1));
|
||||
const std::uint64_t want = q < alpha ? 1u : 0u;
|
||||
EXPECT_EQ(dpf::reconstruct(ny0, ny1) & native.first.cmp().mask, want) << int(q);
|
||||
EXPECT_EQ(dpf::reconstruct(by0, by1) & blocked.first.cmp().mask, want) << int(q);
|
||||
EXPECT_TRUE(dpf::verify(n0, n1)) << int(q);
|
||||
EXPECT_TRUE(dpf::verify(b0, b1)) << int(q);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, ExtractableFp61FullDomainSketch)
|
||||
{
|
||||
const Input alpha = 0x7f;
|
||||
const dpf::fp61 beta{42};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::extractable{}, dpf::verifiable{});
|
||||
std::array<dpf::fp61, 256> s0{}, s1{}, r{};
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto y0 = *dpf::eval_point(k0, q, dpf::prove(a));
|
||||
const auto y1 = *dpf::eval_point(k1, q, dpf::prove(b));
|
||||
EXPECT_EQ(dpf::reconstruct(y0, y1), q == alpha ? beta : dpf::fp61{0}) << int(q);
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << int(q);
|
||||
s0[static_cast<std::size_t>(x)] = y0.raw();
|
||||
s1[static_cast<std::size_t>(x)] = y1.raw();
|
||||
r[static_cast<std::size_t>(x)] = dpf::fp61{static_cast<std::uint64_t>(3 * x + 1)};
|
||||
}
|
||||
const auto honest0 = s0;
|
||||
EXPECT_TRUE(dpf::sketch_verify(dpf::sketch_fold(s0, r), dpf::sketch_fold(s1, r)));
|
||||
// A second hot point is weight 2. Changing only the magnitude of the
|
||||
// single hot point stays weight 1 and must still verify.
|
||||
s0[0] = s0[0] + beta;
|
||||
EXPECT_FALSE(dpf::sketch_verify(dpf::sketch_fold(s0, r), dpf::sketch_fold(s1, r)));
|
||||
|
||||
auto r_bad = r;
|
||||
r_bad[alpha] = r_bad[alpha] + dpf::fp61{1};
|
||||
EXPECT_FALSE(dpf::sketch_verify(
|
||||
dpf::sketch_fold(honest0, r_bad), dpf::sketch_fold(s1, r)));
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, SeedAndWordCorruptionAreVisible)
|
||||
{
|
||||
const Input alpha = 0x2a;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, std::uint64_t{5}, dpf::verifiable{});
|
||||
EXPECT_TRUE(dpf::same_public_part(k0, k1));
|
||||
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
|
||||
k0.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::same_public_part(k0, k1));
|
||||
int proof_fail = 0;
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
dpf::proof_token a{}, b{};
|
||||
(void)*dpf::eval_point(k0, q, dpf::prove(a));
|
||||
(void)*dpf::eval_point(k1, q, dpf::prove(b));
|
||||
if (!dpf::verify(a, b))
|
||||
++proof_fail;
|
||||
}
|
||||
EXPECT_GT(proof_fail, 0);
|
||||
|
||||
auto [h0, h1] = dpf::make_dpf(alpha, std::uint64_t{5}, dpf::verifiable{});
|
||||
// A level-0 word is invisible when that party's root control bit is 0.
|
||||
// Flip every correction word so a later on-path level is corrupted.
|
||||
auto & words = const_cast<typename std::decay_t<decltype(h0)>::correction_words_array &>(
|
||||
h0.correction_words());
|
||||
for (auto & word : words)
|
||||
word = simde_mm_xor_si128(word, simde_mm_set1_epi8(0x5a));
|
||||
EXPECT_FALSE(dpf::same_public_part(h0, h1));
|
||||
int value_fail = 0;
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const Input q = static_cast<Input>(x);
|
||||
const auto got = dpf::reconstruct(*dpf::eval_point(h0, q), *dpf::eval_point(h1, q));
|
||||
const std::uint64_t want = q == alpha ? 5u : 0u;
|
||||
if (got != want)
|
||||
++value_fail;
|
||||
}
|
||||
EXPECT_GT(value_fail, 0);
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, EmptySequenceProofVerifies)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{1}, std::uint64_t{1}, dpf::verifiable{});
|
||||
const std::vector<Input> none;
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_sequence(k0, none.begin(), none.end(), dpf::prove(a));
|
||||
dpf::prove_sequence(k1, none.begin(), none.end(), dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, GenevalFullDomainProofAndPartyOrder)
|
||||
{
|
||||
const Input alpha = 0x3c;
|
||||
const Input x0 = 0x10;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const std::uint64_t y = 0x7e;
|
||||
auto g = dpf::geneval_full(x0, x1, tape(), y);
|
||||
ASSERT_EQ(g.party0.size(), 256u);
|
||||
EXPECT_TRUE(dpf::verify(g.proof0, g.proof1));
|
||||
for (int q = 0; q < 256; ++q)
|
||||
{
|
||||
const auto want = static_cast<Input>(q) == alpha ? y : 0ull;
|
||||
EXPECT_EQ(static_cast<std::uint64_t>(g.party0[static_cast<std::size_t>(q)]
|
||||
- g.party1[static_cast<std::size_t>(q)]),
|
||||
want) << q;
|
||||
if (g.party0[static_cast<std::size_t>(q)] != g.party1[static_cast<std::size_t>(q)])
|
||||
{
|
||||
EXPECT_NE(static_cast<std::uint64_t>(g.party1[static_cast<std::size_t>(q)]
|
||||
- g.party0[static_cast<std::size_t>(q)]),
|
||||
want) << q;
|
||||
}
|
||||
}
|
||||
g.proof0[0] = simde_mm_xor_si128(g.proof0[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(g.proof0, g.proof1));
|
||||
}
|
||||
|
||||
TEST(VdpfAdversarial, MacDetectsValueAndTagCorruption)
|
||||
{
|
||||
const auto key = dpf::sample_mac_key<dpf::fp61>();
|
||||
auto [a, b] = dpf::mac_share_value(dpf::fp61{20}, key);
|
||||
EXPECT_TRUE(dpf::mac_verify(a, b, key));
|
||||
const auto scaled = dpf::mac_scale(a, dpf::fp61{2});
|
||||
const auto scaled_b = dpf::mac_scale(b, dpf::fp61{2});
|
||||
EXPECT_TRUE(dpf::mac_verify(scaled, scaled_b, key));
|
||||
EXPECT_EQ((scaled.value + scaled_b.value).raw(), (dpf::fp61{20} * dpf::fp61{2}).raw());
|
||||
|
||||
a.value = a.value + dpf::fp61{1};
|
||||
EXPECT_FALSE(dpf::mac_verify(a, b, key));
|
||||
a.value = a.value - dpf::fp61{1};
|
||||
a.tag = a.tag + dpf::fp61{1};
|
||||
EXPECT_FALSE(dpf::mac_verify(a, b, key));
|
||||
|
||||
a.tag = a.tag - dpf::fp61{1};
|
||||
std::array<dpf::mac_share<dpf::fp61>, 1> left{a};
|
||||
std::array<dpf::mac_share<dpf::fp61>, 2> right{b, b};
|
||||
std::array<dpf::fp61, 1> coeffs{dpf::fp61{1}};
|
||||
EXPECT_FALSE(dpf::mac_verify_batch(left, right, coeffs, key));
|
||||
}
|
||||
399
test/tests/vdpf_regression_test.cpp
Normal file
399
test/tests/vdpf_regression_test.cpp
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
/// @file vdpf_regression_test.cpp
|
||||
/// @brief Holistic VDPF regressions from DCF/Grotto proof bring-up failures.
|
||||
/// @details Patterns covered:
|
||||
/// - path-memo re-fold cancel (blocked parks / native spine)
|
||||
/// - off-path vs on-path blocked proves (CS level vs checkpoint index)
|
||||
/// - domain-tag survival past `hash_node` level masking
|
||||
/// - keygen `make_cs` level must match fold level
|
||||
/// - warm interval memoizer must not skip upper proof folds
|
||||
/// - multi-endpoint signed/unsigned prefix aggregates
|
||||
/// - endpoint-equal-alpha prefix stability across RNG draws
|
||||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/prefix_parity.hpp"
|
||||
#include "grotto/offset_horner.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Input = std::uint8_t;
|
||||
|
||||
bool tokens_equal(const dpf::proof_token & a, const dpf::proof_token & b)
|
||||
{
|
||||
return dpf::detail::vdpf::proof_equal(a, b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// --- Domain separation / CS tagging ---------------------------------------
|
||||
|
||||
TEST(VdpfRegression, HashNodeRetainsHighLevelBits)
|
||||
{
|
||||
// fold_spine_tag is bit 15; masking to 8 bits would drop it and make
|
||||
// blocked CS verify against native folds.
|
||||
constexpr std::size_t tagged =
|
||||
dpf::detail::blocked::fold_spine_tag | std::size_t{3};
|
||||
const simde__m128i seed = simde_mm_set_epi64x(0x1111, 0x2222);
|
||||
const auto h_native = dpf::detail::vdpf::hash_node(3, 0x2a, seed);
|
||||
const auto h_tagged = dpf::detail::vdpf::hash_node(tagged, 0x2a, seed);
|
||||
EXPECT_NE(std::memcmp(&h_native, &h_tagged, sizeof(h_native)), 0);
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, BlockedKeygenCsUsesSpineTag)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a},
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
auto [n0, n1] = dpf::make_dpf(Input{0x2a}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
// Public CS arrays must diverge: blocked keygen tags make_cs levels.
|
||||
EXPECT_NE(std::memcmp(k0.correction_seeds().data(),
|
||||
n0.correction_seeds().data(),
|
||||
sizeof(decltype(k0)::correction_seeds_array)),
|
||||
0);
|
||||
(void)k1;
|
||||
(void)n1;
|
||||
}
|
||||
|
||||
// --- Path-memo idempotence (re-fold cancel) --------------------------------
|
||||
|
||||
TEST(VdpfRegression, BlockedEvalShareProveIsIdempotentOnWarmPath)
|
||||
{
|
||||
// Re-folding parked siblings on a warm path XORs them out of the token.
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a},
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
const Input x{0x11}; // off-path, parks right children
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::detail::vdpf::init_proof(a, k0);
|
||||
dpf::detail::vdpf::init_proof(b, k1);
|
||||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::detail::blocked::eval_share(k0, x, p0, &a);
|
||||
dpf::detail::blocked::eval_share(k1, x, p1, &b);
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
const auto once0 = a;
|
||||
const auto once1 = b;
|
||||
dpf::detail::blocked::eval_share(k0, x, p0, &a);
|
||||
dpf::detail::blocked::eval_share(k1, x, p1, &b);
|
||||
EXPECT_TRUE(tokens_equal(a, once0));
|
||||
EXPECT_TRUE(tokens_equal(b, once1));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, NativePathProveIsIdempotentOnWarmPath)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
const Input x{0x11};
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::detail::vdpf::init_proof(a, k0);
|
||||
dpf::detail::vdpf::init_proof(b, k1);
|
||||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::detail::ensure_level(k0, x, p0, decltype(k0)::depth, &a);
|
||||
dpf::detail::ensure_level(k1, x, p1, decltype(k1)::depth, &b);
|
||||
const auto once0 = a;
|
||||
const auto once1 = b;
|
||||
dpf::detail::ensure_level(k0, x, p0, decltype(k0)::depth, &a);
|
||||
dpf::detail::ensure_level(k1, x, p1, decltype(k1)::depth, &b);
|
||||
EXPECT_TRUE(tokens_equal(a, once0));
|
||||
EXPECT_TRUE(tokens_equal(b, once1));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, PublicProveOnWarmPathMatchesColdNonZero)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, std::uint64_t{9},
|
||||
dpf::verifiable{});
|
||||
const Input x{0x2a};
|
||||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::proof_token cold0{}, cold1{};
|
||||
(void)*dpf::eval_point(k0, x, dpf::prove(cold0));
|
||||
(void)*dpf::eval_point(k1, x, dpf::prove(cold1));
|
||||
EXPECT_TRUE(dpf::verify(cold0, cold1));
|
||||
EXPECT_FALSE(tokens_equal(cold0, dpf::detail::vdpf::zero_proof()));
|
||||
|
||||
(void)*dpf::eval_point(k0, x, path0);
|
||||
(void)*dpf::eval_point(k1, x, path1);
|
||||
dpf::proof_token warm0{}, warm1{};
|
||||
(void)*dpf::eval_point(k0, x, dpf::prove(warm0), path0);
|
||||
(void)*dpf::eval_point(k1, x, dpf::prove(warm1), path1);
|
||||
EXPECT_TRUE(dpf::verify(warm0, warm1));
|
||||
EXPECT_TRUE(tokens_equal(warm0, cold0));
|
||||
EXPECT_TRUE(tokens_equal(warm1, cold1));
|
||||
EXPECT_FALSE(dpf::verify(dpf::detail::vdpf::zero_proof(),
|
||||
dpf::detail::vdpf::zero_proof()));
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, BlockedMultiEndpointSignedPrefixKeepsSharedParks)
|
||||
{
|
||||
// Two endpoints that share a left turn: without resume-aware park folds
|
||||
// the second walk cancels the first's parked contribution.
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x40},
|
||||
dpf::block_width<4>(dpf::gt(std::uint64_t{1})), dpf::verifiable{});
|
||||
// Both 0x10 and 0x18 start with MSB bits that park the same early sibling
|
||||
// under a typical block schedule on uint8.
|
||||
const std::array<Input, 2> ends{Input{0x10}, Input{0x18}};
|
||||
|
||||
dpf::proof_token multi0{}, multi1{};
|
||||
auto s0 = grotto::signed_prefix_parities(k0, ends, dpf::prove(multi0));
|
||||
auto s1 = grotto::signed_prefix_parities(k1, ends, dpf::prove(multi1));
|
||||
EXPECT_TRUE(dpf::verify(multi0, multi1));
|
||||
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||
EXPECT_EQ((s0[i] + s1[i]) & k0.cmp().mask,
|
||||
(dpf::eval_point(dpf::cmp, k0, ends[i]).raw()
|
||||
+ dpf::eval_point(dpf::cmp, k1, ends[i]).raw())
|
||||
& k0.cmp().mask);
|
||||
|
||||
// Reference: fold e0 then e1 on one path without re-init (same as API).
|
||||
dpf::proof_token ref0{}, ref1{};
|
||||
dpf::detail::vdpf::init_proof(ref0, k0);
|
||||
dpf::detail::vdpf::init_proof(ref1, k1);
|
||||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::detail::blocked::eval_share(k0, ends[0], p0, &ref0);
|
||||
dpf::detail::blocked::eval_share(k0, ends[1], p0, &ref0);
|
||||
dpf::detail::blocked::eval_share(k1, ends[0], p1, &ref1);
|
||||
dpf::detail::blocked::eval_share(k1, ends[1], p1, &ref1);
|
||||
dpf::detail::vdpf::fold_output_binding(ref0, k0);
|
||||
dpf::detail::vdpf::fold_output_binding(ref1, k1);
|
||||
EXPECT_TRUE(tokens_equal(multi0, ref0));
|
||||
EXPECT_TRUE(tokens_equal(multi1, ref1));
|
||||
|
||||
// Replaying only the tip endpoint on the warm path is idempotent.
|
||||
dpf::proof_token path0{}, path1{};
|
||||
dpf::detail::vdpf::init_proof(path0, k0);
|
||||
dpf::detail::vdpf::init_proof(path1, k1);
|
||||
auto q0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto q1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::detail::blocked::eval_share(k0, ends[0], q0, &path0);
|
||||
dpf::detail::blocked::eval_share(k0, ends[1], q0, &path0);
|
||||
dpf::detail::blocked::eval_share(k1, ends[0], q1, &path1);
|
||||
dpf::detail::blocked::eval_share(k1, ends[1], q1, &path1);
|
||||
const auto path_once0 = path0;
|
||||
const auto path_once1 = path1;
|
||||
dpf::detail::blocked::eval_share(k0, ends[1], q0, &path0);
|
||||
dpf::detail::blocked::eval_share(k1, ends[1], q1, &path1);
|
||||
EXPECT_TRUE(tokens_equal(path0, path_once0));
|
||||
EXPECT_TRUE(tokens_equal(path1, path_once1));
|
||||
}
|
||||
|
||||
// --- Off-path / on-path / all-ones blocked point ---------------------------
|
||||
|
||||
TEST(VdpfRegression, BlockedPointProveOffPathOnPathAndAllOnes)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a},
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
const Input queries[] = {
|
||||
Input{0x2a}, // on-path
|
||||
Input{0x11}, // off-path with parks
|
||||
Input{0xff}, // never parks (all right)
|
||||
Input{0x00}, // parks every level
|
||||
};
|
||||
for (Input x : queries)
|
||||
{
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::eval_point(dpf::cmp, k0, x, dpf::prove(a));
|
||||
dpf::eval_point(dpf::cmp, k1, x, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b)) << "query=" << int(x);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, BlockedTokenDiffersFromNativeForSameAlpha)
|
||||
{
|
||||
const Input alpha{0x2a};
|
||||
const Input x{0x11};
|
||||
auto [b0, b1] = dpf::make_dpf(alpha,
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
auto [n0, n1] = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token bp0{}, bp1{}, np0{}, np1{};
|
||||
dpf::eval_point(dpf::cmp, b0, x, dpf::prove(bp0));
|
||||
dpf::eval_point(dpf::cmp, b1, x, dpf::prove(bp1));
|
||||
dpf::eval_point(dpf::cmp, n0, x, dpf::prove(np0));
|
||||
dpf::eval_point(dpf::cmp, n1, x, dpf::prove(np1));
|
||||
EXPECT_TRUE(dpf::verify(bp0, bp1));
|
||||
EXPECT_TRUE(dpf::verify(np0, np1));
|
||||
EXPECT_FALSE(tokens_equal(bp0, np0));
|
||||
}
|
||||
|
||||
// --- Warm interval memoizer ------------------------------------------------
|
||||
|
||||
TEST(VdpfRegression, CmpIntervalProveIgnoresWarmMemoizer)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x20}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
using Key = std::decay_t<decltype(k0)>;
|
||||
const Input lo{0x10};
|
||||
const Input hi{0x28};
|
||||
|
||||
dpf::proof_token cold0{}, cold1{};
|
||||
dpf::prove_cmp_interval(k0, lo, hi, dpf::prove(cold0));
|
||||
dpf::prove_cmp_interval(k1, lo, hi, dpf::prove(cold1));
|
||||
EXPECT_TRUE(dpf::verify(cold0, cold1));
|
||||
|
||||
// Warm a full-tree memo on a wider interval, then prove on [lo,hi]
|
||||
// through the same memo. Proving must clear/revisit upper levels.
|
||||
constexpr std::size_t stop =
|
||||
Key::cmp_depth == 0 ? Key::depth : Key::cmp_depth;
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
dpf::detail::incr::cmp_full_interval_memo<Key, stop> memo0{256};
|
||||
dpf::detail::incr::cmp_full_interval_memo<Key, stop> memo1{256};
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
auto warm_buf0 =
|
||||
dpf::make_output_buffer(dpf::cmp, k0, Input{0x00}, Input{0x3f});
|
||||
auto warm_buf1 =
|
||||
dpf::make_output_buffer(dpf::cmp, k1, Input{0x00}, Input{0x3f});
|
||||
dpf::eval_interval(dpf::cmp, k0, Input{0x00}, Input{0x3f}, warm_buf0,
|
||||
memo0);
|
||||
dpf::eval_interval(dpf::cmp, k1, Input{0x00}, Input{0x3f}, warm_buf1,
|
||||
memo1);
|
||||
|
||||
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, lo, hi);
|
||||
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, lo, hi);
|
||||
dpf::proof_token warm0{}, warm1{};
|
||||
dpf::eval_interval(dpf::cmp, k0, lo, hi, buf0, memo0, dpf::prove(warm0));
|
||||
dpf::eval_interval(dpf::cmp, k1, lo, hi, buf1, memo1, dpf::prove(warm1));
|
||||
EXPECT_TRUE(dpf::verify(warm0, warm1));
|
||||
EXPECT_TRUE(tokens_equal(cold0, warm0));
|
||||
EXPECT_TRUE(tokens_equal(cold1, warm1));
|
||||
}
|
||||
|
||||
// --- Sequence / path-memo aggregate without re-init ------------------------
|
||||
|
||||
TEST(VdpfRegression, CmpSequenceProveMatchesIntervalRunCovers)
|
||||
{
|
||||
// prove_cmp_sequence folds contiguous runs as intervals, not point walks.
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x55}, dpf::gt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
const Input xs[] = {0x10, 0x11, 0x40, 0x41};
|
||||
|
||||
dpf::proof_token seq0{}, seq1{};
|
||||
dpf::prove_cmp_sequence(k0, std::begin(xs), std::end(xs), dpf::prove(seq0));
|
||||
dpf::prove_cmp_sequence(k1, std::begin(xs), std::end(xs), dpf::prove(seq1));
|
||||
EXPECT_TRUE(dpf::verify(seq0, seq1));
|
||||
|
||||
dpf::proof_token runs0{}, runs1{};
|
||||
dpf::detail::vdpf::init_proof(runs0, k0);
|
||||
dpf::detail::vdpf::init_proof(runs1, k1);
|
||||
dpf::detail::incr::prove_fold_cmp_interval(k0, Input{0x10}, Input{0x11},
|
||||
runs0);
|
||||
dpf::detail::incr::prove_fold_cmp_interval(k0, Input{0x40}, Input{0x41},
|
||||
runs0);
|
||||
dpf::detail::incr::prove_fold_cmp_interval(k1, Input{0x10}, Input{0x11},
|
||||
runs1);
|
||||
dpf::detail::incr::prove_fold_cmp_interval(k1, Input{0x40}, Input{0x41},
|
||||
runs1);
|
||||
dpf::detail::vdpf::fold_output_binding(runs0, k0);
|
||||
dpf::detail::vdpf::fold_output_binding(runs1, k1);
|
||||
EXPECT_TRUE(tokens_equal(seq0, runs0));
|
||||
EXPECT_TRUE(tokens_equal(seq1, runs1));
|
||||
|
||||
// eval_sequence(cmp, ..., prove) uses path-memo point folds — a different
|
||||
// transcript from interval-run covers. Both must verify; they need not match.
|
||||
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, 4);
|
||||
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, 4);
|
||||
dpf::proof_token e0{}, e1{};
|
||||
dpf::eval_sequence(dpf::cmp, k0, std::begin(xs), std::end(xs), buf0,
|
||||
dpf::prove(e0));
|
||||
dpf::eval_sequence(dpf::cmp, k1, std::begin(xs), std::end(xs), buf1,
|
||||
dpf::prove(e1));
|
||||
EXPECT_TRUE(dpf::verify(e0, e1));
|
||||
EXPECT_FALSE(tokens_equal(seq0, e0));
|
||||
}
|
||||
|
||||
// --- Unsigned prefix vs resumed point folds --------------------------------
|
||||
|
||||
TEST(VdpfRegression, UnsignedPrefixTokenMatchesResumedPointFolds)
|
||||
{
|
||||
// Bit payload: unsigned prefix parity is a bit-DPF / XOR gadget.
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, dpf::bit{1}, dpf::verifiable{});
|
||||
const std::array<Input, 3> ends{Input{0x10}, Input{0x2b}, Input{0x40}};
|
||||
|
||||
dpf::proof_token pref0{}, pref1{};
|
||||
auto [p0, n0] = grotto::prefix_parities(k0, ends, dpf::prove(pref0));
|
||||
auto [p1, n1] = grotto::prefix_parities(k1, ends, dpf::prove(pref1));
|
||||
(void)n0;
|
||||
(void)n1;
|
||||
EXPECT_TRUE(dpf::verify(pref0, pref1));
|
||||
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||
EXPECT_EQ(p0[i] ^ p1[i], ends[i] > Input{0x2a});
|
||||
|
||||
const auto again0 = pref0;
|
||||
const auto again1 = pref1;
|
||||
grotto::prefix_parities(k0, ends, dpf::prove(pref0));
|
||||
grotto::prefix_parities(k1, ends, dpf::prove(pref1));
|
||||
EXPECT_TRUE(tokens_equal(pref0, again0));
|
||||
EXPECT_TRUE(tokens_equal(pref1, again1));
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, PrefixParityAtAlphaIsExclusiveAndStable)
|
||||
{
|
||||
// Exclusive of alpha: endpoint == alpha must reconstruct to 0 for every
|
||||
// key. A uint64 payload is the wrong gadget type and used to look "flaky".
|
||||
const Input alpha{0x2a};
|
||||
const std::array<Input, 1> ends{alpha};
|
||||
for (int t = 0; t < 32; ++t)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::bit{1});
|
||||
auto [p0, n0] = grotto::prefix_parities(k0, ends);
|
||||
auto [p1, n1] = grotto::prefix_parities(k1, ends);
|
||||
(void)n0;
|
||||
(void)n1;
|
||||
EXPECT_FALSE(p0[0] ^ p1[0]) << "trial " << t;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Offset Horner / poly multi-power batch --------------------------------
|
||||
|
||||
TEST(VdpfRegression, OffsetHornerPerPowerTokensIndependent)
|
||||
{
|
||||
constexpr std::size_t D = 2;
|
||||
const Input center = 12;
|
||||
auto mat = grotto::make_offset_horner_keys<Input, D>(center, dpf::verifiable{});
|
||||
const std::vector<Input> knots{0, 10, 50};
|
||||
const std::vector<std::array<std::uint64_t, D + 1>> coeff{
|
||||
{1, 0, 0}, {0, 2, 0}, {7, 1, 3}};
|
||||
const Input eta = 3;
|
||||
dpf::proof_token t0[D + 1]{}, t1[D + 1]{};
|
||||
const auto v0 = grotto::offset_horner_eval<0, D, Input, true>(
|
||||
mat, knots, coeff, eta, t0);
|
||||
const auto v1 = grotto::offset_horner_eval<1, D, Input, true>(
|
||||
mat, knots, coeff, eta, t1);
|
||||
EXPECT_EQ(v0 + v1, grotto::offset_horner_clear<D>(center, knots, coeff, eta));
|
||||
for (std::size_t m = 0; m <= D; ++m)
|
||||
EXPECT_TRUE(dpf::verify(t0[m], t1[m]));
|
||||
// Tamper power 1 only; other powers still verify.
|
||||
t0[1][0] = simde_mm_xor_si128(t0[1][0], simde_mm_set1_epi8(0x7e));
|
||||
EXPECT_FALSE(dpf::verify(t0[1], t1[1]));
|
||||
EXPECT_TRUE(dpf::verify(t0[0], t1[0]));
|
||||
EXPECT_TRUE(dpf::verify(t0[2], t1[2]));
|
||||
}
|
||||
|
||||
TEST(VdpfRegression, SignedPrefixBlockedProveValuesMatchDense)
|
||||
{
|
||||
auto [b0, b1] = dpf::make_dpf(Input{0x30},
|
||||
dpf::block_width<4>(dpf::gt(std::uint64_t{1})), dpf::verifiable{});
|
||||
auto [d0, d1] = dpf::make_dpf(Input{0x30}, dpf::gt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
const std::array<Input, 3> ends{Input{0x10}, Input{0x20}, Input{0x50}};
|
||||
dpf::proof_token bp0{}, bp1{};
|
||||
auto bs0 = grotto::signed_prefix_parities(b0, ends, dpf::prove(bp0));
|
||||
auto bs1 = grotto::signed_prefix_parities(b1, ends, dpf::prove(bp1));
|
||||
EXPECT_TRUE(dpf::verify(bp0, bp1));
|
||||
auto ds0 = grotto::signed_prefix_parities(d0, ends);
|
||||
auto ds1 = grotto::signed_prefix_parities(d1, ends);
|
||||
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ((bs0[i] + bs1[i]) & b0.cmp().mask,
|
||||
(ds0[i] + ds1[i]) & d0.cmp().mask);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
|
@ -6,6 +7,9 @@
|
|||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/offset_horner.hpp"
|
||||
#include "grotto/offset_poly.hpp"
|
||||
#include "grotto/carry.hpp"
|
||||
|
||||
using Interior = dpf::prg::aes128;
|
||||
using Exterior = dpf::prg::aes128;
|
||||
|
|
@ -39,11 +43,13 @@ TEST(Verifiable, TamperedCwRejects)
|
|||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{3},
|
||||
std::uint64_t{1}, dpf::verifiable{});
|
||||
|
||||
// Flip one bit of a public correction word on party 0's view of the
|
||||
// shared CW array by rebuilding an otherwise-identical key is hard;
|
||||
// instead flip cs after the fact via const_cast of the seed storage.
|
||||
auto & cs = const_cast<dpf::cs_block &>(k0.correction_seeds()[0]);
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
// Flip every correction seed so an on-path level with control bit 1
|
||||
// mixes the tamper (a single level-0 flip is invisible when t=0 there).
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
|
||||
k0.correction_seeds()))
|
||||
{
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
|
||||
}
|
||||
|
||||
dpf::proof_token pi0{}, pi1{};
|
||||
(void)*dpf::eval_point(k0, Input{3}, dpf::prove(pi0));
|
||||
|
|
@ -69,6 +75,10 @@ TEST(Verifiable, BatchVerify)
|
|||
EXPECT_TRUE(dpf::verify_batch(left, right));
|
||||
left[2][0] = simde_mm_xor_si128(left[2][0], simde_mm_set1_epi8(0xff));
|
||||
EXPECT_FALSE(dpf::verify_batch(left, right));
|
||||
// Restore and swap second halves of two slots — must still reject.
|
||||
left[2][0] = simde_mm_xor_si128(left[2][0], simde_mm_set1_epi8(0xff));
|
||||
std::swap(left[1][1], left[3][1]);
|
||||
EXPECT_FALSE(dpf::verify_batch(left, right));
|
||||
right.pop_back();
|
||||
EXPECT_FALSE(dpf::verify_batch(left, right));
|
||||
}
|
||||
|
|
@ -175,6 +185,65 @@ TEST(Verifiable, IntervalProve)
|
|||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, IntervalEvalProveMatchesProveInterval)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{0x20},
|
||||
std::uint64_t{7}, dpf::verifiable{});
|
||||
dpf::proof_token p0{}, p1{}, e0{}, e1{};
|
||||
dpf::prove_interval(k0, Input{0x10}, Input{0x18}, dpf::prove(p0));
|
||||
dpf::prove_interval(k1, Input{0x10}, Input{0x18}, dpf::prove(p1));
|
||||
EXPECT_TRUE(dpf::verify(p0, p1));
|
||||
|
||||
auto buf0 = dpf::make_output_buffer_for_interval(k0, Input{0x10}, Input{0x18});
|
||||
auto buf1 = dpf::make_output_buffer_for_interval(k1, Input{0x10}, Input{0x18});
|
||||
dpf::eval_interval(k0, Input{0x10}, Input{0x18}, buf0, dpf::prove(e0));
|
||||
dpf::eval_interval(k1, Input{0x10}, Input{0x18}, buf1, dpf::prove(e1));
|
||||
EXPECT_TRUE(dpf::verify(e0, e1));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(p0, e0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(p1, e1));
|
||||
|
||||
// Tamper party 0's token after an honest fold.
|
||||
e0[0] = simde_mm_xor_si128(e0[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(e0, e1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, FullProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{0x55},
|
||||
std::uint64_t{3}, dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_full(k0, dpf::prove(a));
|
||||
dpf::prove_full(k1, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
a[0] = simde_mm_xor_si128(a[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, SequenceProveIntervalCovers)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{0x22},
|
||||
std::uint64_t{9}, dpf::verifiable{});
|
||||
// Two runs: [0x10,0x12] and [0x20,0x21], plus an isolated 0x30.
|
||||
const Input xs[] = {0x10, 0x11, 0x12, 0x20, 0x21, 0x30};
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_sequence(k0, std::begin(xs), std::end(xs), dpf::prove(a));
|
||||
dpf::prove_sequence(k1, std::begin(xs), std::end(xs), dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
|
||||
// eval_sequence(..., prove) uses the same interval-run covers.
|
||||
auto buf0 = dpf::make_output_buffer_for_subsequence(k0, std::begin(xs), std::end(xs));
|
||||
auto buf1 = dpf::make_output_buffer_for_subsequence(k1, std::begin(xs), std::end(xs));
|
||||
dpf::proof_token e0{}, e1{};
|
||||
dpf::eval_sequence(k0, std::begin(xs), std::end(xs), buf0, dpf::prove(e0));
|
||||
dpf::eval_sequence(k1, std::begin(xs), std::end(xs), buf1, dpf::prove(e1));
|
||||
EXPECT_TRUE(dpf::verify(e0, e1));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(a, e0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(b, e1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, DoernerShelatProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
|
|
@ -209,3 +278,431 @@ TEST(Verifiable, DoernerShelatProve)
|
|||
beta);
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, CmpIntervalProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x20}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_interval(k0, Input{0x10}, Input{0x28}, dpf::prove(a));
|
||||
dpf::prove_cmp_interval(k1, Input{0x10}, Input{0x28}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
|
||||
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, Input{0x10}, Input{0x28});
|
||||
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, Input{0x10}, Input{0x28});
|
||||
dpf::proof_token e0{}, e1{};
|
||||
dpf::eval_interval(dpf::cmp, k0, Input{0x10}, Input{0x28}, buf0, dpf::prove(e0));
|
||||
dpf::eval_interval(dpf::cmp, k1, Input{0x10}, Input{0x28}, buf1, dpf::prove(e1));
|
||||
EXPECT_TRUE(dpf::verify(e0, e1));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(a, e0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(b, e1));
|
||||
e0[0] = simde_mm_xor_si128(e0[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(e0, e1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, CmpFullAndSequenceProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x55}, dpf::gt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_full(k0, dpf::prove(a));
|
||||
dpf::prove_cmp_full(k1, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
|
||||
const Input xs[] = {0x10, 0x11, 0x20};
|
||||
dpf::proof_token s0{}, s1{};
|
||||
dpf::prove_cmp_sequence(k0, std::begin(xs), std::end(xs), dpf::prove(s0));
|
||||
dpf::prove_cmp_sequence(k1, std::begin(xs), std::end(xs), dpf::prove(s1));
|
||||
EXPECT_TRUE(dpf::verify(s0, s1));
|
||||
|
||||
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, 3);
|
||||
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, 3);
|
||||
dpf::proof_token e0{}, e1{};
|
||||
dpf::eval_sequence(dpf::cmp, k0, std::begin(xs), std::end(xs), buf0,
|
||||
dpf::prove(e0));
|
||||
dpf::eval_sequence(dpf::cmp, k1, std::begin(xs), std::end(xs), buf1,
|
||||
dpf::prove(e1));
|
||||
EXPECT_TRUE(dpf::verify(e0, e1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, CmpInnerProductProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x18}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
const std::uint64_t w[] = {1, 2, 3, 4};
|
||||
dpf::proof_token a{}, b{};
|
||||
const auto d0 = dpf::eval_inner_product(dpf::cmp, k0, Input{0x10}, Input{0x13},
|
||||
w, dpf::prove(a));
|
||||
const auto d1 = dpf::eval_inner_product(dpf::cmp, k1, Input{0x10}, Input{0x13},
|
||||
w, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
(void)d0;
|
||||
(void)d1;
|
||||
}
|
||||
|
||||
TEST(Verifiable, IdcfPointProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, dpf::idcf(dpf::gt(std::uint64_t{1})),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::eval_point(dpf::cmp, k0, Input{0x30}, dpf::prove(a));
|
||||
dpf::eval_point(dpf::cmp, k1, Input{0x30}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
a[0] = simde_mm_xor_si128(a[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, BlockedCmpPointProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a},
|
||||
dpf::block_width<4>(dpf::lt(std::uint64_t{1})), dpf::verifiable{});
|
||||
EXPECT_GT(decltype(k0)::cmp_block, 0u);
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::eval_point(dpf::cmp, k0, Input{0x11}, dpf::prove(a));
|
||||
dpf::eval_point(dpf::cmp, k1, Input{0x11}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
|
||||
// Native key of the same alpha yields a different token domain.
|
||||
auto [n0, n1] = dpf::make_dpf(Input{0x2a}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token c{}, d{};
|
||||
dpf::eval_point(dpf::cmp, n0, Input{0x11}, dpf::prove(c));
|
||||
dpf::eval_point(dpf::cmp, n1, Input{0x11}, dpf::prove(d));
|
||||
EXPECT_TRUE(dpf::verify(c, d));
|
||||
EXPECT_FALSE(dpf::detail::vdpf::proof_equal(a, c));
|
||||
|
||||
a[0] = simde_mm_xor_si128(a[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, BlockedCmpIntervalProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x20},
|
||||
dpf::block_width<4>(dpf::gt(std::uint64_t{1})), dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_interval(k0, Input{0x1c}, Input{0x24}, dpf::prove(a));
|
||||
dpf::prove_cmp_interval(k1, Input{0x1c}, Input{0x24}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, GenevalTrieProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x2a;
|
||||
Input x0 = 0x11;
|
||||
Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
struct Pad
|
||||
{
|
||||
std::uint64_t n = 1;
|
||||
simde__m128i block()
|
||||
{
|
||||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||||
static_cast<long long>(n * 9 + 3));
|
||||
n += 2;
|
||||
return v;
|
||||
}
|
||||
void fill(void * p, std::size_t nbytes)
|
||||
{
|
||||
auto * b = static_cast<unsigned char *>(p);
|
||||
for (std::size_t i = 0; i < nbytes; ++i)
|
||||
b[i] = static_cast<unsigned char>(n + i * 17);
|
||||
n += nbytes;
|
||||
}
|
||||
std::uint8_t bit() { return static_cast<std::uint8_t>(n++ & 1u); }
|
||||
};
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
dpf::ds_randomness<simde__m128i (*)(), Pad> rng{
|
||||
dpf::uniform_sample<simde__m128i>, Pad{}};
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
const Input qs[] = {0x2a, 0x2b};
|
||||
auto got = dpf::geneval_point(x0, x1, qs[0], rng, std::uint64_t{7});
|
||||
EXPECT_TRUE(dpf::verify(got.proof0, got.proof1));
|
||||
got.proof0[0] = simde_mm_xor_si128(got.proof0[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(got.proof0, got.proof1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, GenevalCmpProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x30;
|
||||
Input x0 = 0x05;
|
||||
Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
struct Pad
|
||||
{
|
||||
std::uint64_t n = 1;
|
||||
simde__m128i block()
|
||||
{
|
||||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||||
static_cast<long long>(n * 9 + 3));
|
||||
n += 2;
|
||||
return v;
|
||||
}
|
||||
void fill(void * p, std::size_t nbytes)
|
||||
{
|
||||
auto * b = static_cast<unsigned char *>(p);
|
||||
for (std::size_t i = 0; i < nbytes; ++i)
|
||||
b[i] = static_cast<unsigned char>(n + i * 17);
|
||||
n += nbytes;
|
||||
}
|
||||
std::uint8_t bit() { return static_cast<std::uint8_t>(n++ & 1u); }
|
||||
};
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
dpf::ds_randomness<simde__m128i (*)(), Pad> rng{
|
||||
dpf::uniform_sample<simde__m128i>, Pad{}};
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
const Input ends[] = {0x10, 0x20, 0x40};
|
||||
auto got = dpf::geneval_cmp(x0, x1, std::begin(ends), std::end(ends), rng,
|
||||
dpf::gt(std::uint64_t{1}));
|
||||
EXPECT_TRUE(dpf::verify(got.proof0, got.proof1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, OffsetHornerProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
constexpr std::size_t D = 1;
|
||||
const Input center = 12;
|
||||
auto mat = grotto::make_offset_horner_keys<Input, D>(center, dpf::verifiable{});
|
||||
EXPECT_TRUE(decltype(mat)::is_verifiable);
|
||||
const std::vector<Input> knots{0, 10, 50};
|
||||
const std::vector<std::array<std::uint64_t, D + 1>> coeff{
|
||||
{1, 0}, {0, 2}, {7, 1}};
|
||||
const Input eta = 3;
|
||||
dpf::proof_token t0[D + 1]{}, t1[D + 1]{};
|
||||
const auto v0 = grotto::offset_horner_eval<0, D, Input, true>(
|
||||
mat, knots, coeff, eta, t0);
|
||||
const auto v1 = grotto::offset_horner_eval<1, D, Input, true>(
|
||||
mat, knots, coeff, eta, t1);
|
||||
EXPECT_EQ(v0 + v1, grotto::offset_horner_clear<D>(center, knots, coeff, eta));
|
||||
for (std::size_t m = 0; m <= D; ++m)
|
||||
EXPECT_TRUE(dpf::verify(t0[m], t1[m]));
|
||||
t0[0][0] = simde_mm_xor_si128(t0[0][0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(t0[0], t1[0]));
|
||||
}
|
||||
|
||||
TEST(Verifiable, PathPaintLcpProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x2a}, dpf::lcp(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_interval(k0, Input{0x20}, Input{0x2f}, dpf::prove(a));
|
||||
dpf::prove_cmp_interval(k1, Input{0x20}, Input{0x2f}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
a[0] = simde_mm_xor_si128(a[0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, OffsetPolyProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input center = 12;
|
||||
auto mat = grotto::make_offset_poly_keys(center, 1, dpf::verifiable{});
|
||||
EXPECT_TRUE(mat.verifiable);
|
||||
const std::vector<Input> knots{0, 10, 50};
|
||||
const std::vector<std::vector<std::uint64_t>> coeff{{1, 0}, {0, 2}, {7, 1}};
|
||||
const Input eta = 3;
|
||||
dpf::proof_token t0[2]{}, t1[2]{};
|
||||
const auto v0 = grotto::offset_poly_eval<0>(mat, knots, coeff, eta, t0);
|
||||
const auto v1 = grotto::offset_poly_eval<1>(mat, knots, coeff, eta, t1);
|
||||
EXPECT_EQ(v0 + v1, grotto::offset_poly_clear(center, knots, coeff, eta));
|
||||
EXPECT_TRUE(dpf::verify(t0[0], t1[0]));
|
||||
EXPECT_TRUE(dpf::verify(t0[1], t1[1]));
|
||||
t0[1][0] = simde_mm_xor_si128(t0[1][0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(t0[1], t1[1]));
|
||||
EXPECT_TRUE(dpf::verify(t0[0], t1[0]));
|
||||
}
|
||||
|
||||
TEST(Verifiable, BlockedCmpIntervalTamperRejects)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x20},
|
||||
dpf::block_width<4>(dpf::gt(std::uint64_t{1})), dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::prove_cmp_interval(k0, Input{0x1c}, Input{0x24}, dpf::prove(a));
|
||||
dpf::prove_cmp_interval(k1, Input{0x1c}, Input{0x24}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
|
||||
k0.correction_seeds()))
|
||||
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(0x11));
|
||||
dpf::proof_token c{}, d{};
|
||||
dpf::prove_cmp_interval(k0, Input{0x1c}, Input{0x24}, dpf::prove(c));
|
||||
dpf::prove_cmp_interval(k1, Input{0x1c}, Input{0x24}, dpf::prove(d));
|
||||
EXPECT_FALSE(dpf::verify(c, d));
|
||||
}
|
||||
|
||||
TEST(Verifiable, GenevalOffsetHornerProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
constexpr std::size_t D = 1;
|
||||
const Input center = 20;
|
||||
const Input share = 0x3c;
|
||||
const Input other = static_cast<Input>(center ^ share);
|
||||
const Input eta = 3;
|
||||
struct Pad
|
||||
{
|
||||
std::uint64_t n = 1;
|
||||
simde__m128i block()
|
||||
{
|
||||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||||
static_cast<long long>(n * 9 + 3));
|
||||
n += 2;
|
||||
return v;
|
||||
}
|
||||
void fill(void * p, std::size_t nbytes)
|
||||
{
|
||||
auto * b = static_cast<unsigned char *>(p);
|
||||
for (std::size_t i = 0; i < nbytes; ++i)
|
||||
b[i] = static_cast<unsigned char>(n + i * 17);
|
||||
n += nbytes;
|
||||
}
|
||||
std::uint8_t bit() { return static_cast<std::uint8_t>(n++ & 1u); }
|
||||
};
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
dpf::ds_randomness<simde__m128i (*)(), Pad> rng{
|
||||
dpf::uniform_sample<simde__m128i>, Pad{}};
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
const std::vector<Input> knots{0, 15, 40};
|
||||
const std::vector<std::array<std::uint64_t, D + 1>> coeff{{1, 0}, {2, 1}, {0, 3}};
|
||||
auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff,
|
||||
std::move(rng));
|
||||
for (std::size_t m = 0; m <= D; ++m)
|
||||
EXPECT_TRUE(dpf::verify(got.proof0[m], got.proof1[m]));
|
||||
got.proof0[0][0] = simde_mm_xor_si128(got.proof0[0][0], simde_mm_set1_epi8(1));
|
||||
EXPECT_FALSE(dpf::verify(got.proof0[0], got.proof1[0]));
|
||||
}
|
||||
|
||||
TEST(Verifiable, ZeroTokenRejects)
|
||||
{
|
||||
dpf::proof_token z = dpf::detail::vdpf::zero_proof();
|
||||
EXPECT_FALSE(dpf::verify(z, z));
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{3},
|
||||
std::uint64_t{1}, dpf::verifiable{});
|
||||
dpf::proof_token a{}, b{};
|
||||
(void)*dpf::eval_point(k0, Input{3}, dpf::prove(a));
|
||||
(void)*dpf::eval_point(k1, Input{3}, dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
EXPECT_FALSE(dpf::verify(a, z));
|
||||
EXPECT_FALSE(dpf::verify(z, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, ColdAndWarmProveMatch)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{0x2a},
|
||||
std::uint64_t{7}, dpf::verifiable{});
|
||||
const Input x{0x11};
|
||||
dpf::proof_token cold0{}, cold1{};
|
||||
(void)*dpf::eval_point(k0, x, dpf::prove(cold0));
|
||||
(void)*dpf::eval_point(k1, x, dpf::prove(cold1));
|
||||
EXPECT_TRUE(dpf::verify(cold0, cold1));
|
||||
EXPECT_FALSE(dpf::detail::vdpf::proof_equal(cold0,
|
||||
dpf::detail::vdpf::zero_proof()));
|
||||
|
||||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||||
dpf::proof_token warm0{}, warm1{};
|
||||
(void)*dpf::eval_point(k0, x, dpf::prove(warm0), p0);
|
||||
(void)*dpf::eval_point(k1, x, dpf::prove(warm1), p1);
|
||||
// Second prove on the warm memoizer must match the cold token.
|
||||
dpf::proof_token again0{}, again1{};
|
||||
(void)*dpf::eval_point(k0, x, dpf::prove(again0), p0);
|
||||
(void)*dpf::eval_point(k1, x, dpf::prove(again1), p1);
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(cold0, warm0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(cold0, again0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(cold1, again1));
|
||||
EXPECT_TRUE(dpf::verify(again0, again1));
|
||||
}
|
||||
|
||||
TEST(Verifiable, LeafTamperRejectsBoundProof)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{5},
|
||||
std::uint64_t{11}, dpf::verifiable{});
|
||||
auto & leaf = std::get<0>(
|
||||
const_cast<typename std::decay_t<decltype(k0)>::leaf_wrapper_tuple &>(
|
||||
k0.leaf_nodes)).raw_leaf();
|
||||
auto * bytes = reinterpret_cast<unsigned char *>(&leaf);
|
||||
bytes[0] = static_cast<unsigned char>(bytes[0] ^ 0x5a);
|
||||
|
||||
dpf::proof_token a{}, b{};
|
||||
(void)*dpf::eval_point(k0, Input{5}, dpf::prove(a));
|
||||
(void)*dpf::eval_point(k1, Input{5}, dpf::prove(b));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, ValueWordTamperRejectsBoundProof)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(Input{0x20}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
auto & vcw = const_cast<typename std::decay_t<decltype(k0)>::value_cw_array &>(
|
||||
k0.value_cw());
|
||||
vcw[0] = static_cast<typename std::decay_t<decltype(k0)>::value_cw_word>(
|
||||
static_cast<std::uint64_t>(vcw[0]) ^ 1ull);
|
||||
|
||||
dpf::proof_token a{}, b{};
|
||||
dpf::eval_point(dpf::cmp, k0, Input{0x10}, dpf::prove(a));
|
||||
dpf::eval_point(dpf::cmp, k1, Input{0x10}, dpf::prove(b));
|
||||
EXPECT_FALSE(dpf::verify(a, b));
|
||||
}
|
||||
|
||||
TEST(Verifiable, MakeCsMatchesHashNodeXor)
|
||||
{
|
||||
// Local transcript of party/oblivious_hash.hpp: cs = H(s0) XOR H(s1).
|
||||
const std::size_t level = 3;
|
||||
const psnip_uint64_t prefix = 0x2a;
|
||||
const simde__m128i s0 = simde_mm_set_epi64x(0x1111, 0x2222);
|
||||
const simde__m128i s1 = simde_mm_set_epi64x(0x3333, 0x4444);
|
||||
const auto cs = dpf::detail::vdpf::make_cs(level, prefix, s0, s1);
|
||||
const auto h0 = dpf::detail::vdpf::hash_node(level, prefix, s0);
|
||||
const auto h1 = dpf::detail::vdpf::hash_node(level, prefix, s1);
|
||||
dpf::cs_block xor_h{
|
||||
simde_mm_xor_si128(h0[0], h1[0]),
|
||||
simde_mm_xor_si128(h0[1], h1[1]),
|
||||
simde_mm_xor_si128(h0[2], h1[2]),
|
||||
simde_mm_xor_si128(h0[3], h1[3])};
|
||||
EXPECT_EQ(std::memcmp(&cs, &xor_h, sizeof(cs)), 0);
|
||||
|
||||
constexpr std::size_t tagged =
|
||||
dpf::detail::blocked::fold_spine_tag | level;
|
||||
const auto cs_b = dpf::detail::vdpf::make_cs(tagged, prefix, s0, s1);
|
||||
const auto hb0 = dpf::detail::vdpf::hash_node(tagged, prefix, s0);
|
||||
const auto hb1 = dpf::detail::vdpf::hash_node(tagged, prefix, s1);
|
||||
dpf::cs_block xor_hb{
|
||||
simde_mm_xor_si128(hb0[0], hb1[0]),
|
||||
simde_mm_xor_si128(hb0[1], hb1[1]),
|
||||
simde_mm_xor_si128(hb0[2], hb1[2]),
|
||||
simde_mm_xor_si128(hb0[3], hb1[3])};
|
||||
EXPECT_EQ(std::memcmp(&cs_b, &xor_hb, sizeof(cs_b)), 0);
|
||||
EXPECT_NE(std::memcmp(&cs, &cs_b, sizeof(cs)), 0);
|
||||
}
|
||||
|
||||
TEST(Verifiable, InnerProductProve)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf<Interior, Exterior>(Input{0x10},
|
||||
std::uint64_t{3}, dpf::verifiable{});
|
||||
std::array<std::uint64_t, 8> w{};
|
||||
w.fill(1);
|
||||
dpf::proof_token a{}, b{}, p0{}, p1{};
|
||||
dpf::eval_inner_product(k0, Input{0x0c}, Input{0x13}, w,
|
||||
dpf::prove(a));
|
||||
dpf::eval_inner_product(k1, Input{0x0c}, Input{0x13}, w,
|
||||
dpf::prove(b));
|
||||
EXPECT_TRUE(dpf::verify(a, b));
|
||||
dpf::prove_interval(k0, Input{0x0c}, Input{0x13}, dpf::prove(p0));
|
||||
dpf::prove_interval(k1, Input{0x0c}, Input{0x13}, dpf::prove(p1));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(a, p0));
|
||||
EXPECT_TRUE(dpf::detail::vdpf::proof_equal(b, p1));
|
||||
}
|
||||
|
||||
|
|
|
|||
211
test/tests/walk_helpers_test.cpp
Normal file
211
test/tests/walk_helpers_test.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
// Walk helpers fold a small operation into an existing DPF walk. See
|
||||
// dpf/eval_walk.hpp.
|
||||
|
||||
TEST(WalkHelpers, RotatePairedInnerProduct)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t r = 50;
|
||||
constexpr std::uint8_t a = 42;
|
||||
const std::size_t s = (n - static_cast<std::size_t>(
|
||||
static_cast<std::uint8_t>(r - a))) % n;
|
||||
|
||||
std::vector<std::uint64_t> table(n);
|
||||
for (std::size_t i = 0; i < n; ++i) table[i] = i * 7 + 1;
|
||||
|
||||
auto [k0, k1] = dpf::make_dpf(r, std::uint64_t{1});
|
||||
|
||||
std::vector<std::uint64_t> manual(n);
|
||||
for (std::size_t i = 0; i < n; ++i) manual[i] = table[(i + s) % n];
|
||||
const auto ref = dpf::reconstruct(
|
||||
dpf::eval_full_inner_product(dpf::paired, k0, manual),
|
||||
dpf::eval_full_inner_product(dpf::paired, k1, manual));
|
||||
|
||||
const auto got = dpf::reconstruct(
|
||||
dpf::eval_full_inner_product(dpf::paired, k0, table, dpf::rotate{s}),
|
||||
dpf::eval_full_inner_product(dpf::paired, k1, table, dpf::rotate{s}));
|
||||
|
||||
EXPECT_EQ(got, ref);
|
||||
EXPECT_EQ(got, table[(r + s) % n]);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, FullAddIntoHistogram)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
std::vector<dpf::field64> h0(n), h1(n);
|
||||
for (auto bin : std::array<std::uint8_t, 3>{3, 3, 7})
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(bin, dpf::field64{1});
|
||||
dpf::eval_full_add_into(h0, k0);
|
||||
dpf::eval_full_add_into(h1, k1);
|
||||
}
|
||||
EXPECT_EQ((h0[3] - h1[3]).raw(), 2u);
|
||||
EXPECT_EQ((h0[7] - h1[7]).raw(), 1u);
|
||||
EXPECT_EQ((h0[0] - h1[0]).raw(), 0u);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, FullAddIntoRotate)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t r = 10;
|
||||
constexpr std::size_t shift = 32;
|
||||
std::vector<std::uint64_t> d0(n, 0), d1(n, 0);
|
||||
auto [w0, w1] = dpf::make_dpf(r, std::uint64_t{7});
|
||||
dpf::eval_full_add_into(d0, w0, dpf::rotate{shift});
|
||||
dpf::eval_full_add_into(d1, w1, dpf::rotate{shift});
|
||||
const std::size_t hot = (r + shift) % n;
|
||||
EXPECT_EQ(d0[hot] - d1[hot], 7u);
|
||||
EXPECT_EQ(d0[r] - d1[r], 0u);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, FullAddIntoSketch)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t address = 9;
|
||||
const dpf::fp61 message{42};
|
||||
auto [k0, k1] = dpf::make_dpf(address, message, dpf::extractable{});
|
||||
|
||||
std::vector<dpf::fp61> box0(n), box1(n);
|
||||
std::vector<dpf::fp61> challenge(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
challenge[i] = dpf::fp61{static_cast<std::uint64_t>(i + 1)};
|
||||
|
||||
dpf::sketch_share s0{}, s1{};
|
||||
auto sk0 = dpf::sketch(s0, challenge);
|
||||
auto sk1 = dpf::sketch(s1, challenge);
|
||||
dpf::eval_full_add_into(box0, k0, sk0);
|
||||
dpf::eval_full_add_into(box1, k1, sk1);
|
||||
|
||||
EXPECT_EQ(box0[address] - box1[address], message);
|
||||
EXPECT_EQ((box0[0] - box1[0]).raw(), 0u);
|
||||
EXPECT_TRUE(dpf::sketch_verify(s0, s1));
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, CyclicShift)
|
||||
{
|
||||
std::vector<int> v(8);
|
||||
for (int i = 0; i < 8; ++i) v[i] = i;
|
||||
auto sh = dpf::cyclic_shift(v, 3);
|
||||
for (std::size_t i = 0; i < v.size(); ++i)
|
||||
EXPECT_EQ(sh[i], v[(i + v.size() - 3) % v.size()]);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, PackBitColumns)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t alpha = 42;
|
||||
auto [a0, a1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
auto [b0, b1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
const auto p0 = dpf::pack_bit_columns(a0, b0);
|
||||
const auto p1 = dpf::pack_bit_columns(a1, b1);
|
||||
for (std::size_t row = 0; row < n; ++row)
|
||||
{
|
||||
const std::uint64_t opened = p0[row] ^ p1[row];
|
||||
EXPECT_EQ(opened, row == alpha ? 0b11u : 0u) << "row " << row;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, ModBitColumns)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t alpha = 42;
|
||||
auto [a0, a1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
auto [b0, b1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
auto [c0, c1] = dpf::make_dpf(alpha, dpf::bit::one);
|
||||
|
||||
auto expect_mod = [&](auto modulus, const auto & k0, const auto & k1, const auto & k2)
|
||||
{
|
||||
constexpr unsigned m = decltype(modulus)::value;
|
||||
const auto packed = dpf::pack_bit_columns(k0, k1, k2);
|
||||
const auto got = dpf::mod_bit_columns<m>(k0, k1, k2);
|
||||
ASSERT_EQ(got.size(), n);
|
||||
for (std::size_t row = 0; row < n; ++row)
|
||||
EXPECT_EQ(static_cast<unsigned>(got[row]), packed[row] % m) << "row " << row;
|
||||
};
|
||||
expect_mod(std::integral_constant<unsigned, 5>{}, a0, b0, c0);
|
||||
expect_mod(std::integral_constant<unsigned, 128>{}, a0, b0, c0);
|
||||
expect_mod(std::integral_constant<unsigned, 200>{}, a1, b1, c1);
|
||||
expect_mod(std::integral_constant<unsigned, 1000>{}, a0, b1, c0);
|
||||
expect_mod(std::integral_constant<unsigned, 32768>{}, a0, b0, c0);
|
||||
|
||||
const auto one = dpf::mod_bit_columns<7>(a0);
|
||||
const auto bits = dpf::pack_bit_columns(a0);
|
||||
for (std::size_t row = 0; row < n; ++row)
|
||||
EXPECT_EQ(static_cast<unsigned>(one[row]), bits[row] % 7u);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, UnitSignBitLeaf)
|
||||
{
|
||||
constexpr std::uint8_t r = 50;
|
||||
int w0 = 0, w1 = 0;
|
||||
auto [k0, k1] = dpf::make_dpf(r, dpf::bit::one, dpf::unit_sign{w0, w1});
|
||||
const int sign = w0 - w1;
|
||||
EXPECT_TRUE(sign == 1 || sign == -1);
|
||||
EXPECT_EQ(w0, static_cast<int>(static_cast<bool>((*dpf::eval_point(k0, r)).raw())));
|
||||
EXPECT_EQ(w1, static_cast<int>(static_cast<bool>((*dpf::eval_point(k1, r)).raw())));
|
||||
// The key still opens as an ordinary unit bit DPF.
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, r), *dpf::eval_point(k1, r)),
|
||||
dpf::bit::one);
|
||||
EXPECT_EQ(dpf::reconstruct(*dpf::eval_point(k0, std::uint8_t{0}),
|
||||
*dpf::eval_point(k1, std::uint8_t{0})), dpf::bit::zero);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, CmpFullInnerProduct)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
constexpr std::uint8_t threshold = 50;
|
||||
auto [c0, c1] = dpf::make_dpf(threshold, dpf::gt(std::uint64_t{1}));
|
||||
std::vector<std::uint64_t> w(n, 0);
|
||||
w[90] = 8; w[200] = 3; w[30] = 5; // 90,200 are > 50
|
||||
const auto h0 = dpf::eval_full_inner_product(dpf::cmp, c0, w);
|
||||
const auto h1 = dpf::eval_full_inner_product(dpf::cmp, c1, w);
|
||||
EXPECT_EQ(dpf::reconstruct_cmp_halves(h0, h1).raw(), 11u);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, Dpf3FullAddInto)
|
||||
{
|
||||
constexpr std::size_t n = 256;
|
||||
std::vector<dpf::fp61> l1(n), l2(n), l3(n);
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(std::uint8_t{5}, dpf::fp61{100});
|
||||
dpf::eval_full_add_into(l1, k1);
|
||||
dpf::eval_full_add_into(l2, k2);
|
||||
dpf::eval_full_add_into(l3, k3);
|
||||
auto open2 = [](dpf::fp61 a, dpf::fp61 b) {
|
||||
return dpf::shamir3::reconstruct(dpf::shamir3::share{1, a},
|
||||
dpf::shamir3::share{2, b});
|
||||
};
|
||||
EXPECT_EQ(open2(l1[5], l2[5]), dpf::fp61{100});
|
||||
EXPECT_EQ(open2(l1[0], l2[0]).raw(), 0u);
|
||||
}
|
||||
|
||||
TEST(WalkHelpers, PrefixesAndPrefixInnerProduct)
|
||||
{
|
||||
constexpr std::uint8_t left = 0xA0; // prefix "101"
|
||||
auto [k0, k1] = dpf::make_dpf(left,
|
||||
dpf::idpf(std::uint64_t{1}, std::uint64_t{1}, std::uint64_t{1}));
|
||||
|
||||
auto [buf0, it0] = dpf::eval_prefixes(dpf::out<0, 1>, k0);
|
||||
auto [buf1, it1] = dpf::eval_prefixes(dpf::out<0, 1>, k1);
|
||||
EXPECT_EQ(dpf::reconstruct(buf0[0], buf1[0]), 0u);
|
||||
EXPECT_EQ(dpf::reconstruct(buf0[1], buf1[1]), 1u);
|
||||
|
||||
std::vector<std::uint64_t> vals1{11, 23};
|
||||
const auto dot1 =
|
||||
dpf::eval_prefix_inner_product(dpf::out<0, 1>, k0, vals1)
|
||||
- dpf::eval_prefix_inner_product(dpf::out<0, 1>, k1, vals1);
|
||||
EXPECT_EQ(dot1, vals1[1]);
|
||||
|
||||
std::vector<std::uint64_t> vals3(8);
|
||||
for (std::size_t i = 0; i < 8; ++i) vals3[i] = 100 + i;
|
||||
const auto dot3 =
|
||||
dpf::eval_prefix_inner_product(dpf::out<2, 3>, k0, vals3)
|
||||
- dpf::eval_prefix_inner_product(dpf::out<2, 3>, k1, vals3);
|
||||
EXPECT_EQ(dot3, vals3[0b101]);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/fixedpoint.hpp"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
#include "asio.hpp"
|
||||
#define LIBDPF_HAS_ASIO
|
||||
|
|
@ -591,7 +595,7 @@ TEST(WildcardTest, PackedSmallWildcardsAtNonTerminalAssignAll)
|
|||
dpf::at<10>(w, w, w, w),
|
||||
uint16_t{99});
|
||||
|
||||
ASSERT_THROW((void)dpf::eval_point(dpf::out<0, 10>, dpf0, x), std::runtime_error);
|
||||
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});
|
||||
|
|
@ -617,3 +621,189 @@ TEST(WildcardTest, PackedSmallWildcardsAtNonTerminalAssignAll)
|
|||
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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "grotto/window_lut.hpp"
|
||||
|
||||
|
|
@ -77,6 +78,18 @@ long double reference(grotto::window which, unsigned k, long double x)
|
|||
return acosl(x);
|
||||
case grotto::window::probit:
|
||||
return 0;
|
||||
case grotto::window::hardelish:
|
||||
if (x <= -1.0L)
|
||||
return 0;
|
||||
if (x >= 1.0L)
|
||||
return x;
|
||||
if (x >= 0.0L)
|
||||
return x * (x + 1.0L) / 2.0L;
|
||||
return expm1l(x) * (x + 1.0L) / 2.0L;
|
||||
case grotto::window::lecun_tanh:
|
||||
return 1.7159L * tanhl(2.0L * x / 3.0L);
|
||||
case grotto::window::one_minus_sigmoid:
|
||||
return 1.0L - sigmoid(x);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -154,6 +167,51 @@ TEST(WindowLut, ExhaustiveLowPrecision)
|
|||
expect_close(grotto::window::asin, k, raw);
|
||||
expect_close(grotto::window::acos, k, raw);
|
||||
}
|
||||
const std::int64_t step = k == 8 ? 1 : 17;
|
||||
for (std::int64_t raw = -2 * one; raw <= 2 * one; raw += step)
|
||||
expect_close(grotto::window::hardelish, k, raw);
|
||||
const std::int64_t lecun_span = std::int64_t{20} << k;
|
||||
for (std::int64_t raw = -lecun_span; raw <= lecun_span; raw += step)
|
||||
{
|
||||
expect_close(grotto::window::lecun_tanh, k, raw);
|
||||
expect_close(grotto::window::one_minus_sigmoid, k, raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WindowLut, HighPrecisionSamples)
|
||||
{
|
||||
for (unsigned k : {16u, 24u, 32u})
|
||||
{
|
||||
const auto one = std::int64_t{1} << k;
|
||||
const auto step = std::int64_t{1} << (k - 8);
|
||||
for (std::int64_t raw = -2 * one; raw <= 2 * one; raw += step)
|
||||
expect_close(grotto::window::hardelish, k, raw);
|
||||
const std::int64_t span = std::int64_t{20} << k;
|
||||
const auto wide = std::int64_t{1} << (k - 6);
|
||||
for (std::int64_t raw = -span; raw <= span; raw += wide)
|
||||
{
|
||||
expect_close(grotto::window::lecun_tanh, k, raw);
|
||||
expect_close(grotto::window::one_minus_sigmoid, k, raw);
|
||||
}
|
||||
expect_close(grotto::window::hardelish, k, -one + 1);
|
||||
expect_close(grotto::window::hardelish, k, -1);
|
||||
expect_close(grotto::window::lecun_tanh, k, 1);
|
||||
expect_close(grotto::window::lecun_tanh, k, span);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WindowLut, OneMinusSigmoidComplementsSigmoid)
|
||||
{
|
||||
for (unsigned k : {8u, 16u, 32u})
|
||||
{
|
||||
const auto one = std::int64_t{1} << k;
|
||||
for (std::int64_t raw : {std::int64_t{-8} * one, -one, std::int64_t{0}, one, std::int64_t{8} * one})
|
||||
{
|
||||
const auto sig = grotto::eval_window(grotto::window::sigmoid, k, raw);
|
||||
const auto comp = grotto::eval_window(grotto::window::one_minus_sigmoid, k, raw);
|
||||
EXPECT_EQ(sig + comp, one) << k << " " << raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
577
test/tests/wrap_and_rotate_test.cpp
Normal file
577
test/tests/wrap_and_rotate_test.cpp
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename A, typename B>
|
||||
auto recon(const A & a, const B & b)
|
||||
{
|
||||
if constexpr (dpf::is_secret_share_v<std::decay_t<A>>
|
||||
&& dpf::is_secret_share_v<std::decay_t<B>>)
|
||||
return dpf::reconstruct(a, b);
|
||||
else
|
||||
{
|
||||
using T = std::common_type_t<std::decay_t<A>, std::decay_t<B>>;
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>)
|
||||
return static_cast<T>(a - b);
|
||||
else
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1, typename InputT>
|
||||
void assign_input_local(Key0 & k0, Key1 & k1, InputT alpha)
|
||||
{
|
||||
const InputT a0 = static_cast<InputT>(0x12);
|
||||
const InputT a1 = static_cast<InputT>(alpha - a0);
|
||||
const auto sh0 = k0.offset_x.compute_and_get_share(a0);
|
||||
const auto sh1 = k1.offset_x.compute_and_get_share(a1);
|
||||
k0.offset_x.reconstruct(sh1);
|
||||
k1.offset_x.reconstruct(sh0);
|
||||
}
|
||||
|
||||
template <typename InputT>
|
||||
std::size_t inclusive_span(InputT from, InputT to)
|
||||
{
|
||||
constexpr auto bits = dpf::utils::bitlength_of_v<InputT>;
|
||||
constexpr auto to_int = dpf::utils::to_integral_type<InputT>{};
|
||||
auto span = to_int(to) - to_int(from);
|
||||
if constexpr (bits < dpf::utils::bitlength_of_v<decltype(span)>)
|
||||
span &= (decltype(span){1} << bits) - 1;
|
||||
return static_cast<std::size_t>(span) + 1;
|
||||
}
|
||||
|
||||
template <typename InputT, typename Fn>
|
||||
void for_inclusive_wrap(InputT from, InputT to, Fn && fn)
|
||||
{
|
||||
constexpr auto to_int = dpf::utils::to_integral_type<InputT>{};
|
||||
const auto n = inclusive_span(from, to);
|
||||
InputT x = from;
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
fn(x);
|
||||
x = static_cast<InputT>(to_int(x) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// utils: interval_wraps / split_leaf_nodes / get_leafnodes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WrapUtils, IntervalWrapsUnsigned)
|
||||
{
|
||||
EXPECT_FALSE(dpf::utils::interval_wraps(std::uint8_t{10}, std::uint8_t{20}, 8));
|
||||
EXPECT_TRUE(dpf::utils::interval_wraps(std::uint8_t{200}, std::uint8_t{10}, 8));
|
||||
EXPECT_FALSE(dpf::utils::interval_wraps(std::uint8_t{0}, std::uint8_t{255}, 8));
|
||||
EXPECT_TRUE(dpf::utils::interval_wraps(std::uint8_t{255}, std::uint8_t{0}, 8));
|
||||
EXPECT_FALSE(dpf::utils::interval_wraps(0, 0, 0));
|
||||
}
|
||||
|
||||
TEST(WrapUtils, IntervalWrapsMaskedLowBits)
|
||||
{
|
||||
// Only low 4 bits participate; 0x1F vs 0x02 wraps in nibble space.
|
||||
EXPECT_TRUE(dpf::utils::interval_wraps(0x1Fu, 0x02u, 4));
|
||||
EXPECT_FALSE(dpf::utils::interval_wraps(0x12u, 0x1Eu, 4));
|
||||
}
|
||||
|
||||
TEST(WrapUtils, SplitNonWrapSingleSegment)
|
||||
{
|
||||
auto segs = dpf::utils::split_leaf_nodes(std::uint8_t{3}, std::uint8_t{10}, 8, false);
|
||||
ASSERT_EQ(segs.n, 1u);
|
||||
EXPECT_EQ(segs.seg[0].from_node, 3);
|
||||
EXPECT_EQ(segs.seg[0].to_node, 10);
|
||||
EXPECT_EQ(segs.seg[0].count, 7u);
|
||||
EXPECT_EQ(segs.total, 7u);
|
||||
}
|
||||
|
||||
TEST(WrapUtils, SplitWrapTwoSegments)
|
||||
{
|
||||
// depth < bitwidth so domain_end is representable.
|
||||
auto segs = dpf::utils::split_leaf_nodes(
|
||||
std::uint16_t{250}, std::uint16_t{4}, 8, true);
|
||||
ASSERT_EQ(segs.n, 2u);
|
||||
EXPECT_EQ(segs.seg[0].from_node, 250);
|
||||
EXPECT_EQ(segs.seg[0].to_node, 256);
|
||||
EXPECT_EQ(segs.seg[0].count, 6u);
|
||||
EXPECT_EQ(segs.seg[1].from_node, 0);
|
||||
EXPECT_EQ(segs.seg[1].to_node, 4);
|
||||
EXPECT_EQ(segs.seg[1].count, 4u);
|
||||
EXPECT_EQ(segs.total, 10u);
|
||||
}
|
||||
|
||||
TEST(WrapUtils, SplitWrapShallowDepth)
|
||||
{
|
||||
// depth 6 => domain_end 64
|
||||
auto segs = dpf::utils::split_leaf_nodes(std::uint8_t{60}, std::uint8_t{3}, 6, true);
|
||||
ASSERT_EQ(segs.n, 2u);
|
||||
EXPECT_EQ(segs.seg[0].from_node, 60);
|
||||
EXPECT_EQ(segs.seg[0].to_node, 64);
|
||||
EXPECT_EQ(segs.seg[0].count, 4u);
|
||||
EXPECT_EQ(segs.seg[1].from_node, 0);
|
||||
EXPECT_EQ(segs.seg[1].to_node, 3);
|
||||
EXPECT_EQ(segs.seg[1].count, 3u);
|
||||
EXPECT_EQ(segs.total, 7u);
|
||||
}
|
||||
|
||||
TEST(WrapUtils, InputWrapWithFromNodeLeToNodeStillSplits)
|
||||
{
|
||||
// Packing can make from_node <= to_node while the input still wraps.
|
||||
// Collapsing would omit the duplicated shared leaf.
|
||||
auto segs = dpf::utils::split_leaf_nodes(std::uint8_t{2}, std::uint8_t{5}, 6, true);
|
||||
ASSERT_EQ(segs.n, 2u);
|
||||
EXPECT_EQ(segs.seg[0].from_node, 2);
|
||||
EXPECT_EQ(segs.seg[0].to_node, 64);
|
||||
EXPECT_EQ(segs.seg[1].from_node, 0);
|
||||
EXPECT_EQ(segs.seg[1].to_node, 5);
|
||||
EXPECT_GT(segs.total, 5u - 2u);
|
||||
}
|
||||
|
||||
TEST(WrapUtils, LeafCountAgreesWithSplitTotal)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(In{1}, std::uint32_t{1});
|
||||
using key_t = std::decay_t<decltype(k0)>;
|
||||
auto check = [](In from, In to)
|
||||
{
|
||||
In ff = from, ft = to;
|
||||
dpf::utils::flip_msb_if_signed_integral(ff);
|
||||
dpf::utils::flip_msb_if_signed_integral(ft);
|
||||
constexpr auto to_int = dpf::utils::to_integral_type<In>{};
|
||||
using integral = typename key_t::integral_type;
|
||||
const auto from_i = static_cast<integral>(to_int(ff));
|
||||
const auto to_i = static_cast<integral>(to_int(ft));
|
||||
const bool wraps = dpf::utils::interval_wraps(from_i, to_i, 8);
|
||||
const auto segs = dpf::utils::split_leaf_nodes(
|
||||
dpf::utils::get_from_node<key_t>(ff),
|
||||
dpf::utils::get_to_node<key_t>(ft),
|
||||
static_cast<std::size_t>(key_t::depth), wraps);
|
||||
EXPECT_EQ((dpf::utils::get_leafnodes_in_output_interval<key_t>(from, to)),
|
||||
segs.total);
|
||||
};
|
||||
check(In{10}, In{20});
|
||||
check(In{200}, In{10});
|
||||
check(In{10}, In{9});
|
||||
check(In{0}, In{255});
|
||||
(void)k1;
|
||||
}
|
||||
|
||||
TEST(WrapUtils, LeafCountVariesWithAlignmentWhenPacked)
|
||||
{
|
||||
using In = std::int8_t;
|
||||
auto [k0, k1] = dpf::make_dpf(In{0}, std::uint32_t{7});
|
||||
using key_t = std::decay_t<decltype(k0)>;
|
||||
ASSERT_GT(key_t::outputs_per_leaf, 1u);
|
||||
const In from{-40}, to{10};
|
||||
const auto n0 = dpf::utils::get_leafnodes_in_output_interval<key_t>(from, to);
|
||||
bool saw_larger = false;
|
||||
for (int d = 0; d < 64; ++d)
|
||||
{
|
||||
const In tfrom = static_cast<In>(from + d);
|
||||
const In tto = static_cast<In>(to + d);
|
||||
const auto n = dpf::utils::get_leafnodes_in_output_interval<key_t>(tfrom, tto);
|
||||
if (n > n0)
|
||||
saw_larger = true;
|
||||
}
|
||||
EXPECT_TRUE(saw_larger);
|
||||
(void)k1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// rotation_iterable: indexing past the wrap (the deferred-view bug class)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(RotationIterable, OperatorBracketMatchesIteratorOrder)
|
||||
{
|
||||
std::vector<int> values{10, 20, 30, 40, 50};
|
||||
dpf::rotation_iterable rot(values.begin(), values.end(), 3);
|
||||
std::vector<int> by_it;
|
||||
for (auto it = rot.begin(); it != rot.end(); ++it)
|
||||
by_it.push_back(*it);
|
||||
EXPECT_EQ(by_it, (std::vector<int>{40, 50, 10, 20, 30}));
|
||||
|
||||
std::vector<int> by_idx;
|
||||
for (std::ptrdiff_t i = 0; i < static_cast<std::ptrdiff_t>(values.size()); ++i)
|
||||
by_idx.push_back(rot[i]);
|
||||
EXPECT_EQ(by_idx, by_it);
|
||||
}
|
||||
|
||||
TEST(RotationIterable, NegativeAndFullCycleDistanceNormalize)
|
||||
{
|
||||
std::vector<int> values{1, 2, 3, 4};
|
||||
dpf::rotation_iterable neg(values.begin(), values.end(), -1);
|
||||
EXPECT_EQ(neg.distance(), 3);
|
||||
EXPECT_EQ(neg[0], 4);
|
||||
EXPECT_EQ(neg[1], 1);
|
||||
|
||||
dpf::rotation_iterable full(values.begin(), values.end(), 8);
|
||||
EXPECT_EQ(full.distance(), 0);
|
||||
EXPECT_EQ(full[0], 1);
|
||||
EXPECT_EQ(full[3], 4);
|
||||
}
|
||||
|
||||
TEST(RotationIterable, WrappingSubrangeViaIndexDoesNotWalkPastEnd)
|
||||
{
|
||||
// Contiguous std::next from rot.begin() past the physical end is wrong
|
||||
// for a wrapping logical slice; operator[] with modulo is the safe path.
|
||||
std::vector<int> values{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
dpf::rotation_iterable rot(values.begin(), values.end(), 5);
|
||||
// Logical slice starting at index 6 of the unrotated domain, length 4:
|
||||
// rotated indices (6+5)%8 ... => values 3,4,5,6 in rot order from start 6.
|
||||
const std::size_t start = 6;
|
||||
const std::size_t count = 4;
|
||||
const std::size_t n = values.size();
|
||||
std::vector<int> got;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
got.push_back(rot[static_cast<std::ptrdiff_t>((start + i) % n)]);
|
||||
EXPECT_EQ(got, (std::vector<int>{3, 4, 5, 6}));
|
||||
}
|
||||
|
||||
TEST(RotationIterable, BidirectionalRoundTrip)
|
||||
{
|
||||
std::vector<int> values{1, 2, 3, 4, 5};
|
||||
dpf::rotation_iterable rot(values.begin(), values.end(), 2);
|
||||
auto it = rot.begin();
|
||||
++it;
|
||||
++it;
|
||||
EXPECT_EQ(*it, 5);
|
||||
--it;
|
||||
EXPECT_EQ(*it, 4);
|
||||
--it;
|
||||
EXPECT_EQ(it, rot.begin());
|
||||
EXPECT_EQ(*it, 3);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Eager eval_interval wrap vs pointwise
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(EagerWrap, Uint8MatchesPointwise)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
using Out = std::uint32_t;
|
||||
constexpr In from{200}, to{10}, alpha{250};
|
||||
constexpr Out beta{0xABCDEF01u};
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
|
||||
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
|
||||
auto a = std::begin(it0);
|
||||
auto b = std::begin(it1);
|
||||
std::size_t n = 0;
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
ASSERT_NE(a, std::end(it0));
|
||||
ASSERT_NE(b, std::end(it1));
|
||||
EXPECT_EQ(recon(*a, *b),
|
||||
recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)))
|
||||
<< "x=" << +x;
|
||||
++a;
|
||||
++b;
|
||||
++n;
|
||||
});
|
||||
EXPECT_EQ(a, std::end(it0));
|
||||
EXPECT_EQ(b, std::end(it1));
|
||||
EXPECT_EQ(n, inclusive_span(from, to));
|
||||
(void)buf0;
|
||||
(void)buf1;
|
||||
}
|
||||
|
||||
TEST(EagerWrap, SameLeafWrapMatchesPointwise)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
using Out = std::uint32_t;
|
||||
// from=10, to=9 wraps almost the full domain; opl may share a leaf.
|
||||
auto [k0, k1] = dpf::make_dpf(In{40}, Out{0x11111111u});
|
||||
auto [buf0, it0] = dpf::eval_interval(k0, In{10}, In{9});
|
||||
auto [buf1, it1] = dpf::eval_interval(k1, In{10}, In{9});
|
||||
auto a = std::begin(it0);
|
||||
auto b = std::begin(it1);
|
||||
for_inclusive_wrap(In{10}, In{9}, [&](In x)
|
||||
{
|
||||
ASSERT_NE(a, std::end(it0));
|
||||
ASSERT_NE(b, std::end(it1));
|
||||
EXPECT_EQ(recon(*a, *b),
|
||||
recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)));
|
||||
++a;
|
||||
++b;
|
||||
});
|
||||
EXPECT_EQ(a, std::end(it0));
|
||||
EXPECT_EQ(b, std::end(it1));
|
||||
(void)buf0;
|
||||
(void)buf1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wildcard offset: memoizer / buffer must size on tree coordinates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OffsetSizing, LogicalMemoizerCanUndersizeAfterAssign)
|
||||
{
|
||||
using In = std::int8_t;
|
||||
using Out = std::uint32_t;
|
||||
constexpr In from{-40}, to{10};
|
||||
using key_t = std::decay_t<decltype(
|
||||
std::get<0>(dpf::make_dpf(dpf::wildcard_value<In>{}, Out{7})))>;
|
||||
ASSERT_GT(key_t::outputs_per_leaf, 1u);
|
||||
|
||||
const auto n_logical =
|
||||
dpf::utils::get_leafnodes_in_output_interval<key_t>(from, to);
|
||||
bool found = false;
|
||||
for (int trial = 0; trial < 256; ++trial)
|
||||
{
|
||||
auto [k0, k1] = dpf::make_dpf(dpf::wildcard_value<In>{}, Out{7});
|
||||
const In alpha = static_cast<In>(trial - 128);
|
||||
assign_input_local(k0, k1, alpha);
|
||||
const auto tfrom = k0.offset_x(from);
|
||||
const auto tto = k0.offset_x(to);
|
||||
const auto n_tree =
|
||||
dpf::utils::get_leafnodes_in_output_interval<key_t>(tfrom, tto);
|
||||
|
||||
In ff = tfrom, ft = tto;
|
||||
dpf::utils::flip_msb_if_signed_integral(ff);
|
||||
dpf::utils::flip_msb_if_signed_integral(ft);
|
||||
constexpr auto to_int = dpf::utils::to_integral_type<In>{};
|
||||
using integral = typename key_t::integral_type;
|
||||
const bool wraps = dpf::utils::interval_wraps(
|
||||
static_cast<integral>(to_int(ff)),
|
||||
static_cast<integral>(to_int(ft)), 8);
|
||||
// Single-segment undersize: each wrap half may still fit in n_logical.
|
||||
if (wraps || n_tree <= n_logical)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
auto small = dpf::make_basic_interval_memoizer<key_t>(from, to);
|
||||
auto buf = dpf::make_output_buffer_for_interval(k0, from, to);
|
||||
EXPECT_THROW(
|
||||
(void)dpf::eval_interval(k0, from, to, buf, small),
|
||||
std::exception);
|
||||
|
||||
auto memo = dpf::make_basic_interval_memoizer(k0, from, to);
|
||||
auto buf2 = dpf::make_output_buffer_for_interval(k0, from, to);
|
||||
auto it = dpf::eval_interval(k0, from, to, buf2, memo);
|
||||
auto it1_buf = dpf::make_output_buffer_for_interval(k1, from, to);
|
||||
auto memo1 = dpf::make_basic_interval_memoizer(k1, from, to);
|
||||
auto it1 = dpf::eval_interval(k1, from, to, it1_buf, memo1);
|
||||
auto a = std::begin(it);
|
||||
auto b = std::begin(it1);
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
ASSERT_NE(a, std::end(it));
|
||||
ASSERT_NE(b, std::end(it1));
|
||||
EXPECT_EQ(recon(*a, *b),
|
||||
recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)));
|
||||
++a;
|
||||
++b;
|
||||
});
|
||||
break;
|
||||
}
|
||||
ASSERT_TRUE(found) << "no non-wrapping misaligned offset found";
|
||||
}
|
||||
|
||||
TEST(OffsetSizing, ConvenienceEvalIntervalSurvivesSignedWildcardSweep)
|
||||
{
|
||||
using In = std::int8_t;
|
||||
using Out = std::uint32_t;
|
||||
constexpr In from{-40}, to{10};
|
||||
constexpr Out beta{9};
|
||||
int ok = 0;
|
||||
for (int trial = 0; trial < 64; ++trial)
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<In>{}, beta);
|
||||
assign_input_local(d0, d1, static_cast<In>(trial * 3 - 96));
|
||||
auto buf0 = dpf::make_output_buffer_for_interval(d0, from, to);
|
||||
auto buf1 = dpf::make_output_buffer_for_interval(d1, from, to);
|
||||
// Default memoizer path must not throw after the sizing fix.
|
||||
auto it0 = dpf::eval_interval(d0, from, to, buf0);
|
||||
auto it1 = dpf::eval_interval(d1, from, to, buf1);
|
||||
auto a = std::begin(it0);
|
||||
auto b = std::begin(it1);
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
ASSERT_NE(a, std::end(it0));
|
||||
ASSERT_NE(b, std::end(it1));
|
||||
EXPECT_EQ(recon(*a, *b),
|
||||
recon(*dpf::eval_point(d0, x), *dpf::eval_point(d1, x)))
|
||||
<< "x=" << +x << " trial=" << trial;
|
||||
++a;
|
||||
++b;
|
||||
});
|
||||
++ok;
|
||||
}
|
||||
EXPECT_EQ(ok, 64);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Deferred wrap: index-based view vs eager (regression for contiguous-next bug)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(DeferWrap, WrappingUint8MatchesEagerAndPointwise)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
using Out = std::uint32_t;
|
||||
constexpr In from{200}, to{10};
|
||||
constexpr Out beta{42};
|
||||
for (unsigned alpha_i = 0; alpha_i < 256; alpha_i += 37)
|
||||
{
|
||||
const In alpha = static_cast<In>(alpha_i);
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<In>{}, beta);
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto def0 = dpf::defer_eval_interval(d0, from, to, buf0);
|
||||
auto def1 = dpf::defer_eval_interval(d1, from, to, buf1);
|
||||
assign_input_local(d0, d1, alpha);
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_interval(d0, from, to);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_interval(d1, from, to);
|
||||
auto eager0 = dpf::eval_interval(d0, from, to, eager_buf0);
|
||||
auto eager1 = dpf::eval_interval(d1, from, to, eager_buf1);
|
||||
|
||||
auto v0 = def0.get();
|
||||
auto v1 = def1.get();
|
||||
auto it_a = std::begin(v0);
|
||||
auto it_b = std::begin(v1);
|
||||
auto it_c = std::begin(eager0);
|
||||
auto it_d = std::begin(eager1);
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
ASSERT_NE(it_a, std::end(v0));
|
||||
ASSERT_NE(it_b, std::end(v1));
|
||||
ASSERT_NE(it_c, std::end(eager0));
|
||||
ASSERT_NE(it_d, std::end(eager1));
|
||||
const auto y_def = recon(*it_a, *it_b);
|
||||
const auto y_eag = recon(*it_c, *it_d);
|
||||
const auto y_pt = recon(*dpf::eval_point(d0, x),
|
||||
*dpf::eval_point(d1, x));
|
||||
EXPECT_EQ(y_def, y_eag) << "x=" << +x << " alpha=" << +alpha;
|
||||
EXPECT_EQ(y_def, y_pt) << "x=" << +x << " alpha=" << +alpha;
|
||||
++it_a;
|
||||
++it_b;
|
||||
++it_c;
|
||||
++it_d;
|
||||
});
|
||||
EXPECT_EQ(it_a, std::end(v0));
|
||||
EXPECT_EQ(it_b, std::end(v1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeferWrap, SignedSpanAcrossZeroMatchesEager)
|
||||
{
|
||||
using In = std::int8_t;
|
||||
using Out = std::uint32_t;
|
||||
constexpr In from{-5}, to{5};
|
||||
constexpr Out beta{3};
|
||||
for (int trial = 0; trial < 32; ++trial)
|
||||
{
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<In>{}, beta);
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto def0 = dpf::defer_eval_interval(d0, from, to, buf0);
|
||||
auto def1 = dpf::defer_eval_interval(d1, from, to, buf1);
|
||||
assign_input_local(d0, d1, static_cast<In>(trial * 7 - 112));
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_interval(d0, from, to);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_interval(d1, from, to);
|
||||
auto eager0 = dpf::eval_interval(d0, from, to, eager_buf0);
|
||||
auto eager1 = dpf::eval_interval(d1, from, to, eager_buf1);
|
||||
|
||||
auto v0 = def0.get();
|
||||
auto v1 = def1.get();
|
||||
auto a = std::begin(v0);
|
||||
auto b = std::begin(v1);
|
||||
auto c = std::begin(eager0);
|
||||
auto d = std::begin(eager1);
|
||||
while (a != std::end(v0))
|
||||
{
|
||||
ASSERT_NE(b, std::end(v1));
|
||||
ASSERT_NE(c, std::end(eager0));
|
||||
ASSERT_NE(d, std::end(eager1));
|
||||
EXPECT_EQ(recon(*a, *b), recon(*c, *d));
|
||||
++a;
|
||||
++b;
|
||||
++c;
|
||||
++d;
|
||||
}
|
||||
EXPECT_EQ(b, std::end(v1));
|
||||
EXPECT_EQ(c, std::end(eager0));
|
||||
EXPECT_EQ(d, std::end(eager1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeferWrap, MultiOplUnalignedWrappingMatchesEager)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
using Out = std::uint64_t;
|
||||
constexpr In from{0xF1}, to{0x0E};
|
||||
constexpr Out beta{0x55};
|
||||
auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value<In>{}, beta);
|
||||
ASSERT_GT(decltype(d0)::outputs_per_leaf, std::size_t{1});
|
||||
auto buf0 = dpf::make_output_buffer_for_full(d0);
|
||||
auto buf1 = dpf::make_output_buffer_for_full(d1);
|
||||
auto def0 = dpf::defer_eval_interval(d0, from, to, buf0);
|
||||
auto def1 = dpf::defer_eval_interval(d1, from, to, buf1);
|
||||
assign_input_local(d0, d1, In{0xA3});
|
||||
|
||||
auto eager_buf0 = dpf::make_output_buffer_for_interval(d0, from, to);
|
||||
auto eager_buf1 = dpf::make_output_buffer_for_interval(d1, from, to);
|
||||
auto eager0 = dpf::eval_interval(d0, from, to, eager_buf0);
|
||||
auto eager1 = dpf::eval_interval(d1, from, to, eager_buf1);
|
||||
|
||||
auto v0 = def0.get();
|
||||
auto v1 = def1.get();
|
||||
auto a = std::begin(v0);
|
||||
auto b = std::begin(v1);
|
||||
auto c = std::begin(eager0);
|
||||
auto d = std::begin(eager1);
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
ASSERT_NE(a, std::end(v0));
|
||||
ASSERT_NE(b, std::end(v1));
|
||||
ASSERT_NE(c, std::end(eager0));
|
||||
ASSERT_NE(d, std::end(eager1));
|
||||
EXPECT_EQ(recon(*a, *b), recon(*c, *d)) << "x=" << +x;
|
||||
EXPECT_EQ(recon(*a, *b),
|
||||
recon(*dpf::eval_point(d0, x), *dpf::eval_point(d1, x)));
|
||||
++a;
|
||||
++b;
|
||||
++c;
|
||||
++d;
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inner-product wrap already covered; keep a wildcard-offset variant
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(InnerProductWrap, WildcardOffsetWrappingMatchesPoints)
|
||||
{
|
||||
using In = std::uint8_t;
|
||||
constexpr In from{250}, to{4};
|
||||
auto [k0, k1] = dpf::make_dpf(dpf::wildcard_value<In>{}, std::uint32_t{9});
|
||||
assign_input_local(k0, k1, In{1});
|
||||
|
||||
std::vector<std::uint32_t> w;
|
||||
std::uint64_t expect = 0;
|
||||
for_inclusive_wrap(from, to, [&](In x)
|
||||
{
|
||||
w.push_back(static_cast<std::uint32_t>(w.size() + 1));
|
||||
expect += static_cast<std::uint64_t>(
|
||||
recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)))
|
||||
* w.back();
|
||||
});
|
||||
EXPECT_EQ(recon(
|
||||
dpf::eval_inner_product(dpf::paired, k0, from, to, w),
|
||||
dpf::eval_inner_product(dpf::paired, k1, from, to, w)),
|
||||
expect);
|
||||
}
|
||||
350
test/tests/yao_share_test.cpp
Normal file
350
test/tests/yao_share_test.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/yao.hpp"
|
||||
#include "dpf/yao_share.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename Ring>
|
||||
Ring xor_bits(const std::vector<std::uint8_t> & a, const std::vector<std::uint8_t> & b)
|
||||
{
|
||||
Ring v{};
|
||||
for (std::size_t i = 0; i < a.size() && i < 8u * sizeof(Ring); ++i)
|
||||
v = static_cast<Ring>(v | (static_cast<Ring>(a[i] ^ b[i]) << i));
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
Ring low_mask(unsigned width)
|
||||
{
|
||||
if (width == 0 || width >= 8u * sizeof(Ring))
|
||||
return static_cast<Ring>(~Ring{0});
|
||||
return static_cast<Ring>((Ring{1} << width) - Ring{1});
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
void expect_additive(Ring secret, unsigned width)
|
||||
{
|
||||
const unsigned bits = width == 0 ? static_cast<unsigned>(8u * sizeof(Ring)) : width;
|
||||
const Ring r = dpf::uniform_sample<Ring>();
|
||||
const auto a0 = dpf::additive_share<Ring, 0>::from_raw(r);
|
||||
const auto a1 = dpf::additive_share<Ring, 1>::from_raw(static_cast<Ring>(secret - r));
|
||||
const auto [y0, y1] = dpf::yao::a2y(a0, a1, width);
|
||||
ASSERT_EQ(y0.size(), bits);
|
||||
ASSERT_EQ(y1.size(), bits);
|
||||
EXPECT_EQ(xor_bits<Ring>(y0, y1), static_cast<Ring>(secret & low_mask<Ring>(bits)));
|
||||
const auto [b0, b1] = dpf::yao::y2a<Ring>(y0, y1, bits);
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), static_cast<Ring>(secret & low_mask<Ring>(bits)));
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
void expect_subtractive(Ring s0, Ring s1, unsigned width)
|
||||
{
|
||||
const unsigned bits = width == 0 ? static_cast<unsigned>(8u * sizeof(Ring)) : width;
|
||||
const Ring opened = static_cast<Ring>(s0 - s1);
|
||||
const auto b0 = dpf::subtractive_share<Ring, 0>::from_raw(s0);
|
||||
const auto b1 = dpf::subtractive_share<Ring, 1>::from_raw(s1);
|
||||
const auto [y0, y1] = dpf::yao::b2y(b0, b1, width);
|
||||
EXPECT_EQ(xor_bits<Ring>(y0, y1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||||
const auto [z0, z1] = dpf::yao::y2b<Ring>(y0, y1, bits);
|
||||
EXPECT_EQ(dpf::reconstruct(z0, z1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||||
|
||||
const auto f0 = b0.as_fss();
|
||||
const auto f1 = b1.as_fss();
|
||||
const auto [g0, g1] = dpf::yao::fss2y(f0, f1, width);
|
||||
EXPECT_EQ(xor_bits<Ring>(g0, g1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||||
const auto [h0, h1] = dpf::yao::y2fss<Ring>(g0, g1, bits);
|
||||
EXPECT_EQ(dpf::reconstruct(h0, h1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(YaoShare, WidthSweep)
|
||||
{
|
||||
const unsigned widths64[] = {1, 2, 7, 8, 9, 16, 31, 32, 63, 64, 0};
|
||||
for (unsigned w : widths64)
|
||||
expect_additive<std::uint64_t>(0x1234abcd5ull, w);
|
||||
const unsigned widths32[] = {1, 8, 31, 32, 0};
|
||||
for (unsigned w : widths32)
|
||||
expect_additive<std::uint32_t>(0x89abcdefu, w);
|
||||
expect_additive<std::uint16_t>(0xBEEFu, 0);
|
||||
expect_additive<std::uint16_t>(0xBEEFu, 9);
|
||||
expect_additive<std::uint8_t>(0xA5u, 0);
|
||||
expect_additive<std::uint8_t>(0xA5u, 3);
|
||||
}
|
||||
|
||||
TEST(YaoShare, CornerSecrets)
|
||||
{
|
||||
expect_additive<std::uint64_t>(0, 64);
|
||||
expect_additive<std::uint64_t>(1, 64);
|
||||
expect_additive<std::uint64_t>(~std::uint64_t{0}, 64);
|
||||
expect_additive<std::uint64_t>(std::uint64_t{1} << 63, 64);
|
||||
expect_additive<std::uint64_t>(0x105u, 8);
|
||||
expect_subtractive<std::uint64_t>(0, 0, 64);
|
||||
expect_subtractive<std::uint64_t>(1, 3, 64);
|
||||
expect_subtractive<std::uint64_t>(3, 1, 16);
|
||||
expect_subtractive<std::uint32_t>(0, 1, 32);
|
||||
expect_subtractive<std::uint8_t>(0x10, 0x10, 8);
|
||||
}
|
||||
|
||||
TEST(YaoShare, RandomRoundTrips)
|
||||
{
|
||||
for (int i = 0; i < 24; ++i)
|
||||
{
|
||||
const auto secret = dpf::uniform_sample<std::uint64_t>();
|
||||
expect_additive<std::uint64_t>(secret, 64);
|
||||
const auto s0 = dpf::uniform_sample<std::uint32_t>();
|
||||
const auto s1 = dpf::uniform_sample<std::uint32_t>();
|
||||
expect_subtractive<std::uint32_t>(s0, s1, 32);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoShare, BitFlipChangesThatPlace)
|
||||
{
|
||||
const auto a0 = dpf::additive_share<std::uint64_t, 0>::from_raw(0x111u);
|
||||
const auto a1 = dpf::additive_share<std::uint64_t, 1>::from_raw(0x222u);
|
||||
auto [y0, y1] = dpf::yao::a2y(a0, a1, 12);
|
||||
const auto opened = xor_bits<std::uint64_t>(y0, y1);
|
||||
for (unsigned i = 0; i < 12; ++i)
|
||||
{
|
||||
auto flipped = y0;
|
||||
flipped[i] = static_cast<std::uint8_t>(flipped[i] ^ 1u);
|
||||
EXPECT_EQ(xor_bits<std::uint64_t>(flipped, y1), opened ^ (std::uint64_t{1} << i));
|
||||
const auto [b0, b1] = dpf::yao::y2a<std::uint64_t>(flipped, y1, 12);
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), opened ^ (std::uint64_t{1} << i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoShare, DefaultWidthUsesTheVector)
|
||||
{
|
||||
const auto a0 = dpf::additive_share<std::uint16_t, 0>::from_raw(0x00FFu);
|
||||
const auto a1 = dpf::additive_share<std::uint16_t, 1>::from_raw(0);
|
||||
const auto [y0, y1] = dpf::yao::a2y(a0, a1, 8);
|
||||
const auto [b0, b1] = dpf::yao::y2a<std::uint16_t>(y0, y1);
|
||||
EXPECT_EQ(dpf::reconstruct(b0, b1), 0x00FFu);
|
||||
}
|
||||
|
||||
TEST(YaoShare, RejectsBadInputs)
|
||||
{
|
||||
const auto a0 = dpf::additive_share<std::uint64_t, 0>::from_raw(1);
|
||||
const auto a1 = dpf::additive_share<std::uint64_t, 1>::from_raw(0);
|
||||
EXPECT_THROW(dpf::yao::a2y(a0, a1, 65), std::invalid_argument);
|
||||
EXPECT_THROW(dpf::yao::a2y(
|
||||
dpf::additive_share<std::uint8_t, 0>::from_raw(1),
|
||||
dpf::additive_share<std::uint8_t, 1>::from_raw(0), 9),
|
||||
std::invalid_argument);
|
||||
|
||||
std::vector<std::uint8_t> bits(8, 0);
|
||||
std::vector<std::uint8_t> shortv(3, 0);
|
||||
EXPECT_THROW(dpf::yao::y2a<std::uint64_t>(shortv, bits, 8), std::invalid_argument);
|
||||
bits[4] = 2;
|
||||
EXPECT_THROW(dpf::yao::y2a<std::uint64_t>(bits, std::vector<std::uint8_t>(8, 0), 8),
|
||||
std::invalid_argument);
|
||||
|
||||
const auto rss = dpf::make_replicated_shares<std::uint32_t>(1, 2, 3);
|
||||
auto r0 = std::get<0>(rss);
|
||||
r0.next = 99;
|
||||
EXPECT_THROW(dpf::yao::rss2y(r0, std::get<1>(rss)), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(YaoShare, ReplicatedManyAndFresh)
|
||||
{
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
const auto x0 = dpf::uniform_sample<std::uint32_t>();
|
||||
const auto x1 = dpf::uniform_sample<std::uint32_t>();
|
||||
const auto x2 = dpf::uniform_sample<std::uint32_t>();
|
||||
const auto secret = static_cast<std::uint32_t>(x0 + x1 + x2);
|
||||
const auto shares = dpf::make_replicated_shares(x0, x1, x2);
|
||||
const auto [y0, y1] = dpf::yao::rss2y(std::get<0>(shares), std::get<1>(shares));
|
||||
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), secret);
|
||||
const auto back = dpf::yao::y2rss<std::uint32_t>(y0, y1);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<1>(back)), secret);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<1>(back), std::get<2>(back)), secret);
|
||||
EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<2>(back)), secret);
|
||||
const auto again = dpf::yao::y2rss<std::uint32_t>(y0, y1);
|
||||
EXPECT_NE(std::get<0>(back).own, std::get<0>(again).own);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoShare, ConcreteAdditiveSum)
|
||||
{
|
||||
const auto a0 = dpf::additive_share<std::uint16_t, 0>::from_raw(0x1234);
|
||||
const auto a1 = dpf::additive_share<std::uint16_t, 1>::from_raw(0x0100);
|
||||
const auto [y0, y1] = dpf::yao::a2y(a0, a1, 16);
|
||||
EXPECT_EQ(xor_bits<std::uint16_t>(y0, y1), static_cast<std::uint16_t>(0x1334));
|
||||
}
|
||||
|
||||
TEST(YaoShare, PointLeafOnAndOff)
|
||||
{
|
||||
for (int n = 0; n < 16; ++n)
|
||||
{
|
||||
const auto alpha = dpf::uniform_sample<std::uint8_t>();
|
||||
const auto beta = dpf::uniform_sample<std::uint32_t>();
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto s0 = *dpf::eval_point(k0, alpha);
|
||||
auto s1 = *dpf::eval_point(k1, alpha);
|
||||
const auto [y0, y1] = dpf::yao::b2y(s0, s1, 32);
|
||||
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), beta);
|
||||
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(y0, y1, 32);
|
||||
EXPECT_EQ(dpf::reconstruct(z0, z1), beta);
|
||||
|
||||
const auto other = static_cast<std::uint8_t>(alpha + 1u);
|
||||
auto t0 = *dpf::eval_point(k0, other);
|
||||
auto t1 = *dpf::eval_point(k1, other);
|
||||
const auto [u0, u1] = dpf::yao::b2y(t0, t1, 32);
|
||||
EXPECT_EQ(xor_bits<std::uint32_t>(u0, u1), 0u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoShare, PointLeafPlusPublicAndSecondLeaf)
|
||||
{
|
||||
const std::uint8_t alpha = 9;
|
||||
const std::uint32_t beta = 40;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto s0 = *dpf::eval_point(k0, alpha);
|
||||
auto s1 = *dpf::eval_point(k1, alpha);
|
||||
s0 += std::uint32_t{5};
|
||||
const auto [y0, y1] = dpf::yao::b2y(s0, s1, 32);
|
||||
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), 45u);
|
||||
|
||||
auto [j0, j1] = dpf::make_dpf(alpha, std::uint32_t{7});
|
||||
auto u0 = *dpf::eval_point(k0, alpha) + *dpf::eval_point(j0, alpha);
|
||||
auto u1 = *dpf::eval_point(k1, alpha) + *dpf::eval_point(j1, alpha);
|
||||
const auto [v0, v1] = dpf::yao::b2y(u0, u1, 32);
|
||||
EXPECT_EQ(xor_bits<std::uint32_t>(v0, v1), 47u);
|
||||
}
|
||||
|
||||
TEST(YaoShare, ComparisonLeaf)
|
||||
{
|
||||
const std::uint32_t alpha = 100u;
|
||||
const std::uint64_t yt = 5u;
|
||||
const std::uint64_t yf = 9u;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
|
||||
const std::uint64_t mask = k0.cmp().mask;
|
||||
auto c0 = dpf::eval_point(dpf::cmp, k0, 50u);
|
||||
auto c1 = dpf::eval_point(dpf::cmp, k1, 50u);
|
||||
const auto [y0, y1] = dpf::yao::a2y(c0, c1, 64);
|
||||
const auto bits = xor_bits<std::uint64_t>(y0, y1);
|
||||
EXPECT_EQ(bits, dpf::reconstruct(c0, c1));
|
||||
EXPECT_EQ(bits & mask, yt);
|
||||
|
||||
auto d0 = dpf::eval_point(dpf::cmp, k0, alpha);
|
||||
auto d1 = dpf::eval_point(dpf::cmp, k1, alpha);
|
||||
const auto [z0, z1] = dpf::yao::a2y(d0, d1, 64);
|
||||
EXPECT_EQ(xor_bits<std::uint64_t>(z0, z1) & mask, yf);
|
||||
}
|
||||
|
||||
TEST(YaoShare, LeafBitsThroughANetlist)
|
||||
{
|
||||
const std::uint8_t alpha = 42;
|
||||
const std::uint32_t beta = 0x6bu;
|
||||
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||||
auto s0 = *dpf::eval_point(k0, alpha);
|
||||
auto s1 = *dpf::eval_point(k1, alpha);
|
||||
const auto [p0, p1] = dpf::yao::b2y(s0, s1, 8);
|
||||
|
||||
dpf::yao::netlist n;
|
||||
dpf::yao::bit in[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
in[i] = n.shared_in();
|
||||
n.out(n.and_(in[0], in[1]));
|
||||
for (int i = 0; i < 8; ++i)
|
||||
n.out(in[i]);
|
||||
|
||||
auto [o0, o1] = dpf::yao::eval_pair(n, p0.data(), p1.data());
|
||||
ASSERT_EQ(o0.size(), 9u);
|
||||
EXPECT_EQ(o0[0] ^ o1[0], static_cast<std::uint8_t>((beta & 1u) & ((beta >> 1) & 1u)));
|
||||
for (int i = 0; i < 8; ++i)
|
||||
EXPECT_EQ(o0[static_cast<std::size_t>(i + 1)] ^ o1[static_cast<std::size_t>(i + 1)],
|
||||
static_cast<std::uint8_t>((beta >> i) & 1u));
|
||||
|
||||
std::vector<std::uint8_t> and0{o0[0]};
|
||||
std::vector<std::uint8_t> and1{o1[0]};
|
||||
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(and0, and1, 1);
|
||||
EXPECT_EQ(dpf::reconstruct(z0, z1), 1u);
|
||||
}
|
||||
|
||||
TEST(YaoShare, RandomNetlistMatchesPlain)
|
||||
{
|
||||
std::uint32_t rng = 0xC0FFEEu;
|
||||
auto nxt = [&] {
|
||||
rng = rng * 1664525u + 1013904223u;
|
||||
return rng;
|
||||
};
|
||||
for (int trial = 0; trial < 20; ++trial)
|
||||
{
|
||||
dpf::yao::netlist n;
|
||||
dpf::yao::bit w[24];
|
||||
std::uint8_t sem[4];
|
||||
std::uint8_t p0[4];
|
||||
std::uint8_t p1[4];
|
||||
int nwire = 4;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
w[i] = n.shared_in();
|
||||
sem[i] = static_cast<std::uint8_t>(nxt() & 1u);
|
||||
p0[i] = static_cast<std::uint8_t>(nxt() & 1u);
|
||||
p1[i] = static_cast<std::uint8_t>(sem[i] ^ p0[i]);
|
||||
}
|
||||
for (int g = 0; g < 32; ++g)
|
||||
{
|
||||
const int ia = static_cast<int>(nxt() % static_cast<std::uint32_t>(nwire));
|
||||
const int ib = static_cast<int>(nxt() % static_cast<std::uint32_t>(nwire));
|
||||
dpf::yao::bit d;
|
||||
switch (nxt() % 3u)
|
||||
{
|
||||
case 0: d = n.xor_(w[ia], w[ib]); break;
|
||||
case 1: d = n.and_(w[ia], w[ib]); break;
|
||||
default: d = n.not_(w[ia]); break;
|
||||
}
|
||||
if (nwire < 24)
|
||||
w[nwire++] = d;
|
||||
}
|
||||
for (int i = 0; i < 4; ++i)
|
||||
n.out(w[nwire - 1 - i]);
|
||||
const auto plain = dpf::yao::eval_plain(n, sem);
|
||||
EXPECT_EQ(dpf::yao::eval_local(n, sem), plain) << trial;
|
||||
auto [a, b] = dpf::yao::eval_pair(n, p0, p1);
|
||||
for (std::size_t i = 0; i < plain.size(); ++i)
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(a[i] ^ b[i]), plain[i]) << trial;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoShare, MixedPrivateInputs)
|
||||
{
|
||||
dpf::yao::netlist n;
|
||||
const auto a = n.priv_in(0);
|
||||
const auto b = n.priv_in(1);
|
||||
const auto c = n.shared_in();
|
||||
n.out(n.xor_(n.and_(a, b), c));
|
||||
n.out(n.xor_public(c, 0));
|
||||
n.out(n.not_(n.not_(a)));
|
||||
const std::uint8_t p0[3] = {1, 0, 1};
|
||||
const std::uint8_t p1[3] = {9, 1, 0};
|
||||
auto [o0, o1] = dpf::yao::eval_pair(n, p0, p1);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(o0[0] ^ o1[0]), 0u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(o0[1] ^ o1[1]), 1u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(o0[2] ^ o1[2]), 1u);
|
||||
}
|
||||
|
||||
TEST(YaoShare, NetlistRejects)
|
||||
{
|
||||
dpf::yao::netlist n;
|
||||
EXPECT_THROW(n.priv_in(2), std::invalid_argument);
|
||||
EXPECT_THROW(n.xor_(dpf::yao::bit{3}, dpf::yao::bit{0}), std::invalid_argument);
|
||||
const auto a = n.shared_in();
|
||||
n.out(a);
|
||||
EXPECT_THROW(n.shared_in(), std::logic_error);
|
||||
std::uint8_t bad = 2;
|
||||
EXPECT_THROW(dpf::yao::eval_local(n, &bad), std::invalid_argument);
|
||||
}
|
||||
314
test/tests/yao_stack_test.cpp
Normal file
314
test/tests/yao_stack_test.cpp
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/yao.hpp"
|
||||
#include "dpf/yao_stack.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::yao::bit;
|
||||
using dpf::yao::netlist;
|
||||
|
||||
netlist and_n(unsigned n)
|
||||
{
|
||||
netlist nl;
|
||||
std::vector<bit> in;
|
||||
in.reserve(n);
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
in.push_back(nl.shared_in());
|
||||
bit acc = in[0];
|
||||
for (unsigned i = 1; i < n; ++i)
|
||||
acc = nl.and_(acc, in[i]);
|
||||
nl.out(acc);
|
||||
return nl;
|
||||
}
|
||||
|
||||
netlist xor2()
|
||||
{
|
||||
netlist nl;
|
||||
auto a = nl.shared_in();
|
||||
auto b = nl.shared_in();
|
||||
nl.out(nl.xor_(a, b));
|
||||
return nl;
|
||||
}
|
||||
|
||||
std::uint8_t plain_one(const netlist & nl, const std::vector<std::uint8_t> & in)
|
||||
{
|
||||
return dpf::yao::eval_plain(nl, in.data())[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(YaoStack, IfCostsTheHeavierBranch)
|
||||
{
|
||||
auto heavy = and_n(3);
|
||||
auto light = and_n(2);
|
||||
EXPECT_EQ(heavy.n_and(), 2u);
|
||||
EXPECT_EQ(light.n_and(), 1u);
|
||||
std::vector<std::uint8_t> h0{1, 1, 1};
|
||||
std::vector<std::uint8_t> h1{0, 0, 0};
|
||||
std::vector<std::uint8_t> l0{1, 1};
|
||||
std::vector<std::uint8_t> l1{0, 1};
|
||||
for (std::uint8_t c0 = 0; c0 < 2; ++c0)
|
||||
{
|
||||
for (std::uint8_t c1 = 0; c1 < 2; ++c1)
|
||||
{
|
||||
auto got = dpf::yao::eval_if(heavy, light, c0, c1,
|
||||
h0.data(), h1.data(), l0.data(), l1.data());
|
||||
EXPECT_EQ(got.stack_blocks, 4u);
|
||||
EXPECT_EQ(got.naive_blocks, 6u);
|
||||
EXPECT_LT(got.stack_blocks, got.naive_blocks);
|
||||
const std::uint8_t sem = static_cast<std::uint8_t>(c0 ^ c1);
|
||||
const std::uint8_t want = (sem == 0) ? plain_one(heavy, {1, 1, 1})
|
||||
: plain_one(light, {1, 0});
|
||||
ASSERT_EQ(got.share0.size(), 1u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.share0[0] ^ got.share1[0]), want);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoStack, OneHotCostsTheHeaviestBranch)
|
||||
{
|
||||
auto a = and_n(2);
|
||||
auto b = xor2();
|
||||
auto c = and_n(4);
|
||||
EXPECT_EQ(a.n_and(), 1u);
|
||||
EXPECT_EQ(b.n_and(), 0u);
|
||||
EXPECT_EQ(c.n_and(), 3u);
|
||||
std::vector<netlist> branches;
|
||||
branches.push_back(a);
|
||||
branches.push_back(b);
|
||||
branches.push_back(c);
|
||||
std::vector<std::vector<std::uint8_t>> p0{
|
||||
{1, 1},
|
||||
{1, 0},
|
||||
{1, 1, 1, 0},
|
||||
};
|
||||
std::vector<std::vector<std::uint8_t>> p1{
|
||||
{0, 0},
|
||||
{0, 1},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
for (std::uint16_t idx = 0; idx < 3; ++idx)
|
||||
{
|
||||
auto got = dpf::yao::eval_one_hot(branches, idx, 0, p0, p1);
|
||||
EXPECT_EQ(got.stack_blocks, 6u);
|
||||
EXPECT_EQ(got.naive_blocks, 8u);
|
||||
std::vector<std::uint8_t> sem(p0[idx].size());
|
||||
for (std::size_t i = 0; i < sem.size(); ++i)
|
||||
sem[i] = static_cast<std::uint8_t>(p0[idx][i] ^ p1[idx][i]);
|
||||
const std::uint8_t want = plain_one(branches[idx], sem);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.share0[0] ^ got.share1[0]), want);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::uint8_t xor_share(const std::vector<std::uint8_t> & a,
|
||||
const std::vector<std::uint8_t> & b, std::size_t i)
|
||||
{
|
||||
return static_cast<std::uint8_t>(a[i] ^ b[i]);
|
||||
}
|
||||
|
||||
netlist not1()
|
||||
{
|
||||
netlist nl;
|
||||
nl.out(nl.not_(nl.shared_in()));
|
||||
return nl;
|
||||
}
|
||||
|
||||
netlist two_outs()
|
||||
{
|
||||
netlist nl;
|
||||
auto a = nl.shared_in();
|
||||
auto b = nl.shared_in();
|
||||
nl.out(nl.and_(a, b));
|
||||
nl.out(nl.xor_(a, b));
|
||||
return nl;
|
||||
}
|
||||
|
||||
netlist not_and_id()
|
||||
{
|
||||
netlist nl;
|
||||
auto a = nl.shared_in();
|
||||
nl.shared_in();
|
||||
nl.out(nl.not_(a));
|
||||
nl.out(a);
|
||||
return nl;
|
||||
}
|
||||
|
||||
netlist priv_and()
|
||||
{
|
||||
netlist nl;
|
||||
auto p = nl.priv_in(0);
|
||||
auto s = nl.shared_in();
|
||||
nl.out(nl.and_(p, s));
|
||||
return nl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(YaoStack, EveryShareSplitOfBothBranches)
|
||||
{
|
||||
auto then_nl = and_n(2);
|
||||
auto else_nl = not1();
|
||||
for (std::uint8_t c0 = 0; c0 < 2; ++c0)
|
||||
{
|
||||
for (std::uint8_t c1 = 0; c1 < 2; ++c1)
|
||||
{
|
||||
for (std::uint8_t a0 = 0; a0 < 2; ++a0)
|
||||
{
|
||||
for (std::uint8_t a1 = 0; a1 < 2; ++a1)
|
||||
{
|
||||
for (std::uint8_t b0 = 0; b0 < 2; ++b0)
|
||||
{
|
||||
for (std::uint8_t b1 = 0; b1 < 2; ++b1)
|
||||
{
|
||||
for (std::uint8_t e0 = 0; e0 < 2; ++e0)
|
||||
{
|
||||
for (std::uint8_t e1 = 0; e1 < 2; ++e1)
|
||||
{
|
||||
const std::uint8_t tp0[2] = {a0, b0};
|
||||
const std::uint8_t tp1[2] = {a1, b1};
|
||||
const std::uint8_t ep0[1] = {e0};
|
||||
const std::uint8_t ep1[1] = {e1};
|
||||
auto got = dpf::yao::eval_if(then_nl, else_nl, c0, c1,
|
||||
tp0, tp1, ep0, ep1);
|
||||
const std::uint8_t sem =
|
||||
static_cast<std::uint8_t>(c0 ^ c1);
|
||||
std::uint8_t want = 0;
|
||||
if (sem == 0)
|
||||
{
|
||||
const std::uint8_t in[2] = {
|
||||
xor_share({a0}, {a1}, 0),
|
||||
static_cast<std::uint8_t>(b0 ^ b1),
|
||||
};
|
||||
want = plain_one(then_nl, {in[0], in[1]});
|
||||
}
|
||||
else
|
||||
{
|
||||
want = plain_one(else_nl,
|
||||
{static_cast<std::uint8_t>(e0 ^ e1)});
|
||||
}
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(
|
||||
got.share0[0] ^ got.share1[0]),
|
||||
want);
|
||||
EXPECT_EQ(got.stack_blocks, 2u);
|
||||
EXPECT_EQ(got.naive_blocks, 2u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoStack, TwoOutputsAndAPrivateInput)
|
||||
{
|
||||
auto then_nl = two_outs();
|
||||
auto else_nl = not_and_id();
|
||||
const std::uint8_t tp0[2] = {1, 0};
|
||||
const std::uint8_t tp1[2] = {1, 1};
|
||||
const std::uint8_t ep0[2] = {0, 1};
|
||||
const std::uint8_t ep1[2] = {1, 0};
|
||||
auto got = dpf::yao::eval_if(then_nl, else_nl, 1, 0, tp0, tp1, ep0, ep1);
|
||||
ASSERT_EQ(got.share0.size(), 2u);
|
||||
const std::uint8_t sem_in[2] = {static_cast<std::uint8_t>(1 ^ 1),
|
||||
static_cast<std::uint8_t>(0 ^ 1)};
|
||||
auto want = dpf::yao::eval_plain(then_nl, sem_in);
|
||||
for (std::size_t i = 0; i < 2; ++i)
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.share0[i] ^ got.share1[i]), want[i]);
|
||||
|
||||
auto priv = priv_and();
|
||||
auto neg = not1();
|
||||
const std::uint8_t pp0[2] = {1, 1};
|
||||
const std::uint8_t pp1[2] = {0, 1};
|
||||
const std::uint8_t np0[1] = {0};
|
||||
const std::uint8_t np1[1] = {0};
|
||||
auto branched = dpf::yao::eval_if(priv, neg, 0, 1, pp0, pp1, np0, np1);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(branched.share0[0] ^ branched.share1[0]),
|
||||
plain_one(neg, {0}));
|
||||
}
|
||||
|
||||
TEST(YaoStack, EmptyBranchesCostNothing)
|
||||
{
|
||||
netlist left;
|
||||
netlist right;
|
||||
auto a = left.shared_in();
|
||||
left.out(left.xor_(a, left.shared_in()));
|
||||
auto b = right.shared_in();
|
||||
right.out(right.not_(right.xor_(b, right.shared_in())));
|
||||
const std::uint8_t z[2] = {1, 1};
|
||||
auto got = dpf::yao::eval_if(left, right, 0, 0, z, z, z, z);
|
||||
EXPECT_EQ(got.stack_blocks, 0u);
|
||||
EXPECT_EQ(got.naive_blocks, 0u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.share0[0] ^ got.share1[0]), 0);
|
||||
}
|
||||
|
||||
TEST(YaoStack, OneHotEveryIndexAndShare)
|
||||
{
|
||||
std::vector<netlist> branches;
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
branches.push_back(i % 2 == 0 ? and_n(2) : xor2());
|
||||
const std::uint8_t patterns[2][2] = {{0, 0}, {1, 1}};
|
||||
for (std::uint16_t index = 0; index < 8; ++index)
|
||||
{
|
||||
for (std::uint16_t mask = 0; mask < 2; ++mask)
|
||||
{
|
||||
const std::uint16_t p0 = static_cast<std::uint16_t>(index ^ mask);
|
||||
const std::uint16_t p1 = mask;
|
||||
std::vector<std::vector<std::uint8_t>> in0(8), in1(8);
|
||||
for (unsigned b = 0; b < 8; ++b)
|
||||
{
|
||||
in0[b] = {patterns[b & 1][0], static_cast<std::uint8_t>(b & 1u)};
|
||||
in1[b] = {patterns[b & 1][1], 1};
|
||||
}
|
||||
auto got = dpf::yao::eval_one_hot(branches, p0, p1, in0, in1);
|
||||
EXPECT_EQ(got.stack_blocks, 2u);
|
||||
EXPECT_EQ(got.naive_blocks, 8u);
|
||||
EXPECT_LT(got.stack_blocks, got.naive_blocks);
|
||||
std::vector<std::uint8_t> sem{
|
||||
static_cast<std::uint8_t>(in0[index][0] ^ in1[index][0]),
|
||||
static_cast<std::uint8_t>(in0[index][1] ^ in1[index][1]),
|
||||
};
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(got.share0[0] ^ got.share1[0]),
|
||||
plain_one(branches[index], sem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(YaoStack, RejectsBadShapes)
|
||||
{
|
||||
auto a = and_n(2);
|
||||
auto b = not1();
|
||||
const std::uint8_t bit = 1;
|
||||
EXPECT_THROW(dpf::yao::eval_if(a, b, 2, 0, &bit, &bit, &bit, &bit),
|
||||
std::invalid_argument);
|
||||
netlist one;
|
||||
one.out(one.shared_in());
|
||||
netlist two;
|
||||
auto t0 = two.shared_in();
|
||||
auto t1 = two.shared_in();
|
||||
two.out(t0);
|
||||
two.out(t1);
|
||||
EXPECT_THROW(dpf::yao::eval_if(one, two, 0, 0, &bit, &bit, &bit, &bit),
|
||||
std::invalid_argument);
|
||||
std::vector<netlist> only{a};
|
||||
EXPECT_THROW(dpf::yao::eval_one_hot(only, 0, 0, {{}}, {{}}), std::invalid_argument);
|
||||
std::vector<netlist> too_many(9, a);
|
||||
EXPECT_THROW(dpf::yao::eval_one_hot(too_many, 0, 0,
|
||||
std::vector<std::vector<std::uint8_t>>(9),
|
||||
std::vector<std::vector<std::uint8_t>>(9)),
|
||||
std::invalid_argument);
|
||||
std::vector<netlist> pair{a, xor2()};
|
||||
EXPECT_THROW(dpf::yao::eval_one_hot(pair, 2, 0, {{1, 1}, {1, 1}}, {{0, 0}, {0, 0}}),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(dpf::yao::eval_one_hot(pair, 0, 0, {{1}, {1, 1}}, {{0, 0}, {0, 0}}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
421
test/tests/yao_test.cpp
Normal file
421
test/tests/yao_test.cpp
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "aes_mmo_ref.hpp"
|
||||
#include "dpf/net/trio.hpp"
|
||||
#include "dpf/prg_aes.hpp"
|
||||
#include "dpf/random.hpp"
|
||||
#include "dpf/yao.hpp"
|
||||
#include "dpf/yao_aes.hpp"
|
||||
#include "dpf/verifiable.hpp"
|
||||
#include "dpf/yao_share.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::yao::bit;
|
||||
using dpf::yao::bits8;
|
||||
using dpf::yao::netlist;
|
||||
|
||||
void bytes_to_bits(const std::uint8_t * bytes, int nbytes, std::uint8_t * bits)
|
||||
{
|
||||
for (int i = 0; i < nbytes; ++i)
|
||||
for (int k = 0; k < 8; ++k)
|
||||
bits[i * 8 + k] = static_cast<std::uint8_t>((bytes[i] >> (7 - k)) & 1u);
|
||||
}
|
||||
|
||||
void bits_to_bytes(const std::uint8_t * bits, int nbytes, std::uint8_t * bytes)
|
||||
{
|
||||
for (int i = 0; i < nbytes; ++i)
|
||||
{
|
||||
bytes[i] = 0;
|
||||
for (int k = 0; k < 8; ++k)
|
||||
bytes[i] = static_cast<std::uint8_t>(bytes[i] | (bits[i * 8 + k] << (7 - k)));
|
||||
}
|
||||
}
|
||||
|
||||
std::uint8_t pack_byte(const std::uint8_t * bits)
|
||||
{
|
||||
std::uint8_t b = 0;
|
||||
bits_to_bytes(bits, 1, &b);
|
||||
return b;
|
||||
}
|
||||
|
||||
netlist sbox_netlist()
|
||||
{
|
||||
netlist n;
|
||||
const bits8 in = dpf::yao::shared_byte(n);
|
||||
dpf::yao::out_byte(n, dpf::yao::sbox(n, in));
|
||||
return n;
|
||||
}
|
||||
|
||||
void expand_key(const std::uint8_t key[16], std::uint8_t rk[11][16])
|
||||
{
|
||||
std::uint8_t w[176];
|
||||
std::memcpy(w, key, 16);
|
||||
int n = 16;
|
||||
std::uint8_t rcon = 1;
|
||||
while (n < 176)
|
||||
{
|
||||
std::uint8_t t[4];
|
||||
std::memcpy(t, w + n - 4, 4);
|
||||
if (n % 16 == 0)
|
||||
{
|
||||
const std::uint8_t tmp = t[0];
|
||||
t[0] = t[1];
|
||||
t[1] = t[2];
|
||||
t[2] = t[3];
|
||||
t[3] = tmp;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
t[i] = dpf::party::aes_ref::sbox_at(t[i]);
|
||||
t[0] = static_cast<std::uint8_t>(t[0] ^ rcon);
|
||||
rcon = dpf::party::aes_ref::xtime(rcon);
|
||||
}
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
w[n] = static_cast<std::uint8_t>(w[n - 16] ^ t[i]);
|
||||
++n;
|
||||
}
|
||||
}
|
||||
std::memcpy(rk, w, 176);
|
||||
}
|
||||
|
||||
void encrypt_aes128(std::uint8_t s[16], const std::uint8_t rk[11][16])
|
||||
{
|
||||
using namespace dpf::party::aes_ref;
|
||||
add_round_key(s, rk[0]);
|
||||
for (int r = 1; r < 10; ++r)
|
||||
{
|
||||
sub_bytes(s);
|
||||
shift_rows(s);
|
||||
mix_columns(s);
|
||||
add_round_key(s, rk[r]);
|
||||
}
|
||||
sub_bytes(s);
|
||||
shift_rows(s);
|
||||
add_round_key(s, rk[10]);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> reconstruct(std::vector<std::uint8_t> a,
|
||||
const std::vector<std::uint8_t> & b)
|
||||
{
|
||||
EXPECT_EQ(a.size(), b.size());
|
||||
for (std::size_t i = 0; i < a.size(); ++i)
|
||||
a[i] = static_cast<std::uint8_t>(a[i] ^ b[i]);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename Fn0, typename Fn1>
|
||||
void run_pair(Fn0 && fn0, Fn1 && fn1)
|
||||
{
|
||||
const auto dir = std::filesystem::temp_directory_path()
|
||||
/ ("libdpf_yao_" + std::to_string(::getpid()));
|
||||
std::filesystem::create_directories(dir);
|
||||
std::exception_ptr ep0;
|
||||
std::exception_ptr ep1;
|
||||
std::thread t0([&] {
|
||||
try
|
||||
{
|
||||
auto net = dpf::net::trio::connect_pair(dpf::net::role::p0, dir.string());
|
||||
fn0(net);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ep0 = std::current_exception();
|
||||
}
|
||||
});
|
||||
std::thread t1([&] {
|
||||
try
|
||||
{
|
||||
auto net = dpf::net::trio::connect_pair(dpf::net::role::p1, dir.string());
|
||||
fn1(net);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ep1 = std::current_exception();
|
||||
}
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
std::filesystem::remove_all(dir);
|
||||
if (ep0)
|
||||
std::rethrow_exception(ep0);
|
||||
if (ep1)
|
||||
std::rethrow_exception(ep1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Yao, AndXorNot)
|
||||
{
|
||||
netlist n;
|
||||
const bit a = n.shared_in();
|
||||
const bit b = n.shared_in();
|
||||
const bit x = n.xor_(a, b);
|
||||
const bit y = n.and_(a, b);
|
||||
const bit z = n.not_(a);
|
||||
const bit w = n.xnor_(a, b);
|
||||
n.out(x);
|
||||
n.out(y);
|
||||
n.out(z);
|
||||
n.out(w);
|
||||
n.out(n.xor_public(y, 1));
|
||||
EXPECT_EQ(n.n_and(), 1u);
|
||||
for (int av = 0; av < 2; ++av)
|
||||
{
|
||||
for (int bv = 0; bv < 2; ++bv)
|
||||
{
|
||||
const std::uint8_t in[2] = {
|
||||
static_cast<std::uint8_t>(av), static_cast<std::uint8_t>(bv)};
|
||||
const auto plain = dpf::yao::eval_plain(n, in);
|
||||
const auto got = dpf::yao::eval_local(n, in);
|
||||
const std::uint8_t expect[5] = {
|
||||
static_cast<std::uint8_t>(av ^ bv),
|
||||
static_cast<std::uint8_t>(av & bv),
|
||||
static_cast<std::uint8_t>(av ^ 1),
|
||||
static_cast<std::uint8_t>(1 ^ av ^ bv),
|
||||
static_cast<std::uint8_t>((av & bv) ^ 1)};
|
||||
EXPECT_EQ(plain, (std::vector<std::uint8_t>(expect, expect + 5)));
|
||||
EXPECT_EQ(got, plain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Yao, SboxPlainAndGarbled)
|
||||
{
|
||||
const netlist n = sbox_netlist();
|
||||
EXPECT_EQ(n.n_and(), static_cast<std::uint32_t>(aes_bp::and_count));
|
||||
for (int x = 0; x < 256; ++x)
|
||||
{
|
||||
const auto byte = static_cast<std::uint8_t>(x);
|
||||
std::uint8_t bits[8];
|
||||
bytes_to_bits(&byte, 1, bits);
|
||||
const auto plain = dpf::yao::eval_plain(n, bits);
|
||||
const auto garbled = dpf::yao::eval_local(n, bits);
|
||||
EXPECT_EQ(pack_byte(plain.data()), dpf::party::aes_ref::sbox_at(byte)) << x;
|
||||
EXPECT_EQ(garbled, plain) << x;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Yao, SboxShares)
|
||||
{
|
||||
const netlist n = sbox_netlist();
|
||||
const std::uint8_t byte = 0x53;
|
||||
std::uint8_t semantic[8];
|
||||
bytes_to_bits(&byte, 1, semantic);
|
||||
std::uint8_t p0[8], p1[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
p0[i] = static_cast<std::uint8_t>(dpf::uniform_sample<unsigned char>() & 1u);
|
||||
p1[i] = static_cast<std::uint8_t>(semantic[i] ^ p0[i]);
|
||||
}
|
||||
auto [a, b] = dpf::yao::eval_pair(n, p0, p1);
|
||||
EXPECT_EQ(pack_byte(reconstruct(a, b).data()), dpf::party::aes_ref::sbox_at(byte));
|
||||
}
|
||||
|
||||
TEST(Yao, Aes128Nist)
|
||||
{
|
||||
const std::uint8_t key[16] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
|
||||
const std::uint8_t pt[16] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
|
||||
const std::uint8_t ct[16] = {
|
||||
0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30,
|
||||
0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a};
|
||||
|
||||
std::uint8_t rk[11][16];
|
||||
expand_key(key, rk);
|
||||
std::uint8_t state[16];
|
||||
std::memcpy(state, pt, 16);
|
||||
encrypt_aes128(state, rk);
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(state, state + 16),
|
||||
std::vector<std::uint8_t>(ct, ct + 16));
|
||||
|
||||
const netlist n = dpf::yao::aes128_netlist();
|
||||
EXPECT_EQ(n.n_and(), 6400u);
|
||||
std::uint8_t bits[256];
|
||||
bytes_to_bits(pt, 16, bits);
|
||||
bytes_to_bits(key, 16, bits + 128);
|
||||
const auto plain = dpf::yao::eval_plain(n, bits);
|
||||
std::uint8_t got[16];
|
||||
bits_to_bytes(plain.data(), 16, got);
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(got, got + 16),
|
||||
std::vector<std::uint8_t>(ct, ct + 16));
|
||||
|
||||
const auto garbled = dpf::yao::eval_local(n, bits);
|
||||
bits_to_bytes(garbled.data(), 16, got);
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(got, got + 16),
|
||||
std::vector<std::uint8_t>(ct, ct + 16));
|
||||
}
|
||||
|
||||
TEST(Yao, AesMmoMatchesPrg)
|
||||
{
|
||||
const std::uint32_t positions[] = {0u, 1u, 0x01020304u};
|
||||
const std::uint8_t msg[16] = {
|
||||
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
|
||||
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
|
||||
simde__m128i block{};
|
||||
std::memcpy(&block, msg, 16);
|
||||
std::uint8_t bits[128];
|
||||
dpf::yao::block_to_bits(bits, block);
|
||||
for (std::uint32_t pos : positions)
|
||||
{
|
||||
const auto expect = dpf::prg::aes128::eval(block, pos);
|
||||
const netlist n = dpf::yao::aes_mmo_netlist(pos);
|
||||
EXPECT_EQ(n.n_and(), 5120u);
|
||||
const auto got_bits = dpf::yao::eval_local(n, bits);
|
||||
const auto got = dpf::yao::bits_to_block(got_bits.data());
|
||||
std::uint8_t a[16], b[16];
|
||||
std::memcpy(a, &expect, 16);
|
||||
std::memcpy(b, &got, 16);
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(a, a + 16),
|
||||
std::vector<std::uint8_t>(b, b + 16)) << pos;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Yao, SessionSboxAndReuse)
|
||||
{
|
||||
const netlist gates = [] {
|
||||
netlist n;
|
||||
const bit a = n.priv_in(0);
|
||||
const bit b = n.priv_in(1);
|
||||
const bit c = n.shared_in();
|
||||
n.out(n.xor_(n.and_(a, b), c));
|
||||
n.out(n.not_(c));
|
||||
return n;
|
||||
}();
|
||||
const netlist priv_xor = [] {
|
||||
netlist n;
|
||||
const bit a = n.priv_in(0);
|
||||
const bit b = n.priv_in(0);
|
||||
n.out(n.xor_(a, b));
|
||||
return n;
|
||||
}();
|
||||
const netlist box = sbox_netlist();
|
||||
const std::uint8_t byte = 0xa7;
|
||||
std::uint8_t semantic[8];
|
||||
bytes_to_bits(&byte, 1, semantic);
|
||||
std::uint8_t sp0[8], sp1[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
sp0[i] = static_cast<std::uint8_t>(i & 1u);
|
||||
sp1[i] = static_cast<std::uint8_t>(semantic[i] ^ sp0[i]);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> g0, g1, s0, s1, x0, x1;
|
||||
run_pair(
|
||||
[&](dpf::net::trio & net) {
|
||||
dpf::yao::session s;
|
||||
const std::uint8_t xin[2] = {1, 1};
|
||||
x0 = s.eval(0, priv_xor, xin, net.to(dpf::net::role::p1));
|
||||
const std::uint8_t in0[3] = {1, 0, 1};
|
||||
g0 = s.eval(0, gates, in0, net.to(dpf::net::role::p1));
|
||||
const std::uint8_t in1[3] = {1, 0, 0};
|
||||
auto second = s.eval(0, gates, in1, net.to(dpf::net::role::p1));
|
||||
g0.insert(g0.end(), second.begin(), second.end());
|
||||
s0 = s.eval(0, box, sp0, net.to(dpf::net::role::p1));
|
||||
},
|
||||
[&](dpf::net::trio & net) {
|
||||
dpf::yao::session s;
|
||||
const std::uint8_t xin[2] = {0, 0};
|
||||
x1 = s.eval(1, priv_xor, xin, net.to(dpf::net::role::p0));
|
||||
const std::uint8_t in0[3] = {0, 1, 0};
|
||||
g1 = s.eval(1, gates, in0, net.to(dpf::net::role::p0));
|
||||
const std::uint8_t in1[3] = {0, 0, 1};
|
||||
auto second = s.eval(1, gates, in1, net.to(dpf::net::role::p0));
|
||||
g1.insert(g1.end(), second.begin(), second.end());
|
||||
s1 = s.eval(1, box, sp1, net.to(dpf::net::role::p0));
|
||||
});
|
||||
|
||||
EXPECT_EQ(reconstruct(x0, x1), std::vector<std::uint8_t>{0});
|
||||
const auto opened = reconstruct(g0, g1);
|
||||
const std::uint8_t expect_gates[4] = {0, 0, 1, 0};
|
||||
EXPECT_EQ(std::vector<std::uint8_t>(opened.begin(), opened.begin() + 4),
|
||||
std::vector<std::uint8_t>(expect_gates, expect_gates + 4));
|
||||
EXPECT_EQ(pack_byte(reconstruct(s0, s1).data()),
|
||||
dpf::party::aes_ref::sbox_at(byte));
|
||||
}
|
||||
|
||||
TEST(Yao, LeafBitsOnSession)
|
||||
{
|
||||
const std::uint32_t beta = 0x6bu;
|
||||
const auto s0 = dpf::subtractive_share<std::uint32_t, 0>::from_raw(beta);
|
||||
const auto s1 = dpf::subtractive_share<std::uint32_t, 1>::from_raw(0);
|
||||
const auto [p0, p1] = dpf::yao::b2y(s0, s1, 8);
|
||||
const dpf::yao::netlist n = [] {
|
||||
dpf::yao::netlist c;
|
||||
dpf::yao::bit in[8];
|
||||
for (int i = 0; i < 8; ++i)
|
||||
in[i] = c.shared_in();
|
||||
c.out(c.and_(in[0], in[1]));
|
||||
return c;
|
||||
}();
|
||||
std::vector<std::uint8_t> o0, o1;
|
||||
bool locked = false;
|
||||
run_pair(
|
||||
[&](dpf::net::trio & net) {
|
||||
dpf::yao::session s;
|
||||
auto & link = net.to(dpf::net::role::p1);
|
||||
o0 = s.eval(0, n, p0.data(), link);
|
||||
try
|
||||
{
|
||||
s.eval(1, n, p0.data(), link);
|
||||
}
|
||||
catch (const std::logic_error &)
|
||||
{
|
||||
locked = true;
|
||||
}
|
||||
},
|
||||
[&](dpf::net::trio & net) {
|
||||
dpf::yao::session s;
|
||||
o1 = s.eval(1, n, p1.data(), net.to(dpf::net::role::p0));
|
||||
});
|
||||
EXPECT_TRUE(locked);
|
||||
ASSERT_EQ(o0.size(), 1u);
|
||||
EXPECT_EQ(static_cast<std::uint8_t>(o0[0] ^ o1[0]), 1u);
|
||||
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(o0, o1, 1);
|
||||
EXPECT_EQ(dpf::reconstruct(z0, z1), 1u);
|
||||
}
|
||||
|
||||
TEST(Yao, CorrectionLevelMatchesMakeCs)
|
||||
{
|
||||
const simde__m128i s0 = dpf::uniform_sample<simde__m128i>();
|
||||
const simde__m128i s1 = dpf::uniform_sample<simde__m128i>();
|
||||
const std::uint64_t prefix = 0x0123456789abcdefull;
|
||||
const std::uint64_t share0 = 0x1111222233334444ull;
|
||||
const std::uint64_t share1 = prefix ^ share0;
|
||||
const std::size_t level = 0x10ffu;
|
||||
std::uint8_t in0[dpf::yao::correction_level_in_bits];
|
||||
std::uint8_t in1[dpf::yao::correction_level_in_bits];
|
||||
dpf::yao::pack_correction_level(0, s0, share0, level, in0);
|
||||
dpf::yao::pack_correction_level(1, s1, share1, level, in1);
|
||||
const auto nl = dpf::yao::correction_level_netlist();
|
||||
EXPECT_EQ(nl.n_and(), dpf::yao::correction_level_ands);
|
||||
EXPECT_EQ(nl.n_in(), static_cast<std::uint32_t>(dpf::yao::correction_level_in_bits));
|
||||
auto [a, b] = dpf::yao::eval_pair(nl, in0, in1);
|
||||
ASSERT_EQ(a.size(), dpf::yao::correction_level_out_bits);
|
||||
const auto expect = dpf::detail::vdpf::make_cs(level, prefix, s0, s1);
|
||||
for (int lane = 0; lane < 4; ++lane)
|
||||
{
|
||||
std::uint8_t bits[128];
|
||||
for (int i = 0; i < 128; ++i)
|
||||
bits[i] = static_cast<std::uint8_t>(
|
||||
a[static_cast<std::size_t>(lane * 128 + i)]
|
||||
^ b[static_cast<std::size_t>(lane * 128 + i)]);
|
||||
const auto got = dpf::yao::bits_to_block(bits);
|
||||
EXPECT_EQ(std::memcmp(&got, &expect[static_cast<std::size_t>(lane)], 16), 0)
|
||||
<< lane;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue