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
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue